From 36d22d82aa202bb199967e9512281e9a53db42c9 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 21:33:14 +0200 Subject: Adding upstream version 115.7.0esr. Signed-off-by: Daniel Baumann --- js/src/frontend/AbstractScopePtr.cpp | 61 + js/src/frontend/AbstractScopePtr.h | 90 + js/src/frontend/AsyncEmitter.cpp | 217 + js/src/frontend/AsyncEmitter.h | 174 + js/src/frontend/BytecodeCompilation.h | 108 + js/src/frontend/BytecodeCompiler.cpp | 1565 +++ js/src/frontend/BytecodeCompiler.h | 226 + js/src/frontend/BytecodeControlStructures.cpp | 392 + js/src/frontend/BytecodeControlStructures.h | 205 + js/src/frontend/BytecodeEmitter.cpp | 12031 +++++++++++++++++++++ js/src/frontend/BytecodeEmitter.h | 1095 ++ js/src/frontend/BytecodeOffset.h | 153 + js/src/frontend/BytecodeSection.cpp | 195 + js/src/frontend/BytecodeSection.h | 393 + js/src/frontend/CForEmitter.cpp | 179 + js/src/frontend/CForEmitter.h | 175 + js/src/frontend/CallOrNewEmitter.cpp | 365 + js/src/frontend/CallOrNewEmitter.h | 352 + js/src/frontend/CompilationStencil.h | 1842 ++++ js/src/frontend/CompileScript.cpp | 175 + js/src/frontend/DecoratorEmitter.cpp | 840 ++ js/src/frontend/DecoratorEmitter.h | 59 + js/src/frontend/DefaultEmitter.cpp | 75 + js/src/frontend/DefaultEmitter.h | 65 + js/src/frontend/DestructuringFlavor.h | 24 + js/src/frontend/DoWhileEmitter.cpp | 69 + js/src/frontend/DoWhileEmitter.h | 80 + js/src/frontend/EitherParser.h | 58 + js/src/frontend/ElemOpEmitter.cpp | 257 + js/src/frontend/ElemOpEmitter.h | 267 + js/src/frontend/EmitterScope.cpp | 1117 ++ js/src/frontend/EmitterScope.h | 210 + js/src/frontend/ErrorReporter.h | 342 + js/src/frontend/ExpressionStatementEmitter.cpp | 54 + js/src/frontend/ExpressionStatementEmitter.h | 81 + js/src/frontend/FoldConstants.cpp | 1581 +++ js/src/frontend/FoldConstants.h | 53 + js/src/frontend/ForInEmitter.cpp | 154 + js/src/frontend/ForInEmitter.h | 117 + js/src/frontend/ForOfEmitter.cpp | 223 + js/src/frontend/ForOfEmitter.h | 116 + js/src/frontend/ForOfLoopControl.cpp | 196 + js/src/frontend/ForOfLoopControl.h | 97 + js/src/frontend/Frontend2.cpp | 701 ++ js/src/frontend/Frontend2.h | 61 + js/src/frontend/FrontendContext.cpp | 237 + js/src/frontend/FrontendContext.h | 260 + js/src/frontend/FullParseHandler.h | 1195 +++ js/src/frontend/FunctionEmitter.cpp | 1017 ++ js/src/frontend/FunctionEmitter.h | 436 + js/src/frontend/FunctionSyntaxKind.h | 41 + js/src/frontend/GenerateReservedWords.py | 232 + js/src/frontend/IfEmitter.cpp | 286 + js/src/frontend/IfEmitter.h | 311 + js/src/frontend/IteratorKind.h | 16 + js/src/frontend/JumpList.cpp | 46 + js/src/frontend/JumpList.h | 84 + js/src/frontend/LabelEmitter.cpp | 40 + js/src/frontend/LabelEmitter.h | 65 + js/src/frontend/LexicalScopeEmitter.cpp | 57 + js/src/frontend/LexicalScopeEmitter.h | 94 + js/src/frontend/ModuleSharedContext.h | 47 + js/src/frontend/NameAnalysisTypes.h | 390 + js/src/frontend/NameCollections.h | 455 + js/src/frontend/NameFunctions.cpp | 531 + js/src/frontend/NameFunctions.h | 27 + js/src/frontend/NameOpEmitter.cpp | 481 + js/src/frontend/NameOpEmitter.h | 182 + js/src/frontend/ObjLiteral.cpp | 537 + js/src/frontend/ObjLiteral.h | 772 ++ js/src/frontend/ObjectEmitter.cpp | 897 ++ js/src/frontend/ObjectEmitter.h | 849 ++ js/src/frontend/OptionalEmitter.cpp | 142 + js/src/frontend/OptionalEmitter.h | 220 + js/src/frontend/ParseContext-inl.h | 177 + js/src/frontend/ParseContext.cpp | 723 ++ js/src/frontend/ParseContext.h | 622 ++ js/src/frontend/ParseNode.cpp | 430 + js/src/frontend/ParseNode.h | 2556 +++++ js/src/frontend/ParseNodeVerify.cpp | 50 + js/src/frontend/ParseNodeVerify.h | 48 + js/src/frontend/ParseNodeVisitor.h | 136 + js/src/frontend/Parser.cpp | 13078 +++++++++++++++++++++++ js/src/frontend/Parser.h | 1955 ++++ js/src/frontend/ParserAtom.cpp | 1316 +++ js/src/frontend/ParserAtom.h | 905 ++ js/src/frontend/PrivateOpEmitter.cpp | 331 + js/src/frontend/PrivateOpEmitter.h | 231 + js/src/frontend/PropOpEmitter.cpp | 244 + js/src/frontend/PropOpEmitter.h | 254 + js/src/frontend/ReservedWords.h | 78 + js/src/frontend/ScopeBindingCache.h | 281 + js/src/frontend/ScopeIndex.h | 32 + js/src/frontend/ScriptIndex.h | 42 + js/src/frontend/SelfHostedIter.h | 28 + js/src/frontend/SharedContext-inl.h | 23 + js/src/frontend/SharedContext.cpp | 409 + js/src/frontend/SharedContext.h | 747 ++ js/src/frontend/SourceNotes.cpp | 13 + js/src/frontend/SourceNotes.h | 428 + js/src/frontend/Stencil.cpp | 5363 ++++++++++ js/src/frontend/Stencil.h | 1141 ++ js/src/frontend/StencilXdr.cpp | 1536 +++ js/src/frontend/StencilXdr.h | 217 + js/src/frontend/SwitchEmitter.cpp | 414 + js/src/frontend/SwitchEmitter.h | 474 + js/src/frontend/SyntaxParseHandler.h | 797 ++ js/src/frontend/TDZCheckCache.cpp | 76 + js/src/frontend/TDZCheckCache.h | 59 + js/src/frontend/TaggedParserAtomIndexHasher.h | 44 + js/src/frontend/Token.h | 218 + js/src/frontend/TokenKind.h | 333 + js/src/frontend/TokenStream.cpp | 3829 +++++++ js/src/frontend/TokenStream.h | 2962 +++++ js/src/frontend/TryEmitter.cpp | 317 + js/src/frontend/TryEmitter.h | 226 + js/src/frontend/TypedIndex.h | 42 + js/src/frontend/UsedNameTracker.h | 259 + js/src/frontend/ValueUsage.h | 30 + js/src/frontend/WhileEmitter.cpp | 85 + js/src/frontend/WhileEmitter.h | 90 + js/src/frontend/align_stack_comment.py | 108 + js/src/frontend/moz.build | 98 + js/src/frontend/smoosh/Cargo.toml | 23 + js/src/frontend/smoosh/build.rs | 27 + js/src/frontend/smoosh/cbindgen.toml | 19 + js/src/frontend/smoosh/moz.build | 15 + js/src/frontend/smoosh/src/lib.rs | 769 ++ 128 files changed, 81545 insertions(+) create mode 100644 js/src/frontend/AbstractScopePtr.cpp create mode 100644 js/src/frontend/AbstractScopePtr.h create mode 100644 js/src/frontend/AsyncEmitter.cpp create mode 100644 js/src/frontend/AsyncEmitter.h create mode 100644 js/src/frontend/BytecodeCompilation.h create mode 100644 js/src/frontend/BytecodeCompiler.cpp create mode 100644 js/src/frontend/BytecodeCompiler.h create mode 100644 js/src/frontend/BytecodeControlStructures.cpp create mode 100644 js/src/frontend/BytecodeControlStructures.h create mode 100644 js/src/frontend/BytecodeEmitter.cpp create mode 100644 js/src/frontend/BytecodeEmitter.h create mode 100644 js/src/frontend/BytecodeOffset.h create mode 100644 js/src/frontend/BytecodeSection.cpp create mode 100644 js/src/frontend/BytecodeSection.h create mode 100644 js/src/frontend/CForEmitter.cpp create mode 100644 js/src/frontend/CForEmitter.h create mode 100644 js/src/frontend/CallOrNewEmitter.cpp create mode 100644 js/src/frontend/CallOrNewEmitter.h create mode 100644 js/src/frontend/CompilationStencil.h create mode 100644 js/src/frontend/CompileScript.cpp create mode 100644 js/src/frontend/DecoratorEmitter.cpp create mode 100644 js/src/frontend/DecoratorEmitter.h create mode 100644 js/src/frontend/DefaultEmitter.cpp create mode 100644 js/src/frontend/DefaultEmitter.h create mode 100644 js/src/frontend/DestructuringFlavor.h create mode 100644 js/src/frontend/DoWhileEmitter.cpp create mode 100644 js/src/frontend/DoWhileEmitter.h create mode 100644 js/src/frontend/EitherParser.h create mode 100644 js/src/frontend/ElemOpEmitter.cpp create mode 100644 js/src/frontend/ElemOpEmitter.h create mode 100644 js/src/frontend/EmitterScope.cpp create mode 100644 js/src/frontend/EmitterScope.h create mode 100644 js/src/frontend/ErrorReporter.h create mode 100644 js/src/frontend/ExpressionStatementEmitter.cpp create mode 100644 js/src/frontend/ExpressionStatementEmitter.h create mode 100644 js/src/frontend/FoldConstants.cpp create mode 100644 js/src/frontend/FoldConstants.h create mode 100644 js/src/frontend/ForInEmitter.cpp create mode 100644 js/src/frontend/ForInEmitter.h create mode 100644 js/src/frontend/ForOfEmitter.cpp create mode 100644 js/src/frontend/ForOfEmitter.h create mode 100644 js/src/frontend/ForOfLoopControl.cpp create mode 100644 js/src/frontend/ForOfLoopControl.h create mode 100644 js/src/frontend/Frontend2.cpp create mode 100644 js/src/frontend/Frontend2.h create mode 100644 js/src/frontend/FrontendContext.cpp create mode 100644 js/src/frontend/FrontendContext.h create mode 100644 js/src/frontend/FullParseHandler.h create mode 100644 js/src/frontend/FunctionEmitter.cpp create mode 100644 js/src/frontend/FunctionEmitter.h create mode 100644 js/src/frontend/FunctionSyntaxKind.h create mode 100644 js/src/frontend/GenerateReservedWords.py create mode 100644 js/src/frontend/IfEmitter.cpp create mode 100644 js/src/frontend/IfEmitter.h create mode 100644 js/src/frontend/IteratorKind.h create mode 100644 js/src/frontend/JumpList.cpp create mode 100644 js/src/frontend/JumpList.h create mode 100644 js/src/frontend/LabelEmitter.cpp create mode 100644 js/src/frontend/LabelEmitter.h create mode 100644 js/src/frontend/LexicalScopeEmitter.cpp create mode 100644 js/src/frontend/LexicalScopeEmitter.h create mode 100644 js/src/frontend/ModuleSharedContext.h create mode 100644 js/src/frontend/NameAnalysisTypes.h create mode 100644 js/src/frontend/NameCollections.h create mode 100644 js/src/frontend/NameFunctions.cpp create mode 100644 js/src/frontend/NameFunctions.h create mode 100644 js/src/frontend/NameOpEmitter.cpp create mode 100644 js/src/frontend/NameOpEmitter.h create mode 100644 js/src/frontend/ObjLiteral.cpp create mode 100644 js/src/frontend/ObjLiteral.h create mode 100644 js/src/frontend/ObjectEmitter.cpp create mode 100644 js/src/frontend/ObjectEmitter.h create mode 100644 js/src/frontend/OptionalEmitter.cpp create mode 100644 js/src/frontend/OptionalEmitter.h create mode 100644 js/src/frontend/ParseContext-inl.h create mode 100644 js/src/frontend/ParseContext.cpp create mode 100644 js/src/frontend/ParseContext.h create mode 100644 js/src/frontend/ParseNode.cpp create mode 100644 js/src/frontend/ParseNode.h create mode 100644 js/src/frontend/ParseNodeVerify.cpp create mode 100644 js/src/frontend/ParseNodeVerify.h create mode 100644 js/src/frontend/ParseNodeVisitor.h create mode 100644 js/src/frontend/Parser.cpp create mode 100644 js/src/frontend/Parser.h create mode 100644 js/src/frontend/ParserAtom.cpp create mode 100644 js/src/frontend/ParserAtom.h create mode 100644 js/src/frontend/PrivateOpEmitter.cpp create mode 100644 js/src/frontend/PrivateOpEmitter.h create mode 100644 js/src/frontend/PropOpEmitter.cpp create mode 100644 js/src/frontend/PropOpEmitter.h create mode 100644 js/src/frontend/ReservedWords.h create mode 100644 js/src/frontend/ScopeBindingCache.h create mode 100644 js/src/frontend/ScopeIndex.h create mode 100644 js/src/frontend/ScriptIndex.h create mode 100644 js/src/frontend/SelfHostedIter.h create mode 100644 js/src/frontend/SharedContext-inl.h create mode 100644 js/src/frontend/SharedContext.cpp create mode 100644 js/src/frontend/SharedContext.h create mode 100644 js/src/frontend/SourceNotes.cpp create mode 100644 js/src/frontend/SourceNotes.h create mode 100644 js/src/frontend/Stencil.cpp create mode 100644 js/src/frontend/Stencil.h create mode 100644 js/src/frontend/StencilXdr.cpp create mode 100644 js/src/frontend/StencilXdr.h create mode 100644 js/src/frontend/SwitchEmitter.cpp create mode 100644 js/src/frontend/SwitchEmitter.h create mode 100644 js/src/frontend/SyntaxParseHandler.h create mode 100644 js/src/frontend/TDZCheckCache.cpp create mode 100644 js/src/frontend/TDZCheckCache.h create mode 100644 js/src/frontend/TaggedParserAtomIndexHasher.h create mode 100644 js/src/frontend/Token.h create mode 100644 js/src/frontend/TokenKind.h create mode 100644 js/src/frontend/TokenStream.cpp create mode 100644 js/src/frontend/TokenStream.h create mode 100644 js/src/frontend/TryEmitter.cpp create mode 100644 js/src/frontend/TryEmitter.h create mode 100644 js/src/frontend/TypedIndex.h create mode 100644 js/src/frontend/UsedNameTracker.h create mode 100644 js/src/frontend/ValueUsage.h create mode 100644 js/src/frontend/WhileEmitter.cpp create mode 100644 js/src/frontend/WhileEmitter.h create mode 100755 js/src/frontend/align_stack_comment.py create mode 100644 js/src/frontend/moz.build create mode 100644 js/src/frontend/smoosh/Cargo.toml create mode 100644 js/src/frontend/smoosh/build.rs create mode 100644 js/src/frontend/smoosh/cbindgen.toml create mode 100644 js/src/frontend/smoosh/moz.build create mode 100644 js/src/frontend/smoosh/src/lib.rs (limited to 'js/src/frontend') diff --git a/js/src/frontend/AbstractScopePtr.cpp b/js/src/frontend/AbstractScopePtr.cpp new file mode 100644 index 0000000000..a2dd16a104 --- /dev/null +++ b/js/src/frontend/AbstractScopePtr.cpp @@ -0,0 +1,61 @@ +/* -*- 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/AbstractScopePtr.h" + +#include "mozilla/Assertions.h" + +#include "frontend/CompilationStencil.h" // CompilationState +#include "frontend/Stencil.h" +#include "js/Vector.h" +#include "vm/Scope.h" // for FunctionScope + +using namespace js; +using namespace js::frontend; + +ScopeStencil& AbstractScopePtr::scopeData() const { + MOZ_ASSERT(isScopeStencil()); + return compilationState_.scopeData[index_]; +} + +ScopeKind AbstractScopePtr::kind() const { + if (isScopeStencil()) { + return scopeData().kind(); + } + return compilationState_.scopeContext.enclosingScopeKind; +} + +AbstractScopePtr AbstractScopePtr::enclosing() const { + MOZ_ASSERT(isScopeStencil()); + return scopeData().enclosing(compilationState_); +} + +bool AbstractScopePtr::hasEnvironment() const { + if (isScopeStencil()) { + return scopeData().hasEnvironment(); + } + return compilationState_.scopeContext.enclosingScopeHasEnvironment; +} + +bool AbstractScopePtr::isArrow() const { + MOZ_ASSERT(is()); + if (isScopeStencil()) { + return scopeData().isArrow(); + } + return compilationState_.scopeContext.enclosingScopeIsArrow; +} + +#ifdef DEBUG +bool AbstractScopePtr::hasNonSyntacticScopeOnChain() const { + if (isScopeStencil()) { + if (kind() == ScopeKind::NonSyntactic) { + return true; + } + return enclosing().hasNonSyntacticScopeOnChain(); + } + return compilationState_.scopeContext.hasNonSyntacticScopeOnChain; +} +#endif diff --git a/js/src/frontend/AbstractScopePtr.h b/js/src/frontend/AbstractScopePtr.h new file mode 100644 index 0000000000..33867632f4 --- /dev/null +++ b/js/src/frontend/AbstractScopePtr.h @@ -0,0 +1,90 @@ +/* -*- 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/. */ + +#ifndef frontend_AbstractScopePtr_h +#define frontend_AbstractScopePtr_h + +#include + +#include "frontend/ScopeIndex.h" +#include "vm/ScopeKind.h" // For ScopeKind + +namespace js { +class Scope; +class GlobalScope; +class EvalScope; + +namespace frontend { +struct CompilationState; +class ScopeStencil; +} // namespace frontend + +// An interface class to support Scope queries in the frontend without requiring +// a GC Allocated scope to necessarily exist. +// +// This abstracts Scope* and a ScopeStencil type used within the frontend before +// the Scope is allocated. +// +// Queries to GC Scope should be pre-calculated and stored into ScopeContext. +class AbstractScopePtr { + private: + // ScopeIndex::invalid() if this points CompilationInput.enclosingScope. + ScopeIndex index_; + + frontend::CompilationState& compilationState_; + + public: + friend class js::Scope; + + AbstractScopePtr(frontend::CompilationState& compilationState, + ScopeIndex index) + : index_(index), compilationState_(compilationState) {} + + static AbstractScopePtr compilationEnclosingScope( + frontend::CompilationState& compilationState) { + return AbstractScopePtr(compilationState, ScopeIndex::invalid()); + } + + private: + bool isScopeStencil() const { return index_.isValid(); } + + frontend::ScopeStencil& scopeData() const; + + public: + // This allows us to check whether or not this provider wraps + // or otherwise would reify to a particular scope type. + template + bool is() const { + static_assert(std::is_base_of_v, + "Trying to ask about non-Scope type"); + return kind() == T::classScopeKind_; + } + + ScopeKind kind() const; + AbstractScopePtr enclosing() const; + bool hasEnvironment() const; + // Valid iff is + bool isArrow() const; + +#ifdef DEBUG + bool hasNonSyntacticScopeOnChain() const; +#endif +}; + +// Specializations of AbstractScopePtr::is +template <> +inline bool AbstractScopePtr::is() const { + return kind() == ScopeKind::Global || kind() == ScopeKind::NonSyntactic; +} + +template <> +inline bool AbstractScopePtr::is() const { + return kind() == ScopeKind::Eval || kind() == ScopeKind::StrictEval; +} + +} // namespace js + +#endif // frontend_AbstractScopePtr_h diff --git a/js/src/frontend/AsyncEmitter.cpp b/js/src/frontend/AsyncEmitter.cpp new file mode 100644 index 0000000000..8596f4ba7f --- /dev/null +++ b/js/src/frontend/AsyncEmitter.cpp @@ -0,0 +1,217 @@ +/* -*- 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/AsyncEmitter.h" + +#include "mozilla/Assertions.h" // MOZ_ASSERT + +#include "frontend/BytecodeEmitter.h" // BytecodeEmitter +#include "frontend/NameOpEmitter.h" // NameOpEmitter +#include "frontend/ParserAtom.h" // TaggedParserAtomIndex +#include "vm/AsyncFunctionResolveKind.h" // AsyncFunctionResolveKind +#include "vm/Opcodes.h" // JSOp + +using namespace js; +using namespace js::frontend; + +bool AsyncEmitter::prepareForParamsWithExpressionOrDestructuring() { + MOZ_ASSERT(state_ == State::Start); +#ifdef DEBUG + state_ = State::Parameters; +#endif + + rejectTryCatch_.emplace(bce_, TryEmitter::Kind::TryCatch, + TryEmitter::ControlKind::NonSyntactic); + return rejectTryCatch_->emitTry(); +} + +bool AsyncEmitter::prepareForParamsWithoutExpressionOrDestructuring() { + MOZ_ASSERT(state_ == State::Start); +#ifdef DEBUG + state_ = State::Parameters; +#endif + return true; +} + +bool AsyncEmitter::emitParamsEpilogue() { + MOZ_ASSERT(state_ == State::Parameters); + + if (rejectTryCatch_) { + // If we get here, we need to reset the TryEmitter. Parameters can't reuse + // the reject try-catch block from the function body, because the body + // may have pushed an additional var-environment. This messes up scope + // resolution for the |.generator| variable, because we'd need different + // hops to reach |.generator| depending on whether the error was thrown + // from the parameters or the function body. + if (!emitRejectCatch()) { + return false; + } + } + +#ifdef DEBUG + state_ = State::PostParams; +#endif + return true; +} + +bool AsyncEmitter::prepareForModule() { + // Unlike functions, modules do not have params that we need to worry about. + // Instead, this code is for setting up the required generator that will be + // used for top level await. Before we can start using top-level await in + // modules, we need to emit a + // |.generator| which we can use to pause and resume execution. + MOZ_ASSERT(state_ == State::Start); + MOZ_ASSERT(bce_->lookupName(TaggedParserAtomIndex::WellKnown::dotGenerator()) + .hasKnownSlot()); + + NameOpEmitter noe(bce_, TaggedParserAtomIndex::WellKnown::dotGenerator(), + NameOpEmitter::Kind::Initialize); + if (!noe.prepareForRhs()) { + // [stack] + return false; + } + if (!bce_->emit1(JSOp::Generator)) { + // [stack] GEN + return false; + } + if (!noe.emitAssignment()) { + // [stack] GEN + return false; + } + if (!bce_->emit1(JSOp::Pop)) { + // [stack] + return false; + } + +#ifdef DEBUG + state_ = State::ModulePrologue; +#endif + + return true; +} + +bool AsyncEmitter::prepareForBody() { + MOZ_ASSERT(state_ == State::PostParams || state_ == State::ModulePrologue); + + rejectTryCatch_.emplace(bce_, TryEmitter::Kind::TryCatch, + TryEmitter::ControlKind::NonSyntactic); +#ifdef DEBUG + state_ = State::Body; +#endif + return rejectTryCatch_->emitTry(); +} + +bool AsyncEmitter::emitEndFunction() { +#ifdef DEBUG + MOZ_ASSERT(state_ == State::Body); +#endif + + // The final yield has already been emitted + // by FunctionScriptEmitter::emitEndBody(). + + if (!emitRejectCatch()) { + return false; + } + +#ifdef DEBUG + state_ = State::End; +#endif + return true; +} + +bool AsyncEmitter::emitEndModule() { +#ifdef DEBUG + MOZ_ASSERT(state_ == State::Body); +#endif + + if (!emitFinalYield()) { + return false; + } + + if (!emitRejectCatch()) { + return false; + } + +#ifdef DEBUG + state_ = State::End; +#endif + return true; +} + +bool AsyncEmitter::emitFinalYield() { + if (!bce_->emit1(JSOp::Undefined)) { + // [stack] UNDEF + return false; + } + + if (!bce_->emitGetDotGeneratorInInnermostScope()) { + // [stack] UNDEF GEN + return false; + } + + if (!bce_->emit2(JSOp::AsyncResolve, + uint8_t(AsyncFunctionResolveKind::Fulfill))) { + // [stack] PROMISE + return false; + } + + if (!bce_->emit1(JSOp::SetRval)) { + // [stack] + return false; + } + + if (!bce_->emitGetDotGeneratorInInnermostScope()) { + // [stack] GEN + return false; + } + + if (!bce_->emitYieldOp(JSOp::FinalYieldRval)) { + // [stack] + return false; + } + + return true; +} + +bool AsyncEmitter::emitRejectCatch() { + if (!rejectTryCatch_->emitCatch()) { + // [stack] EXC + return false; + } + + if (!bce_->emitGetDotGeneratorInInnermostScope()) { + // [stack] EXC GEN + return false; + } + + if (!bce_->emit2(JSOp::AsyncResolve, + uint8_t(AsyncFunctionResolveKind::Reject))) { + // [stack] PROMISE + return false; + } + + if (!bce_->emit1(JSOp::SetRval)) { + // [stack] + return false; + } + + if (!bce_->emitGetDotGeneratorInInnermostScope()) { + // [stack] GEN + return false; + } + + if (!bce_->emitYieldOp(JSOp::FinalYieldRval)) { + // [stack] + return false; + } + + if (!rejectTryCatch_->emitEnd()) { + return false; + } + + rejectTryCatch_.reset(); + return true; +} diff --git a/js/src/frontend/AsyncEmitter.h b/js/src/frontend/AsyncEmitter.h new file mode 100644 index 0000000000..778f708c4f --- /dev/null +++ b/js/src/frontend/AsyncEmitter.h @@ -0,0 +1,174 @@ +/* -*- 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/. */ + +#ifndef frontend_AsyncEmitter_h +#define frontend_AsyncEmitter_h + +#include "mozilla/Attributes.h" // MOZ_STACK_CLASS + +#include "frontend/TryEmitter.h" // TryEmitter + +namespace js { +namespace frontend { + +struct BytecodeEmitter; + +// Class for emitting Bytecode associated with the AsyncFunctionGenerator. +// +// Usage: +// +// For an async function, the params have to be handled separately, +// because the body may have pushed an additional var environment, changing +// the number of hops required to reach the |.generator| variable. In order +// to handle this, we can't reuse the same TryCatch emitter. +// +// Simple case - For a function without expression or destructuring +// parameters: +// `async function f() {}`, +// AsyncEmitter ae(this); +// +// ae.prepareForParamsWithoutExpressionOrDestructuring(); +// // Emit Params. +// ... +// ae.paramsEpilogue(); // We need to emit the epilogue before the extra +// VarScope emitExtraBodyVarScope(); +// +// // Emit new scope +// ae.prepareForBody(); +// +// // Emit body of the Function. +// ... +// +// ae.emitEndFunction(); +// +// Complex case - For a function with expression or destructuring parameters: +// `async function f() {}`, +// AsyncEmitter ae(this); +// +// ae.prepareForParamsWithExpressionOrDestructuring(); +// +// // Emit Params. +// ... +// ae.paramsEpilogue(); // We need to emit the epilogue before the extra +// // VarScope +// emitExtraBodyVarScope(); +// +// // Emit new scope +// ae.prepareForBody(); +// +// // Emit body of the Function. +// ... +// +// // The final yield is emitted in FunctionScriptEmitter::emitEndBody(). +// ae.emitEndFunction(); +// +// +// Async Module case - For a module with `await` in the top level: +// AsyncEmitter ae(this); +// ae.prepareForModule(); // prepareForModule is used to setup the generator +// // for the async module. +// switchToMain(); +// ... +// +// // Emit new scope +// ae.prepareForBody(); +// +// // Emit body of the Script. +// +// ae.emitEndModule(); +// + +class MOZ_STACK_CLASS AsyncEmitter { + private: + BytecodeEmitter* bce_; + + // try-catch block for async function parameter and body. + mozilla::Maybe rejectTryCatch_; + +#ifdef DEBUG + // The state of this emitter. + // + // +-------+ + // | Start |-+ + // +-------+ | + // | + // +----------+ + // | + // | [Parameters with Expression or Destructuring] + // | prepareForParamsWithExpressionOrDestructuring +------------+ + // +----------------------------------------------------| Parameters |-->+ + // | +------------+ | + // | | + // | [Parameters Without Expression or Destructuring] | + // | prepareForParamsWithoutExpressionOrDestructuring +------------+ | + // +----------------------------------------------------| Parameters |-->+ + // | +------------+ | + // | [Modules] | + // | prepareForModule +----------------+ | + // +-------------------->| ModulePrologue |--+ | + // +----------------+ | | + // | | + // | | + // +-----------------------------------------+ | + // | | + // | | + // V +------------+ paramsEpilogue | + // +<--------------------| PostParams |<---------------------------------+ + // | +------------+ + // | + // | [Script body] + // | prepareForBody +---------+ + // +-------------------->| Body |--------+ + // +---------+ | + // +----------------------------------------+ + // | + // | [Functions] + // | emitEndFunction +-----+ + // +--------------------->| End | + // | +-----+ + // | + // | [Modules] + // | emitEndModule +-----+ + // +--------------------->| End | + // +-----+ + + enum class State { + // The initial state. + Start, + + Parameters, + + ModulePrologue, + + PostParams, + + Body, + + End, + }; + + State state_ = State::Start; +#endif + + [[nodiscard]] bool emitRejectCatch(); + [[nodiscard]] bool emitFinalYield(); + + public: + explicit AsyncEmitter(BytecodeEmitter* bce) : bce_(bce){}; + + [[nodiscard]] bool prepareForParamsWithoutExpressionOrDestructuring(); + [[nodiscard]] bool prepareForParamsWithExpressionOrDestructuring(); + [[nodiscard]] bool prepareForModule(); + [[nodiscard]] bool emitParamsEpilogue(); + [[nodiscard]] bool prepareForBody(); + [[nodiscard]] bool emitEndFunction(); + [[nodiscard]] bool emitEndModule(); +}; + +} /* namespace frontend */ +} /* namespace js */ + +#endif /* frontend_AsyncEmitter_h */ diff --git a/js/src/frontend/BytecodeCompilation.h b/js/src/frontend/BytecodeCompilation.h new file mode 100644 index 0000000000..f6bd649725 --- /dev/null +++ b/js/src/frontend/BytecodeCompilation.h @@ -0,0 +1,108 @@ +/* -*- 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/. */ + +#ifndef frontend_BytecodeCompilation_h +#define frontend_BytecodeCompilation_h + +#include "mozilla/AlreadyAddRefed.h" +#include "mozilla/Utf8.h" // mozilla::Utf8Unit + +#include "frontend/ScriptIndex.h" // ScriptIndex +#include "js/CompileOptions.h" // JS::ReadOnlyCompileOptions, JS::InstantiateOptions +#include "js/SourceText.h" // JS::SourceText +#include "js/TypeDecls.h" // JS::Handle (fwd) +#include "js/UniquePtr.h" // js::UniquePtr +#include "vm/ScopeKind.h" // js::ScopeKind + +namespace js { + +class Scope; +class LifoAlloc; +class FrontendContext; + +namespace frontend { + +struct CompilationInput; +struct CompilationGCOutput; +struct CompilationStencil; +struct ExtensibleCompilationStencil; +class ScopeBindingCache; + +extern already_AddRefed CompileGlobalScriptToStencil( + JSContext* maybeCx, FrontendContext* fc, js::LifoAlloc& tempLifoAlloc, + CompilationInput& input, ScopeBindingCache* scopeCache, + JS::SourceText& srcBuf, ScopeKind scopeKind); + +extern already_AddRefed CompileGlobalScriptToStencil( + JSContext* maybeCx, FrontendContext* fc, js::LifoAlloc& tempLifoAlloc, + CompilationInput& input, ScopeBindingCache* scopeCache, + JS::SourceText& srcBuf, ScopeKind scopeKind); + +extern UniquePtr +CompileGlobalScriptToExtensibleStencil(JSContext* maybeCx, FrontendContext* fc, + CompilationInput& input, + ScopeBindingCache* scopeCache, + JS::SourceText& srcBuf, + ScopeKind scopeKind); + +extern UniquePtr +CompileGlobalScriptToExtensibleStencil( + JSContext* maybeCx, FrontendContext* fc, CompilationInput& input, + ScopeBindingCache* scopeCache, JS::SourceText& srcBuf, + ScopeKind scopeKind); + +// Perform some operation to reduce the time taken by instantiation. +// +// Part of InstantiateStencils can be done by calling PrepareForInstantiate. +// PrepareForInstantiate is GC-free operation that can be performed +// off-main-thread without parse global. +[[nodiscard]] extern bool PrepareForInstantiate( + JSContext* maybeCx, FrontendContext* fc, CompilationInput& input, + const CompilationStencil& stencil, CompilationGCOutput& gcOutput); + +[[nodiscard]] extern bool InstantiateStencils(JSContext* cx, + CompilationInput& input, + const CompilationStencil& stencil, + CompilationGCOutput& gcOutput); + +extern JSScript* CompileGlobalScript(JSContext* cx, FrontendContext* fc, + const JS::ReadOnlyCompileOptions& options, + JS::SourceText& srcBuf, + ScopeKind scopeKind); + +extern JSScript* CompileGlobalScript(JSContext* cx, FrontendContext* fc, + const JS::ReadOnlyCompileOptions& options, + JS::SourceText& srcBuf, + ScopeKind scopeKind); + +extern JSScript* CompileEvalScript(JSContext* cx, + const JS::ReadOnlyCompileOptions& options, + JS::SourceText& srcBuf, + JS::Handle enclosingScope, + JS::Handle enclosingEnv); + +extern bool DelazifyCanonicalScriptedFunction(JSContext* cx, + FrontendContext* fc, + JS::Handle fun); + +extern already_AddRefed DelazifyCanonicalScriptedFunction( + JSContext* cx, FrontendContext* fc, ScopeBindingCache* scopeCache, + CompilationStencil& context, ScriptIndex scriptIndex); + +// Certain compile options will disable the syntax parser entirely. +inline bool CanLazilyParse(const JS::ReadOnlyCompileOptions& options) { + return !options.discardSource && !options.sourceIsLazy && + !options.forceFullParse(); +} + +void FireOnNewScript(JSContext* cx, const JS::InstantiateOptions& options, + JS::Handle script); + +} // namespace frontend + +} // namespace js + +#endif // frontend_BytecodeCompilation_h diff --git a/js/src/frontend/BytecodeCompiler.cpp b/js/src/frontend/BytecodeCompiler.cpp new file mode 100644 index 0000000000..e4c709e7e7 --- /dev/null +++ b/js/src/frontend/BytecodeCompiler.cpp @@ -0,0 +1,1565 @@ +/* -*- 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/BytecodeCompiler.h" + +#include "mozilla/Attributes.h" +#include "mozilla/Maybe.h" +#include "mozilla/Utf8.h" // mozilla::Utf8Unit +#include "mozilla/Variant.h" // mozilla::Variant + +#include "debugger/DebugAPI.h" +#include "ds/LifoAlloc.h" +#include "frontend/BytecodeCompilation.h" +#include "frontend/BytecodeEmitter.h" +#include "frontend/CompilationStencil.h" +#include "frontend/EitherParser.h" +#ifdef JS_ENABLE_SMOOSH +# include "frontend/Frontend2.h" // Smoosh +#endif +#include "frontend/FrontendContext.h" // AutoReportFrontendContext +#include "frontend/ModuleSharedContext.h" +#include "js/experimental/JSStencil.h" +#include "js/Modules.h" // JS::ImportAssertionVector +#include "js/SourceText.h" +#include "js/UniquePtr.h" +#include "vm/FunctionFlags.h" // FunctionFlags +#include "vm/GeneratorAndAsyncKind.h" // js::GeneratorKind, js::FunctionAsyncKind +#include "vm/HelperThreads.h" // StartOffThreadDelazification, WaitForAllDelazifyTasks +#include "vm/JSContext.h" +#include "vm/JSScript.h" // ScriptSource, UncompressedSourceCache +#include "vm/ModuleBuilder.h" // js::ModuleBuilder +#include "vm/Time.h" // AutoIncrementalTimer +#include "wasm/AsmJS.h" + +#include "vm/GeckoProfiler-inl.h" +#include "vm/JSContext-inl.h" + +using namespace js; +using namespace js::frontend; + +using mozilla::Maybe; +using mozilla::Utf8Unit; + +using JS::CompileOptions; +using JS::ReadOnlyCompileOptions; +using JS::SourceText; + +// RAII class to check the frontend reports an exception when it fails to +// compile a script. +class MOZ_RAII AutoAssertReportedException { +#ifdef DEBUG + JSContext* maybeCx_; + FrontendContext* fc_; + bool check_; + + public: + explicit AutoAssertReportedException(JSContext* maybeCx, FrontendContext* fc) + : maybeCx_(maybeCx), fc_(fc), check_(true) {} + void reset() { check_ = false; } + ~AutoAssertReportedException() { + if (!check_) { + return; + } + + // Error while compiling self-hosted code isn't set as an exception. + // TODO: Remove this once all errors are added to frontend context. + if (maybeCx_ && !maybeCx_->runtime()->hasInitializedSelfHosting()) { + return; + } + + // TODO: Remove this once JSContext is removed from frontend. + if (maybeCx_ && !maybeCx_->isHelperThreadContext()) { + MOZ_ASSERT(maybeCx_->isExceptionPending() || fc_->hadErrors()); + } else { + MOZ_ASSERT(fc_->hadErrors()); + } + } +#else + public: + explicit AutoAssertReportedException(JSContext*, FrontendContext*) {} + void reset() {} +#endif +}; + +static bool EmplaceEmitter(CompilationState& compilationState, + Maybe& emitter, FrontendContext* fc, + const EitherParser& parser, SharedContext* sc); + +template +class MOZ_STACK_CLASS SourceAwareCompiler { + protected: + SourceText& sourceBuffer_; + + CompilationState compilationState_; + + Maybe> syntaxParser; + Maybe> parser; + FrontendContext* fc_ = nullptr; + + using TokenStreamPosition = frontend::TokenStreamPosition; + + protected: + explicit SourceAwareCompiler(FrontendContext* fc, + LifoAllocScope& parserAllocScope, + CompilationInput& input, + SourceText& sourceBuffer) + : sourceBuffer_(sourceBuffer), + compilationState_(fc, parserAllocScope, input) { + MOZ_ASSERT(sourceBuffer_.get() != nullptr); + } + + [[nodiscard]] bool init(FrontendContext* fc, ScopeBindingCache* scopeCache, + InheritThis inheritThis = InheritThis::No, + JSObject* enclosingEnv = nullptr) { + if (!compilationState_.init(fc, scopeCache, inheritThis, enclosingEnv)) { + return false; + } + + return createSourceAndParser(fc); + } + + // Call this before calling compile{Global,Eval}Script. + [[nodiscard]] bool createSourceAndParser(FrontendContext* fc); + + void assertSourceAndParserCreated() const { + MOZ_ASSERT(compilationState_.source != nullptr); + MOZ_ASSERT(parser.isSome()); + } + + void assertSourceParserAndScriptCreated() { assertSourceAndParserCreated(); } + + [[nodiscard]] bool emplaceEmitter(Maybe& emitter, + SharedContext* sharedContext) { + return EmplaceEmitter(compilationState_, emitter, fc_, + EitherParser(parser.ptr()), sharedContext); + } + + bool canHandleParseFailure(const Directives& newDirectives); + + void handleParseFailure( + const Directives& newDirectives, TokenStreamPosition& startPosition, + CompilationState::CompilationStatePosition& startStatePosition); + + public: + CompilationState& compilationState() { return compilationState_; }; + + ExtensibleCompilationStencil& stencil() { return compilationState_; } +}; + +template +class MOZ_STACK_CLASS ScriptCompiler : public SourceAwareCompiler { + using Base = SourceAwareCompiler; + + protected: + using Base::compilationState_; + using Base::parser; + using Base::sourceBuffer_; + + using Base::assertSourceParserAndScriptCreated; + using Base::canHandleParseFailure; + using Base::emplaceEmitter; + using Base::handleParseFailure; + + using typename Base::TokenStreamPosition; + + public: + explicit ScriptCompiler(FrontendContext* fc, LifoAllocScope& parserAllocScope, + CompilationInput& input, + SourceText& sourceBuffer) + : Base(fc, parserAllocScope, input, sourceBuffer) {} + + using Base::init; + using Base::stencil; + + [[nodiscard]] bool compile(JSContext* cx, SharedContext* sc); +}; + +#ifdef JS_ENABLE_SMOOSH +[[nodiscard]] static bool TrySmoosh( + JSContext* cx, FrontendContext* fc, CompilationInput& input, + JS::SourceText& srcBuf, + UniquePtr& stencilOut) { + MOZ_ASSERT(!stencilOut); + + if (!cx->options().trySmoosh()) { + return true; + } + + JSRuntime* rt = cx->runtime(); + if (!Smoosh::tryCompileGlobalScriptToExtensibleStencil(cx, fc, input, srcBuf, + stencilOut)) { + return false; + } + + if (cx->options().trackNotImplemented()) { + if (stencilOut) { + rt->parserWatcherFile.put("1"); + } else { + rt->parserWatcherFile.put("0"); + } + } + + if (!stencilOut) { + fprintf(stderr, "Falling back!\n"); + return true; + } + + return stencilOut->source->assignSource(fc, input.options, srcBuf); +} + +[[nodiscard]] static bool TrySmoosh( + JSContext* cx, FrontendContext* fc, CompilationInput& input, + JS::SourceText& srcBuf, + UniquePtr& stencilOut) { + MOZ_ASSERT(!stencilOut); + return true; +} +#endif // JS_ENABLE_SMOOSH + +using BytecodeCompilerOutput = + mozilla::Variant, + RefPtr, CompilationGCOutput*>; + +// Compile global script, and return it as one of: +// * ExtensibleCompilationStencil (without instantiation) +// * CompilationStencil (without instantiation, has no external dependency) +// * CompilationGCOutput (with instantiation). +template +[[nodiscard]] static bool CompileGlobalScriptToStencilAndMaybeInstantiate( + JSContext* maybeCx, FrontendContext* fc, js::LifoAlloc& tempLifoAlloc, + CompilationInput& input, ScopeBindingCache* scopeCache, + JS::SourceText& srcBuf, ScopeKind scopeKind, + BytecodeCompilerOutput& output) { +#ifdef JS_ENABLE_SMOOSH + if (maybeCx) { + UniquePtr extensibleStencil; + if (!TrySmoosh(maybeCx, fc, input, srcBuf, extensibleStencil)) { + return false; + } + if (extensibleStencil) { + if (input.options.populateDelazificationCache() && + !maybeCx->isHelperThreadContext()) { + BorrowingCompilationStencil borrowingStencil(*extensibleStencil); + StartOffThreadDelazification(maybeCx, input.options, borrowingStencil); + + // When we are trying to validate whether on-demand delazification + // generate the same stencil as concurrent delazification, we want to + // parse everything eagerly off-thread ahead of re-parsing everything on + // demand, to compare the outcome. + if (input.options.waitForDelazificationCache()) { + WaitForAllDelazifyTasks(maybeCx->runtime()); + } + } + if (output.is>()) { + output.as>() = + std::move(extensibleStencil); + } else if (output.is>()) { + RefPtr stencil = + fc->getAllocator()->new_( + std::move(extensibleStencil)); + if (!stencil) { + return false; + } + + output.as>() = std::move(stencil); + } else { + BorrowingCompilationStencil borrowingStencil(*extensibleStencil); + if (!InstantiateStencils(maybeCx, input, borrowingStencil, + *(output.as()))) { + return false; + } + } + return true; + } + } +#endif // JS_ENABLE_SMOOSH + + if (input.options.selfHostingMode) { + if (!input.initForSelfHostingGlobal(fc)) { + return false; + } + } else { + if (!input.initForGlobal(fc)) { + return false; + } + } + + AutoAssertReportedException assertException(maybeCx, fc); + + LifoAllocScope parserAllocScope(&tempLifoAlloc); + ScriptCompiler compiler(fc, parserAllocScope, input, srcBuf); + if (!compiler.init(fc, scopeCache)) { + return false; + } + + SourceExtent extent = SourceExtent::makeGlobalExtent( + srcBuf.length(), input.options.lineno, input.options.column); + + GlobalSharedContext globalsc(fc, scopeKind, input.options, + compiler.compilationState().directives, extent); + + if (!compiler.compile(maybeCx, &globalsc)) { + return false; + } + + if (input.options.populateDelazificationCache() && maybeCx && + !maybeCx->isHelperThreadContext()) { + BorrowingCompilationStencil borrowingStencil(compiler.stencil()); + StartOffThreadDelazification(maybeCx, input.options, borrowingStencil); + + // When we are trying to validate whether on-demand delazification + // generate the same stencil as concurrent delazification, we want to + // parse everything eagerly off-thread ahead of re-parsing everything on + // demand, to compare the outcome. + if (input.options.waitForDelazificationCache()) { + WaitForAllDelazifyTasks(maybeCx->runtime()); + } + } + + if (output.is>()) { + auto stencil = + fc->getAllocator()->make_unique( + std::move(compiler.stencil())); + if (!stencil) { + return false; + } + output.as>() = std::move(stencil); + } else if (output.is>()) { + Maybe pseudoFrame; + if (maybeCx) { + pseudoFrame.emplace(maybeCx, "script emit", + JS::ProfilingCategoryPair::JS_Parsing); + } + + auto extensibleStencil = + fc->getAllocator()->make_unique( + std::move(compiler.stencil())); + if (!extensibleStencil) { + return false; + } + + RefPtr stencil = + fc->getAllocator()->new_( + std::move(extensibleStencil)); + if (!stencil) { + return false; + } + + output.as>() = std::move(stencil); + } else { + MOZ_ASSERT(maybeCx); + BorrowingCompilationStencil borrowingStencil(compiler.stencil()); + if (!InstantiateStencils(maybeCx, input, borrowingStencil, + *(output.as()))) { + return false; + } + } + + assertException.reset(); + return true; +} + +template +static already_AddRefed CompileGlobalScriptToStencilImpl( + JSContext* maybeCx, FrontendContext* fc, js::LifoAlloc& tempLifoAlloc, + CompilationInput& input, ScopeBindingCache* scopeCache, + JS::SourceText& srcBuf, ScopeKind scopeKind) { + using OutputType = RefPtr; + BytecodeCompilerOutput output((OutputType())); + if (!CompileGlobalScriptToStencilAndMaybeInstantiate( + maybeCx, fc, tempLifoAlloc, input, scopeCache, srcBuf, scopeKind, + output)) { + return nullptr; + } + return output.as().forget(); +} + +already_AddRefed frontend::CompileGlobalScriptToStencil( + JSContext* cx, FrontendContext* fc, js::LifoAlloc& tempLifoAlloc, + CompilationInput& input, ScopeBindingCache* scopeCache, + JS::SourceText& srcBuf, ScopeKind scopeKind) { + return CompileGlobalScriptToStencilImpl(cx, fc, tempLifoAlloc, input, + scopeCache, srcBuf, scopeKind); +} + +already_AddRefed frontend::CompileGlobalScriptToStencil( + JSContext* cx, FrontendContext* fc, js::LifoAlloc& tempLifoAlloc, + CompilationInput& input, ScopeBindingCache* scopeCache, + JS::SourceText& srcBuf, ScopeKind scopeKind) { + return CompileGlobalScriptToStencilImpl(cx, fc, tempLifoAlloc, input, + scopeCache, srcBuf, scopeKind); +} + +template +static UniquePtr +CompileGlobalScriptToExtensibleStencilImpl(JSContext* maybeCx, + FrontendContext* fc, + CompilationInput& input, + ScopeBindingCache* scopeCache, + JS::SourceText& srcBuf, + ScopeKind scopeKind) { + using OutputType = UniquePtr; + BytecodeCompilerOutput output((OutputType())); + if (!CompileGlobalScriptToStencilAndMaybeInstantiate( + maybeCx, fc, maybeCx->tempLifoAlloc(), input, scopeCache, srcBuf, + scopeKind, output)) { + return nullptr; + } + return std::move(output.as()); +} + +UniquePtr +frontend::CompileGlobalScriptToExtensibleStencil( + JSContext* maybeCx, FrontendContext* fc, CompilationInput& input, + ScopeBindingCache* scopeCache, JS::SourceText& srcBuf, + ScopeKind scopeKind) { + return CompileGlobalScriptToExtensibleStencilImpl( + maybeCx, fc, input, scopeCache, srcBuf, scopeKind); +} + +UniquePtr +frontend::CompileGlobalScriptToExtensibleStencil( + JSContext* cx, FrontendContext* fc, CompilationInput& input, + ScopeBindingCache* scopeCache, JS::SourceText& srcBuf, + ScopeKind scopeKind) { + return CompileGlobalScriptToExtensibleStencilImpl(cx, fc, input, scopeCache, + srcBuf, scopeKind); +} + +bool frontend::InstantiateStencils(JSContext* cx, CompilationInput& input, + const CompilationStencil& stencil, + CompilationGCOutput& gcOutput) { + { + AutoGeckoProfilerEntry pseudoFrame(cx, "stencil instantiate", + JS::ProfilingCategoryPair::JS_Parsing); + + if (!CompilationStencil::instantiateStencils(cx, input, stencil, + gcOutput)) { + return false; + } + } + + // Enqueue an off-thread source compression task after finishing parsing. + if (!cx->isHelperThreadContext()) { + if (!stencil.source->tryCompressOffThread(cx)) { + return false; + } + + Rooted script(cx, gcOutput.script); + const JS::InstantiateOptions instantiateOptions(input.options); + FireOnNewScript(cx, instantiateOptions, script); + } + + return true; +} + +bool frontend::PrepareForInstantiate(JSContext* maybeCx, FrontendContext* fc, + CompilationInput& input, + const CompilationStencil& stencil, + CompilationGCOutput& gcOutput) { + Maybe pseudoFrame; + if (maybeCx) { + pseudoFrame.emplace(maybeCx, "stencil instantiate", + JS::ProfilingCategoryPair::JS_Parsing); + } + + return CompilationStencil::prepareForInstantiate(fc, input.atomCache, stencil, + gcOutput); +} + +template +static JSScript* CompileGlobalScriptImpl( + JSContext* cx, FrontendContext* fc, + const JS::ReadOnlyCompileOptions& options, JS::SourceText& srcBuf, + ScopeKind scopeKind) { + Rooted input(cx, CompilationInput(options)); + Rooted gcOutput(cx); + BytecodeCompilerOutput output(gcOutput.address()); + NoScopeBindingCache scopeCache; + if (!CompileGlobalScriptToStencilAndMaybeInstantiate( + cx, fc, cx->tempLifoAlloc(), input.get(), &scopeCache, srcBuf, + scopeKind, output)) { + return nullptr; + } + return gcOutput.get().script; +} + +JSScript* frontend::CompileGlobalScript( + JSContext* cx, FrontendContext* fc, + const JS::ReadOnlyCompileOptions& options, JS::SourceText& srcBuf, + ScopeKind scopeKind) { + return CompileGlobalScriptImpl(cx, fc, options, srcBuf, scopeKind); +} + +JSScript* frontend::CompileGlobalScript( + JSContext* cx, FrontendContext* fc, + const JS::ReadOnlyCompileOptions& options, JS::SourceText& srcBuf, + ScopeKind scopeKind) { + return CompileGlobalScriptImpl(cx, fc, options, srcBuf, scopeKind); +} + +template +static JSScript* CompileEvalScriptImpl( + JSContext* cx, const JS::ReadOnlyCompileOptions& options, + SourceText& srcBuf, JS::Handle enclosingScope, + JS::Handle enclosingEnv) { + JS::Rooted script(cx); + { + AutoReportFrontendContext fc(cx); + AutoAssertReportedException assertException(cx, &fc); + + Rooted input(cx, CompilationInput(options)); + if (!input.get().initForEval(&fc, enclosingScope)) { + return nullptr; + } + + LifoAllocScope parserAllocScope(&cx->tempLifoAlloc()); + + ScopeBindingCache* scopeCache = &cx->caches().scopeCache; + ScriptCompiler compiler(&fc, parserAllocScope, input.get(), srcBuf); + if (!compiler.init(&fc, scopeCache, InheritThis::Yes, enclosingEnv)) { + return nullptr; + } + + uint32_t len = srcBuf.length(); + SourceExtent extent = + SourceExtent::makeGlobalExtent(len, options.lineno, options.column); + EvalSharedContext evalsc(&fc, compiler.compilationState(), extent); + if (!compiler.compile(cx, &evalsc)) { + return nullptr; + } + + Rooted gcOutput(cx); + { + BorrowingCompilationStencil borrowingStencil(compiler.stencil()); + if (!InstantiateStencils(cx, input.get(), borrowingStencil, + gcOutput.get())) { + return nullptr; + } + } + + assertException.reset(); + script = gcOutput.get().script; + } + return script; +} + +JSScript* frontend::CompileEvalScript(JSContext* cx, + const JS::ReadOnlyCompileOptions& options, + JS::SourceText& srcBuf, + JS::Handle enclosingScope, + JS::Handle enclosingEnv) { + return CompileEvalScriptImpl(cx, options, srcBuf, enclosingScope, + enclosingEnv); +} + +template +class MOZ_STACK_CLASS ModuleCompiler final : public SourceAwareCompiler { + using Base = SourceAwareCompiler; + + using Base::assertSourceParserAndScriptCreated; + using Base::compilationState_; + using Base::emplaceEmitter; + using Base::parser; + + public: + explicit ModuleCompiler(FrontendContext* fc, LifoAllocScope& parserAllocScope, + CompilationInput& input, + SourceText& sourceBuffer) + : Base(fc, parserAllocScope, input, sourceBuffer) {} + + using Base::init; + using Base::stencil; + + [[nodiscard]] bool compile(JSContext* maybeCx, FrontendContext* fc); +}; + +template +class MOZ_STACK_CLASS StandaloneFunctionCompiler final + : public SourceAwareCompiler { + using Base = SourceAwareCompiler; + + using Base::assertSourceAndParserCreated; + using Base::canHandleParseFailure; + using Base::compilationState_; + using Base::emplaceEmitter; + using Base::handleParseFailure; + using Base::parser; + using Base::sourceBuffer_; + + using typename Base::TokenStreamPosition; + + public: + explicit StandaloneFunctionCompiler(FrontendContext* fc, + LifoAllocScope& parserAllocScope, + CompilationInput& input, + SourceText& sourceBuffer) + : Base(fc, parserAllocScope, input, sourceBuffer) {} + + using Base::init; + using Base::stencil; + + private: + FunctionNode* parse(JSContext* cx, FunctionSyntaxKind syntaxKind, + GeneratorKind generatorKind, FunctionAsyncKind asyncKind, + const Maybe& parameterListEnd); + + public: + [[nodiscard]] bool compile(JSContext* cx, FunctionSyntaxKind syntaxKind, + GeneratorKind generatorKind, + FunctionAsyncKind asyncKind, + const Maybe& parameterListEnd); +}; + +template +bool SourceAwareCompiler::createSourceAndParser(FrontendContext* fc) { + const auto& options = compilationState_.input.options; + + fc_ = fc; + + if (!compilationState_.source->assignSource(fc, options, sourceBuffer_)) { + return false; + } + + MOZ_ASSERT(compilationState_.canLazilyParse == + CanLazilyParse(compilationState_.input.options)); + if (compilationState_.canLazilyParse) { + syntaxParser.emplace(fc_, options, sourceBuffer_.units(), + sourceBuffer_.length(), + /* foldConstants = */ false, compilationState_, + /* syntaxParser = */ nullptr); + if (!syntaxParser->checkOptions()) { + return false; + } + } + + parser.emplace(fc_, options, sourceBuffer_.units(), sourceBuffer_.length(), + /* foldConstants = */ true, compilationState_, + syntaxParser.ptrOr(nullptr)); + parser->ss = compilationState_.source.get(); + return parser->checkOptions(); +} + +static bool EmplaceEmitter(CompilationState& compilationState, + Maybe& emitter, FrontendContext* fc, + const EitherParser& parser, SharedContext* sc) { + BytecodeEmitter::EmitterMode emitterMode = + sc->selfHosted() ? BytecodeEmitter::SelfHosting : BytecodeEmitter::Normal; + emitter.emplace(fc, parser, sc, compilationState, emitterMode); + return emitter->init(); +} + +template +bool SourceAwareCompiler::canHandleParseFailure( + const Directives& newDirectives) { + // Try to reparse if no parse errors were thrown and the directives changed. + // + // NOTE: + // Only the following two directive changes force us to reparse the script: + // - The "use asm" directive was encountered. + // - The "use strict" directive was encountered and duplicate parameter names + // are present. We reparse in this case to display the error at the correct + // source location. See |Parser::hasValidSimpleStrictParameterNames()|. + return !parser->anyChars.hadError() && + compilationState_.directives != newDirectives; +} + +template +void SourceAwareCompiler::handleParseFailure( + const Directives& newDirectives, TokenStreamPosition& startPosition, + CompilationState::CompilationStatePosition& startStatePosition) { + MOZ_ASSERT(canHandleParseFailure(newDirectives)); + + // Rewind to starting position to retry. + parser->tokenStream.rewind(startPosition); + compilationState_.rewind(startStatePosition); + + // Assignment must be monotonic to prevent reparsing iloops + MOZ_ASSERT_IF(compilationState_.directives.strict(), newDirectives.strict()); + MOZ_ASSERT_IF(compilationState_.directives.asmJS(), newDirectives.asmJS()); + compilationState_.directives = newDirectives; +} + +template +bool ScriptCompiler::compile(JSContext* maybeCx, SharedContext* sc) { + assertSourceParserAndScriptCreated(); + + TokenStreamPosition startPosition(parser->tokenStream); + + // Emplace the topLevel stencil + MOZ_ASSERT(compilationState_.scriptData.length() == + CompilationStencil::TopLevelIndex); + if (!compilationState_.appendScriptStencilAndData(sc->fc_)) { + return false; + } + + ParseNode* pn; + { + Maybe pseudoFrame; + if (maybeCx) { + pseudoFrame.emplace(maybeCx, "script parsing", + JS::ProfilingCategoryPair::JS_Parsing); + } + if (sc->isEvalContext()) { + pn = parser->evalBody(sc->asEvalContext()); + } else { + pn = parser->globalBody(sc->asGlobalContext()); + } + } + + if (!pn) { + // Global and eval scripts don't get reparsed after a new directive was + // encountered: + // - "use strict" doesn't require any special error reporting for scripts. + // - "use asm" directives don't have an effect in global/eval contexts. + MOZ_ASSERT(!canHandleParseFailure(compilationState_.directives)); + return false; + } + + { + // Successfully parsed. Emit the script. + Maybe pseudoFrame; + if (maybeCx) { + pseudoFrame.emplace(maybeCx, "script emit", + JS::ProfilingCategoryPair::JS_Parsing); + } + + Maybe emitter; + if (!emplaceEmitter(emitter, sc)) { + return false; + } + + if (!emitter->emitScript(pn)) { + return false; + } + } + + MOZ_ASSERT(!this->fc_->hadErrors()); + + return true; +} + +template +bool ModuleCompiler::compile(JSContext* maybeCx, FrontendContext* fc) { + // Emplace the topLevel stencil + MOZ_ASSERT(compilationState_.scriptData.length() == + CompilationStencil::TopLevelIndex); + if (!compilationState_.appendScriptStencilAndData(fc)) { + return false; + } + + ModuleBuilder builder(fc, parser.ptr()); + + const auto& options = compilationState_.input.options; + + uint32_t len = this->sourceBuffer_.length(); + SourceExtent extent = + SourceExtent::makeGlobalExtent(len, options.lineno, options.column); + ModuleSharedContext modulesc(fc, options, builder, extent); + + ParseNode* pn = parser->moduleBody(&modulesc); + if (!pn) { + return false; + } + + Maybe emitter; + if (!emplaceEmitter(emitter, &modulesc)) { + return false; + } + + if (!emitter->emitScript(pn->as().body())) { + return false; + } + + StencilModuleMetadata& moduleMetadata = *compilationState_.moduleMetadata; + + builder.finishFunctionDecls(moduleMetadata); + + MOZ_ASSERT(!this->fc_->hadErrors()); + + return true; +} + +// Parse a standalone JS function, which might appear as the value of an +// event handler attribute in an HTML tag, or in a Function() +// constructor. +template +FunctionNode* StandaloneFunctionCompiler::parse( + JSContext* cx, FunctionSyntaxKind syntaxKind, GeneratorKind generatorKind, + FunctionAsyncKind asyncKind, const Maybe& parameterListEnd) { + assertSourceAndParserCreated(); + + TokenStreamPosition startPosition(parser->tokenStream); + auto startStatePosition = compilationState_.getPosition(); + + // Speculatively parse using the default directives implied by the context. + // If a directive is encountered (e.g., "use strict") that changes how the + // function should have been parsed, we backup and reparse with the new set + // of directives. + + FunctionNode* fn; + for (;;) { + Directives newDirectives = compilationState_.directives; + fn = parser->standaloneFunction(parameterListEnd, syntaxKind, generatorKind, + asyncKind, compilationState_.directives, + &newDirectives); + if (fn) { + break; + } + + // Maybe we encountered a new directive. See if we can try again. + if (!canHandleParseFailure(newDirectives)) { + return nullptr; + } + + handleParseFailure(newDirectives, startPosition, startStatePosition); + } + + return fn; +} + +// Compile a standalone JS function. +template +bool StandaloneFunctionCompiler::compile( + JSContext* cx, FunctionSyntaxKind syntaxKind, GeneratorKind generatorKind, + FunctionAsyncKind asyncKind, const Maybe& parameterListEnd) { + FunctionNode* parsedFunction = + parse(cx, syntaxKind, generatorKind, asyncKind, parameterListEnd); + if (!parsedFunction) { + return false; + } + + FunctionBox* funbox = parsedFunction->funbox(); + + if (funbox->isInterpreted()) { + Maybe emitter; + if (!emplaceEmitter(emitter, funbox)) { + return false; + } + + if (!emitter->emitFunctionScript(parsedFunction)) { + return false; + } + + // The parser extent has stripped off the leading `function...` but + // we want the SourceExtent used in the final standalone script to + // start from the beginning of the buffer, and use the provided + // line and column. + const auto& options = compilationState_.input.options; + compilationState_.scriptExtra[CompilationStencil::TopLevelIndex].extent = + SourceExtent{/* sourceStart = */ 0, + sourceBuffer_.length(), + funbox->extent().toStringStart, + funbox->extent().toStringEnd, + options.lineno, + options.column}; + } else { + // The asm.js module was created by parser. Instantiation below will + // allocate the JSFunction that wraps it. + MOZ_ASSERT(funbox->isAsmJSModule()); + MOZ_ASSERT(compilationState_.asmJS->moduleMap.has(funbox->index())); + MOZ_ASSERT(compilationState_.scriptData[CompilationStencil::TopLevelIndex] + .functionFlags.isAsmJSNative()); + } + + return true; +} + +// Compile module, and return it as one of: +// * ExtensibleCompilationStencil (without instantiation) +// * CompilationStencil (without instantiation, has no external dependency) +// * CompilationGCOutput (with instantiation). +template +[[nodiscard]] static bool ParseModuleToStencilAndMaybeInstantiate( + JSContext* maybeCx, FrontendContext* fc, js::LifoAlloc& tempLifoAlloc, + CompilationInput& input, ScopeBindingCache* scopeCache, + SourceText& srcBuf, BytecodeCompilerOutput& output) { + MOZ_ASSERT(srcBuf.get()); + + if (!input.initForModule(fc)) { + return false; + } + + AutoAssertReportedException assertException(maybeCx, fc); + + LifoAllocScope parserAllocScope(&tempLifoAlloc); + ModuleCompiler compiler(fc, parserAllocScope, input, srcBuf); + if (!compiler.init(fc, scopeCache)) { + return false; + } + + if (!compiler.compile(maybeCx, fc)) { + return false; + } + + if (output.is>()) { + auto stencil = + fc->getAllocator()->make_unique( + std::move(compiler.stencil())); + if (!stencil) { + return false; + } + output.as>() = std::move(stencil); + } else if (output.is>()) { + Maybe pseudoFrame; + if (maybeCx) { + pseudoFrame.emplace(maybeCx, "script emit", + JS::ProfilingCategoryPair::JS_Parsing); + } + + auto extensibleStencil = + fc->getAllocator()->make_unique( + std::move(compiler.stencil())); + if (!extensibleStencil) { + return false; + } + + RefPtr stencil = + fc->getAllocator()->new_( + std::move(extensibleStencil)); + if (!stencil) { + return false; + } + + output.as>() = std::move(stencil); + } else { + MOZ_ASSERT(maybeCx); + BorrowingCompilationStencil borrowingStencil(compiler.stencil()); + if (!InstantiateStencils(maybeCx, input, borrowingStencil, + *(output.as()))) { + return false; + } + } + + assertException.reset(); + return true; +} + +template +already_AddRefed ParseModuleToStencilImpl( + JSContext* maybeCx, FrontendContext* fc, js::LifoAlloc& tempLifoAlloc, + CompilationInput& input, ScopeBindingCache* scopeCache, + SourceText& srcBuf) { + using OutputType = RefPtr; + BytecodeCompilerOutput output((OutputType())); + if (!ParseModuleToStencilAndMaybeInstantiate( + maybeCx, fc, tempLifoAlloc, input, scopeCache, srcBuf, output)) { + return nullptr; + } + return output.as().forget(); +} + +already_AddRefed frontend::ParseModuleToStencil( + JSContext* maybeCx, FrontendContext* fc, js::LifoAlloc& tempLifoAlloc, + CompilationInput& input, ScopeBindingCache* scopeCache, + SourceText& srcBuf) { + return ParseModuleToStencilImpl(maybeCx, fc, tempLifoAlloc, input, scopeCache, + srcBuf); +} + +already_AddRefed frontend::ParseModuleToStencil( + JSContext* maybeCx, FrontendContext* fc, js::LifoAlloc& tempLifoAlloc, + CompilationInput& input, ScopeBindingCache* scopeCache, + SourceText& srcBuf) { + return ParseModuleToStencilImpl(maybeCx, fc, tempLifoAlloc, input, scopeCache, + srcBuf); +} + +template +UniquePtr ParseModuleToExtensibleStencilImpl( + JSContext* cx, FrontendContext* fc, js::LifoAlloc& tempLifoAlloc, + CompilationInput& input, ScopeBindingCache* scopeCache, + SourceText& srcBuf) { + using OutputType = UniquePtr; + BytecodeCompilerOutput output((OutputType())); + if (!ParseModuleToStencilAndMaybeInstantiate(cx, fc, tempLifoAlloc, input, + scopeCache, srcBuf, output)) { + return nullptr; + } + return std::move(output.as()); +} + +UniquePtr +frontend::ParseModuleToExtensibleStencil(JSContext* cx, FrontendContext* fc, + js::LifoAlloc& tempLifoAlloc, + CompilationInput& input, + ScopeBindingCache* scopeCache, + SourceText& srcBuf) { + return ParseModuleToExtensibleStencilImpl(cx, fc, tempLifoAlloc, input, + scopeCache, srcBuf); +} + +UniquePtr +frontend::ParseModuleToExtensibleStencil(JSContext* cx, FrontendContext* fc, + js::LifoAlloc& tempLifoAlloc, + CompilationInput& input, + ScopeBindingCache* scopeCache, + SourceText& srcBuf) { + return ParseModuleToExtensibleStencilImpl(cx, fc, tempLifoAlloc, input, + scopeCache, srcBuf); +} + +template +static ModuleObject* CompileModuleImpl( + JSContext* cx, FrontendContext* fc, + const JS::ReadOnlyCompileOptions& optionsInput, SourceText& srcBuf) { + AutoAssertReportedException assertException(cx, fc); + + CompileOptions options(cx, optionsInput); + options.setModule(); + + Rooted input(cx, CompilationInput(options)); + Rooted gcOutput(cx); + BytecodeCompilerOutput output(gcOutput.address()); + + NoScopeBindingCache scopeCache; + if (!ParseModuleToStencilAndMaybeInstantiate(cx, fc, cx->tempLifoAlloc(), + input.get(), &scopeCache, srcBuf, + output)) { + return nullptr; + } + + assertException.reset(); + return gcOutput.get().module; +} + +ModuleObject* frontend::CompileModule(JSContext* cx, FrontendContext* fc, + const JS::ReadOnlyCompileOptions& options, + SourceText& srcBuf) { + return CompileModuleImpl(cx, fc, options, srcBuf); +} + +ModuleObject* frontend::CompileModule(JSContext* cx, FrontendContext* fc, + const JS::ReadOnlyCompileOptions& options, + SourceText& srcBuf) { + return CompileModuleImpl(cx, fc, options, srcBuf); +} + +static bool InstantiateLazyFunction(JSContext* cx, CompilationInput& input, + CompilationStencil& stencil, + BytecodeCompilerOutput& output) { + // We do check the type, but do not write anything to it as this is not + // necessary for lazy function, as the script is patched inside the + // JSFunction when instantiating. + MOZ_ASSERT(output.is()); + MOZ_ASSERT(!output.as()); + + mozilla::DebugOnly lazyFlags = + static_cast(input.immutableFlags()); + + Rooted gcOutput(cx); + + if (input.source->hasEncoder()) { + if (!input.source->addDelazificationToIncrementalEncoding(cx, stencil)) { + return false; + } + } + + if (!CompilationStencil::instantiateStencils(cx, input, stencil, + gcOutput.get())) { + return false; + } + + // NOTE: After instantiation succeeds and bytecode is attached, the rest of + // this operation should be infallible. Any failure during + // delazification should restore the function back to a consistent + // lazy state. + + MOZ_ASSERT(lazyFlags == gcOutput.get().script->immutableFlags()); + MOZ_ASSERT(gcOutput.get().script->outermostScope()->hasOnChain( + ScopeKind::NonSyntactic) == + gcOutput.get().script->immutableFlags().hasFlag( + JSScript::ImmutableFlags::HasNonSyntacticScope)); + + return true; +} + +enum class GetCachedResult { + // Similar to return false. + Error, + + // We have not found any entry. + NotFound, + + // We have found an entry, and set everything according to the desired + // BytecodeCompilerOutput out-param. + Found +}; + +// When we have a cache hit, the addPtr out-param would evaluate to a true-ish +// value. +static GetCachedResult GetCachedLazyFunctionStencilMaybeInstantiate( + JSContext* cx, FrontendContext* fc, CompilationInput& input, + BytecodeCompilerOutput& output) { + RefPtr stencil; + { + StencilCache& cache = cx->runtime()->caches().delazificationCache; + auto guard = cache.isSourceCached(input.source); + if (!guard) { + return GetCachedResult::NotFound; + } + + // Before releasing the guard, which is locking the cache, we increment the + // reference counter such that we do not reclaim the CompilationStencil + // while we are instantiating it. + StencilContext key(input.source, input.extent()); + stencil = cache.lookup(guard, key); + if (!stencil) { + return GetCachedResult::NotFound; + } + } + + if (output.is>()) { + output.as>() = stencil; + return GetCachedResult::Found; + } + + if (output.is>()) { + auto extensible = + fc->getAllocator()->make_unique(input); + if (!extensible) { + return GetCachedResult::Error; + } + if (!extensible->cloneFrom(fc, *stencil)) { + return GetCachedResult::Error; + } + + output.as>() = + std::move(extensible); + return GetCachedResult::Found; + } + + if (!InstantiateLazyFunction(cx, input, *stencil, output)) { + return GetCachedResult::Error; + } + + return GetCachedResult::Found; +} + +template +static bool CompileLazyFunctionToStencilMaybeInstantiate( + JSContext* cx, FrontendContext* fc, CompilationInput& input, + ScopeBindingCache* scopeCache, const Unit* units, size_t length, + BytecodeCompilerOutput& output) { + MOZ_ASSERT(input.source); + + AutoAssertReportedException assertException(cx, fc); + if (input.options.consumeDelazificationCache()) { + auto res = + GetCachedLazyFunctionStencilMaybeInstantiate(cx, fc, input, output); + switch (res) { + case GetCachedResult::Error: + return false; + case GetCachedResult::Found: + assertException.reset(); + return true; + case GetCachedResult::NotFound: + break; + } + } + + InheritThis inheritThis = + input.functionFlags().isArrow() ? InheritThis::Yes : InheritThis::No; + + LifoAllocScope parserAllocScope(&cx->tempLifoAlloc()); + CompilationState compilationState(fc, parserAllocScope, input); + compilationState.setFunctionKey(input.extent()); + MOZ_ASSERT(!compilationState.isInitialStencil()); + if (!compilationState.init(fc, scopeCache, inheritThis)) { + return false; + } + + Parser parser(fc, input.options, units, length, + /* foldConstants = */ true, + compilationState, + /* syntaxParser = */ nullptr); + if (!parser.checkOptions()) { + return false; + } + + FunctionNode* pn = parser.standaloneLazyFunction( + input, input.extent().toStringStart, input.strict(), + input.generatorKind(), input.asyncKind()); + if (!pn) { + return false; + } + + BytecodeEmitter bce(fc, &parser, pn->funbox(), compilationState, + BytecodeEmitter::LazyFunction); + if (!bce.init(pn->pn_pos)) { + return false; + } + + if (!bce.emitFunctionScript(pn)) { + return false; + } + + // NOTE: Only allow relazification if there was no lazy PrivateScriptData. + // This excludes non-leaf functions and all script class constructors. + bool hadLazyScriptData = input.hasPrivateScriptData(); + bool isRelazifiableAfterDelazify = input.isRelazifiable(); + if (isRelazifiableAfterDelazify && !hadLazyScriptData) { + compilationState.scriptData[CompilationStencil::TopLevelIndex] + .setAllowRelazify(); + } + + if (input.options.checkDelazificationCache()) { + using OutputType = RefPtr; + BytecodeCompilerOutput cached((OutputType())); + auto res = + GetCachedLazyFunctionStencilMaybeInstantiate(cx, fc, input, cached); + if (res == GetCachedResult::Error) { + return false; + } + // Cached results might be removed by GCs. + if (res == GetCachedResult::Found) { + auto& concurrentSharedData = cached.as().get()->sharedData; + auto concurrentData = + concurrentSharedData.isSingle() + ? concurrentSharedData.asSingle()->get()->immutableData() + : concurrentSharedData.asBorrow() + ->asSingle() + ->get() + ->immutableData(); + auto ondemandData = + compilationState.sharedData.asSingle()->get()->immutableData(); + MOZ_RELEASE_ASSERT(concurrentData.Length() == ondemandData.Length(), + "Non-deterministic stencils"); + for (size_t i = 0; i < concurrentData.Length(); i++) { + MOZ_RELEASE_ASSERT(concurrentData[i] == ondemandData[i], + "Non-deterministic stencils"); + } + } + } + + if (output.is>()) { + auto stencil = + fc->getAllocator()->make_unique( + std::move(compilationState)); + if (!stencil) { + return false; + } + output.as>() = std::move(stencil); + } else if (output.is>()) { + Maybe pseudoFrame; + if (cx) { + pseudoFrame.emplace(cx, "script emit", + JS::ProfilingCategoryPair::JS_Parsing); + } + + auto extensibleStencil = + fc->getAllocator()->make_unique( + std::move(compilationState)); + if (!extensibleStencil) { + return false; + } + + RefPtr stencil = + fc->getAllocator()->new_( + std::move(extensibleStencil)); + if (!stencil) { + return false; + } + + output.as>() = std::move(stencil); + } else { + BorrowingCompilationStencil borrowingStencil(compilationState); + if (!InstantiateLazyFunction(cx, input, borrowingStencil, output)) { + return false; + } + } + + assertException.reset(); + return true; +} + +template +static bool DelazifyCanonicalScriptedFunctionImpl( + JSContext* cx, FrontendContext* fc, ScopeBindingCache* scopeCache, + HandleFunction fun, Handle lazy, ScriptSource* ss) { + MOZ_ASSERT(!lazy->hasBytecode(), "Script is already compiled!"); + MOZ_ASSERT(lazy->function() == fun); + + MOZ_DIAGNOSTIC_ASSERT(!fun->isGhost()); + + AutoIncrementalTimer timer(cx->realm()->timers.delazificationTime); + + size_t sourceStart = lazy->sourceStart(); + size_t sourceLength = lazy->sourceEnd() - lazy->sourceStart(); + + MOZ_ASSERT(ss->hasSourceText()); + + // Parse and compile the script from source. + UncompressedSourceCache::AutoHoldEntry holder; + + MOZ_ASSERT(ss->hasSourceType()); + + ScriptSource::PinnedUnits units(cx, ss, holder, sourceStart, + sourceLength); + if (!units.get()) { + return false; + } + + JS::CompileOptions options(cx); + options.setMutedErrors(lazy->mutedErrors()) + .setFileAndLine(lazy->filename(), lazy->lineno()) + .setColumn(lazy->column()) + .setScriptSourceOffset(lazy->sourceStart()) + .setNoScriptRval(false) + .setSelfHostingMode(false) + .setEagerDelazificationStrategy(lazy->delazificationMode()); + + Rooted input(cx, CompilationInput(options)); + input.get().initFromLazy(cx, lazy, ss); + + CompilationGCOutput* unusedGcOutput = nullptr; + BytecodeCompilerOutput output(unusedGcOutput); + return CompileLazyFunctionToStencilMaybeInstantiate( + cx, fc, input.get(), scopeCache, units.get(), sourceLength, output); +} + +bool frontend::DelazifyCanonicalScriptedFunction(JSContext* cx, + FrontendContext* fc, + HandleFunction fun) { + Maybe pseudoFrame; + if (cx) { + pseudoFrame.emplace(cx, "script delazify", + JS::ProfilingCategoryPair::JS_Parsing); + } + + Rooted lazy(cx, fun->baseScript()); + ScriptSource* ss = lazy->scriptSource(); + ScopeBindingCache* scopeCache = &cx->caches().scopeCache; + + if (ss->hasSourceType()) { + // UTF-8 source text. + return DelazifyCanonicalScriptedFunctionImpl(cx, fc, scopeCache, + fun, lazy, ss); + } + + MOZ_ASSERT(ss->hasSourceType()); + + // UTF-16 source text. + return DelazifyCanonicalScriptedFunctionImpl(cx, fc, scopeCache, + fun, lazy, ss); +} + +template +static already_AddRefed +DelazifyCanonicalScriptedFunctionImpl(JSContext* cx, FrontendContext* fc, + ScopeBindingCache* scopeCache, + CompilationStencil& context, + ScriptIndex scriptIndex) { + ScriptStencilRef script{context, scriptIndex}; + const ScriptStencilExtra& extra = script.scriptExtra(); + +#if defined(EARLY_BETA_OR_EARLIER) || defined(DEBUG) + const ScriptStencil& data = script.scriptData(); + MOZ_ASSERT(!data.hasSharedData(), "Script is already compiled!"); + MOZ_DIAGNOSTIC_ASSERT(!data.isGhost()); +#endif + + Maybe timer; + if (cx->realm()) { + timer.emplace(cx->realm()->timers.delazificationTime); + } + + size_t sourceStart = extra.extent.sourceStart; + size_t sourceLength = extra.extent.sourceEnd - sourceStart; + + ScriptSource* ss = context.source; + MOZ_ASSERT(ss->hasSourceText()); + + // Parse and compile the script from source. + UncompressedSourceCache::AutoHoldEntry holder; + + MOZ_ASSERT(ss->hasSourceType()); + + ScriptSource::PinnedUnits units(cx, ss, holder, sourceStart, + sourceLength); + if (!units.get()) { + return nullptr; + } + + JS::CompileOptions options(cx); + options.setMutedErrors(ss->mutedErrors()) + .setFileAndLine(ss->filename(), extra.extent.lineno) + .setColumn(extra.extent.column) + .setScriptSourceOffset(sourceStart) + .setNoScriptRval(false) + .setSelfHostingMode(false); + + Rooted input(cx, CompilationInput(options)); + input.get().initFromStencil(context, scriptIndex, ss); + + using OutputType = RefPtr; + BytecodeCompilerOutput output((OutputType())); + if (!CompileLazyFunctionToStencilMaybeInstantiate( + cx, fc, input.get(), scopeCache, units.get(), sourceLength, output)) { + return nullptr; + } + return output.as().forget(); +} + +already_AddRefed +frontend::DelazifyCanonicalScriptedFunction(JSContext* cx, FrontendContext* fc, + ScopeBindingCache* scopeCache, + CompilationStencil& context, + ScriptIndex scriptIndex) { + Maybe pseudoFrame; + if (cx) { + pseudoFrame.emplace(cx, "stencil script delazify", + JS::ProfilingCategoryPair::JS_Parsing); + } + + ScriptSource* ss = context.source; + if (ss->hasSourceType()) { + // UTF-8 source text. + return DelazifyCanonicalScriptedFunctionImpl( + cx, fc, scopeCache, context, scriptIndex); + } + + // UTF-16 source text. + MOZ_ASSERT(ss->hasSourceType()); + return DelazifyCanonicalScriptedFunctionImpl(cx, fc, scopeCache, + context, scriptIndex); +} + +static JSFunction* CompileStandaloneFunction( + JSContext* cx, const JS::ReadOnlyCompileOptions& options, + JS::SourceText& srcBuf, const Maybe& parameterListEnd, + FunctionSyntaxKind syntaxKind, GeneratorKind generatorKind, + FunctionAsyncKind asyncKind, Handle enclosingScope = nullptr) { + JS::Rooted fun(cx); + { + AutoReportFrontendContext fc(cx); + AutoAssertReportedException assertException(cx, &fc); + + Rooted input(cx, CompilationInput(options)); + if (enclosingScope) { + if (!input.get().initForStandaloneFunctionInNonSyntacticScope( + &fc, enclosingScope)) { + return nullptr; + } + } else { + if (!input.get().initForStandaloneFunction(cx, &fc)) { + return nullptr; + } + } + + LifoAllocScope parserAllocScope(&cx->tempLifoAlloc()); + InheritThis inheritThis = (syntaxKind == FunctionSyntaxKind::Arrow) + ? InheritThis::Yes + : InheritThis::No; + ScopeBindingCache* scopeCache = &cx->caches().scopeCache; + StandaloneFunctionCompiler compiler(&fc, parserAllocScope, + input.get(), srcBuf); + if (!compiler.init(&fc, scopeCache, inheritThis)) { + return nullptr; + } + + if (!compiler.compile(cx, syntaxKind, generatorKind, asyncKind, + parameterListEnd)) { + return nullptr; + } + + Rooted gcOutput(cx); + RefPtr source; + { + BorrowingCompilationStencil borrowingStencil(compiler.stencil()); + if (!CompilationStencil::instantiateStencils( + cx, input.get(), borrowingStencil, gcOutput.get())) { + return nullptr; + } + source = borrowingStencil.source; + } + + fun = gcOutput.get().getFunctionNoBaseIndex( + CompilationStencil::TopLevelIndex); + MOZ_ASSERT(fun->hasBytecode() || IsAsmJSModule(fun)); + + // Enqueue an off-thread source compression task after finishing parsing. + if (!cx->isHelperThreadContext()) { + if (!source->tryCompressOffThread(cx)) { + return nullptr; + } + } + + // Note: If AsmJS successfully compiles, the into.script will still be + // nullptr. In this case we have compiled to a native function instead of an + // interpreted script. + if (gcOutput.get().script) { + if (parameterListEnd) { + source->setParameterListEnd(*parameterListEnd); + } + + MOZ_ASSERT(!cx->isHelperThreadContext()); + + const JS::InstantiateOptions instantiateOptions(options); + Rooted script(cx, gcOutput.get().script); + FireOnNewScript(cx, instantiateOptions, script); + } + + assertException.reset(); + } + return fun; +} + +JSFunction* frontend::CompileStandaloneFunction( + JSContext* cx, const JS::ReadOnlyCompileOptions& options, + JS::SourceText& srcBuf, const Maybe& parameterListEnd, + FunctionSyntaxKind syntaxKind) { + return CompileStandaloneFunction(cx, options, srcBuf, parameterListEnd, + syntaxKind, GeneratorKind::NotGenerator, + FunctionAsyncKind::SyncFunction); +} + +JSFunction* frontend::CompileStandaloneGenerator( + JSContext* cx, const JS::ReadOnlyCompileOptions& options, + JS::SourceText& srcBuf, const Maybe& parameterListEnd, + FunctionSyntaxKind syntaxKind) { + return CompileStandaloneFunction(cx, options, srcBuf, parameterListEnd, + syntaxKind, GeneratorKind::Generator, + FunctionAsyncKind::SyncFunction); +} + +JSFunction* frontend::CompileStandaloneAsyncFunction( + JSContext* cx, const ReadOnlyCompileOptions& options, + JS::SourceText& srcBuf, const Maybe& parameterListEnd, + FunctionSyntaxKind syntaxKind) { + return CompileStandaloneFunction(cx, options, srcBuf, parameterListEnd, + syntaxKind, GeneratorKind::NotGenerator, + FunctionAsyncKind::AsyncFunction); +} + +JSFunction* frontend::CompileStandaloneAsyncGenerator( + JSContext* cx, const ReadOnlyCompileOptions& options, + JS::SourceText& srcBuf, const Maybe& parameterListEnd, + FunctionSyntaxKind syntaxKind) { + return CompileStandaloneFunction(cx, options, srcBuf, parameterListEnd, + syntaxKind, GeneratorKind::Generator, + FunctionAsyncKind::AsyncFunction); +} + +JSFunction* frontend::CompileStandaloneFunctionInNonSyntacticScope( + JSContext* cx, const JS::ReadOnlyCompileOptions& options, + JS::SourceText& srcBuf, const Maybe& parameterListEnd, + FunctionSyntaxKind syntaxKind, Handle enclosingScope) { + MOZ_ASSERT(enclosingScope); + return CompileStandaloneFunction(cx, options, srcBuf, parameterListEnd, + syntaxKind, GeneratorKind::NotGenerator, + FunctionAsyncKind::SyncFunction, + enclosingScope); +} + +void frontend::FireOnNewScript(JSContext* cx, + const JS::InstantiateOptions& options, + JS::Handle script) { + if (!options.hideFromNewScriptInitial()) { + DebugAPI::onNewScript(cx, script); + } +} diff --git a/js/src/frontend/BytecodeCompiler.h b/js/src/frontend/BytecodeCompiler.h new file mode 100644 index 0000000000..eb67377795 --- /dev/null +++ b/js/src/frontend/BytecodeCompiler.h @@ -0,0 +1,226 @@ +/* -*- 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/. */ + +#ifndef frontend_BytecodeCompiler_h +#define frontend_BytecodeCompiler_h + +#include "mozilla/Maybe.h" +#include "mozilla/Utf8.h" // mozilla::Utf8Unit + +#include "NamespaceImports.h" + +#include "ds/LifoAlloc.h" +#include "frontend/FunctionSyntaxKind.h" +#include "js/SourceText.h" +#include "js/UniquePtr.h" // js::UniquePtr + +/* + * Structure of all of the support classes. + * + * Parser: described in Parser.h. + * + * BytecodeCompiler.cpp: BytecodeCompiler.h *and* BytecodeCompilation.h. + * This is the "driver", the high-level operations like "compile this source to + * bytecode". It calls the parser, bytecode emitter, etc. + * + * ParseContext.h and SharedContext.h: Both have similar purposes. They're split + * because ParseContext contains information used only by the parser, and + * SharedContext contains information used by both the parser and + * BytecodeEmitter. + * + * SharedContext.h: class Directives: this contains boolean flags for tracking + * if we're in asm.js or "use strict" code. The "use strict" bit is stored in + * SharedContext, and additionally, the full Directives class is stored in + * ParseContext - if a direcive is encountered while parsing, this is updated, + * and checked in GeneralParser::functionDefinition, and if it changed, the + * whole function is re-parsed with the new flags. + * + * SharedContext.h: abstract class SharedContext: This class contains two + * different groups of flags: + * + * Parse context information. This is information conceptually "passed down" + * into parsing sub-nodes. This is like "are we parsing strict code?", and so + * the parser can make decisions of how to parse based off that. + * + * Gathered-while-parsing information. This is information conceptually + * "returned up" from parsing sub-nodes. This is like "did we see a use strict + * directive"? + * + * Additionally, subclasses (GlobalSharedContext, ModuleSharedContext, + * EvalSharedContext, and FunctionBox) contain binding information, scope + * information, and other such bits of data. + * + * ParseContext.h: class UsedNameTracker: Track which bindings are used in which + * scopes. This helps determine which bindings are closed-over, which affects + * how they're stored; and whether special bindings like `this` and `arguments` + * can be optimized away. + * + * ParseContext.h: class ParseContext: Extremely complex class that serves a lot + * of purposes, but it's a single class - essentially no derived classes - so + * it's a little easier to comprehend all at once. (SourceParseContext does + * derive from ParseContext, but they does nothing except adjust the + * constructor's arguments). + * Note it uses a thing called Nestable, which implements a stack of objects: + * you can push (and pop) instances to a stack (linked list) as you parse + * further into the parse tree. You may push to this stack via calling the + * constructor with a GeneralParser as an argument (usually `this`), which + * pushes itself onto `this->pc` (so it does get assigned/pushed, even though no + * assignment ever appears directly in the parser) + * + * ParseContext contains a pointer to a SharedContext. + * + * There's a decent chunk of flags/data collection in here too, some "pass-down" + * data and some "return-up" data. + * + * ParseContext also contains a significant number of *sub*-Nestables as fields + * of itself (nestables inside nestables). Note you also push/pop to these via + * passing `Parser->pc`, which the constructor of the sub-nestable knows which + * ParseContext field to push to. The sub-nestables are: + * + * ParseContext::Statement: stack of statements. + * `if (x) { while (true) { try { ..stack of [if, while, try].. } ... } }` + * + * ParseContext::LabelStatement: interspersed in Statement stack, for labeled + * statements, for e.g. `label: while (true) { break label; }` + * + * ParseContext::ClassStatement: interspersed in Statement stack, for classes + * the parser is currently inside of. + * + * ParseContext::Scope: Set of variables in each scope (stack of sets): + * `{ let a; let b; { let c; } }` + * (this gets complicated with `var`, etc., check the class for docs) + */ + +class JSLinearString; + +namespace JS { +class JS_PUBLIC_API ReadOnlyCompileOptions; +} + +namespace js { + +class ModuleObject; +class ScriptSourceObject; +class FrontendContext; + +namespace frontend { + +struct CompilationInput; +struct CompilationStencil; +struct ExtensibleCompilationStencil; +struct CompilationGCOutput; +class ErrorReporter; +class FunctionBox; +class ParseNode; +class TaggedParserAtomIndex; +class ScopeBindingCache; + +// Compile a module of the given source using the given options. +ModuleObject* CompileModule(JSContext* cx, FrontendContext* fc, + const JS::ReadOnlyCompileOptions& options, + JS::SourceText& srcBuf); +ModuleObject* CompileModule(JSContext* cx, FrontendContext* fc, + const JS::ReadOnlyCompileOptions& options, + JS::SourceText& srcBuf); + +// Parse a module of the given source. This is an internal API; if you want to +// compile a module as a user, use CompileModule above. +already_AddRefed ParseModuleToStencil( + JSContext* maybeCx, FrontendContext* fc, js::LifoAlloc& tempLifoAlloc, + CompilationInput& input, ScopeBindingCache* scopeCache, + JS::SourceText& srcBuf); +already_AddRefed ParseModuleToStencil( + JSContext* maybeCx, FrontendContext* fc, js::LifoAlloc& tempLifoAlloc, + CompilationInput& input, ScopeBindingCache* scopeCache, + JS::SourceText& srcBuf); + +UniquePtr ParseModuleToExtensibleStencil( + JSContext* cx, FrontendContext* fc, js::LifoAlloc& tempLifoAlloc, + CompilationInput& input, ScopeBindingCache* scopeCache, + JS::SourceText& srcBuf); +UniquePtr ParseModuleToExtensibleStencil( + JSContext* cx, FrontendContext* fc, js::LifoAlloc& tempLifoAlloc, + CompilationInput& input, ScopeBindingCache* scopeCache, + JS::SourceText& srcBuf); + +// +// Compile a single function. The source in srcBuf must match the ECMA-262 +// FunctionExpression production. +// +// If nonzero, parameterListEnd is the offset within srcBuf where the parameter +// list is expected to end. During parsing, if we find that it ends anywhere +// else, it's a SyntaxError. This is used to implement the Function constructor; +// it's how we detect that these weird cases are SyntaxErrors: +// +// Function("/*", "*/x) {") +// Function("x){ if (3", "return x;}") +// +[[nodiscard]] JSFunction* CompileStandaloneFunction( + JSContext* cx, const JS::ReadOnlyCompileOptions& options, + JS::SourceText& srcBuf, + const mozilla::Maybe& parameterListEnd, + frontend::FunctionSyntaxKind syntaxKind); + +[[nodiscard]] JSFunction* CompileStandaloneGenerator( + JSContext* cx, const JS::ReadOnlyCompileOptions& options, + JS::SourceText& srcBuf, + const mozilla::Maybe& parameterListEnd, + frontend::FunctionSyntaxKind syntaxKind); + +[[nodiscard]] JSFunction* CompileStandaloneAsyncFunction( + JSContext* cx, const JS::ReadOnlyCompileOptions& options, + JS::SourceText& srcBuf, + const mozilla::Maybe& parameterListEnd, + frontend::FunctionSyntaxKind syntaxKind); + +[[nodiscard]] JSFunction* CompileStandaloneAsyncGenerator( + JSContext* cx, const JS::ReadOnlyCompileOptions& options, + JS::SourceText& srcBuf, + const mozilla::Maybe& parameterListEnd, + frontend::FunctionSyntaxKind syntaxKind); + +// Compile a single function in given enclosing non-syntactic scope. +[[nodiscard]] JSFunction* CompileStandaloneFunctionInNonSyntacticScope( + JSContext* cx, const JS::ReadOnlyCompileOptions& options, + JS::SourceText& srcBuf, + const mozilla::Maybe& parameterListEnd, + frontend::FunctionSyntaxKind syntaxKind, Handle enclosingScope); + +/* + * True if str consists of an IdentifierStart character, followed by one or + * more IdentifierPart characters, i.e. it matches the IdentifierName production + * in the language spec. + * + * This returns true even if str is a keyword like "if". + * + * Defined in TokenStream.cpp. + */ +bool IsIdentifier(JSLinearString* str); + +bool IsIdentifierNameOrPrivateName(JSLinearString* str); + +/* + * As above, but taking chars + length. + */ +bool IsIdentifier(const Latin1Char* chars, size_t length); +bool IsIdentifier(const char16_t* chars, size_t length); + +/* + * ASCII variant with known length. + */ +bool IsIdentifierASCII(char c); +bool IsIdentifierASCII(char c1, char c2); + +bool IsIdentifierNameOrPrivateName(const Latin1Char* chars, size_t length); +bool IsIdentifierNameOrPrivateName(const char16_t* chars, size_t length); + +/* True if str is a keyword. Defined in TokenStream.cpp. */ +bool IsKeyword(TaggedParserAtomIndex atom); + +} /* namespace frontend */ +} /* namespace js */ + +#endif /* frontend_BytecodeCompiler_h */ diff --git a/js/src/frontend/BytecodeControlStructures.cpp b/js/src/frontend/BytecodeControlStructures.cpp new file mode 100644 index 0000000000..1d8d8f5317 --- /dev/null +++ b/js/src/frontend/BytecodeControlStructures.cpp @@ -0,0 +1,392 @@ +/* -*- 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/BytecodeControlStructures.h" + +#include "frontend/BytecodeEmitter.h" // BytecodeEmitter +#include "frontend/EmitterScope.h" // EmitterScope +#include "frontend/ForOfLoopControl.h" // ForOfLoopControl +#include "frontend/SwitchEmitter.h" // SwitchEmitter +#include "vm/Opcodes.h" // JSOp + +using namespace js; +using namespace js::frontend; + +using mozilla::Maybe; + +NestableControl::NestableControl(BytecodeEmitter* bce, StatementKind kind) + : Nestable(&bce->innermostNestableControl), + kind_(kind), + emitterScope_(bce->innermostEmitterScopeNoCheck()) {} + +BreakableControl::BreakableControl(BytecodeEmitter* bce, StatementKind kind) + : NestableControl(bce, kind) { + MOZ_ASSERT(is()); +} + +bool BreakableControl::patchBreaks(BytecodeEmitter* bce) { + return bce->emitJumpTargetAndPatch(breaks); +} + +LabelControl::LabelControl(BytecodeEmitter* bce, TaggedParserAtomIndex label, + BytecodeOffset startOffset) + : BreakableControl(bce, StatementKind::Label), + label_(label), + startOffset_(startOffset) {} + +LoopControl::LoopControl(BytecodeEmitter* bce, StatementKind loopKind) + : BreakableControl(bce, loopKind), tdzCache_(bce) { + MOZ_ASSERT(is()); + + LoopControl* enclosingLoop = findNearest(enclosing()); + + stackDepth_ = bce->bytecodeSection().stackDepth(); + loopDepth_ = enclosingLoop ? enclosingLoop->loopDepth_ + 1 : 1; +} + +bool LoopControl::emitContinueTarget(BytecodeEmitter* bce) { + // Note: this is always called after emitting the loop body so we must have + // emitted all 'continues' by now. + return bce->emitJumpTargetAndPatch(continues); +} + +bool LoopControl::emitLoopHead(BytecodeEmitter* bce, + const Maybe& nextPos) { + // Insert a Nop if needed to ensure the script does not start with a + // JSOp::LoopHead. This avoids JIT issues with prologue code + try notes + // or OSR. See bug 1602390 and bug 1602681. + if (bce->bytecodeSection().offset().toUint32() == 0) { + if (!bce->emit1(JSOp::Nop)) { + return false; + } + } + + if (nextPos) { + if (!bce->updateSourceCoordNotes(*nextPos)) { + return false; + } + } + + MOZ_ASSERT(loopDepth_ > 0); + + head_ = {bce->bytecodeSection().offset()}; + + BytecodeOffset off; + if (!bce->emitJumpTargetOp(JSOp::LoopHead, &off)) { + return false; + } + SetLoopHeadDepthHint(bce->bytecodeSection().code(off), loopDepth_); + + return true; +} + +bool LoopControl::emitLoopEnd(BytecodeEmitter* bce, JSOp op, + TryNoteKind tryNoteKind) { + JumpList jump; + if (!bce->emitJumpNoFallthrough(op, &jump)) { + return false; + } + bce->patchJumpsToTarget(jump, head_); + + // Create a fallthrough for closing iterators, and as a target for break + // statements. + JumpTarget breakTarget; + if (!bce->emitJumpTarget(&breakTarget)) { + return false; + } + if (!patchBreaks(bce)) { + return false; + } + if (!bce->addTryNote(tryNoteKind, bce->bytecodeSection().stackDepth(), + headOffset(), breakTarget.offset)) { + return false; + } + return true; +} + +TryFinallyControl::TryFinallyControl(BytecodeEmitter* bce, StatementKind kind) + : NestableControl(bce, kind) { + MOZ_ASSERT(is()); +} + +bool TryFinallyControl::allocateContinuation(NestableControl* target, + NonLocalExitKind kind, + uint32_t* idx) { + for (uint32_t i = 0; i < continuations_.length(); i++) { + if (continuations_[i].target_ == target && + continuations_[i].kind_ == kind) { + *idx = i + SpecialContinuations::Count; + return true; + } + } + *idx = continuations_.length() + SpecialContinuations::Count; + return continuations_.emplaceBack(target, kind); +} + +bool TryFinallyControl::emitContinuations(BytecodeEmitter* bce) { + SwitchEmitter::TableGenerator tableGen(bce); + for (uint32_t i = 0; i < continuations_.length(); i++) { + if (!tableGen.addNumber(i + SpecialContinuations::Count)) { + return false; + } + } + tableGen.finish(continuations_.length()); + MOZ_RELEASE_ASSERT(tableGen.isValid()); + + InternalSwitchEmitter se(bce); + if (!se.validateCaseCount(continuations_.length())) { + return false; + } + if (!se.emitTable(tableGen)) { + return false; + } + + // Continuation index 0 is special-cased to be the fallthrough block. + // Non-default switch cases are numbered 1-N. + uint32_t caseIdx = SpecialContinuations::Count; + for (TryFinallyContinuation& continuation : continuations_) { + if (!se.emitCaseBody(caseIdx++, tableGen)) { + return false; + } + // Resume the non-local control flow that was intercepted by + // this finally. + NonLocalExitControl nle(bce, continuation.kind_); + if (!nle.emitNonLocalJump(continuation.target_, this)) { + return false; + } + } + + // The only unhandled case is the fallthrough case, which is handled + // by the switch default. + if (!se.emitDefaultBody()) { + return false; + } + if (!se.emitEnd()) { + return false; + } + return true; +} + +NonLocalExitControl::NonLocalExitControl(BytecodeEmitter* bce, + NonLocalExitKind kind) + : bce_(bce), + savedScopeNoteIndex_(bce->bytecodeSection().scopeNoteList().length()), + savedDepth_(bce->bytecodeSection().stackDepth()), + openScopeNoteIndex_(bce->innermostEmitterScope()->noteIndex()), + kind_(kind) {} + +NonLocalExitControl::~NonLocalExitControl() { + for (uint32_t n = savedScopeNoteIndex_; + n < bce_->bytecodeSection().scopeNoteList().length(); n++) { + bce_->bytecodeSection().scopeNoteList().recordEnd( + n, bce_->bytecodeSection().offset()); + } + bce_->bytecodeSection().setStackDepth(savedDepth_); +} + +bool NonLocalExitControl::emitReturn(BytecodeOffset setRvalOffset) { + MOZ_ASSERT(kind_ == NonLocalExitKind::Return); + setRvalOffset_ = setRvalOffset; + return emitNonLocalJump(nullptr); +} + +bool NonLocalExitControl::leaveScope(EmitterScope* es) { + if (!es->leave(bce_, /* nonLocal = */ true)) { + return false; + } + + // As we pop each scope due to the non-local jump, emit notes that + // record the extent of the enclosing scope. These notes will have + // their ends recorded in ~NonLocalExitControl(). + GCThingIndex enclosingScopeIndex = ScopeNote::NoScopeIndex; + if (es->enclosingInFrame()) { + enclosingScopeIndex = es->enclosingInFrame()->index(); + } + if (!bce_->bytecodeSection().scopeNoteList().append( + enclosingScopeIndex, bce_->bytecodeSection().offset(), + openScopeNoteIndex_)) { + return false; + } + openScopeNoteIndex_ = bce_->bytecodeSection().scopeNoteList().length() - 1; + + return true; +} + +/* + * Emit additional bytecode(s) for non-local jumps. + */ +bool NonLocalExitControl::emitNonLocalJump(NestableControl* target, + NestableControl* startingAfter) { + NestableControl* startingControl = startingAfter + ? startingAfter->enclosing() + : bce_->innermostNestableControl; + EmitterScope* es = startingAfter ? startingAfter->emitterScope() + : bce_->innermostEmitterScope(); + + int npops = 0; + + AutoCheckUnstableEmitterScope cues(bce_); + + // We emit IteratorClose bytecode inline. 'continue' statements do + // not call IteratorClose for the loop they are continuing. + bool emitIteratorCloseAtTarget = kind_ != NonLocalExitKind::Continue; + + auto flushPops = [&npops](BytecodeEmitter* bce) { + if (npops && !bce->emitPopN(npops)) { + return false; + } + npops = 0; + return true; + }; + + // If we are closing multiple for-of loops, the resulting FOR_OF_ITERCLOSE + // trynotes must be appropriately nested. Each FOR_OF_ITERCLOSE starts when + // we close the corresponding for-of iterator, and continues until the + // actual jump. + Vector forOfIterCloseScopeStarts(bce_->fc); + + // If we have to execute a finally block, then we will jump there now and + // continue the non-local jump from the end of the finally block. + bool jumpingToFinally = false; + + // Walk the nestable control stack and patch jumps. + for (NestableControl* control = startingControl; + control != target && !jumpingToFinally; control = control->enclosing()) { + // Walk the scope stack and leave the scopes we entered. Leaving a scope + // may emit administrative ops like JSOp::PopLexicalEnv but never anything + // that manipulates the stack. + for (; es != control->emitterScope(); es = es->enclosingInFrame()) { + if (!leaveScope(es)) { + return false; + } + } + + switch (control->kind()) { + case StatementKind::Finally: { + TryFinallyControl& finallyControl = control->as(); + if (finallyControl.emittingSubroutine()) { + /* + * There's a [resume-index-or-exception, throwing] pair on + * the stack that we need to pop. If the script is not a + * noScriptRval script, we also need to pop the cached rval. + */ + if (bce_->sc->noScriptRval()) { + npops += 2; + } else { + npops += 3; + } + } else { + jumpingToFinally = true; + + if (!flushPops(bce_)) { + return false; + } + uint32_t idx; + if (!finallyControl.allocateContinuation(target, kind_, &idx)) { + return false; + } + if (!bce_->emitJumpToFinally(&finallyControl.finallyJumps_, idx)) { + return false; + } + } + break; + } + + case StatementKind::ForOfLoop: { + if (!flushPops(bce_)) { + return false; + } + BytecodeOffset tryNoteStart; + ForOfLoopControl& loopinfo = control->as(); + if (!loopinfo.emitPrepareForNonLocalJumpFromScope( + bce_, *es, + /* isTarget = */ false, &tryNoteStart)) { + // [stack] ... + return false; + } + if (!forOfIterCloseScopeStarts.append(tryNoteStart)) { + return false; + } + break; + } + + case StatementKind::ForInLoop: + if (!flushPops(bce_)) { + return false; + } + + // The iterator and the current value are on the stack. + if (!bce_->emit1(JSOp::EndIter)) { + // [stack] ... + return false; + } + break; + + default: + break; + } + } + + if (!flushPops(bce_)) { + return false; + } + + if (!jumpingToFinally) { + if (target && emitIteratorCloseAtTarget && target->is()) { + BytecodeOffset tryNoteStart; + ForOfLoopControl& loopinfo = target->as(); + if (!loopinfo.emitPrepareForNonLocalJumpFromScope(bce_, *es, + /* isTarget = */ true, + &tryNoteStart)) { + // [stack] ... UNDEF UNDEF UNDEF + return false; + } + if (!forOfIterCloseScopeStarts.append(tryNoteStart)) { + return false; + } + } + + EmitterScope* targetEmitterScope = + target ? target->emitterScope() : bce_->varEmitterScope; + for (; es != targetEmitterScope; es = es->enclosingInFrame()) { + if (!leaveScope(es)) { + return false; + } + } + switch (kind_) { + case NonLocalExitKind::Continue: { + LoopControl* loop = &target->as(); + if (!bce_->emitJump(JSOp::Goto, &loop->continues)) { + return false; + } + break; + } + case NonLocalExitKind::Break: { + BreakableControl* breakable = &target->as(); + if (!bce_->emitJump(JSOp::Goto, &breakable->breaks)) { + return false; + } + break; + } + case NonLocalExitKind::Return: + MOZ_ASSERT(!target); + if (!bce_->finishReturn(setRvalOffset_)) { + return false; + } + break; + } + } + + // Close FOR_OF_ITERCLOSE trynotes. + BytecodeOffset end = bce_->bytecodeSection().offset(); + for (BytecodeOffset start : forOfIterCloseScopeStarts) { + if (!bce_->addTryNote(TryNoteKind::ForOfIterClose, 0, start, end)) { + return false; + } + } + + return true; +} diff --git a/js/src/frontend/BytecodeControlStructures.h b/js/src/frontend/BytecodeControlStructures.h new file mode 100644 index 0000000000..b246aa04ad --- /dev/null +++ b/js/src/frontend/BytecodeControlStructures.h @@ -0,0 +1,205 @@ +/* -*- 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/. */ + +#ifndef frontend_BytecodeControlStructures_h +#define frontend_BytecodeControlStructures_h + +#include "mozilla/Assertions.h" // MOZ_ASSERT +#include "mozilla/Maybe.h" // mozilla::Maybe + +#include // int32_t, uint32_t + +#include "ds/Nestable.h" // Nestable +#include "frontend/BytecodeOffset.h" // BytecodeOffset +#include "frontend/JumpList.h" // JumpList, JumpTarget +#include "frontend/ParserAtom.h" // TaggedParserAtomIndex +#include "frontend/SharedContext.h" // StatementKind, StatementKindIsLoop, StatementKindIsUnlabeledBreakTarget +#include "frontend/TDZCheckCache.h" // TDZCheckCache +#include "vm/StencilEnums.h" // TryNoteKind + +namespace js { +namespace frontend { + +struct BytecodeEmitter; +class EmitterScope; + +class NestableControl : public Nestable { + StatementKind kind_; + + // The innermost scope when this was pushed. + EmitterScope* emitterScope_; + + protected: + NestableControl(BytecodeEmitter* bce, StatementKind kind); + + public: + using Nestable::enclosing; + using Nestable::findNearest; + + StatementKind kind() const { return kind_; } + + EmitterScope* emitterScope() const { return emitterScope_; } + + template + bool is() const; + + template + T& as() { + MOZ_ASSERT(this->is()); + return static_cast(*this); + } +}; + +class BreakableControl : public NestableControl { + public: + // Offset of the last break. + JumpList breaks; + + BreakableControl(BytecodeEmitter* bce, StatementKind kind); + + [[nodiscard]] bool patchBreaks(BytecodeEmitter* bce); +}; +template <> +inline bool NestableControl::is() const { + return StatementKindIsUnlabeledBreakTarget(kind_) || + kind_ == StatementKind::Label; +} + +class LabelControl : public BreakableControl { + TaggedParserAtomIndex label_; + + // The code offset when this was pushed. Used for effectfulness checking. + BytecodeOffset startOffset_; + + public: + LabelControl(BytecodeEmitter* bce, TaggedParserAtomIndex label, + BytecodeOffset startOffset); + + TaggedParserAtomIndex label() const { return label_; } + + BytecodeOffset startOffset() const { return startOffset_; } +}; +template <> +inline bool NestableControl::is() const { + return kind_ == StatementKind::Label; +} + +class LoopControl : public BreakableControl { + // Loops' children are emitted in dominance order, so they can always + // have a TDZCheckCache. + TDZCheckCache tdzCache_; + + // Here's the basic structure of a loop: + // + // head: + // JSOp::LoopHead + // {loop condition/body} + // + // continueTarget: + // {loop update if present} + // + // # Loop end, backward jump + // JSOp::Goto/JSOp::JumpIfTrue head + // + // breakTarget: + + // The bytecode offset of JSOp::LoopHead. + JumpTarget head_; + + // Stack depth when this loop was pushed on the control stack. + int32_t stackDepth_; + + // The loop nesting depth. Used as a hint to Ion. + uint32_t loopDepth_; + + public: + // Offset of the last continue in the loop. + JumpList continues; + + LoopControl(BytecodeEmitter* bce, StatementKind loopKind); + + BytecodeOffset headOffset() const { return head_.offset; } + + [[nodiscard]] bool emitContinueTarget(BytecodeEmitter* bce); + + // `nextPos` is the offset in the source code for the character that + // corresponds to the next instruction after JSOp::LoopHead. + // Can be Nothing() if not available. + [[nodiscard]] bool emitLoopHead(BytecodeEmitter* bce, + const mozilla::Maybe& nextPos); + + [[nodiscard]] bool emitLoopEnd(BytecodeEmitter* bce, JSOp op, + TryNoteKind tryNoteKind); +}; +template <> +inline bool NestableControl::is() const { + return StatementKindIsLoop(kind_); +} + +enum class NonLocalExitKind { Continue, Break, Return }; + +class TryFinallyContinuation { + public: + TryFinallyContinuation(NestableControl* target, NonLocalExitKind kind) + : target_(target), kind_(kind) {} + + NestableControl* target_; + NonLocalExitKind kind_; +}; + +class TryFinallyControl : public NestableControl { + bool emittingSubroutine_ = false; + + public: + // Offset of the last jump to this `finally`. + JumpList finallyJumps_; + + js::Vector continuations_; + + TryFinallyControl(BytecodeEmitter* bce, StatementKind kind); + + void setEmittingSubroutine() { emittingSubroutine_ = true; } + + bool emittingSubroutine() const { return emittingSubroutine_; } + + enum SpecialContinuations { Fallthrough, Count }; + bool allocateContinuation(NestableControl* target, NonLocalExitKind kind, + uint32_t* idx); + bool emitContinuations(BytecodeEmitter* bce); +}; +template <> +inline bool NestableControl::is() const { + return kind_ == StatementKind::Try || kind_ == StatementKind::Finally; +} + +class NonLocalExitControl { + BytecodeEmitter* bce_; + const uint32_t savedScopeNoteIndex_; + const int savedDepth_; + uint32_t openScopeNoteIndex_; + NonLocalExitKind kind_; + + // The offset of a `JSOp::SetRval` that can be rewritten as a + // `JSOp::Return` if we don't generate any code for this + // NonLocalExitControl. + BytecodeOffset setRvalOffset_ = BytecodeOffset::invalidOffset(); + + [[nodiscard]] bool leaveScope(EmitterScope* es); + + public: + NonLocalExitControl(const NonLocalExitControl&) = delete; + NonLocalExitControl(BytecodeEmitter* bce, NonLocalExitKind kind); + ~NonLocalExitControl(); + + [[nodiscard]] bool emitNonLocalJump(NestableControl* target, + NestableControl* startingAfter = nullptr); + [[nodiscard]] bool emitReturn(BytecodeOffset setRvalOffset); +}; + +} /* namespace frontend */ +} /* namespace js */ + +#endif /* frontend_BytecodeControlStructures_h */ diff --git a/js/src/frontend/BytecodeEmitter.cpp b/js/src/frontend/BytecodeEmitter.cpp new file mode 100644 index 0000000000..24155fc7e6 --- /dev/null +++ b/js/src/frontend/BytecodeEmitter.cpp @@ -0,0 +1,12031 @@ +/* -*- 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 bytecode generation. + */ + +#include "frontend/BytecodeEmitter.h" + +#include "mozilla/Casting.h" // mozilla::AssertedCast +#include "mozilla/DebugOnly.h" // mozilla::DebugOnly +#include "mozilla/FloatingPoint.h" // mozilla::NumberEqualsInt32, mozilla::NumberIsInt32 +#include "mozilla/HashTable.h" // mozilla::HashSet +#include "mozilla/Maybe.h" // mozilla::{Maybe,Nothing,Some} +#include "mozilla/PodOperations.h" // mozilla::PodCopy +#include "mozilla/Saturate.h" +#include "mozilla/Variant.h" // mozilla::AsVariant + +#include +#include +#include + +#include "jstypes.h" // JS_BIT + +#include "frontend/AbstractScopePtr.h" // ScopeIndex +#include "frontend/BytecodeControlStructures.h" // NestableControl, BreakableControl, LabelControl, LoopControl, TryFinallyControl +#include "frontend/CallOrNewEmitter.h" // CallOrNewEmitter +#include "frontend/CForEmitter.h" // CForEmitter +#include "frontend/DecoratorEmitter.h" // DecoratorEmitter +#include "frontend/DefaultEmitter.h" // DefaultEmitter +#include "frontend/DoWhileEmitter.h" // DoWhileEmitter +#include "frontend/ElemOpEmitter.h" // ElemOpEmitter +#include "frontend/EmitterScope.h" // EmitterScope +#include "frontend/ExpressionStatementEmitter.h" // ExpressionStatementEmitter +#include "frontend/ForInEmitter.h" // ForInEmitter +#include "frontend/ForOfEmitter.h" // ForOfEmitter +#include "frontend/FunctionEmitter.h" // FunctionEmitter, FunctionScriptEmitter, FunctionParamsEmitter +#include "frontend/IfEmitter.h" // IfEmitter, InternalIfEmitter, CondEmitter +#include "frontend/LabelEmitter.h" // LabelEmitter +#include "frontend/LexicalScopeEmitter.h" // LexicalScopeEmitter +#include "frontend/ModuleSharedContext.h" // ModuleSharedContext +#include "frontend/NameAnalysisTypes.h" // PrivateNameKind +#include "frontend/NameFunctions.h" // NameFunctions +#include "frontend/NameOpEmitter.h" // NameOpEmitter +#include "frontend/ObjectEmitter.h" // PropertyEmitter, ObjectEmitter, ClassEmitter +#include "frontend/OptionalEmitter.h" // OptionalEmitter +#include "frontend/ParseNode.h" // ParseNodeKind, ParseNode and subclasses +#include "frontend/Parser.h" // Parser +#include "frontend/ParserAtom.h" // ParserAtomsTable, ParserAtom +#include "frontend/PrivateOpEmitter.h" // PrivateOpEmitter +#include "frontend/PropOpEmitter.h" // PropOpEmitter +#include "frontend/SourceNotes.h" // SrcNote, SrcNoteType, SrcNoteWriter +#include "frontend/SwitchEmitter.h" // SwitchEmitter +#include "frontend/TaggedParserAtomIndexHasher.h" // TaggedParserAtomIndexHasher +#include "frontend/TDZCheckCache.h" // TDZCheckCache +#include "frontend/TryEmitter.h" // TryEmitter +#include "frontend/WhileEmitter.h" // WhileEmitter +#include "js/friend/ErrorMessages.h" // JSMSG_* +#include "js/friend/StackLimits.h" // AutoCheckRecursionLimit +#include "util/StringBuffer.h" // StringBuffer +#include "vm/BytecodeUtil.h" // JOF_*, IsArgOp, IsLocalOp, SET_UINT24, SET_ICINDEX, BytecodeFallsThrough, BytecodeIsJumpTarget +#include "vm/CompletionKind.h" // CompletionKind +#include "vm/FunctionPrefixKind.h" // FunctionPrefixKind +#include "vm/GeneratorObject.h" // AbstractGeneratorObject +#include "vm/Opcodes.h" // JSOp, JSOpLength_* +#include "vm/PropMap.h" // SharedPropMap::MaxPropsForNonDictionary +#include "vm/Scope.h" // GetScopeDataTrailingNames +#include "vm/SharedStencil.h" // ScopeNote +#include "vm/ThrowMsgKind.h" // ThrowMsgKind +#include "vm/WellKnownAtom.h" // js_*_str + +using namespace js; +using namespace js::frontend; + +using mozilla::AssertedCast; +using mozilla::AsVariant; +using mozilla::DebugOnly; +using mozilla::Maybe; +using mozilla::Nothing; +using mozilla::NumberEqualsInt32; +using mozilla::NumberIsInt32; +using mozilla::PodCopy; +using mozilla::Some; + +static bool ParseNodeRequiresSpecialLineNumberNotes(ParseNode* pn) { + // The few node types listed below are exceptions to the usual + // location-source-note-emitting code in BytecodeEmitter::emitTree(). + // Single-line `while` loops and C-style `for` loops require careful + // handling to avoid strange stepping behavior. + // Functions usually shouldn't have location information (bug 1431202). + + ParseNodeKind kind = pn->getKind(); + return kind == ParseNodeKind::WhileStmt || kind == ParseNodeKind::ForStmt || + kind == ParseNodeKind::Function; +} + +static bool NeedsFieldInitializer(ParseNode* member, bool inStaticContext) { + // For the purposes of bytecode emission, StaticClassBlocks are treated as if + // they were static initializers. + return (member->is() && inStaticContext) || + (member->is() && + member->as().isStatic() == inStaticContext); +} + +static bool NeedsAccessorInitializer(ParseNode* member, bool isStatic) { + if (isStatic) { + return false; + } + return member->is() && + member->as().name().isKind(ParseNodeKind::PrivateName) && + !member->as().isStatic() && + member->as().accessorType() != AccessorType::None; +} + +static bool ShouldSuppressBreakpointsAndSourceNotes( + SharedContext* sc, BytecodeEmitter::EmitterMode emitterMode) { + // Suppress for all self-hosting code. + if (emitterMode == BytecodeEmitter::EmitterMode::SelfHosting) { + return true; + } + + // Suppress for synthesized class constructors. + if (sc->isFunctionBox()) { + FunctionBox* funbox = sc->asFunctionBox(); + return funbox->isSyntheticFunction() && funbox->isClassConstructor(); + } + + return false; +} + +BytecodeEmitter::BytecodeEmitter(BytecodeEmitter* parent, FrontendContext* fc, + SharedContext* sc, + const ErrorReporter& errorReporter, + CompilationState& compilationState, + EmitterMode emitterMode) + : sc(sc), + fc(fc), + parent(parent), + bytecodeSection_(fc, sc->extent().lineno, sc->extent().column), + perScriptData_(fc, compilationState), + errorReporter_(errorReporter), + compilationState(compilationState), + suppressBreakpointsAndSourceNotes( + ShouldSuppressBreakpointsAndSourceNotes(sc, emitterMode)), + emitterMode(emitterMode) { + MOZ_ASSERT_IF(parent, fc == parent->fc); +} + +BytecodeEmitter::BytecodeEmitter(BytecodeEmitter* parent, SharedContext* sc) + : BytecodeEmitter(parent, parent->fc, sc, parent->errorReporter_, + parent->compilationState, parent->emitterMode) {} + +BytecodeEmitter::BytecodeEmitter(FrontendContext* fc, + const EitherParser& parser, SharedContext* sc, + CompilationState& compilationState, + EmitterMode emitterMode) + : BytecodeEmitter(nullptr, fc, sc, parser.errorReporter(), compilationState, + emitterMode) { + ep_.emplace(parser); +} + +void BytecodeEmitter::initFromBodyPosition(TokenPos bodyPosition) { + setScriptStartOffsetIfUnset(bodyPosition.begin); + setFunctionBodyEndPos(bodyPosition.end); +} + +bool BytecodeEmitter::init() { + if (!parent) { + if (!compilationState.prepareSharedDataStorage(fc)) { + return false; + } + } + return perScriptData_.init(fc); +} + +bool BytecodeEmitter::init(TokenPos bodyPosition) { + initFromBodyPosition(bodyPosition); + return init(); +} + +template +T* BytecodeEmitter::findInnermostNestableControl() const { + return NestableControl::findNearest(innermostNestableControl); +} + +template bool */> +T* BytecodeEmitter::findInnermostNestableControl(Predicate predicate) const { + return NestableControl::findNearest(innermostNestableControl, predicate); +} + +NameLocation BytecodeEmitter::lookupName(TaggedParserAtomIndex name) { + return innermostEmitterScope()->lookup(this, name); +} + +void BytecodeEmitter::lookupPrivate(TaggedParserAtomIndex name, + NameLocation& loc, + Maybe& brandLoc) { + innermostEmitterScope()->lookupPrivate(this, name, loc, brandLoc); +} + +Maybe BytecodeEmitter::locationOfNameBoundInScope( + TaggedParserAtomIndex name, EmitterScope* target) { + return innermostEmitterScope()->locationBoundInScope(name, target); +} + +template +Maybe BytecodeEmitter::locationOfNameBoundInScopeType( + TaggedParserAtomIndex name, EmitterScope* source) { + EmitterScope* aScope = source; + while (!aScope->scope(this).is()) { + aScope = aScope->enclosingInFrame(); + } + return source->locationBoundInScope(name, aScope); +} + +bool BytecodeEmitter::markStepBreakpoint() { + if (skipBreakpointSrcNotes()) { + return true; + } + + if (!newSrcNote(SrcNoteType::StepSep)) { + return false; + } + + if (!newSrcNote(SrcNoteType::Breakpoint)) { + return false; + } + + // We track the location of the most recent separator for use in + // markSimpleBreakpoint. Note that this means that the position must already + // be set before markStepBreakpoint is called. + bytecodeSection().updateSeparatorPosition(); + + return true; +} + +bool BytecodeEmitter::markSimpleBreakpoint() { + if (skipBreakpointSrcNotes()) { + return true; + } + + // If a breakable call ends up being the same location as the most recent + // expression start, we need to skip marking it breakable in order to avoid + // having two breakpoints with the same line/column position. + // Note: This assumes that the position for the call has already been set. + if (!bytecodeSection().isDuplicateLocation()) { + if (!newSrcNote(SrcNoteType::Breakpoint)) { + return false; + } + } + + return true; +} + +bool BytecodeEmitter::emitCheck(JSOp op, ptrdiff_t delta, + BytecodeOffset* offset) { + size_t oldLength = bytecodeSection().code().length(); + *offset = BytecodeOffset(oldLength); + + size_t newLength = oldLength + size_t(delta); + if (MOZ_UNLIKELY(newLength > MaxBytecodeLength)) { + ReportAllocationOverflow(fc); + return false; + } + + if (!bytecodeSection().code().growByUninitialized(delta)) { + return false; + } + + if (BytecodeOpHasIC(op)) { + // Even if every bytecode op is a JOF_IC op and the function has ARGC_LIMIT + // arguments, numICEntries cannot overflow. + static_assert(MaxBytecodeLength + 1 /* this */ + ARGC_LIMIT <= UINT32_MAX, + "numICEntries must not overflow"); + bytecodeSection().incrementNumICEntries(); + } + + return true; +} + +#ifdef DEBUG +bool BytecodeEmitter::checkStrictOrSloppy(JSOp op) { + if (IsCheckStrictOp(op) && !sc->strict()) { + return false; + } + if (IsCheckSloppyOp(op) && sc->strict()) { + return false; + } + return true; +} +#endif + +bool BytecodeEmitter::emit1(JSOp op) { + MOZ_ASSERT(checkStrictOrSloppy(op)); + + BytecodeOffset offset; + if (!emitCheck(op, 1, &offset)) { + return false; + } + + jsbytecode* code = bytecodeSection().code(offset); + code[0] = jsbytecode(op); + bytecodeSection().updateDepth(op, offset); + return true; +} + +bool BytecodeEmitter::emit2(JSOp op, uint8_t op1) { + MOZ_ASSERT(checkStrictOrSloppy(op)); + + BytecodeOffset offset; + if (!emitCheck(op, 2, &offset)) { + return false; + } + + jsbytecode* code = bytecodeSection().code(offset); + code[0] = jsbytecode(op); + code[1] = jsbytecode(op1); + bytecodeSection().updateDepth(op, offset); + return true; +} + +bool BytecodeEmitter::emit3(JSOp op, jsbytecode op1, jsbytecode op2) { + MOZ_ASSERT(checkStrictOrSloppy(op)); + + /* These should filter through emitVarOp. */ + MOZ_ASSERT(!IsArgOp(op)); + MOZ_ASSERT(!IsLocalOp(op)); + + BytecodeOffset offset; + if (!emitCheck(op, 3, &offset)) { + return false; + } + + jsbytecode* code = bytecodeSection().code(offset); + code[0] = jsbytecode(op); + code[1] = op1; + code[2] = op2; + bytecodeSection().updateDepth(op, offset); + return true; +} + +bool BytecodeEmitter::emitN(JSOp op, size_t extra, BytecodeOffset* offset) { + MOZ_ASSERT(checkStrictOrSloppy(op)); + ptrdiff_t length = 1 + ptrdiff_t(extra); + + BytecodeOffset off; + if (!emitCheck(op, length, &off)) { + return false; + } + + jsbytecode* code = bytecodeSection().code(off); + code[0] = jsbytecode(op); + /* The remaining |extra| bytes are set by the caller */ + + /* + * Don't updateDepth if op's use-count comes from the immediate + * operand yet to be stored in the extra bytes after op. + */ + if (CodeSpec(op).nuses >= 0) { + bytecodeSection().updateDepth(op, off); + } + + if (offset) { + *offset = off; + } + return true; +} + +bool BytecodeEmitter::emitJumpTargetOp(JSOp op, BytecodeOffset* off) { + MOZ_ASSERT(BytecodeIsJumpTarget(op)); + + // Record the current IC-entry index at start of this op. + uint32_t numEntries = bytecodeSection().numICEntries(); + + size_t n = GetOpLength(op) - 1; + MOZ_ASSERT(GetOpLength(op) >= 1 + ICINDEX_LEN); + + if (!emitN(op, n, off)) { + return false; + } + + SET_ICINDEX(bytecodeSection().code(*off), numEntries); + return true; +} + +bool BytecodeEmitter::emitJumpTarget(JumpTarget* target) { + BytecodeOffset off = bytecodeSection().offset(); + + // Alias consecutive jump targets. + if (bytecodeSection().lastTargetOffset().valid() && + off == bytecodeSection().lastTargetOffset() + + BytecodeOffsetDiff(JSOpLength_JumpTarget)) { + target->offset = bytecodeSection().lastTargetOffset(); + return true; + } + + target->offset = off; + bytecodeSection().setLastTargetOffset(off); + + BytecodeOffset opOff; + return emitJumpTargetOp(JSOp::JumpTarget, &opOff); +} + +bool BytecodeEmitter::emitJumpNoFallthrough(JSOp op, JumpList* jump) { + BytecodeOffset offset; + if (!emitCheck(op, 5, &offset)) { + return false; + } + + jsbytecode* code = bytecodeSection().code(offset); + code[0] = jsbytecode(op); + MOZ_ASSERT(!jump->offset.valid() || + (0 <= jump->offset.value() && jump->offset < offset)); + jump->push(bytecodeSection().code(BytecodeOffset(0)), offset); + bytecodeSection().updateDepth(op, offset); + return true; +} + +bool BytecodeEmitter::emitJump(JSOp op, JumpList* jump) { + if (!emitJumpNoFallthrough(op, jump)) { + return false; + } + if (BytecodeFallsThrough(op)) { + JumpTarget fallthrough; + if (!emitJumpTarget(&fallthrough)) { + return false; + } + } + return true; +} + +void BytecodeEmitter::patchJumpsToTarget(JumpList jump, JumpTarget target) { + MOZ_ASSERT( + !jump.offset.valid() || + (0 <= jump.offset.value() && jump.offset <= bytecodeSection().offset())); + MOZ_ASSERT(0 <= target.offset.value() && + target.offset <= bytecodeSection().offset()); + MOZ_ASSERT_IF( + jump.offset.valid() && + target.offset + BytecodeOffsetDiff(4) <= bytecodeSection().offset(), + BytecodeIsJumpTarget(JSOp(*bytecodeSection().code(target.offset)))); + jump.patchAll(bytecodeSection().code(BytecodeOffset(0)), target); +} + +bool BytecodeEmitter::emitJumpTargetAndPatch(JumpList jump) { + if (!jump.offset.valid()) { + return true; + } + JumpTarget target; + if (!emitJumpTarget(&target)) { + return false; + } + patchJumpsToTarget(jump, target); + return true; +} + +bool BytecodeEmitter::emitCall(JSOp op, uint16_t argc, + const Maybe& sourceCoordOffset) { + if (sourceCoordOffset.isSome()) { + if (!updateSourceCoordNotes(*sourceCoordOffset)) { + return false; + } + } + return emit3(op, ARGC_LO(argc), ARGC_HI(argc)); +} + +bool BytecodeEmitter::emitCall(JSOp op, uint16_t argc, ParseNode* pn) { + return emitCall(op, argc, pn ? Some(pn->pn_pos.begin) : Nothing()); +} + +bool BytecodeEmitter::emitDupAt(unsigned slotFromTop, unsigned count) { + MOZ_ASSERT(slotFromTop < unsigned(bytecodeSection().stackDepth())); + MOZ_ASSERT(slotFromTop + 1 >= count); + + if (slotFromTop == 0 && count == 1) { + return emit1(JSOp::Dup); + } + + if (slotFromTop == 1 && count == 2) { + return emit1(JSOp::Dup2); + } + + if (slotFromTop >= Bit(24)) { + reportError(nullptr, JSMSG_TOO_MANY_LOCALS); + return false; + } + + for (unsigned i = 0; i < count; i++) { + BytecodeOffset off; + if (!emitN(JSOp::DupAt, 3, &off)) { + return false; + } + + jsbytecode* pc = bytecodeSection().code(off); + SET_UINT24(pc, slotFromTop); + } + + return true; +} + +bool BytecodeEmitter::emitPopN(unsigned n) { + MOZ_ASSERT(n != 0); + + if (n == 1) { + return emit1(JSOp::Pop); + } + + // 2 JSOp::Pop instructions (2 bytes) are shorter than JSOp::PopN (3 bytes). + if (n == 2) { + return emit1(JSOp::Pop) && emit1(JSOp::Pop); + } + + return emitUint16Operand(JSOp::PopN, n); +} + +bool BytecodeEmitter::emitPickN(uint8_t n) { + MOZ_ASSERT(n != 0); + + if (n == 1) { + return emit1(JSOp::Swap); + } + + return emit2(JSOp::Pick, n); +} + +bool BytecodeEmitter::emitUnpickN(uint8_t n) { + MOZ_ASSERT(n != 0); + + if (n == 1) { + return emit1(JSOp::Swap); + } + + return emit2(JSOp::Unpick, n); +} + +bool BytecodeEmitter::emitCheckIsObj(CheckIsObjectKind kind) { + return emit2(JSOp::CheckIsObj, uint8_t(kind)); +} + +bool BytecodeEmitter::emitBuiltinObject(BuiltinObjectKind kind) { + return emit2(JSOp::BuiltinObject, uint8_t(kind)); +} + +/* Updates line number notes, not column notes. */ +bool BytecodeEmitter::updateLineNumberNotes(uint32_t offset) { + if (skipLocationSrcNotes()) { + return true; + } + + const ErrorReporter& er = errorReporter(); + bool onThisLine; + if (!er.isOnThisLine(offset, bytecodeSection().currentLine(), &onThisLine)) { + er.errorNoOffset(JSMSG_OUT_OF_MEMORY); + return false; + } + + if (!onThisLine) { + unsigned line = er.lineAt(offset); + unsigned delta = line - bytecodeSection().currentLine(); + + // If we use a `SetLine` note below, we want it to be relative to the + // scripts initial line number for better chance of sharing. + unsigned initialLine = sc->extent().lineno; + MOZ_ASSERT(line >= initialLine); + + /* + * Encode any change in the current source line number by using + * either several SrcNoteType::NewLine notes or just one + * SrcNoteType::SetLine note, whichever consumes less space. + * + * NB: We handle backward line number deltas (possible with for + * loops where the update part is emitted after the body, but its + * line number is <= any line number in the body) here by letting + * unsigned delta_ wrap to a very large number, which triggers a + * SrcNoteType::SetLine. + */ + bytecodeSection().setCurrentLine(line, offset); + if (delta >= SrcNote::SetLine::lengthFor(line, initialLine)) { + if (!newSrcNote2(SrcNoteType::SetLine, + SrcNote::SetLine::toOperand(line, initialLine))) { + return false; + } + } else { + do { + if (!newSrcNote(SrcNoteType::NewLine)) { + return false; + } + } while (--delta != 0); + } + + bytecodeSection().updateSeparatorPositionIfPresent(); + } + return true; +} + +/* Updates the line number and column number information in the source notes. */ +bool BytecodeEmitter::updateSourceCoordNotes(uint32_t offset) { + if (!updateLineNumberNotes(offset)) { + return false; + } + + if (skipLocationSrcNotes()) { + return true; + } + + uint32_t columnIndex = errorReporter().columnAt(offset); + MOZ_ASSERT(columnIndex <= ColumnLimit); + + // Assert colspan is always representable. + static_assert((0 - ptrdiff_t(ColumnLimit)) >= SrcNote::ColSpan::MinColSpan); + static_assert((ptrdiff_t(ColumnLimit) - 0) <= SrcNote::ColSpan::MaxColSpan); + + ptrdiff_t colspan = + ptrdiff_t(columnIndex) - ptrdiff_t(bytecodeSection().lastColumn()); + + if (colspan != 0) { + if (!newSrcNote2(SrcNoteType::ColSpan, + SrcNote::ColSpan::toOperand(colspan))) { + return false; + } + bytecodeSection().setLastColumn(columnIndex, offset); + bytecodeSection().updateSeparatorPositionIfPresent(); + } + return true; +} + +uint32_t BytecodeEmitter::getOffsetForLoop(ParseNode* nextpn) { + // Try to give the JSOp::LoopHead the same line number as the next + // instruction. nextpn is often a block, in which case the next instruction + // typically comes from the first statement inside. + if (nextpn->is()) { + nextpn = nextpn->as().scopeBody(); + } + if (nextpn->isKind(ParseNodeKind::StatementList)) { + if (ParseNode* firstStatement = nextpn->as().head()) { + nextpn = firstStatement; + } + } + + return nextpn->pn_pos.begin; +} + +bool BytecodeEmitter::emitUint16Operand(JSOp op, uint32_t operand) { + MOZ_ASSERT(operand <= UINT16_MAX); + if (!emit3(op, UINT16_LO(operand), UINT16_HI(operand))) { + return false; + } + return true; +} + +bool BytecodeEmitter::emitUint32Operand(JSOp op, uint32_t operand) { + BytecodeOffset off; + if (!emitN(op, 4, &off)) { + return false; + } + SET_UINT32(bytecodeSection().code(off), operand); + return true; +} + +bool BytecodeEmitter::emitGoto(NestableControl* target, GotoKind kind) { + NonLocalExitControl nle(this, kind == GotoKind::Continue + ? NonLocalExitKind::Continue + : NonLocalExitKind::Break); + return nle.emitNonLocalJump(target); +} + +AbstractScopePtr BytecodeEmitter::innermostScope() const { + return innermostEmitterScope()->scope(this); +} + +ScopeIndex BytecodeEmitter::innermostScopeIndex() const { + return *innermostEmitterScope()->scopeIndex(this); +} + +bool BytecodeEmitter::emitGCIndexOp(JSOp op, GCThingIndex index) { + MOZ_ASSERT(checkStrictOrSloppy(op)); + + constexpr size_t OpLength = 1 + GCTHING_INDEX_LEN; + MOZ_ASSERT(GetOpLength(op) == OpLength); + + BytecodeOffset offset; + if (!emitCheck(op, OpLength, &offset)) { + return false; + } + + jsbytecode* code = bytecodeSection().code(offset); + code[0] = jsbytecode(op); + SET_GCTHING_INDEX(code, index); + bytecodeSection().updateDepth(op, offset); + return true; +} + +bool BytecodeEmitter::emitAtomOp(JSOp op, TaggedParserAtomIndex atom) { + MOZ_ASSERT(atom); + + // .generator lookups should be emitted as JSOp::GetAliasedVar instead of + // JSOp::GetName etc, to bypass |with| objects on the scope chain. + // It's safe to emit .this lookups though because |with| objects skip + // those. + MOZ_ASSERT_IF(op == JSOp::GetName || op == JSOp::GetGName, + atom != TaggedParserAtomIndex::WellKnown::dotGenerator()); + + GCThingIndex index; + if (!makeAtomIndex(atom, ParserAtom::Atomize::Yes, &index)) { + return false; + } + + return emitAtomOp(op, index); +} + +bool BytecodeEmitter::emitAtomOp(JSOp op, GCThingIndex atomIndex) { + MOZ_ASSERT(JOF_OPTYPE(op) == JOF_ATOM); +#ifdef DEBUG + auto atom = perScriptData().gcThingList().getAtom(atomIndex); + MOZ_ASSERT(compilationState.parserAtoms.isInstantiatedAsJSAtom(atom)); +#endif + return emitGCIndexOp(op, atomIndex); +} + +bool BytecodeEmitter::emitStringOp(JSOp op, TaggedParserAtomIndex atom) { + MOZ_ASSERT(atom); + GCThingIndex index; + if (!makeAtomIndex(atom, ParserAtom::Atomize::No, &index)) { + return false; + } + + return emitStringOp(op, index); +} + +bool BytecodeEmitter::emitStringOp(JSOp op, GCThingIndex atomIndex) { + MOZ_ASSERT(JOF_OPTYPE(op) == JOF_STRING); + return emitGCIndexOp(op, atomIndex); +} + +bool BytecodeEmitter::emitInternedScopeOp(GCThingIndex index, JSOp op) { + MOZ_ASSERT(JOF_OPTYPE(op) == JOF_SCOPE); + MOZ_ASSERT(index < perScriptData().gcThingList().length()); + return emitGCIndexOp(op, index); +} + +bool BytecodeEmitter::emitInternedObjectOp(GCThingIndex index, JSOp op) { + MOZ_ASSERT(JOF_OPTYPE(op) == JOF_OBJECT); + MOZ_ASSERT(index < perScriptData().gcThingList().length()); + return emitGCIndexOp(op, index); +} + +bool BytecodeEmitter::emitRegExp(GCThingIndex index) { + return emitGCIndexOp(JSOp::RegExp, index); +} + +bool BytecodeEmitter::emitLocalOp(JSOp op, uint32_t slot) { + MOZ_ASSERT(JOF_OPTYPE(op) != JOF_ENVCOORD); + MOZ_ASSERT(IsLocalOp(op)); + + BytecodeOffset off; + if (!emitN(op, LOCALNO_LEN, &off)) { + return false; + } + + SET_LOCALNO(bytecodeSection().code(off), slot); + return true; +} + +bool BytecodeEmitter::emitArgOp(JSOp op, uint16_t slot) { + MOZ_ASSERT(IsArgOp(op)); + BytecodeOffset off; + if (!emitN(op, ARGNO_LEN, &off)) { + return false; + } + + SET_ARGNO(bytecodeSection().code(off), slot); + return true; +} + +bool BytecodeEmitter::emitEnvCoordOp(JSOp op, EnvironmentCoordinate ec) { + MOZ_ASSERT(JOF_OPTYPE(op) == JOF_ENVCOORD || + JOF_OPTYPE(op) == JOF_DEBUGCOORD); + + constexpr size_t N = ENVCOORD_HOPS_LEN + ENVCOORD_SLOT_LEN; + MOZ_ASSERT(GetOpLength(op) == 1 + N); + + BytecodeOffset off; + if (!emitN(op, N, &off)) { + return false; + } + + jsbytecode* pc = bytecodeSection().code(off); + SET_ENVCOORD_HOPS(pc, ec.hops()); + pc += ENVCOORD_HOPS_LEN; + SET_ENVCOORD_SLOT(pc, ec.slot()); + pc += ENVCOORD_SLOT_LEN; + return true; +} + +JSOp BytecodeEmitter::strictifySetNameOp(JSOp op) { + switch (op) { + case JSOp::SetName: + if (sc->strict()) { + op = JSOp::StrictSetName; + } + break; + case JSOp::SetGName: + if (sc->strict()) { + op = JSOp::StrictSetGName; + } + break; + default:; + } + return op; +} + +bool BytecodeEmitter::checkSideEffects(ParseNode* pn, bool* answer) { + AutoCheckRecursionLimit recursion(fc); + if (!recursion.check(fc)) { + return false; + } + +restart: + + switch (pn->getKind()) { + // Trivial cases with no side effects. + case ParseNodeKind::EmptyStmt: + case ParseNodeKind::TrueExpr: + case ParseNodeKind::FalseExpr: + case ParseNodeKind::NullExpr: + case ParseNodeKind::RawUndefinedExpr: + case ParseNodeKind::Elision: + case ParseNodeKind::Generator: + MOZ_ASSERT(pn->is()); + *answer = false; + return true; + + case ParseNodeKind::ObjectPropertyName: + case ParseNodeKind::PrivateName: // no side effects, unlike + // ParseNodeKind::Name + case ParseNodeKind::StringExpr: + case ParseNodeKind::TemplateStringExpr: + MOZ_ASSERT(pn->is()); + *answer = false; + return true; + + case ParseNodeKind::RegExpExpr: + MOZ_ASSERT(pn->is()); + *answer = false; + return true; + + case ParseNodeKind::NumberExpr: + MOZ_ASSERT(pn->is()); + *answer = false; + return true; + + case ParseNodeKind::BigIntExpr: + MOZ_ASSERT(pn->is()); + *answer = false; + return true; + + // |this| can throw in derived class constructors, including nested arrow + // functions or eval. + case ParseNodeKind::ThisExpr: + MOZ_ASSERT(pn->is()); + *answer = sc->needsThisTDZChecks(); + return true; + + // |new.target| doesn't have any side-effects. + case ParseNodeKind::NewTargetExpr: { + MOZ_ASSERT(pn->is()); + *answer = false; + return true; + } + + // Trivial binary nodes with more token pos holders. + case ParseNodeKind::ImportMetaExpr: { + MOZ_ASSERT(pn->as().left()->isKind(ParseNodeKind::PosHolder)); + MOZ_ASSERT( + pn->as().right()->isKind(ParseNodeKind::PosHolder)); + *answer = false; + return true; + } + + case ParseNodeKind::BreakStmt: + MOZ_ASSERT(pn->is()); + *answer = true; + return true; + + case ParseNodeKind::ContinueStmt: + MOZ_ASSERT(pn->is()); + *answer = true; + return true; + + case ParseNodeKind::DebuggerStmt: + MOZ_ASSERT(pn->is()); + *answer = true; + return true; + + // Watch out for getters! + case ParseNodeKind::OptionalDotExpr: + case ParseNodeKind::DotExpr: + MOZ_ASSERT(pn->is()); + *answer = true; + return true; + + // Unary cases with side effects only if the child has them. + case ParseNodeKind::TypeOfExpr: + case ParseNodeKind::VoidExpr: + case ParseNodeKind::NotExpr: + return checkSideEffects(pn->as().kid(), answer); + + // Even if the name expression is effect-free, performing ToPropertyKey on + // it might not be effect-free: + // + // RegExp.prototype.toString = () => { throw 42; }; + // ({ [/regex/]: 0 }); // ToPropertyKey(/regex/) throws 42 + // + // function Q() { + // ({ [new.target]: 0 }); + // } + // Q.toString = () => { throw 17; }; + // new Q; // new.target will be Q, ToPropertyKey(Q) throws 17 + case ParseNodeKind::ComputedName: + MOZ_ASSERT(pn->is()); + *answer = true; + return true; + + // Looking up or evaluating the associated name could throw. + case ParseNodeKind::TypeOfNameExpr: + MOZ_ASSERT(pn->is()); + *answer = true; + return true; + + // This unary case has side effects on the enclosing object, sure. But + // that's not the question this function answers: it's whether the + // operation may have a side effect on something *other* than the result + // of the overall operation in which it's embedded. The answer to that + // is no, because an object literal having a mutated prototype only + // produces a value, without affecting anything else. + case ParseNodeKind::MutateProto: + return checkSideEffects(pn->as().kid(), answer); + + // Unary cases with obvious side effects. + case ParseNodeKind::PreIncrementExpr: + case ParseNodeKind::PostIncrementExpr: + case ParseNodeKind::PreDecrementExpr: + case ParseNodeKind::PostDecrementExpr: + case ParseNodeKind::ThrowStmt: + MOZ_ASSERT(pn->is()); + *answer = true; + return true; + + // These might invoke valueOf/toString, even with a subexpression without + // side effects! Consider |+{ valueOf: null, toString: null }|. + case ParseNodeKind::BitNotExpr: + case ParseNodeKind::PosExpr: + case ParseNodeKind::NegExpr: + MOZ_ASSERT(pn->is()); + *answer = true; + return true; + + // This invokes the (user-controllable) iterator protocol. + case ParseNodeKind::Spread: + MOZ_ASSERT(pn->is()); + *answer = true; + return true; + + case ParseNodeKind::InitialYield: + case ParseNodeKind::YieldStarExpr: + case ParseNodeKind::YieldExpr: + case ParseNodeKind::AwaitExpr: + MOZ_ASSERT(pn->is()); + *answer = true; + return true; + + // Deletion generally has side effects, even if isolated cases have none. + case ParseNodeKind::DeleteNameExpr: + case ParseNodeKind::DeletePropExpr: + case ParseNodeKind::DeleteElemExpr: + case ParseNodeKind::DeleteOptionalChainExpr: + MOZ_ASSERT(pn->is()); + *answer = true; + return true; + + // Deletion of a non-Reference expression has side effects only through + // evaluating the expression. + case ParseNodeKind::DeleteExpr: { + ParseNode* expr = pn->as().kid(); + return checkSideEffects(expr, answer); + } + + case ParseNodeKind::ExpressionStmt: + return checkSideEffects(pn->as().kid(), answer); + + // Binary cases with obvious side effects. + case ParseNodeKind::InitExpr: + *answer = true; + return true; + + case ParseNodeKind::AssignExpr: + case ParseNodeKind::AddAssignExpr: + case ParseNodeKind::SubAssignExpr: + case ParseNodeKind::CoalesceAssignExpr: + case ParseNodeKind::OrAssignExpr: + case ParseNodeKind::AndAssignExpr: + case ParseNodeKind::BitOrAssignExpr: + case ParseNodeKind::BitXorAssignExpr: + case ParseNodeKind::BitAndAssignExpr: + case ParseNodeKind::LshAssignExpr: + case ParseNodeKind::RshAssignExpr: + case ParseNodeKind::UrshAssignExpr: + case ParseNodeKind::MulAssignExpr: + case ParseNodeKind::DivAssignExpr: + case ParseNodeKind::ModAssignExpr: + case ParseNodeKind::PowAssignExpr: + MOZ_ASSERT(pn->is()); + *answer = true; + return true; + + case ParseNodeKind::SetThis: + MOZ_ASSERT(pn->is()); + *answer = true; + return true; + + case ParseNodeKind::StatementList: + // Strict equality operations and short circuit operators are well-behaved + // and perform no conversions. + case ParseNodeKind::CoalesceExpr: + case ParseNodeKind::OrExpr: + case ParseNodeKind::AndExpr: + case ParseNodeKind::StrictEqExpr: + case ParseNodeKind::StrictNeExpr: + // Any subexpression of a comma expression could be effectful. + case ParseNodeKind::CommaExpr: + MOZ_ASSERT(!pn->as().empty()); + [[fallthrough]]; + // Subcomponents of a literal may be effectful. + case ParseNodeKind::ArrayExpr: + case ParseNodeKind::ObjectExpr: + for (ParseNode* item : pn->as().contents()) { + if (!checkSideEffects(item, answer)) { + return false; + } + if (*answer) { + return true; + } + } + return true; + +#ifdef ENABLE_RECORD_TUPLE + case ParseNodeKind::RecordExpr: + case ParseNodeKind::TupleExpr: + MOZ_CRASH("Record and Tuple are not supported yet"); +#endif + +#ifdef ENABLE_DECORATORS + case ParseNodeKind::DecoratorList: + MOZ_CRASH("Decorators are not supported yet"); +#endif + + // Most other binary operations (parsed as lists in SpiderMonkey) may + // perform conversions triggering side effects. Math operations perform + // ToNumber and may fail invoking invalid user-defined toString/valueOf: + // |5 < { toString: null }|. |instanceof| throws if provided a + // non-object constructor: |null instanceof null|. |in| throws if given + // a non-object RHS: |5 in null|. + case ParseNodeKind::BitOrExpr: + case ParseNodeKind::BitXorExpr: + case ParseNodeKind::BitAndExpr: + case ParseNodeKind::EqExpr: + case ParseNodeKind::NeExpr: + case ParseNodeKind::LtExpr: + case ParseNodeKind::LeExpr: + case ParseNodeKind::GtExpr: + case ParseNodeKind::GeExpr: + case ParseNodeKind::InstanceOfExpr: + case ParseNodeKind::InExpr: + case ParseNodeKind::PrivateInExpr: + case ParseNodeKind::LshExpr: + case ParseNodeKind::RshExpr: + case ParseNodeKind::UrshExpr: + case ParseNodeKind::AddExpr: + case ParseNodeKind::SubExpr: + case ParseNodeKind::MulExpr: + case ParseNodeKind::DivExpr: + case ParseNodeKind::ModExpr: + case ParseNodeKind::PowExpr: + MOZ_ASSERT(pn->as().count() >= 2); + *answer = true; + return true; + + case ParseNodeKind::PropertyDefinition: + case ParseNodeKind::Case: { + BinaryNode* node = &pn->as(); + if (!checkSideEffects(node->left(), answer)) { + return false; + } + if (*answer) { + return true; + } + return checkSideEffects(node->right(), answer); + } + + // More getters. + case ParseNodeKind::ElemExpr: + case ParseNodeKind::OptionalElemExpr: + MOZ_ASSERT(pn->is()); + *answer = true; + return true; + + // Throws if the operand is not of the right class. Can also call a private + // getter. + case ParseNodeKind::PrivateMemberExpr: + case ParseNodeKind::OptionalPrivateMemberExpr: + *answer = true; + return true; + + // These affect visible names in this code, or in other code. + case ParseNodeKind::ImportDecl: + case ParseNodeKind::ExportFromStmt: + case ParseNodeKind::ExportDefaultStmt: + MOZ_ASSERT(pn->is()); + *answer = true; + return true; + + // Likewise. + case ParseNodeKind::ExportStmt: + MOZ_ASSERT(pn->is()); + *answer = true; + return true; + + case ParseNodeKind::CallImportExpr: + case ParseNodeKind::CallImportSpec: + MOZ_ASSERT(pn->is()); + *answer = true; + return true; + + // Every part of a loop might be effect-free, but looping infinitely *is* + // an effect. (Language lawyer trivia: C++ says threads can be assumed + // to exit or have side effects, C++14 [intro.multithread]p27, so a C++ + // implementation's equivalent of the below could set |*answer = false;| + // if all loop sub-nodes set |*answer = false|!) + case ParseNodeKind::DoWhileStmt: + case ParseNodeKind::WhileStmt: + case ParseNodeKind::ForStmt: + MOZ_ASSERT(pn->is()); + *answer = true; + return true; + + // Declarations affect the name set of the relevant scope. + case ParseNodeKind::VarStmt: + case ParseNodeKind::ConstDecl: + case ParseNodeKind::LetDecl: + MOZ_ASSERT(pn->is()); + *answer = true; + return true; + + case ParseNodeKind::IfStmt: + case ParseNodeKind::ConditionalExpr: { + TernaryNode* node = &pn->as(); + if (!checkSideEffects(node->kid1(), answer)) { + return false; + } + if (*answer) { + return true; + } + if (!checkSideEffects(node->kid2(), answer)) { + return false; + } + if (*answer) { + return true; + } + if ((pn = node->kid3())) { + goto restart; + } + return true; + } + + // Function calls can invoke non-local code. + case ParseNodeKind::NewExpr: + case ParseNodeKind::CallExpr: + case ParseNodeKind::OptionalCallExpr: + case ParseNodeKind::TaggedTemplateExpr: + case ParseNodeKind::SuperCallExpr: + MOZ_ASSERT(pn->is()); + *answer = true; + return true; + + // Function arg lists can contain arbitrary expressions. Technically + // this only causes side-effects if one of the arguments does, but since + // the call being made will always trigger side-effects, it isn't needed. + case ParseNodeKind::Arguments: + MOZ_ASSERT(pn->is()); + *answer = true; + return true; + + case ParseNodeKind::OptionalChain: + MOZ_ASSERT(pn->is()); + *answer = true; + return true; + + // Classes typically introduce names. Even if no name is introduced, + // the heritage and/or class body (through computed property names) + // usually have effects. + case ParseNodeKind::ClassDecl: + MOZ_ASSERT(pn->is()); + *answer = true; + return true; + + // |with| calls |ToObject| on its expression and so throws if that value + // is null/undefined. + case ParseNodeKind::WithStmt: + MOZ_ASSERT(pn->is()); + *answer = true; + return true; + + case ParseNodeKind::ReturnStmt: + MOZ_ASSERT(pn->is()); + *answer = true; + return true; + + case ParseNodeKind::Name: + MOZ_ASSERT(pn->is()); + *answer = true; + return true; + + // Shorthands could trigger getters: the |x| in the object literal in + // |with ({ get x() { throw 42; } }) ({ x });|, for example, triggers + // one. (Of course, it isn't necessary to use |with| for a shorthand to + // trigger a getter.) + case ParseNodeKind::Shorthand: + MOZ_ASSERT(pn->is()); + *answer = true; + return true; + + case ParseNodeKind::Function: + MOZ_ASSERT(pn->is()); + /* + * A named function, contrary to ES3, is no longer effectful, because + * we bind its name lexically (using JSOp::Callee) instead of creating + * an Object instance and binding a readonly, permanent property in it + * (the object and binding can be detected and hijacked or captured). + * This is a bug fix to ES3; it is fixed in ES3.1 drafts. + */ + *answer = false; + return true; + + case ParseNodeKind::Module: + *answer = false; + return true; + + case ParseNodeKind::TryStmt: { + TryNode* tryNode = &pn->as(); + if (!checkSideEffects(tryNode->body(), answer)) { + return false; + } + if (*answer) { + return true; + } + if (LexicalScopeNode* catchScope = tryNode->catchScope()) { + if (!checkSideEffects(catchScope, answer)) { + return false; + } + if (*answer) { + return true; + } + } + if (ParseNode* finallyBlock = tryNode->finallyBlock()) { + if (!checkSideEffects(finallyBlock, answer)) { + return false; + } + } + return true; + } + + case ParseNodeKind::Catch: { + BinaryNode* catchClause = &pn->as(); + if (ParseNode* name = catchClause->left()) { + if (!checkSideEffects(name, answer)) { + return false; + } + if (*answer) { + return true; + } + } + return checkSideEffects(catchClause->right(), answer); + } + + case ParseNodeKind::SwitchStmt: { + SwitchStatement* switchStmt = &pn->as(); + if (!checkSideEffects(&switchStmt->discriminant(), answer)) { + return false; + } + return *answer || + checkSideEffects(&switchStmt->lexicalForCaseList(), answer); + } + + case ParseNodeKind::LabelStmt: + return checkSideEffects(pn->as().statement(), answer); + + case ParseNodeKind::LexicalScope: + return checkSideEffects(pn->as().scopeBody(), answer); + + // We could methodically check every interpolated expression, but it's + // probably not worth the trouble. Treat template strings as effect-free + // only if they don't contain any substitutions. + case ParseNodeKind::TemplateStringListExpr: { + ListNode* list = &pn->as(); + MOZ_ASSERT(!list->empty()); + MOZ_ASSERT((list->count() % 2) == 1, + "template strings must alternate template and substitution " + "parts"); + *answer = list->count() > 1; + return true; + } + + // This should be unreachable but is left as-is for now. + case ParseNodeKind::ParamsBody: + *answer = true; + return true; + + case ParseNodeKind::ForIn: // by ParseNodeKind::For + case ParseNodeKind::ForOf: // by ParseNodeKind::For + case ParseNodeKind::ForHead: // by ParseNodeKind::For + case ParseNodeKind::DefaultConstructor: // by ParseNodeKind::ClassDecl + case ParseNodeKind::ClassBodyScope: // by ParseNodeKind::ClassDecl + case ParseNodeKind::ClassMethod: // by ParseNodeKind::ClassDecl + case ParseNodeKind::ClassField: // by ParseNodeKind::ClassDecl + case ParseNodeKind::ClassNames: // by ParseNodeKind::ClassDecl + case ParseNodeKind::StaticClassBlock: // by ParseNodeKind::ClassDecl + case ParseNodeKind::ClassMemberList: // by ParseNodeKind::ClassDecl + case ParseNodeKind::ImportSpecList: // by ParseNodeKind::Import + case ParseNodeKind::ImportSpec: // by ParseNodeKind::Import + case ParseNodeKind::ImportNamespaceSpec: // by ParseNodeKind::Import + case ParseNodeKind::ImportAssertion: // by ParseNodeKind::Import + case ParseNodeKind::ImportAssertionList: // by ParseNodeKind::Import + case ParseNodeKind::ImportModuleRequest: // by ParseNodeKind::Import + case ParseNodeKind::ExportBatchSpecStmt: // by ParseNodeKind::Export + case ParseNodeKind::ExportSpecList: // by ParseNodeKind::Export + case ParseNodeKind::ExportSpec: // by ParseNodeKind::Export + case ParseNodeKind::ExportNamespaceSpec: // by ParseNodeKind::Export + case ParseNodeKind::CallSiteObj: // by ParseNodeKind::TaggedTemplate + case ParseNodeKind::PosHolder: // by ParseNodeKind::NewTarget + case ParseNodeKind::SuperBase: // by ParseNodeKind::Elem and others + case ParseNodeKind::PropertyNameExpr: // by ParseNodeKind::Dot + MOZ_CRASH("handled by parent nodes"); + + case ParseNodeKind::LastUnused: + case ParseNodeKind::Limit: + MOZ_CRASH("invalid node kind"); + } + + MOZ_CRASH( + "invalid, unenumerated ParseNodeKind value encountered in " + "BytecodeEmitter::checkSideEffects"); +} + +bool BytecodeEmitter::isInLoop() { + return findInnermostNestableControl(); +} + +bool BytecodeEmitter::checkSingletonContext() { + MOZ_ASSERT_IF(sc->treatAsRunOnce(), sc->isTopLevelContext()); + return sc->treatAsRunOnce() && !isInLoop(); +} + +bool BytecodeEmitter::needsImplicitThis() { + // Short-circuit if there is an enclosing 'with' scope. + if (sc->inWith()) { + return true; + } + + // Otherwise see if the current point is under a 'with'. + for (EmitterScope* es = innermostEmitterScope(); es; + es = es->enclosingInFrame()) { + if (es->scope(this).kind() == ScopeKind::With) { + return true; + } + } + + return false; +} + +size_t BytecodeEmitter::countThisEnvironmentHops() { + unsigned numHops = 0; + + for (BytecodeEmitter* current = this; current; current = current->parent) { + for (EmitterScope* es = current->innermostEmitterScope(); es; + es = es->enclosingInFrame()) { + if (es->scope(current).is()) { + if (!es->scope(current).isArrow()) { + // The Parser is responsible for marking the environment as either + // closed-over or used-by-eval which ensure that is must exist. + MOZ_ASSERT(es->scope(current).hasEnvironment()); + return numHops; + } + } + if (es->scope(current).hasEnvironment()) { + numHops++; + } + } + } + + // The "this" environment exists outside of the compilation, but the + // `ScopeContext` recorded the number of additional hops needed, so add + // those in now. + MOZ_ASSERT(sc->allowSuperProperty()); + numHops += compilationState.scopeContext.enclosingThisEnvironmentHops; + return numHops; +} + +bool BytecodeEmitter::emitThisEnvironmentCallee() { + // Get the innermost enclosing function that has a |this| binding. + + // Directly load callee from the frame if possible. + if (sc->isFunctionBox() && !sc->asFunctionBox()->isArrow()) { + return emit1(JSOp::Callee); + } + + // We have to load the callee from the environment chain. + size_t numHops = countThisEnvironmentHops(); + + static_assert( + ENVCOORD_HOPS_LIMIT - 1 <= UINT8_MAX, + "JSOp::EnvCallee operand size should match ENVCOORD_HOPS_LIMIT"); + + MOZ_ASSERT(numHops < ENVCOORD_HOPS_LIMIT - 1); + + return emit2(JSOp::EnvCallee, numHops); +} + +bool BytecodeEmitter::emitSuperBase() { + if (!emitThisEnvironmentCallee()) { + return false; + } + + return emit1(JSOp::SuperBase); +} + +void BytecodeEmitter::reportError(ParseNode* pn, unsigned errorNumber, ...) { + uint32_t offset = pn ? pn->pn_pos.begin : *scriptStartOffset; + + va_list args; + va_start(args, errorNumber); + + errorReporter().errorWithNotesAtVA(nullptr, AsVariant(offset), errorNumber, + &args); + + va_end(args); +} + +void BytecodeEmitter::reportError(uint32_t offset, unsigned errorNumber, ...) { + va_list args; + va_start(args, errorNumber); + + errorReporter().errorWithNotesAtVA(nullptr, AsVariant(offset), errorNumber, + &args); + + va_end(args); +} + +bool BytecodeEmitter::addObjLiteralData(ObjLiteralWriter& writer, + GCThingIndex* outIndex) { + if (!writer.checkForDuplicatedNames(fc)) { + return false; + } + + size_t len = writer.getCode().size(); + auto* code = compilationState.alloc.newArrayUninitialized(len); + if (!code) { + js::ReportOutOfMemory(fc); + return false; + } + memcpy(code, writer.getCode().data(), len); + + ObjLiteralIndex objIndex(compilationState.objLiteralData.length()); + if (uint32_t(objIndex) >= TaggedScriptThingIndex::IndexLimit) { + ReportAllocationOverflow(fc); + return false; + } + if (!compilationState.objLiteralData.emplaceBack(code, len, writer.getKind(), + writer.getFlags(), + writer.getPropertyCount())) { + js::ReportOutOfMemory(fc); + return false; + } + + return perScriptData().gcThingList().append(objIndex, outIndex); +} + +bool BytecodeEmitter::emitPrepareIteratorResult() { + constexpr JSOp op = JSOp::NewObject; + + ObjLiteralWriter writer; + writer.beginShape(op); + + writer.setPropNameNoDuplicateCheck(parserAtoms(), + TaggedParserAtomIndex::WellKnown::value()); + if (!writer.propWithUndefinedValue(fc)) { + return false; + } + writer.setPropNameNoDuplicateCheck(parserAtoms(), + TaggedParserAtomIndex::WellKnown::done()); + if (!writer.propWithUndefinedValue(fc)) { + return false; + } + + GCThingIndex shape; + if (!addObjLiteralData(writer, &shape)) { + return false; + } + + return emitGCIndexOp(op, shape); +} + +bool BytecodeEmitter::emitFinishIteratorResult(bool done) { + if (!emitAtomOp(JSOp::InitProp, TaggedParserAtomIndex::WellKnown::value())) { + return false; + } + if (!emit1(done ? JSOp::True : JSOp::False)) { + return false; + } + if (!emitAtomOp(JSOp::InitProp, TaggedParserAtomIndex::WellKnown::done())) { + return false; + } + return true; +} + +bool BytecodeEmitter::emitGetNameAtLocation(TaggedParserAtomIndex name, + const NameLocation& loc) { + NameOpEmitter noe(this, name, loc, NameOpEmitter::Kind::Get); + if (!noe.emitGet()) { + return false; + } + + return true; +} + +bool BytecodeEmitter::emitGetName(NameNode* name) { + MOZ_ASSERT(name->isKind(ParseNodeKind::Name)); + + return emitGetName(name->name()); +} + +bool BytecodeEmitter::emitGetPrivateName(NameNode* name) { + MOZ_ASSERT(name->isKind(ParseNodeKind::PrivateName)); + return emitGetPrivateName(name->name()); +} + +bool BytecodeEmitter::emitGetPrivateName(TaggedParserAtomIndex nameAtom) { + // The parser ensures the private name is present on the environment chain, + // but its location can be Dynamic or Global when emitting debugger + // eval-in-frame code. + NameLocation location = lookupName(nameAtom); + MOZ_ASSERT(location.kind() == NameLocation::Kind::FrameSlot || + location.kind() == NameLocation::Kind::EnvironmentCoordinate || + location.kind() == NameLocation::Kind::Dynamic || + location.kind() == NameLocation::Kind::Global); + + return emitGetNameAtLocation(nameAtom, location); +} + +bool BytecodeEmitter::emitTDZCheckIfNeeded(TaggedParserAtomIndex name, + const NameLocation& loc, + ValueIsOnStack isOnStack) { + // Dynamic accesses have TDZ checks built into their VM code and should + // never emit explicit TDZ checks. + MOZ_ASSERT(loc.hasKnownSlot()); + MOZ_ASSERT(loc.isLexical() || loc.isPrivateMethod() || loc.isSynthetic()); + + // Private names are implemented as lexical bindings, but it's just an + // implementation detail. Per spec there's no TDZ check when using them. + if (parserAtoms().isPrivateName(name)) { + return true; + } + + Maybe check = + innermostTDZCheckCache->needsTDZCheck(this, name); + if (!check) { + return false; + } + + // We've already emitted a check in this basic block. + if (*check == DontCheckTDZ) { + return true; + } + + // If the value is not on the stack, we have to load it first. + if (isOnStack == ValueIsOnStack::No) { + if (loc.kind() == NameLocation::Kind::FrameSlot) { + if (!emitLocalOp(JSOp::GetLocal, loc.frameSlot())) { + return false; + } + } else { + if (!emitEnvCoordOp(JSOp::GetAliasedVar, loc.environmentCoordinate())) { + return false; + } + } + } + + // Emit the lexical check. + if (loc.kind() == NameLocation::Kind::FrameSlot) { + if (!emitLocalOp(JSOp::CheckLexical, loc.frameSlot())) { + return false; + } + } else { + if (!emitEnvCoordOp(JSOp::CheckAliasedLexical, + loc.environmentCoordinate())) { + return false; + } + } + + // Pop the value if needed. + if (isOnStack == ValueIsOnStack::No) { + if (!emit1(JSOp::Pop)) { + return false; + } + } + + return innermostTDZCheckCache->noteTDZCheck(this, name, DontCheckTDZ); +} + +bool BytecodeEmitter::emitPropLHS(PropertyAccess* prop) { + MOZ_ASSERT(!prop->isSuper()); + + ParseNode* expr = &prop->expression(); + + if (!expr->is() || expr->as().isSuper()) { + // The non-optimized case. + return emitTree(expr); + } + + // If the object operand is also a dotted property reference, reverse the + // list linked via expression() temporarily so we can iterate over it from + // the bottom up (reversing again as we go), to avoid excessive recursion. + PropertyAccess* pndot = &expr->as(); + ParseNode* pnup = nullptr; + ParseNode* pndown; + for (;;) { + // Reverse pndot->expression() to point up, not down. + pndown = &pndot->expression(); + pndot->setExpression(pnup); + if (!pndown->is() || + pndown->as().isSuper()) { + break; + } + pnup = pndot; + pndot = &pndown->as(); + } + + // pndown is a primary expression, not a dotted property reference. + if (!emitTree(pndown)) { + return false; + } + + while (true) { + // Walk back up the list, emitting annotated name ops. + if (!emitAtomOp(JSOp::GetProp, pndot->key().atom())) { + return false; + } + + // Reverse the pndot->expression() link again. + pnup = pndot->maybeExpression(); + pndot->setExpression(pndown); + pndown = pndot; + if (!pnup) { + break; + } + pndot = &pnup->as(); + } + return true; +} + +bool BytecodeEmitter::emitPropIncDec(UnaryNode* incDec, ValueUsage valueUsage) { + PropertyAccess* prop = &incDec->kid()->as(); + bool isSuper = prop->isSuper(); + ParseNodeKind kind = incDec->getKind(); + PropOpEmitter poe( + this, + kind == ParseNodeKind::PostIncrementExpr + ? PropOpEmitter::Kind::PostIncrement + : kind == ParseNodeKind::PreIncrementExpr + ? PropOpEmitter::Kind::PreIncrement + : kind == ParseNodeKind::PostDecrementExpr + ? PropOpEmitter::Kind::PostDecrement + : PropOpEmitter::Kind::PreDecrement, + isSuper ? PropOpEmitter::ObjKind::Super : PropOpEmitter::ObjKind::Other); + if (!poe.prepareForObj()) { + return false; + } + if (isSuper) { + UnaryNode* base = &prop->expression().as(); + if (!emitGetThisForSuperBase(base)) { + // [stack] THIS + return false; + } + } else { + if (!emitPropLHS(prop)) { + // [stack] OBJ + return false; + } + } + if (!poe.emitIncDec(prop->key().atom(), valueUsage)) { + // [stack] RESULT + return false; + } + + return true; +} + +bool BytecodeEmitter::emitNameIncDec(UnaryNode* incDec, ValueUsage valueUsage) { + MOZ_ASSERT(incDec->kid()->isKind(ParseNodeKind::Name)); + + ParseNodeKind kind = incDec->getKind(); + NameNode* name = &incDec->kid()->as(); + NameOpEmitter noe(this, name->atom(), + kind == ParseNodeKind::PostIncrementExpr + ? NameOpEmitter::Kind::PostIncrement + : kind == ParseNodeKind::PreIncrementExpr + ? NameOpEmitter::Kind::PreIncrement + : kind == ParseNodeKind::PostDecrementExpr + ? NameOpEmitter::Kind::PostDecrement + : NameOpEmitter::Kind::PreDecrement); + if (!noe.emitIncDec(valueUsage)) { + return false; + } + + return true; +} + +bool BytecodeEmitter::emitObjAndKey(ParseNode* exprOrSuper, ParseNode* key, + ElemOpEmitter& eoe) { + if (exprOrSuper->isKind(ParseNodeKind::SuperBase)) { + if (!eoe.prepareForObj()) { + // [stack] + return false; + } + UnaryNode* base = &exprOrSuper->as(); + if (!emitGetThisForSuperBase(base)) { + // [stack] THIS + return false; + } + if (!eoe.prepareForKey()) { + // [stack] THIS + return false; + } + if (!emitTree(key)) { + // [stack] THIS KEY + return false; + } + + return true; + } + + if (!eoe.prepareForObj()) { + // [stack] + return false; + } + if (!emitTree(exprOrSuper)) { + // [stack] OBJ + return false; + } + if (!eoe.prepareForKey()) { + // [stack] OBJ? OBJ + return false; + } + if (!emitTree(key)) { + // [stack] OBJ? OBJ KEY + return false; + } + + return true; +} + +bool BytecodeEmitter::emitElemOpBase(JSOp op) { + if (!emit1(op)) { + return false; + } + + return true; +} + +bool BytecodeEmitter::emitElemObjAndKey(PropertyByValue* elem, bool isSuper, + ElemOpEmitter& eoe) { + MOZ_ASSERT(isSuper == elem->expression().isKind(ParseNodeKind::SuperBase)); + return emitObjAndKey(&elem->expression(), &elem->key(), eoe); +} + +static ElemOpEmitter::Kind ConvertIncDecKind(ParseNodeKind kind) { + switch (kind) { + case ParseNodeKind::PostIncrementExpr: + return ElemOpEmitter::Kind::PostIncrement; + case ParseNodeKind::PreIncrementExpr: + return ElemOpEmitter::Kind::PreIncrement; + case ParseNodeKind::PostDecrementExpr: + return ElemOpEmitter::Kind::PostDecrement; + case ParseNodeKind::PreDecrementExpr: + return ElemOpEmitter::Kind::PreDecrement; + default: + MOZ_CRASH("unexpected inc/dec node kind"); + } +} + +static PrivateOpEmitter::Kind PrivateConvertIncDecKind(ParseNodeKind kind) { + switch (kind) { + case ParseNodeKind::PostIncrementExpr: + return PrivateOpEmitter::Kind::PostIncrement; + case ParseNodeKind::PreIncrementExpr: + return PrivateOpEmitter::Kind::PreIncrement; + case ParseNodeKind::PostDecrementExpr: + return PrivateOpEmitter::Kind::PostDecrement; + case ParseNodeKind::PreDecrementExpr: + return PrivateOpEmitter::Kind::PreDecrement; + default: + MOZ_CRASH("unexpected inc/dec node kind"); + } +} + +bool BytecodeEmitter::emitElemIncDec(UnaryNode* incDec, ValueUsage valueUsage) { + PropertyByValue* elemExpr = &incDec->kid()->as(); + bool isSuper = elemExpr->isSuper(); + MOZ_ASSERT(!elemExpr->key().isKind(ParseNodeKind::PrivateName)); + ParseNodeKind kind = incDec->getKind(); + ElemOpEmitter eoe( + this, ConvertIncDecKind(kind), + isSuper ? ElemOpEmitter::ObjKind::Super : ElemOpEmitter::ObjKind::Other); + if (!emitElemObjAndKey(elemExpr, isSuper, eoe)) { + // [stack] # if Super + // [stack] THIS KEY + // [stack] # otherwise + // [stack] OBJ KEY + return false; + } + if (!eoe.emitIncDec(valueUsage)) { + // [stack] RESULT + return false; + } + + return true; +} + +bool BytecodeEmitter::emitCallIncDec(UnaryNode* incDec) { + MOZ_ASSERT(incDec->isKind(ParseNodeKind::PreIncrementExpr) || + incDec->isKind(ParseNodeKind::PostIncrementExpr) || + incDec->isKind(ParseNodeKind::PreDecrementExpr) || + incDec->isKind(ParseNodeKind::PostDecrementExpr)); + + ParseNode* call = incDec->kid(); + MOZ_ASSERT(call->isKind(ParseNodeKind::CallExpr)); + if (!emitTree(call)) { + // [stack] CALLRESULT + return false; + } + if (!emit1(JSOp::ToNumeric)) { + // [stack] N + return false; + } + + // The increment/decrement has no side effects, so proceed to throw for + // invalid assignment target. + return emit2(JSOp::ThrowMsg, uint8_t(ThrowMsgKind::AssignToCall)); +} + +bool BytecodeEmitter::emitPrivateIncDec(UnaryNode* incDec, + ValueUsage valueUsage) { + PrivateMemberAccess* privateExpr = &incDec->kid()->as(); + ParseNodeKind kind = incDec->getKind(); + PrivateOpEmitter xoe(this, PrivateConvertIncDecKind(kind), + privateExpr->privateName().name()); + if (!emitTree(&privateExpr->expression())) { + // [stack] OBJ + return false; + } + if (!xoe.emitReference()) { + // [stack] OBJ NAME + return false; + } + if (!xoe.emitIncDec(valueUsage)) { + // [stack] RESULT + return false; + } + + return true; +} + +bool BytecodeEmitter::emitDouble(double d) { + BytecodeOffset offset; + if (!emitCheck(JSOp::Double, 9, &offset)) { + return false; + } + + jsbytecode* code = bytecodeSection().code(offset); + code[0] = jsbytecode(JSOp::Double); + SET_INLINE_VALUE(code, DoubleValue(d)); + bytecodeSection().updateDepth(JSOp::Double, offset); + return true; +} + +bool BytecodeEmitter::emitNumberOp(double dval) { + int32_t ival; + if (NumberIsInt32(dval, &ival)) { + if (ival == 0) { + return emit1(JSOp::Zero); + } + if (ival == 1) { + return emit1(JSOp::One); + } + if ((int)(int8_t)ival == ival) { + return emit2(JSOp::Int8, uint8_t(int8_t(ival))); + } + + uint32_t u = uint32_t(ival); + if (u < Bit(16)) { + if (!emitUint16Operand(JSOp::Uint16, u)) { + return false; + } + } else if (u < Bit(24)) { + BytecodeOffset off; + if (!emitN(JSOp::Uint24, 3, &off)) { + return false; + } + SET_UINT24(bytecodeSection().code(off), u); + } else { + BytecodeOffset off; + if (!emitN(JSOp::Int32, 4, &off)) { + return false; + } + SET_INT32(bytecodeSection().code(off), ival); + } + return true; + } + + return emitDouble(dval); +} + +/* + * Using MOZ_NEVER_INLINE in here is a workaround for llvm.org/pr14047. + * LLVM is deciding to inline this function which uses a lot of stack space + * into emitTree which is recursive and uses relatively little stack space. + */ +MOZ_NEVER_INLINE bool BytecodeEmitter::emitSwitch(SwitchStatement* switchStmt) { + LexicalScopeNode& lexical = switchStmt->lexicalForCaseList(); + MOZ_ASSERT(lexical.isKind(ParseNodeKind::LexicalScope)); + ListNode* cases = &lexical.scopeBody()->as(); + MOZ_ASSERT(cases->isKind(ParseNodeKind::StatementList)); + + SwitchEmitter se(this); + if (!se.emitDiscriminant(switchStmt->discriminant().pn_pos.begin)) { + return false; + } + + if (!markStepBreakpoint()) { + return false; + } + if (!emitTree(&switchStmt->discriminant())) { + return false; + } + + // Enter the scope before pushing the switch BreakableControl since all + // breaks are under this scope. + + if (!lexical.isEmptyScope()) { + if (!se.emitLexical(lexical.scopeBindings())) { + return false; + } + + // A switch statement may contain hoisted functions inside its + // cases. The PNX_FUNCDEFS flag is propagated from the STATEMENTLIST + // bodies of the cases to the case list. + if (cases->hasTopLevelFunctionDeclarations()) { + for (ParseNode* item : cases->contents()) { + CaseClause* caseClause = &item->as(); + ListNode* statements = caseClause->statementList(); + if (statements->hasTopLevelFunctionDeclarations()) { + if (!emitHoistedFunctionsInList(statements)) { + return false; + } + } + } + } + } else { + MOZ_ASSERT(!cases->hasTopLevelFunctionDeclarations()); + } + + SwitchEmitter::TableGenerator tableGen(this); + uint32_t caseCount = cases->count() - (switchStmt->hasDefault() ? 1 : 0); + if (caseCount == 0) { + tableGen.finish(0); + } else { + for (ParseNode* item : cases->contents()) { + CaseClause* caseClause = &item->as(); + if (caseClause->isDefault()) { + continue; + } + + ParseNode* caseValue = caseClause->caseExpression(); + + if (caseValue->getKind() != ParseNodeKind::NumberExpr) { + tableGen.setInvalid(); + break; + } + + int32_t i; + if (!NumberEqualsInt32(caseValue->as().value(), &i)) { + tableGen.setInvalid(); + break; + } + + if (!tableGen.addNumber(i)) { + return false; + } + } + + tableGen.finish(caseCount); + } + + if (!se.validateCaseCount(caseCount)) { + return false; + } + + bool isTableSwitch = tableGen.isValid(); + if (isTableSwitch) { + if (!se.emitTable(tableGen)) { + return false; + } + } else { + if (!se.emitCond()) { + return false; + } + + // Emit code for evaluating cases and jumping to case statements. + for (ParseNode* item : cases->contents()) { + CaseClause* caseClause = &item->as(); + if (caseClause->isDefault()) { + continue; + } + + if (!se.prepareForCaseValue()) { + return false; + } + + ParseNode* caseValue = caseClause->caseExpression(); + // If the expression is a literal, suppress line number emission so + // that debugging works more naturally. + if (!emitTree( + caseValue, ValueUsage::WantValue, + caseValue->isLiteral() ? SUPPRESS_LINENOTE : EMIT_LINENOTE)) { + return false; + } + + if (!se.emitCaseJump()) { + return false; + } + } + } + + // Emit code for each case's statements. + for (ParseNode* item : cases->contents()) { + CaseClause* caseClause = &item->as(); + if (caseClause->isDefault()) { + if (!se.emitDefaultBody()) { + return false; + } + } else { + if (isTableSwitch) { + ParseNode* caseValue = caseClause->caseExpression(); + MOZ_ASSERT(caseValue->isKind(ParseNodeKind::NumberExpr)); + + NumericLiteral* literal = &caseValue->as(); +#ifdef DEBUG + // Use NumberEqualsInt32 here because switches compare using + // strict equality, which will equate -0 and +0. In contrast + // NumberIsInt32 would return false for -0. + int32_t v; + MOZ_ASSERT(mozilla::NumberEqualsInt32(literal->value(), &v)); +#endif + int32_t i = int32_t(literal->value()); + + if (!se.emitCaseBody(i, tableGen)) { + return false; + } + } else { + if (!se.emitCaseBody()) { + return false; + } + } + } + + if (!emitTree(caseClause->statementList())) { + return false; + } + } + + if (!se.emitEnd()) { + return false; + } + + return true; +} + +bool BytecodeEmitter::allocateResumeIndex(BytecodeOffset offset, + uint32_t* resumeIndex) { + static constexpr uint32_t MaxResumeIndex = BitMask(24); + + static_assert( + MaxResumeIndex < uint32_t(AbstractGeneratorObject::RESUME_INDEX_RUNNING), + "resumeIndex should not include magic AbstractGeneratorObject " + "resumeIndex values"); + static_assert( + MaxResumeIndex <= INT32_MAX / sizeof(uintptr_t), + "resumeIndex * sizeof(uintptr_t) must fit in an int32. JIT code relies " + "on this when loading resume entries from BaselineScript"); + + *resumeIndex = bytecodeSection().resumeOffsetList().length(); + if (*resumeIndex > MaxResumeIndex) { + reportError(nullptr, JSMSG_TOO_MANY_RESUME_INDEXES); + return false; + } + + return bytecodeSection().resumeOffsetList().append(offset.value()); +} + +bool BytecodeEmitter::allocateResumeIndexRange( + mozilla::Span offsets, uint32_t* firstResumeIndex) { + *firstResumeIndex = 0; + + for (size_t i = 0, len = offsets.size(); i < len; i++) { + uint32_t resumeIndex; + if (!allocateResumeIndex(offsets[i], &resumeIndex)) { + return false; + } + if (i == 0) { + *firstResumeIndex = resumeIndex; + } + } + + return true; +} + +bool BytecodeEmitter::emitYieldOp(JSOp op) { + if (op == JSOp::FinalYieldRval) { + return emit1(JSOp::FinalYieldRval); + } + + MOZ_ASSERT(op == JSOp::InitialYield || op == JSOp::Yield || + op == JSOp::Await); + + BytecodeOffset off; + if (!emitN(op, 3, &off)) { + return false; + } + + if (op == JSOp::InitialYield || op == JSOp::Yield) { + bytecodeSection().addNumYields(); + } + + uint32_t resumeIndex; + if (!allocateResumeIndex(bytecodeSection().offset(), &resumeIndex)) { + return false; + } + + SET_RESUMEINDEX(bytecodeSection().code(off), resumeIndex); + + BytecodeOffset unusedOffset; + return emitJumpTargetOp(JSOp::AfterYield, &unusedOffset); +} + +bool BytecodeEmitter::emitPushResumeKind(GeneratorResumeKind kind) { + return emit2(JSOp::ResumeKind, uint8_t(kind)); +} + +bool BytecodeEmitter::emitSetThis(BinaryNode* setThisNode) { + // ParseNodeKind::SetThis is used to update |this| after a super() call + // in a derived class constructor. + + MOZ_ASSERT(setThisNode->isKind(ParseNodeKind::SetThis)); + MOZ_ASSERT(setThisNode->left()->isKind(ParseNodeKind::Name)); + + auto name = setThisNode->left()->as().name(); + + // The 'this' binding is not lexical, but due to super() semantics this + // initialization needs to be treated as a lexical one. + NameLocation loc = lookupName(name); + NameLocation lexicalLoc; + if (loc.kind() == NameLocation::Kind::FrameSlot) { + lexicalLoc = NameLocation::FrameSlot(BindingKind::Let, loc.frameSlot()); + } else if (loc.kind() == NameLocation::Kind::EnvironmentCoordinate) { + EnvironmentCoordinate coord = loc.environmentCoordinate(); + uint8_t hops = AssertedCast(coord.hops()); + lexicalLoc = NameLocation::EnvironmentCoordinate(BindingKind::Let, hops, + coord.slot()); + } else { + MOZ_ASSERT(loc.kind() == NameLocation::Kind::Dynamic); + lexicalLoc = loc; + } + + NameOpEmitter noe(this, name, lexicalLoc, NameOpEmitter::Kind::Initialize); + if (!noe.prepareForRhs()) { + // [stack] + return false; + } + + // Emit the new |this| value. + if (!emitTree(setThisNode->right())) { + // [stack] NEWTHIS + return false; + } + + // Get the original |this| and throw if we already initialized + // it. Do *not* use the NameLocation argument, as that's the special + // lexical location below to deal with super() semantics. + if (!emitGetName(name)) { + // [stack] NEWTHIS THIS + return false; + } + if (!emit1(JSOp::CheckThisReinit)) { + // [stack] NEWTHIS THIS + return false; + } + if (!emit1(JSOp::Pop)) { + // [stack] NEWTHIS + return false; + } + if (!noe.emitAssignment()) { + // [stack] NEWTHIS + return false; + } + + if (!emitInitializeInstanceMembers(true)) { + return false; + } + + return true; +} + +bool BytecodeEmitter::defineHoistedTopLevelFunctions(ParseNode* body) { + MOZ_ASSERT(inPrologue()); + MOZ_ASSERT(sc->isGlobalContext() || (sc->isEvalContext() && !sc->strict())); + MOZ_ASSERT(body->is() || body->is()); + + if (body->is()) { + body = body->as().scopeBody(); + MOZ_ASSERT(body->is()); + } + + if (!body->as().hasTopLevelFunctionDeclarations()) { + return true; + } + + return emitHoistedFunctionsInList(&body->as()); +} + +// For Global and sloppy-Eval scripts, this performs most of the steps of the +// spec's [GlobalDeclarationInstantiation] and [EvalDeclarationInstantiation] +// operations. +// +// Note that while strict-Eval is handled in the same part of the spec, it never +// fails for global-redeclaration checks so those scripts initialize directly in +// their bytecode. +bool BytecodeEmitter::emitDeclarationInstantiation(ParseNode* body) { + if (sc->isModuleContext()) { + // ES Modules have dedicated variable and lexial environments and therefore + // do not have to perform redeclaration checks. We initialize their bindings + // elsewhere in bytecode. + return true; + } + + if (sc->isEvalContext() && sc->strict()) { + // Strict Eval has a dedicated variables (and lexical) environment and + // therefore does not have to perform redeclaration checks. We initialize + // their bindings elsewhere in the bytecode. + return true; + } + + // If we have no variables bindings, then we are done! + if (sc->isGlobalContext()) { + if (!sc->asGlobalContext()->bindings) { + return true; + } + } else { + MOZ_ASSERT(sc->isEvalContext()); + + if (!sc->asEvalContext()->bindings) { + return true; + } + } + +#if DEBUG + // There should be no emitted functions yet. + for (const auto& thing : perScriptData().gcThingList().objects()) { + MOZ_ASSERT(thing.isEmptyGlobalScope() || thing.isScope()); + } +#endif + + // Emit the hoisted functions to gc-things list. There is no bytecode + // generated yet to bind them. + if (!defineHoistedTopLevelFunctions(body)) { + return false; + } + + // Save the last GCThingIndex emitted. The hoisted functions are contained in + // the gc-things list up until this point. This set of gc-things also contain + // initial scopes (of which there must be at least one). + MOZ_ASSERT(perScriptData().gcThingList().length() > 0); + GCThingIndex lastFun = + GCThingIndex(perScriptData().gcThingList().length() - 1); + +#if DEBUG + for (const auto& thing : perScriptData().gcThingList().objects()) { + MOZ_ASSERT(thing.isEmptyGlobalScope() || thing.isScope() || + thing.isFunction()); + } +#endif + + // Check for declaration conflicts and initialize the bindings. + // NOTE: The self-hosting top-level script should not populate the builtins + // directly on the GlobalObject (and instead uses JSOp::GetIntrinsic for + // lookups). + if (emitterMode == BytecodeEmitter::EmitterMode::Normal) { + if (!emitGCIndexOp(JSOp::GlobalOrEvalDeclInstantiation, lastFun)) { + return false; + } + } + + return true; +} + +bool BytecodeEmitter::emitScript(ParseNode* body) { + setScriptStartOffsetIfUnset(body->pn_pos.begin); + + MOZ_ASSERT(inPrologue()); + + TDZCheckCache tdzCache(this); + EmitterScope emitterScope(this); + Maybe topLevelAwait; + if (sc->isGlobalContext()) { + if (!emitterScope.enterGlobal(this, sc->asGlobalContext())) { + return false; + } + } else if (sc->isEvalContext()) { + if (!emitterScope.enterEval(this, sc->asEvalContext())) { + return false; + } + } else { + MOZ_ASSERT(sc->isModuleContext()); + if (!emitterScope.enterModule(this, sc->asModuleContext())) { + return false; + } + if (sc->asModuleContext()->isAsync()) { + topLevelAwait.emplace(this); + } + } + + setFunctionBodyEndPos(body->pn_pos.end); + + bool isSloppyEval = sc->isEvalContext() && !sc->strict(); + if (isSloppyEval && body->is() && + !body->as().isEmptyScope()) { + // Sloppy eval scripts may emit hoisted functions bindings with a + // `JSOp::GlobalOrEvalDeclInstantiation` opcode below. If this eval needs a + // top-level lexical environment, we must ensure that environment is created + // before those functions are created and bound. + // + // This differs from the global-script case below because the global-lexical + // environment exists outside the script itself. In the case of strict eval + // scripts, the `emitterScope` above is already sufficient. + EmitterScope lexicalEmitterScope(this); + LexicalScopeNode* scope = &body->as(); + + if (!lexicalEmitterScope.enterLexical(this, ScopeKind::Lexical, + scope->scopeBindings())) { + return false; + } + + if (!emitDeclarationInstantiation(scope->scopeBody())) { + return false; + } + + switchToMain(); + + ParseNode* scopeBody = scope->scopeBody(); + if (!emitLexicalScopeBody(scopeBody)) { + return false; + } + + if (!updateSourceCoordNotes(scopeBody->pn_pos.end)) { + return false; + } + + if (!lexicalEmitterScope.leave(this)) { + return false; + } + } else { + if (!emitDeclarationInstantiation(body)) { + return false; + } + if (topLevelAwait) { + if (!topLevelAwait->prepareForModule()) { + return false; + } + } + + switchToMain(); + + if (topLevelAwait) { + if (!topLevelAwait->prepareForBody()) { + return false; + } + } + + if (!emitTree(body)) { + // [stack] + return false; + } + + if (!updateSourceCoordNotes(body->pn_pos.end)) { + return false; + } + } + + if (topLevelAwait) { + if (!topLevelAwait->emitEndModule()) { + return false; + } + } + + if (!markSimpleBreakpoint()) { + return false; + } + + if (!emitReturnRval()) { + return false; + } + + if (!emitterScope.leave(this)) { + return false; + } + + if (!NameFunctions(fc, parserAtoms(), body)) { + return false; + } + + // Create a Stencil and convert it into a JSScript. + return intoScriptStencil(CompilationStencil::TopLevelIndex); +} + +js::UniquePtr +BytecodeEmitter::createImmutableScriptData() { + uint32_t nslots; + if (!getNslots(&nslots)) { + return nullptr; + } + + bool isFunction = sc->isFunctionBox(); + uint16_t funLength = isFunction ? sc->asFunctionBox()->length() : 0; + + mozilla::SaturateUint8 propertyCountEstimate = propertyAdditionEstimate; + + // Add fields to the property count estimate. + if (isFunction && sc->asFunctionBox()->useMemberInitializers()) { + propertyCountEstimate += + sc->asFunctionBox()->memberInitializers().numMemberInitializers; + } + + return ImmutableScriptData::new_( + fc, mainOffset(), maxFixedSlots, nslots, bodyScopeIndex, + bytecodeSection().numICEntries(), isFunction, funLength, + propertyCountEstimate.value(), bytecodeSection().code(), + bytecodeSection().notes(), bytecodeSection().resumeOffsetList().span(), + bytecodeSection().scopeNoteList().span(), + bytecodeSection().tryNoteList().span()); +} + +bool BytecodeEmitter::getNslots(uint32_t* nslots) { + uint64_t nslots64 = + maxFixedSlots + static_cast(bytecodeSection().maxStackDepth()); + if (nslots64 > UINT32_MAX) { + reportError(nullptr, JSMSG_NEED_DIET, js_script_str); + return false; + } + *nslots = nslots64; + return true; +} + +bool BytecodeEmitter::emitFunctionScript(FunctionNode* funNode) { + MOZ_ASSERT(inPrologue()); + ParamsBodyNode* paramsBody = funNode->body(); + FunctionBox* funbox = sc->asFunctionBox(); + + setScriptStartOffsetIfUnset(paramsBody->pn_pos.begin); + + // [stack] + + FunctionScriptEmitter fse(this, funbox, Some(paramsBody->pn_pos.begin), + Some(paramsBody->pn_pos.end)); + if (!fse.prepareForParameters()) { + // [stack] + return false; + } + + if (!emitFunctionFormalParameters(paramsBody)) { + // [stack] + return false; + } + + if (!fse.prepareForBody()) { + // [stack] + return false; + } + + if (!emitTree(paramsBody->body())) { + // [stack] + return false; + } + + if (!fse.emitEndBody()) { + // [stack] + return false; + } + + if (funbox->index() == CompilationStencil::TopLevelIndex) { + if (!NameFunctions(fc, parserAtoms(), funNode)) { + return false; + } + } + + return fse.intoStencil(); +} + +bool BytecodeEmitter::emitDestructuringLHSRef(ParseNode* target, + size_t* emitted) { +#ifdef DEBUG + int depth = bytecodeSection().stackDepth(); +#endif + + switch (target->getKind()) { + case ParseNodeKind::Name: + case ParseNodeKind::ArrayExpr: + case ParseNodeKind::ObjectExpr: + // No need to recurse into ParseNodeKind::Array and ParseNodeKind::Object + // subpatterns here, since emitSetOrInitializeDestructuring does the + // recursion when setting or initializing the value. Getting reference + // doesn't recurse. + *emitted = 0; + break; + + case ParseNodeKind::DotExpr: { + PropertyAccess* prop = &target->as(); + bool isSuper = prop->isSuper(); + PropOpEmitter poe(this, PropOpEmitter::Kind::SimpleAssignment, + isSuper ? PropOpEmitter::ObjKind::Super + : PropOpEmitter::ObjKind::Other); + if (!poe.prepareForObj()) { + return false; + } + if (isSuper) { + UnaryNode* base = &prop->expression().as(); + if (!emitGetThisForSuperBase(base)) { + // [stack] THIS SUPERBASE + return false; + } + } else { + if (!emitTree(&prop->expression())) { + // [stack] OBJ + return false; + } + } + if (!poe.prepareForRhs()) { + // [stack] # if Super + // [stack] THIS SUPERBASE + // [stack] # otherwise + // [stack] OBJ + return false; + } + + // SUPERBASE was pushed onto THIS in poe.prepareForRhs above. + *emitted = 1 + isSuper; + break; + } + + case ParseNodeKind::ElemExpr: { + PropertyByValue* elem = &target->as(); + bool isSuper = elem->isSuper(); + MOZ_ASSERT(!elem->key().isKind(ParseNodeKind::PrivateName)); + ElemOpEmitter eoe(this, ElemOpEmitter::Kind::SimpleAssignment, + isSuper ? ElemOpEmitter::ObjKind::Super + : ElemOpEmitter::ObjKind::Other); + if (!emitElemObjAndKey(elem, isSuper, eoe)) { + // [stack] # if Super + // [stack] THIS KEY + // [stack] # otherwise + // [stack] OBJ KEY + return false; + } + if (!eoe.prepareForRhs()) { + // [stack] # if Super + // [stack] THIS KEY SUPERBASE + // [stack] # otherwise + // [stack] OBJ KEY + return false; + } + + // SUPERBASE was pushed onto KEY in eoe.prepareForRhs above. + *emitted = 2 + isSuper; + break; + } + + case ParseNodeKind::PrivateMemberExpr: { + PrivateMemberAccess* privateExpr = &target->as(); + PrivateOpEmitter xoe(this, PrivateOpEmitter::Kind::SimpleAssignment, + privateExpr->privateName().name()); + if (!emitTree(&privateExpr->expression())) { + // [stack] OBJ + return false; + } + if (!xoe.emitReference()) { + // [stack] OBJ NAME + return false; + } + *emitted = xoe.numReferenceSlots(); + break; + } + + case ParseNodeKind::CallExpr: + MOZ_ASSERT_UNREACHABLE( + "Parser::reportIfNotValidSimpleAssignmentTarget " + "rejects function calls as assignment " + "targets in destructuring assignments"); + break; + + default: + MOZ_CRASH("emitDestructuringLHSRef: bad lhs kind"); + } + + MOZ_ASSERT(bytecodeSection().stackDepth() == depth + int(*emitted)); + + return true; +} + +bool BytecodeEmitter::emitSetOrInitializeDestructuring( + ParseNode* target, DestructuringFlavor flav) { + // Now emit the lvalue opcode sequence. If the lvalue is a nested + // destructuring initialiser-form, call ourselves to handle it, then pop + // the matched value. Otherwise emit an lvalue bytecode sequence followed + // by an assignment op. + + switch (target->getKind()) { + case ParseNodeKind::ArrayExpr: + case ParseNodeKind::ObjectExpr: + if (!emitDestructuringOps(&target->as(), flav)) { + return false; + } + // emitDestructuringOps leaves the assigned (to-be-destructured) value on + // top of the stack. + break; + + case ParseNodeKind::Name: { + auto name = target->as().name(); + NameLocation loc = lookupName(name); + NameOpEmitter::Kind kind; + switch (flav) { + case DestructuringFlavor::Declaration: + kind = NameOpEmitter::Kind::Initialize; + break; + + case DestructuringFlavor::Assignment: + kind = NameOpEmitter::Kind::SimpleAssignment; + break; + } + + NameOpEmitter noe(this, name, loc, kind); + if (!noe.prepareForRhs()) { + // [stack] V ENV? + return false; + } + if (noe.emittedBindOp()) { + // This is like ordinary assignment, but with one difference. + // + // In `a = b`, we first determine a binding for `a` (using + // JSOp::BindName or JSOp::BindGName), then we evaluate `b`, then + // a JSOp::SetName instruction. + // + // In `[a] = [b]`, per spec, `b` is evaluated first, then we + // determine a binding for `a`. Then we need to do assignment-- + // but the operands are on the stack in the wrong order for + // JSOp::SetProp, so we have to add a JSOp::Swap. + // + // In the cases where we are emitting a name op, emit a swap + // because of this. + if (!emit1(JSOp::Swap)) { + // [stack] ENV V + return false; + } + } else { + // In cases of emitting a frame slot or environment slot, + // nothing needs be done. + } + if (!noe.emitAssignment()) { + // [stack] V + return false; + } + + break; + } + + case ParseNodeKind::DotExpr: { + // The reference is already pushed by emitDestructuringLHSRef. + // [stack] # if Super + // [stack] THIS SUPERBASE VAL + // [stack] # otherwise + // [stack] OBJ VAL + PropertyAccess* prop = &target->as(); + bool isSuper = prop->isSuper(); + PropOpEmitter poe(this, PropOpEmitter::Kind::SimpleAssignment, + isSuper ? PropOpEmitter::ObjKind::Super + : PropOpEmitter::ObjKind::Other); + if (!poe.skipObjAndRhs()) { + return false; + } + // [stack] # VAL + if (!poe.emitAssignment(prop->key().atom())) { + return false; + } + break; + } + + case ParseNodeKind::ElemExpr: { + // The reference is already pushed by emitDestructuringLHSRef. + // [stack] # if Super + // [stack] THIS KEY SUPERBASE VAL + // [stack] # otherwise + // [stack] OBJ KEY VAL + PropertyByValue* elem = &target->as(); + bool isSuper = elem->isSuper(); + MOZ_ASSERT(!elem->key().isKind(ParseNodeKind::PrivateName)); + ElemOpEmitter eoe(this, ElemOpEmitter::Kind::SimpleAssignment, + isSuper ? ElemOpEmitter::ObjKind::Super + : ElemOpEmitter::ObjKind::Other); + if (!eoe.skipObjAndKeyAndRhs()) { + return false; + } + if (!eoe.emitAssignment()) { + // [stack] VAL + return false; + } + break; + } + + case ParseNodeKind::PrivateMemberExpr: { + // The reference is already pushed by emitDestructuringLHSRef. + // [stack] OBJ NAME VAL + PrivateMemberAccess* privateExpr = &target->as(); + PrivateOpEmitter xoe(this, PrivateOpEmitter::Kind::SimpleAssignment, + privateExpr->privateName().name()); + if (!xoe.skipReference()) { + return false; + } + if (!xoe.emitAssignment()) { + // [stack] VAL + return false; + } + break; + } + + case ParseNodeKind::CallExpr: + MOZ_ASSERT_UNREACHABLE( + "Parser::reportIfNotValidSimpleAssignmentTarget " + "rejects function calls as assignment " + "targets in destructuring assignments"); + break; + + default: + MOZ_CRASH("emitSetOrInitializeDestructuring: bad lhs kind"); + } + + // Pop the assigned value. + if (!emit1(JSOp::Pop)) { + // [stack] # empty + return false; + } + + return true; +} + +JSOp BytecodeEmitter::getIterCallOp(JSOp callOp, + SelfHostedIter selfHostedIter) { + if (emitterMode == BytecodeEmitter::SelfHosting) { + MOZ_ASSERT(selfHostedIter == SelfHostedIter::Allow); + + switch (callOp) { + case JSOp::Call: + return JSOp::CallContent; + case JSOp::CallIter: + return JSOp::CallContentIter; + default: + MOZ_CRASH("Unknown iterator call op"); + } + } + + return callOp; +} + +bool BytecodeEmitter::emitIteratorNext( + const Maybe& callSourceCoordOffset, + IteratorKind iterKind /* = IteratorKind::Sync */, + SelfHostedIter selfHostedIter /* = SelfHostedIter::Deny */) { + MOZ_ASSERT(selfHostedIter == SelfHostedIter::Allow || + emitterMode != BytecodeEmitter::SelfHosting, + ".next() iteration is prohibited in self-hosted code because it" + "can run user-modifiable iteration code"); + + // [stack] ... NEXT ITER + MOZ_ASSERT(bytecodeSection().stackDepth() >= 2); + + if (!emitCall(getIterCallOp(JSOp::Call, selfHostedIter), 0, + callSourceCoordOffset)) { + // [stack] ... RESULT + return false; + } + + if (iterKind == IteratorKind::Async) { + if (!emitAwaitInInnermostScope()) { + // [stack] ... RESULT + return false; + } + } + + if (!emitCheckIsObj(CheckIsObjectKind::IteratorNext)) { + // [stack] ... RESULT + return false; + } + return true; +} + +bool BytecodeEmitter::emitIteratorCloseInScope( + EmitterScope& currentScope, + IteratorKind iterKind /* = IteratorKind::Sync */, + CompletionKind completionKind /* = CompletionKind::Normal */, + SelfHostedIter selfHostedIter /* = SelfHostedIter::Deny */) { + MOZ_ASSERT(selfHostedIter == SelfHostedIter::Allow || + emitterMode != BytecodeEmitter::SelfHosting, + ".close() on iterators is prohibited in self-hosted code because " + "it can run user-modifiable iteration code"); + + if (iterKind == IteratorKind::Sync) { + return emit2(JSOp::CloseIter, uint8_t(completionKind)); + } + + // Generate inline logic corresponding to IteratorClose (ES2021 7.4.6) and + // AsyncIteratorClose (ES2021 7.4.7). Steps numbers apply to both operations. + // + // Callers need to ensure that the iterator object is at the top of the + // stack. + + // For non-Throw completions, we emit the equivalent of: + // + // var returnMethod = GetMethod(iterator, "return"); + // if (returnMethod !== undefined) { + // var innerResult = [Await] Call(returnMethod, iterator); + // CheckIsObj(innerResult); + // } + // + // Whereas for Throw completions, we emit: + // + // try { + // var returnMethod = GetMethod(iterator, "return"); + // if (returnMethod !== undefined) { + // [Await] Call(returnMethod, iterator); + // } + // } catch {} + + Maybe tryCatch; + + if (completionKind == CompletionKind::Throw) { + tryCatch.emplace(this, TryEmitter::Kind::TryCatch, + TryEmitter::ControlKind::NonSyntactic); + + if (!tryCatch->emitTry()) { + // [stack] ... ITER + return false; + } + } + + if (!emit1(JSOp::Dup)) { + // [stack] ... ITER ITER + return false; + } + + // Steps 1-2 are assertions, step 3 is implicit. + + // Step 4. + // + // Get the "return" method. + if (!emitAtomOp(JSOp::GetProp, TaggedParserAtomIndex::WellKnown::return_())) { + // [stack] ... ITER RET + return false; + } + + // Step 5. + // + // Do nothing if "return" is undefined or null. + InternalIfEmitter ifReturnMethodIsDefined(this); + if (!emit1(JSOp::IsNullOrUndefined)) { + // [stack] ... ITER RET NULL-OR-UNDEF + return false; + } + + if (!ifReturnMethodIsDefined.emitThenElse( + IfEmitter::ConditionKind::Negative)) { + // [stack] ... ITER RET + return false; + } + + // Steps 5.c, 7. + // + // Call the "return" method. + if (!emit1(JSOp::Swap)) { + // [stack] ... RET ITER + return false; + } + + if (!emitCall(getIterCallOp(JSOp::Call, selfHostedIter), 0)) { + // [stack] ... RESULT + return false; + } + + // 7.4.7 AsyncIteratorClose, step 5.d. + if (iterKind == IteratorKind::Async) { + if (completionKind != CompletionKind::Throw) { + // Await clobbers rval, so save the current rval. + if (!emit1(JSOp::GetRval)) { + // [stack] ... RESULT RVAL + return false; + } + if (!emit1(JSOp::Swap)) { + // [stack] ... RVAL RESULT + return false; + } + } + + if (!emitAwaitInScope(currentScope)) { + // [stack] ... RVAL? RESULT + return false; + } + + if (completionKind != CompletionKind::Throw) { + if (!emit1(JSOp::Swap)) { + // [stack] ... RESULT RVAL + return false; + } + if (!emit1(JSOp::SetRval)) { + // [stack] ... RESULT + return false; + } + } + } + + // Step 6 (Handled in caller). + + // Step 8. + if (completionKind != CompletionKind::Throw) { + // Check that the "return" result is an object. + if (!emitCheckIsObj(CheckIsObjectKind::IteratorReturn)) { + // [stack] ... RESULT + return false; + } + } + + if (!ifReturnMethodIsDefined.emitElse()) { + // [stack] ... ITER RET + return false; + } + + if (!emit1(JSOp::Pop)) { + // [stack] ... ITER + return false; + } + + if (!ifReturnMethodIsDefined.emitEnd()) { + return false; + } + + if (completionKind == CompletionKind::Throw) { + if (!tryCatch->emitCatch()) { + // [stack] ... ITER EXC + return false; + } + + // Just ignore the exception thrown by call and await. + if (!emit1(JSOp::Pop)) { + // [stack] ... ITER + return false; + } + + if (!tryCatch->emitEnd()) { + // [stack] ... ITER + return false; + } + } + + // Step 9 (Handled in caller). + + return emit1(JSOp::Pop); + // [stack] ... +} + +template +bool BytecodeEmitter::wrapWithDestructuringTryNote(int32_t iterDepth, + InnerEmitter emitter) { + MOZ_ASSERT(bytecodeSection().stackDepth() >= iterDepth); + + // Pad a nop at the beginning of the bytecode covered by the trynote so + // that when unwinding environments, we may unwind to the scope + // corresponding to the pc *before* the start, in case the first bytecode + // emitted by |emitter| is the start of an inner scope. See comment above + // UnwindEnvironmentToTryPc. + if (!emit1(JSOp::TryDestructuring)) { + return false; + } + + BytecodeOffset start = bytecodeSection().offset(); + if (!emitter(this)) { + return false; + } + BytecodeOffset end = bytecodeSection().offset(); + if (start != end) { + return addTryNote(TryNoteKind::Destructuring, iterDepth, start, end); + } + return true; +} + +bool BytecodeEmitter::emitDefault(ParseNode* defaultExpr, ParseNode* pattern) { + // [stack] VALUE + + DefaultEmitter de(this); + if (!de.prepareForDefault()) { + // [stack] + return false; + } + if (!emitInitializer(defaultExpr, pattern)) { + // [stack] DEFAULTVALUE + return false; + } + if (!de.emitEnd()) { + // [stack] VALUE/DEFAULTVALUE + return false; + } + return true; +} + +bool BytecodeEmitter::emitAnonymousFunctionWithName( + ParseNode* node, TaggedParserAtomIndex name) { + MOZ_ASSERT(node->isDirectRHSAnonFunction()); + + if (node->is()) { + // Function doesn't have 'name' property at this point. + // Set function's name at compile time. + if (!setFunName(node->as().funbox(), name)) { + return false; + } + + return emitTree(node); + } + + MOZ_ASSERT(node->is()); + + return emitClass(&node->as(), ClassNameKind::InferredName, name); +} + +bool BytecodeEmitter::emitAnonymousFunctionWithComputedName( + ParseNode* node, FunctionPrefixKind prefixKind) { + MOZ_ASSERT(node->isDirectRHSAnonFunction()); + + if (node->is()) { + if (!emitTree(node)) { + // [stack] NAME FUN + return false; + } + if (!emitDupAt(1)) { + // [stack] NAME FUN NAME + return false; + } + if (!emit2(JSOp::SetFunName, uint8_t(prefixKind))) { + // [stack] NAME FUN + return false; + } + return true; + } + + MOZ_ASSERT(node->is()); + MOZ_ASSERT(prefixKind == FunctionPrefixKind::None); + + return emitClass(&node->as(), ClassNameKind::ComputedName); +} + +bool BytecodeEmitter::setFunName(FunctionBox* funbox, + TaggedParserAtomIndex name) { + // The inferred name may already be set if this function is an interpreted + // lazy function and we OOM'ed after we set the inferred name the first + // time. + if (funbox->hasInferredName()) { + MOZ_ASSERT(!funbox->emitBytecode); + MOZ_ASSERT(funbox->displayAtom() == name); + + return true; + } + + funbox->setInferredName(name); + return true; +} + +bool BytecodeEmitter::emitInitializer(ParseNode* initializer, + ParseNode* pattern) { + if (initializer->isDirectRHSAnonFunction()) { + MOZ_ASSERT(!pattern->isInParens()); + auto name = pattern->as().name(); + if (!emitAnonymousFunctionWithName(initializer, name)) { + return false; + } + } else { + if (!emitTree(initializer)) { + return false; + } + } + + return true; +} + +bool BytecodeEmitter::emitDestructuringOpsArray(ListNode* pattern, + DestructuringFlavor flav) { + MOZ_ASSERT(pattern->isKind(ParseNodeKind::ArrayExpr)); + MOZ_ASSERT(bytecodeSection().stackDepth() != 0); + + // Here's pseudo code for |let [a, b, , c=y, ...d] = x;| + // + // Lines that are annotated "covered by trynote" mean that upon throwing + // an exception, IteratorClose is called on iter only if done is false. + // + // let x, y; + // let a, b, c, d; + // let iter, next, lref, result, done, value; // stack values + // + // iter = x[Symbol.iterator](); + // next = iter.next; + // + // // ==== emitted by loop for a ==== + // lref = GetReference(a); // covered by trynote + // + // result = Call(next, iter); + // done = result.done; + // + // if (done) + // value = undefined; + // else + // value = result.value; + // + // SetOrInitialize(lref, value); // covered by trynote + // + // // ==== emitted by loop for b ==== + // lref = GetReference(b); // covered by trynote + // + // if (done) { + // value = undefined; + // } else { + // result = Call(next, iter); + // done = result.done; + // if (done) + // value = undefined; + // else + // value = result.value; + // } + // + // SetOrInitialize(lref, value); // covered by trynote + // + // // ==== emitted by loop for elision ==== + // if (done) { + // value = undefined; + // } else { + // result = Call(next, iter); + // done = result.done; + // if (done) + // value = undefined; + // else + // value = result.value; + // } + // + // // ==== emitted by loop for c ==== + // lref = GetReference(c); // covered by trynote + // + // if (done) { + // value = undefined; + // } else { + // result = Call(next, iter); + // done = result.done; + // if (done) + // value = undefined; + // else + // value = result.value; + // } + // + // if (value === undefined) + // value = y; // covered by trynote + // + // SetOrInitialize(lref, value); // covered by trynote + // + // // ==== emitted by loop for d ==== + // lref = GetReference(d); // covered by trynote + // + // if (done) + // value = []; + // else + // value = [...iter]; + // + // SetOrInitialize(lref, value); // covered by trynote + // + // // === emitted after loop === + // if (!done) + // IteratorClose(iter); + + // Use an iterator to destructure the RHS, instead of index lookup. We + // must leave the *original* value on the stack. + if (!emit1(JSOp::Dup)) { + // [stack] ... OBJ OBJ + return false; + } + if (!emitIterator()) { + // [stack] ... OBJ NEXT ITER + return false; + } + + // For an empty pattern [], call IteratorClose unconditionally. Nothing + // else needs to be done. + if (!pattern->head()) { + if (!emit1(JSOp::Swap)) { + // [stack] ... OBJ ITER NEXT + return false; + } + if (!emit1(JSOp::Pop)) { + // [stack] ... OBJ ITER + return false; + } + + return emitIteratorCloseInInnermostScope(); + // [stack] ... OBJ + } + + // Push an initial FALSE value for DONE. + if (!emit1(JSOp::False)) { + // [stack] ... OBJ NEXT ITER FALSE + return false; + } + + // TryNoteKind::Destructuring expects the iterator and the done value + // to be the second to top and the top of the stack, respectively. + // IteratorClose is called upon exception only if done is false. + int32_t tryNoteDepth = bytecodeSection().stackDepth(); + + for (ParseNode* member : pattern->contents()) { + bool isFirst = member == pattern->head(); + DebugOnly hasNext = !!member->pn_next; + + ParseNode* subpattern; + if (member->isKind(ParseNodeKind::Spread)) { + subpattern = member->as().kid(); + + MOZ_ASSERT(!subpattern->isKind(ParseNodeKind::AssignExpr)); + } else { + subpattern = member; + } + + ParseNode* lhsPattern = subpattern; + ParseNode* pndefault = nullptr; + if (subpattern->isKind(ParseNodeKind::AssignExpr)) { + lhsPattern = subpattern->as().left(); + pndefault = subpattern->as().right(); + } + + // Number of stack slots emitted for the LHS reference. + size_t emitted = 0; + + // Spec requires LHS reference to be evaluated first. + bool isElision = lhsPattern->isKind(ParseNodeKind::Elision); + if (!isElision) { + auto emitLHSRef = [lhsPattern, &emitted](BytecodeEmitter* bce) { + return bce->emitDestructuringLHSRef(lhsPattern, &emitted); + // [stack] ... OBJ NEXT ITER DONE LREF* + }; + if (!wrapWithDestructuringTryNote(tryNoteDepth, emitLHSRef)) { + return false; + } + } + + // Pick the DONE value to the top of the stack. + if (emitted) { + if (!emitPickN(emitted)) { + // [stack] ... OBJ NEXT ITER LREF* DONE + return false; + } + } + + if (isFirst) { + // If this element is the first, DONE is always FALSE, so pop it. + // + // Non-first elements should emit if-else depending on the + // member pattern, below. + if (!emit1(JSOp::Pop)) { + // [stack] ... OBJ NEXT ITER LREF* + return false; + } + } + + if (member->isKind(ParseNodeKind::Spread)) { + InternalIfEmitter ifThenElse(this); + if (!isFirst) { + // If spread is not the first element of the pattern, + // iterator can already be completed. + // [stack] ... OBJ NEXT ITER LREF* DONE + + if (!ifThenElse.emitThenElse()) { + // [stack] ... OBJ NEXT ITER LREF* + return false; + } + + if (!emitUint32Operand(JSOp::NewArray, 0)) { + // [stack] ... OBJ NEXT ITER LREF* ARRAY + return false; + } + if (!ifThenElse.emitElse()) { + // [stack] ... OBJ NEXT ITER LREF* + return false; + } + } + + // If iterator is not completed, create a new array with the rest + // of the iterator. + if (!emitDupAt(emitted + 1, 2)) { + // [stack] ... OBJ NEXT ITER LREF* NEXT ITER + return false; + } + if (!emitUint32Operand(JSOp::NewArray, 0)) { + // [stack] ... OBJ NEXT ITER LREF* NEXT ITER ARRAY + return false; + } + if (!emitNumberOp(0)) { + // [stack] ... OBJ NEXT ITER LREF* NEXT ITER ARRAY INDEX + return false; + } + if (!emitSpread()) { + // [stack] ... OBJ NEXT ITER LREF* ARRAY INDEX + return false; + } + if (!emit1(JSOp::Pop)) { + // [stack] ... OBJ NEXT ITER LREF* ARRAY + return false; + } + + if (!isFirst) { + if (!ifThenElse.emitEnd()) { + return false; + } + MOZ_ASSERT(ifThenElse.pushed() == 1); + } + + // At this point the iterator is done. Unpick a TRUE value for DONE above + // ITER. + if (!emit1(JSOp::True)) { + // [stack] ... OBJ NEXT ITER LREF* ARRAY TRUE + return false; + } + if (!emitUnpickN(emitted + 1)) { + // [stack] ... OBJ NEXT ITER TRUE LREF* ARRAY + return false; + } + + auto emitAssignment = [lhsPattern, flav](BytecodeEmitter* bce) { + return bce->emitSetOrInitializeDestructuring(lhsPattern, flav); + // [stack] ... OBJ NEXT ITER TRUE + }; + if (!wrapWithDestructuringTryNote(tryNoteDepth, emitAssignment)) { + return false; + } + + MOZ_ASSERT(!hasNext); + break; + } + + InternalIfEmitter ifAlreadyDone(this); + if (!isFirst) { + // [stack] ... OBJ NEXT ITER LREF* DONE + + if (!ifAlreadyDone.emitThenElse()) { + // [stack] ... OBJ NEXT ITER LREF* + return false; + } + + if (!emit1(JSOp::Undefined)) { + // [stack] ... OBJ NEXT ITER LREF* UNDEF + return false; + } + if (!emit1(JSOp::NopDestructuring)) { + // [stack] ... OBJ NEXT ITER LREF* UNDEF + return false; + } + + // The iterator is done. Unpick a TRUE value for DONE above ITER. + if (!emit1(JSOp::True)) { + // [stack] ... OBJ NEXT ITER LREF* UNDEF TRUE + return false; + } + if (!emitUnpickN(emitted + 1)) { + // [stack] ... OBJ NEXT ITER TRUE LREF* UNDEF + return false; + } + + if (!ifAlreadyDone.emitElse()) { + // [stack] ... OBJ NEXT ITER LREF* + return false; + } + } + + if (!emitDupAt(emitted + 1, 2)) { + // [stack] ... OBJ NEXT ITER LREF* NEXT + return false; + } + if (!emitIteratorNext(Some(pattern->pn_pos.begin))) { + // [stack] ... OBJ NEXT ITER LREF* RESULT + return false; + } + if (!emit1(JSOp::Dup)) { + // [stack] ... OBJ NEXT ITER LREF* RESULT RESULT + return false; + } + if (!emitAtomOp(JSOp::GetProp, TaggedParserAtomIndex::WellKnown::done())) { + // [stack] ... OBJ NEXT ITER LREF* RESULT DONE + return false; + } + + if (!emit1(JSOp::Dup)) { + // [stack] ... OBJ NEXT ITER LREF* RESULT DONE DONE + return false; + } + if (!emitUnpickN(emitted + 2)) { + // [stack] ... OBJ NEXT ITER DONE LREF* RESULT DONE + return false; + } + + InternalIfEmitter ifDone(this); + if (!ifDone.emitThenElse()) { + // [stack] ... OBJ NEXT ITER DONE LREF* RESULT + return false; + } + + if (!emit1(JSOp::Pop)) { + // [stack] ... OBJ NEXT ITER DONE LREF* + return false; + } + if (!emit1(JSOp::Undefined)) { + // [stack] ... OBJ NEXT ITER DONE LREF* UNDEF + return false; + } + if (!emit1(JSOp::NopDestructuring)) { + // [stack] ... OBJ NEXT ITER DONE LREF* UNDEF + return false; + } + + if (!ifDone.emitElse()) { + // [stack] ... OBJ NEXT ITER DONE LREF* RESULT + return false; + } + + if (!emitAtomOp(JSOp::GetProp, TaggedParserAtomIndex::WellKnown::value())) { + // [stack] ... OBJ NEXT ITER DONE LREF* VALUE + return false; + } + + if (!ifDone.emitEnd()) { + return false; + } + MOZ_ASSERT(ifDone.pushed() == 0); + + if (!isFirst) { + if (!ifAlreadyDone.emitEnd()) { + return false; + } + MOZ_ASSERT(ifAlreadyDone.pushed() == 2); + } + + if (pndefault) { + auto emitDefault = [pndefault, lhsPattern](BytecodeEmitter* bce) { + return bce->emitDefault(pndefault, lhsPattern); + // [stack] ... OBJ NEXT ITER DONE LREF* VALUE + }; + + if (!wrapWithDestructuringTryNote(tryNoteDepth, emitDefault)) { + return false; + } + } + + if (!isElision) { + auto emitAssignment = [lhsPattern, flav](BytecodeEmitter* bce) { + return bce->emitSetOrInitializeDestructuring(lhsPattern, flav); + // [stack] ... OBJ NEXT ITER DONE + }; + + if (!wrapWithDestructuringTryNote(tryNoteDepth, emitAssignment)) { + return false; + } + } else { + if (!emit1(JSOp::Pop)) { + // [stack] ... OBJ NEXT ITER DONE + return false; + } + } + } + + // The last DONE value is on top of the stack. If not DONE, call + // IteratorClose. + // [stack] ... OBJ NEXT ITER DONE + + InternalIfEmitter ifDone(this); + if (!ifDone.emitThenElse()) { + // [stack] ... OBJ NEXT ITER + return false; + } + if (!emitPopN(2)) { + // [stack] ... OBJ + return false; + } + if (!ifDone.emitElse()) { + // [stack] ... OBJ NEXT ITER + return false; + } + if (!emit1(JSOp::Swap)) { + // [stack] ... OBJ ITER NEXT + return false; + } + if (!emit1(JSOp::Pop)) { + // [stack] ... OBJ ITER + return false; + } + if (!emitIteratorCloseInInnermostScope()) { + // [stack] ... OBJ + return false; + } + if (!ifDone.emitEnd()) { + return false; + } + + return true; +} + +bool BytecodeEmitter::emitComputedPropertyName(UnaryNode* computedPropName) { + MOZ_ASSERT(computedPropName->isKind(ParseNodeKind::ComputedName)); + return emitTree(computedPropName->kid()) && emit1(JSOp::ToPropertyKey); +} + +bool BytecodeEmitter::emitDestructuringOpsObject(ListNode* pattern, + DestructuringFlavor flav) { + MOZ_ASSERT(pattern->isKind(ParseNodeKind::ObjectExpr)); + + // [stack] ... RHS + MOZ_ASSERT(bytecodeSection().stackDepth() > 0); + + if (!emit1(JSOp::CheckObjCoercible)) { + // [stack] ... RHS + return false; + } + + bool needsRestPropertyExcludedSet = + pattern->count() > 1 && pattern->last()->isKind(ParseNodeKind::Spread); + if (needsRestPropertyExcludedSet) { + if (!emitDestructuringObjRestExclusionSet(pattern)) { + // [stack] ... RHS SET + return false; + } + + if (!emit1(JSOp::Swap)) { + // [stack] ... SET RHS + return false; + } + } + + for (ParseNode* member : pattern->contents()) { + ParseNode* subpattern; + if (member->isKind(ParseNodeKind::MutateProto) || + member->isKind(ParseNodeKind::Spread)) { + subpattern = member->as().kid(); + + MOZ_ASSERT_IF(member->isKind(ParseNodeKind::Spread), + !subpattern->isKind(ParseNodeKind::AssignExpr)); + } else { + MOZ_ASSERT(member->isKind(ParseNodeKind::PropertyDefinition) || + member->isKind(ParseNodeKind::Shorthand)); + subpattern = member->as().right(); + } + + ParseNode* lhs = subpattern; + ParseNode* pndefault = nullptr; + if (subpattern->isKind(ParseNodeKind::AssignExpr)) { + lhs = subpattern->as().left(); + pndefault = subpattern->as().right(); + } + + // Number of stack slots emitted for the LHS reference. + size_t emitted = 0; + + // Spec requires LHS reference to be evaluated first. + if (!emitDestructuringLHSRef(lhs, &emitted)) { + // [stack] ... SET? RHS LREF* + return false; + } + + // Duplicate the value being destructured to use as a reference base. + if (!emitDupAt(emitted)) { + // [stack] ... SET? RHS LREF* RHS + return false; + } + + if (member->isKind(ParseNodeKind::Spread)) { + if (!updateSourceCoordNotes(member->pn_pos.begin)) { + return false; + } + + if (!emit1(JSOp::NewInit)) { + // [stack] ... SET? RHS LREF* RHS TARGET + return false; + } + if (!emit1(JSOp::Dup)) { + // [stack] ... SET? RHS LREF* RHS TARGET TARGET + return false; + } + if (!emit2(JSOp::Pick, 2)) { + // [stack] ... SET? RHS LREF* TARGET TARGET RHS + return false; + } + + if (needsRestPropertyExcludedSet) { + if (!emit2(JSOp::Pick, emitted + 4)) { + // [stack] ... RHS LREF* TARGET TARGET RHS SET + return false; + } + } + + CopyOption option = needsRestPropertyExcludedSet ? CopyOption::Filtered + : CopyOption::Unfiltered; + if (!emitCopyDataProperties(option)) { + // [stack] ... RHS LREF* TARGET + return false; + } + + // Destructure TARGET per this member's lhs. + if (!emitSetOrInitializeDestructuring(lhs, flav)) { + // [stack] ... RHS + return false; + } + + MOZ_ASSERT(member == pattern->last(), "Rest property is always last"); + break; + } + + // Now push the property value currently being matched, which is the value + // of the current property name "label" on the left of a colon in the object + // initialiser. + if (member->isKind(ParseNodeKind::MutateProto)) { + if (!emitAtomOp(JSOp::GetProp, + TaggedParserAtomIndex::WellKnown::proto())) { + // [stack] ... SET? RHS LREF* PROP + return false; + } + } else { + MOZ_ASSERT(member->isKind(ParseNodeKind::PropertyDefinition) || + member->isKind(ParseNodeKind::Shorthand)); + + ParseNode* key = member->as().left(); + if (key->isKind(ParseNodeKind::ObjectPropertyName) || + key->isKind(ParseNodeKind::StringExpr)) { + if (!emitAtomOp(JSOp::GetProp, key->as().atom())) { + // [stack] ... SET? RHS LREF* PROP + return false; + } + } else { + if (key->isKind(ParseNodeKind::NumberExpr)) { + if (!emitNumberOp(key->as().value())) { + // [stack]... SET? RHS LREF* RHS KEY + return false; + } + } else { + // Otherwise this is a computed property name. BigInt keys are parsed + // as (synthetic) computed property names, too. + MOZ_ASSERT(key->isKind(ParseNodeKind::ComputedName)); + + if (!emitComputedPropertyName(&key->as())) { + // [stack] ... SET? RHS LREF* RHS KEY + return false; + } + + // Add the computed property key to the exclusion set. + if (needsRestPropertyExcludedSet) { + if (!emitDupAt(emitted + 3)) { + // [stack] ... SET RHS LREF* RHS KEY SET + return false; + } + if (!emitDupAt(1)) { + // [stack] ... SET RHS LREF* RHS KEY SET KEY + return false; + } + if (!emit1(JSOp::Undefined)) { + // [stack] ... SET RHS LREF* RHS KEY SET KEY UNDEFINED + return false; + } + if (!emit1(JSOp::InitElem)) { + // [stack] ... SET RHS LREF* RHS KEY SET + return false; + } + if (!emit1(JSOp::Pop)) { + // [stack] ... SET RHS LREF* RHS KEY + return false; + } + } + } + + // Get the property value. + if (!emitElemOpBase(JSOp::GetElem)) { + // [stack] ... SET? RHS LREF* PROP + return false; + } + } + } + + if (pndefault) { + if (!emitDefault(pndefault, lhs)) { + // [stack] ... SET? RHS LREF* VALUE + return false; + } + } + + // Destructure PROP per this member's lhs. + if (!emitSetOrInitializeDestructuring(lhs, flav)) { + // [stack] ... SET? RHS + return false; + } + } + + return true; +} + +static bool IsDestructuringRestExclusionSetObjLiteralCompatible( + ListNode* pattern) { + uint32_t propCount = 0; + for (ParseNode* member : pattern->contents()) { + if (member->isKind(ParseNodeKind::Spread)) { + MOZ_ASSERT(!member->pn_next, "unexpected trailing element after spread"); + break; + } + + propCount++; + + if (member->isKind(ParseNodeKind::MutateProto)) { + continue; + } + + ParseNode* key = member->as().left(); + if (key->isKind(ParseNodeKind::ObjectPropertyName) || + key->isKind(ParseNodeKind::StringExpr)) { + continue; + } + + // Number and BigInt keys aren't yet supported. Computed property names need + // to be added dynamically. + MOZ_ASSERT(key->isKind(ParseNodeKind::NumberExpr) || + key->isKind(ParseNodeKind::BigIntExpr) || + key->isKind(ParseNodeKind::ComputedName)); + return false; + } + + if (propCount > SharedPropMap::MaxPropsForNonDictionary) { + // JSOp::NewObject cannot accept dictionary-mode objects. + return false; + } + + return true; +} + +bool BytecodeEmitter::emitDestructuringObjRestExclusionSet(ListNode* pattern) { + MOZ_ASSERT(pattern->isKind(ParseNodeKind::ObjectExpr)); + MOZ_ASSERT(pattern->last()->isKind(ParseNodeKind::Spread)); + + // See if we can use ObjLiteral to construct the exclusion set object. + if (IsDestructuringRestExclusionSetObjLiteralCompatible(pattern)) { + if (!emitDestructuringRestExclusionSetObjLiteral(pattern)) { + // [stack] OBJ + return false; + } + } else { + // Take the slow but sure way and start off with a blank object. + if (!emit1(JSOp::NewInit)) { + // [stack] OBJ + return false; + } + } + + for (ParseNode* member : pattern->contents()) { + if (member->isKind(ParseNodeKind::Spread)) { + MOZ_ASSERT(!member->pn_next, "unexpected trailing element after spread"); + break; + } + + TaggedParserAtomIndex pnatom; + if (member->isKind(ParseNodeKind::MutateProto)) { + pnatom = TaggedParserAtomIndex::WellKnown::proto(); + } else { + ParseNode* key = member->as().left(); + if (key->isKind(ParseNodeKind::ObjectPropertyName) || + key->isKind(ParseNodeKind::StringExpr)) { + pnatom = key->as().atom(); + } else if (key->isKind(ParseNodeKind::NumberExpr)) { + if (!emitNumberOp(key->as().value())) { + return false; + } + } else { + // Otherwise this is a computed property name which needs to be added + // dynamically. BigInt keys are parsed as (synthetic) computed property + // names, too. + MOZ_ASSERT(key->isKind(ParseNodeKind::ComputedName)); + continue; + } + } + + // Initialize elements with |undefined|. + if (!emit1(JSOp::Undefined)) { + return false; + } + + if (!pnatom) { + if (!emit1(JSOp::InitElem)) { + return false; + } + } else { + if (!emitAtomOp(JSOp::InitProp, pnatom)) { + return false; + } + } + } + + return true; +} + +bool BytecodeEmitter::emitDestructuringOps(ListNode* pattern, + DestructuringFlavor flav) { + if (pattern->isKind(ParseNodeKind::ArrayExpr)) { + return emitDestructuringOpsArray(pattern, flav); + } + return emitDestructuringOpsObject(pattern, flav); +} + +bool BytecodeEmitter::emitTemplateString(ListNode* templateString) { + bool pushedString = false; + + for (ParseNode* item : templateString->contents()) { + bool isString = (item->getKind() == ParseNodeKind::StringExpr || + item->getKind() == ParseNodeKind::TemplateStringExpr); + + // Skip empty strings. These are very common: a template string like + // `${a}${b}` has three empty strings and without this optimization + // we'd emit four JSOp::Add operations instead of just one. + if (isString && item->as().atom() == + TaggedParserAtomIndex::WellKnown::empty()) { + continue; + } + + if (!isString) { + // We update source notes before emitting the expression + if (!updateSourceCoordNotes(item->pn_pos.begin)) { + return false; + } + } + + if (!emitTree(item)) { + return false; + } + + if (!isString) { + // We need to convert the expression to a string + if (!emit1(JSOp::ToString)) { + return false; + } + } + + if (pushedString) { + // We've pushed two strings onto the stack. Add them together, leaving + // just one. + if (!emit1(JSOp::Add)) { + return false; + } + } else { + pushedString = true; + } + } + + if (!pushedString) { + // All strings were empty, this can happen for something like `${""}`. + // Just push an empty string. + if (!emitStringOp(JSOp::String, + TaggedParserAtomIndex::WellKnown::empty())) { + return false; + } + } + + return true; +} + +bool BytecodeEmitter::emitDeclarationList(ListNode* declList) { + for (ParseNode* decl : declList->contents()) { + ParseNode* pattern; + ParseNode* initializer; + if (decl->isKind(ParseNodeKind::Name)) { + pattern = decl; + initializer = nullptr; + } else { + AssignmentNode* assignNode = &decl->as(); + pattern = assignNode->left(); + initializer = assignNode->right(); + } + + if (pattern->isKind(ParseNodeKind::Name)) { + // initializer can be null here. + if (!emitSingleDeclaration(declList, &pattern->as(), + initializer)) { + return false; + } + } else { + MOZ_ASSERT(pattern->isKind(ParseNodeKind::ArrayExpr) || + pattern->isKind(ParseNodeKind::ObjectExpr)); + MOZ_ASSERT(initializer != nullptr); + + if (!updateSourceCoordNotes(initializer->pn_pos.begin)) { + return false; + } + if (!markStepBreakpoint()) { + return false; + } + if (!emitTree(initializer)) { + return false; + } + + if (!emitDestructuringOps(&pattern->as(), + DestructuringFlavor::Declaration)) { + return false; + } + + if (!emit1(JSOp::Pop)) { + return false; + } + } + } + return true; +} + +bool BytecodeEmitter::emitSingleDeclaration(ListNode* declList, NameNode* decl, + ParseNode* initializer) { + MOZ_ASSERT(decl->isKind(ParseNodeKind::Name)); + + // Nothing to do for initializer-less 'var' declarations, as there's no TDZ. + if (!initializer && declList->isKind(ParseNodeKind::VarStmt)) { + return true; + } + + auto nameAtom = decl->name(); + NameOpEmitter noe(this, nameAtom, NameOpEmitter::Kind::Initialize); + if (!noe.prepareForRhs()) { + // [stack] ENV? + return false; + } + if (!initializer) { + // Lexical declarations are initialized to undefined without an + // initializer. + MOZ_ASSERT(declList->isKind(ParseNodeKind::LetDecl), + "var declarations without initializers handled above, " + "and const declarations must have initializers"); + if (!emit1(JSOp::Undefined)) { + // [stack] ENV? UNDEF + return false; + } + } else { + MOZ_ASSERT(initializer); + + if (!updateSourceCoordNotes(initializer->pn_pos.begin)) { + return false; + } + if (!markStepBreakpoint()) { + return false; + } + if (!emitInitializer(initializer, decl)) { + // [stack] ENV? V + return false; + } + } + if (!noe.emitAssignment()) { + // [stack] V + return false; + } + if (!emit1(JSOp::Pop)) { + // [stack] + return false; + } + + return true; +} + +bool BytecodeEmitter::emitAssignmentRhs( + ParseNode* rhs, TaggedParserAtomIndex anonFunctionName) { + if (rhs->isDirectRHSAnonFunction()) { + if (anonFunctionName) { + return emitAnonymousFunctionWithName(rhs, anonFunctionName); + } + return emitAnonymousFunctionWithComputedName(rhs, FunctionPrefixKind::None); + } + return emitTree(rhs); +} + +// The RHS value to assign is already on the stack, i.e., the next enumeration +// value in a for-in or for-of loop. Offset is the location in the stack of the +// already-emitted rhs. If we emitted a BIND[G]NAME, then the scope is on the +// top of the stack and we need to dig one deeper to get the right RHS value. +bool BytecodeEmitter::emitAssignmentRhs(uint8_t offset) { + if (offset != 1) { + return emitPickN(offset - 1); + } + + return true; +} + +static inline JSOp CompoundAssignmentParseNodeKindToJSOp(ParseNodeKind pnk) { + switch (pnk) { + case ParseNodeKind::InitExpr: + return JSOp::Nop; + case ParseNodeKind::AssignExpr: + return JSOp::Nop; + case ParseNodeKind::AddAssignExpr: + return JSOp::Add; + case ParseNodeKind::SubAssignExpr: + return JSOp::Sub; + case ParseNodeKind::BitOrAssignExpr: + return JSOp::BitOr; + case ParseNodeKind::BitXorAssignExpr: + return JSOp::BitXor; + case ParseNodeKind::BitAndAssignExpr: + return JSOp::BitAnd; + case ParseNodeKind::LshAssignExpr: + return JSOp::Lsh; + case ParseNodeKind::RshAssignExpr: + return JSOp::Rsh; + case ParseNodeKind::UrshAssignExpr: + return JSOp::Ursh; + case ParseNodeKind::MulAssignExpr: + return JSOp::Mul; + case ParseNodeKind::DivAssignExpr: + return JSOp::Div; + case ParseNodeKind::ModAssignExpr: + return JSOp::Mod; + case ParseNodeKind::PowAssignExpr: + return JSOp::Pow; + case ParseNodeKind::CoalesceAssignExpr: + case ParseNodeKind::OrAssignExpr: + case ParseNodeKind::AndAssignExpr: + // Short-circuit assignment operators are handled elsewhere. + [[fallthrough]]; + default: + MOZ_CRASH("unexpected compound assignment op"); + } +} + +bool BytecodeEmitter::emitAssignmentOrInit(ParseNodeKind kind, ParseNode* lhs, + ParseNode* rhs) { + JSOp compoundOp = CompoundAssignmentParseNodeKindToJSOp(kind); + bool isCompound = compoundOp != JSOp::Nop; + bool isInit = kind == ParseNodeKind::InitExpr; + + // We estimate the number of properties this could create + // if used as constructor merely by counting this.foo = assignment + // or init expressions; + // + // This currently doesn't handle this[x] = foo; + if (isInit || kind == ParseNodeKind::AssignExpr) { + if (lhs->isKind(ParseNodeKind::DotExpr)) { + if (lhs->as().expression().isKind( + ParseNodeKind::ThisExpr)) { + propertyAdditionEstimate++; + } + } + } + + MOZ_ASSERT_IF(isInit, lhs->isKind(ParseNodeKind::DotExpr) || + lhs->isKind(ParseNodeKind::ElemExpr) || + lhs->isKind(ParseNodeKind::PrivateMemberExpr)); + + // |name| is used within NameOpEmitter, so its lifetime must surpass |noe|. + TaggedParserAtomIndex name; + + Maybe noe; + Maybe poe; + Maybe eoe; + Maybe xoe; + + // Deal with non-name assignments. + uint8_t offset = 1; + + // Purpose of anonFunctionName: + // + // In normal name assignments (`f = function(){}`), an anonymous function gets + // an inferred name based on the left-hand side name node. + // + // In normal property assignments (`obj.x = function(){}`), the anonymous + // function does not have a computed name, and rhs->isDirectRHSAnonFunction() + // will be false (and anonFunctionName will not be used). However, in field + // initializers (`class C { x = function(){} }`), field initialization is + // implemented via a property or elem assignment (where we are now), and + // rhs->isDirectRHSAnonFunction() is set - so we'll assign the name of the + // function. + TaggedParserAtomIndex anonFunctionName; + + switch (lhs->getKind()) { + case ParseNodeKind::Name: { + name = lhs->as().name(); + anonFunctionName = name; + noe.emplace(this, name, + isCompound ? NameOpEmitter::Kind::CompoundAssignment + : NameOpEmitter::Kind::SimpleAssignment); + break; + } + case ParseNodeKind::DotExpr: { + PropertyAccess* prop = &lhs->as(); + bool isSuper = prop->isSuper(); + poe.emplace(this, + isCompound ? PropOpEmitter::Kind::CompoundAssignment + : isInit ? PropOpEmitter::Kind::PropInit + : PropOpEmitter::Kind::SimpleAssignment, + isSuper ? PropOpEmitter::ObjKind::Super + : PropOpEmitter::ObjKind::Other); + if (!poe->prepareForObj()) { + return false; + } + anonFunctionName = prop->name(); + if (isSuper) { + UnaryNode* base = &prop->expression().as(); + if (!emitGetThisForSuperBase(base)) { + // [stack] THIS SUPERBASE + return false; + } + // SUPERBASE is pushed onto THIS later in poe->emitGet below. + offset += 2; + } else { + if (!emitTree(&prop->expression())) { + // [stack] OBJ + return false; + } + offset += 1; + } + break; + } + case ParseNodeKind::ElemExpr: { + PropertyByValue* elem = &lhs->as(); + bool isSuper = elem->isSuper(); + MOZ_ASSERT(!elem->key().isKind(ParseNodeKind::PrivateName)); + eoe.emplace(this, + isCompound ? ElemOpEmitter::Kind::CompoundAssignment + : isInit ? ElemOpEmitter::Kind::PropInit + : ElemOpEmitter::Kind::SimpleAssignment, + isSuper ? ElemOpEmitter::ObjKind::Super + : ElemOpEmitter::ObjKind::Other); + if (!emitElemObjAndKey(elem, isSuper, *eoe)) { + // [stack] # if Super + // [stack] THIS KEY + // [stack] # otherwise + // [stack] OBJ KEY + return false; + } + if (isSuper) { + // SUPERBASE is pushed onto KEY in eoe->emitGet below. + offset += 3; + } else { + offset += 2; + } + break; + } + case ParseNodeKind::PrivateMemberExpr: { + PrivateMemberAccess* privateExpr = &lhs->as(); + xoe.emplace(this, + isCompound ? PrivateOpEmitter::Kind::CompoundAssignment + : isInit ? PrivateOpEmitter::Kind::PropInit + : PrivateOpEmitter::Kind::SimpleAssignment, + privateExpr->privateName().name()); + if (!emitTree(&privateExpr->expression())) { + // [stack] OBJ + return false; + } + if (!xoe->emitReference()) { + // [stack] OBJ KEY + return false; + } + offset += xoe->numReferenceSlots(); + break; + } + case ParseNodeKind::ArrayExpr: + case ParseNodeKind::ObjectExpr: + break; + case ParseNodeKind::CallExpr: + if (!emitTree(lhs)) { + return false; + } + + // Assignment to function calls is forbidden, but we have to make the + // call first. Now we can throw. + if (!emit2(JSOp::ThrowMsg, uint8_t(ThrowMsgKind::AssignToCall))) { + return false; + } + + // Rebalance the stack to placate stack-depth assertions. + if (!emit1(JSOp::Pop)) { + return false; + } + break; + default: + MOZ_ASSERT(0); + } + + if (isCompound) { + MOZ_ASSERT(rhs); + switch (lhs->getKind()) { + case ParseNodeKind::DotExpr: { + PropertyAccess* prop = &lhs->as(); + if (!poe->emitGet(prop->key().atom())) { + // [stack] # if Super + // [stack] THIS SUPERBASE PROP + // [stack] # otherwise + // [stack] OBJ PROP + return false; + } + break; + } + case ParseNodeKind::ElemExpr: { + if (!eoe->emitGet()) { + // [stack] KEY THIS OBJ ELEM + return false; + } + break; + } + case ParseNodeKind::PrivateMemberExpr: { + if (!xoe->emitGet()) { + // [stack] OBJ KEY VALUE + return false; + } + break; + } + case ParseNodeKind::CallExpr: + // We just emitted a JSOp::ThrowMsg and popped the call's return + // value. Push a random value to make sure the stack depth is + // correct. + if (!emit1(JSOp::Null)) { + // [stack] NULL + return false; + } + break; + default:; + } + } + + switch (lhs->getKind()) { + case ParseNodeKind::Name: + if (!noe->prepareForRhs()) { + // [stack] ENV? VAL? + return false; + } + offset += noe->emittedBindOp(); + break; + case ParseNodeKind::DotExpr: + if (!poe->prepareForRhs()) { + // [stack] # if Simple Assignment with Super + // [stack] THIS SUPERBASE + // [stack] # if Simple Assignment with other + // [stack] OBJ + // [stack] # if Compound Assignment with Super + // [stack] THIS SUPERBASE PROP + // [stack] # if Compound Assignment with other + // [stack] OBJ PROP + return false; + } + break; + case ParseNodeKind::ElemExpr: + if (!eoe->prepareForRhs()) { + // [stack] # if Simple Assignment with Super + // [stack] THIS KEY SUPERBASE + // [stack] # if Simple Assignment with other + // [stack] OBJ KEY + // [stack] # if Compound Assignment with Super + // [stack] THIS KEY SUPERBASE ELEM + // [stack] # if Compound Assignment with other + // [stack] OBJ KEY ELEM + return false; + } + break; + case ParseNodeKind::PrivateMemberExpr: + // no stack adjustment needed + break; + default: + break; + } + + if (rhs) { + if (!emitAssignmentRhs(rhs, anonFunctionName)) { + // [stack] ... VAL? RHS + return false; + } + } else { + // Assumption: Things with pre-emitted RHS values never need to be named. + if (!emitAssignmentRhs(offset)) { + // [stack] ... VAL? RHS + return false; + } + } + + /* If += etc., emit the binary operator with a source note. */ + if (isCompound) { + if (!newSrcNote(SrcNoteType::AssignOp)) { + return false; + } + if (!emit1(compoundOp)) { + // [stack] ... VAL + return false; + } + } + + /* Finally, emit the specialized assignment bytecode. */ + switch (lhs->getKind()) { + case ParseNodeKind::Name: { + if (!noe->emitAssignment()) { + // [stack] VAL + return false; + } + break; + } + case ParseNodeKind::DotExpr: { + PropertyAccess* prop = &lhs->as(); + if (!poe->emitAssignment(prop->key().atom())) { + // [stack] VAL + return false; + } + break; + } + case ParseNodeKind::CallExpr: + // We threw above, so nothing to do here. + break; + case ParseNodeKind::ElemExpr: { + if (!eoe->emitAssignment()) { + // [stack] VAL + return false; + } + break; + } + case ParseNodeKind::PrivateMemberExpr: + if (!xoe->emitAssignment()) { + // [stack] VAL + return false; + } + break; + case ParseNodeKind::ArrayExpr: + case ParseNodeKind::ObjectExpr: + if (!emitDestructuringOps(&lhs->as(), + DestructuringFlavor::Assignment)) { + return false; + } + break; + default: + MOZ_ASSERT(0); + } + return true; +} + +bool BytecodeEmitter::emitShortCircuitAssignment(AssignmentNode* node) { + TDZCheckCache tdzCache(this); + + JSOp op; + switch (node->getKind()) { + case ParseNodeKind::CoalesceAssignExpr: + op = JSOp::Coalesce; + break; + case ParseNodeKind::OrAssignExpr: + op = JSOp::Or; + break; + case ParseNodeKind::AndAssignExpr: + op = JSOp::And; + break; + default: + MOZ_CRASH("Unexpected ParseNodeKind"); + } + + ParseNode* lhs = node->left(); + ParseNode* rhs = node->right(); + + // |name| is used within NameOpEmitter, so its lifetime must surpass |noe|. + TaggedParserAtomIndex name; + + // Select the appropriate emitter based on the left-hand side. + Maybe noe; + Maybe poe; + Maybe eoe; + Maybe xoe; + + int32_t depth = bytecodeSection().stackDepth(); + + // Number of values pushed onto the stack in addition to the lhs value. + int32_t numPushed; + + // Evaluate the left-hand side expression and compute any stack values needed + // for the assignment. + switch (lhs->getKind()) { + case ParseNodeKind::Name: { + name = lhs->as().name(); + noe.emplace(this, name, NameOpEmitter::Kind::CompoundAssignment); + + if (!noe->prepareForRhs()) { + // [stack] ENV? LHS + return false; + } + + numPushed = noe->emittedBindOp(); + break; + } + + case ParseNodeKind::DotExpr: { + PropertyAccess* prop = &lhs->as(); + bool isSuper = prop->isSuper(); + + poe.emplace(this, PropOpEmitter::Kind::CompoundAssignment, + isSuper ? PropOpEmitter::ObjKind::Super + : PropOpEmitter::ObjKind::Other); + + if (!poe->prepareForObj()) { + return false; + } + + if (isSuper) { + UnaryNode* base = &prop->expression().as(); + if (!emitGetThisForSuperBase(base)) { + // [stack] THIS SUPERBASE + return false; + } + } else { + if (!emitTree(&prop->expression())) { + // [stack] OBJ + return false; + } + } + + if (!poe->emitGet(prop->key().atom())) { + // [stack] # if Super + // [stack] THIS SUPERBASE LHS + // [stack] # otherwise + // [stack] OBJ LHS + return false; + } + + if (!poe->prepareForRhs()) { + // [stack] # if Super + // [stack] THIS SUPERBASE LHS + // [stack] # otherwise + // [stack] OBJ LHS + return false; + } + + numPushed = 1 + isSuper; + break; + } + + case ParseNodeKind::ElemExpr: { + PropertyByValue* elem = &lhs->as(); + bool isSuper = elem->isSuper(); + MOZ_ASSERT(!elem->key().isKind(ParseNodeKind::PrivateName)); + eoe.emplace(this, ElemOpEmitter::Kind::CompoundAssignment, + isSuper ? ElemOpEmitter::ObjKind::Super + : ElemOpEmitter::ObjKind::Other); + + if (!emitElemObjAndKey(elem, isSuper, *eoe)) { + // [stack] # if Super + // [stack] THIS KEY + // [stack] # otherwise + // [stack] OBJ KEY + return false; + } + + if (!eoe->emitGet()) { + // [stack] # if Super + // [stack] THIS KEY SUPERBASE LHS + // [stack] # otherwise + // [stack] OBJ KEY LHS + return false; + } + + if (!eoe->prepareForRhs()) { + // [stack] # if Super + // [stack] THIS KEY SUPERBASE LHS + // [stack] # otherwise + // [stack] OBJ KEY LHS + return false; + } + + numPushed = 2 + isSuper; + break; + } + + case ParseNodeKind::PrivateMemberExpr: { + PrivateMemberAccess* privateExpr = &lhs->as(); + xoe.emplace(this, PrivateOpEmitter::Kind::CompoundAssignment, + privateExpr->privateName().name()); + if (!emitTree(&privateExpr->expression())) { + // [stack] OBJ + return false; + } + if (!xoe->emitReference()) { + // [stack] OBJ NAME + return false; + } + if (!xoe->emitGet()) { + // [stack] OBJ NAME LHS + return false; + } + numPushed = xoe->numReferenceSlots(); + break; + } + + default: + MOZ_CRASH(); + } + + MOZ_ASSERT(bytecodeSection().stackDepth() == depth + numPushed + 1); + + // Test for the short-circuit condition. + JumpList jump; + if (!emitJump(op, &jump)) { + // [stack] ... LHS + return false; + } + + // The short-circuit condition wasn't fulfilled, pop the left-hand side value + // which was kept on the stack. + if (!emit1(JSOp::Pop)) { + // [stack] ... + return false; + } + + if (!emitAssignmentRhs(rhs, name)) { + // [stack] ... RHS + return false; + } + + // Perform the actual assignment. + switch (lhs->getKind()) { + case ParseNodeKind::Name: { + if (!noe->emitAssignment()) { + // [stack] RHS + return false; + } + break; + } + + case ParseNodeKind::DotExpr: { + PropertyAccess* prop = &lhs->as(); + + if (!poe->emitAssignment(prop->key().atom())) { + // [stack] RHS + return false; + } + break; + } + + case ParseNodeKind::ElemExpr: { + if (!eoe->emitAssignment()) { + // [stack] RHS + return false; + } + break; + } + + case ParseNodeKind::PrivateMemberExpr: + if (!xoe->emitAssignment()) { + // [stack] RHS + return false; + } + break; + + default: + MOZ_CRASH(); + } + + MOZ_ASSERT(bytecodeSection().stackDepth() == depth + 1); + + // Join with the short-circuit jump and pop anything left on the stack. + if (numPushed > 0) { + JumpList jumpAroundPop; + if (!emitJump(JSOp::Goto, &jumpAroundPop)) { + // [stack] RHS + return false; + } + + if (!emitJumpTargetAndPatch(jump)) { + // [stack] ... LHS + return false; + } + + // Reconstruct the stack depth after the jump. + bytecodeSection().setStackDepth(depth + 1 + numPushed); + + // Move the left-hand side value to the bottom and pop the rest. + if (!emitUnpickN(numPushed)) { + // [stack] LHS ... + return false; + } + if (!emitPopN(numPushed)) { + // [stack] LHS + return false; + } + + if (!emitJumpTargetAndPatch(jumpAroundPop)) { + // [stack] LHS | RHS + return false; + } + } else { + if (!emitJumpTargetAndPatch(jump)) { + // [stack] LHS | RHS + return false; + } + } + + MOZ_ASSERT(bytecodeSection().stackDepth() == depth + 1); + + return true; +} + +bool BytecodeEmitter::emitCallSiteObjectArray(ObjLiteralWriter& writer, + ListNode* cookedOrRaw, + ParseNode* head, uint32_t count) { + DebugOnly idx = 0; + for (ParseNode* pn : cookedOrRaw->contentsFrom(head)) { + MOZ_ASSERT(pn->isKind(ParseNodeKind::TemplateStringExpr) || + pn->isKind(ParseNodeKind::RawUndefinedExpr)); + + if (!emitObjLiteralValue(writer, pn)) { + return false; + } + idx++; + } + MOZ_ASSERT(idx == count); + + return true; +} + +bool BytecodeEmitter::emitCallSiteObject(CallSiteNode* callSiteObj) { + constexpr JSOp op = JSOp::CallSiteObj; + + // The first element of a call-site node is the raw-values list. Skip over it. + ListNode* raw = callSiteObj->rawNodes(); + MOZ_ASSERT(raw->isKind(ParseNodeKind::ArrayExpr)); + ParseNode* head = callSiteObj->head()->pn_next; + + uint32_t count = callSiteObj->count() - 1; + MOZ_ASSERT(count == raw->count()); + + ObjLiteralWriter writer; + writer.beginCallSiteObj(op); + writer.beginDenseArrayElements(); + + // Write elements of the two arrays: the 'cooked' values followed by the + // 'raw' values. + MOZ_RELEASE_ASSERT(count < UINT32_MAX / 2, + "Number of elements for both arrays must fit in uint32_t"); + if (!emitCallSiteObjectArray(writer, callSiteObj, head, count)) { + return false; + } + if (!emitCallSiteObjectArray(writer, raw, raw->head(), count)) { + return false; + } + + GCThingIndex cookedIndex; + if (!addObjLiteralData(writer, &cookedIndex)) { + return false; + } + + MOZ_ASSERT(sc->hasCallSiteObj()); + + return emitInternedObjectOp(cookedIndex, op); +} + +bool BytecodeEmitter::emitCatch(BinaryNode* catchClause) { + // We must be nested under a try-finally statement. + MOZ_ASSERT(innermostNestableControl->is()); + + ParseNode* param = catchClause->left(); + if (!param) { + // Catch parameter was omitted; just discard the exception. + if (!emit1(JSOp::Pop)) { + return false; + } + } else { + switch (param->getKind()) { + case ParseNodeKind::ArrayExpr: + case ParseNodeKind::ObjectExpr: + if (!emitDestructuringOps(¶m->as(), + DestructuringFlavor::Declaration)) { + return false; + } + if (!emit1(JSOp::Pop)) { + return false; + } + break; + + case ParseNodeKind::Name: + if (!emitLexicalInitialization(¶m->as())) { + return false; + } + if (!emit1(JSOp::Pop)) { + return false; + } + break; + + default: + MOZ_ASSERT(0); + } + } + + /* Emit the catch body. */ + return emitTree(catchClause->right()); +} + +// Using MOZ_NEVER_INLINE in here is a workaround for llvm.org/pr14047. See the +// comment on EmitSwitch. +MOZ_NEVER_INLINE bool BytecodeEmitter::emitTry(TryNode* tryNode) { + LexicalScopeNode* catchScope = tryNode->catchScope(); + ParseNode* finallyNode = tryNode->finallyBlock(); + + TryEmitter::Kind kind; + if (catchScope) { + if (finallyNode) { + kind = TryEmitter::Kind::TryCatchFinally; + } else { + kind = TryEmitter::Kind::TryCatch; + } + } else { + MOZ_ASSERT(finallyNode); + kind = TryEmitter::Kind::TryFinally; + } + TryEmitter tryCatch(this, kind, TryEmitter::ControlKind::Syntactic); + + if (!tryCatch.emitTry()) { + return false; + } + + if (!emitTree(tryNode->body())) { + return false; + } + + // If this try has a catch block, emit it. + if (catchScope) { + // The emitted code for a catch block looks like: + // + // [pushlexicalenv] only if any local aliased + // exception + // setlocal 0; pop assign or possibly destructure exception + // < catch block contents > + // debugleaveblock + // [poplexicalenv] only if any local aliased + // if there is a finally block: + // goto + // [jump target for returning from finally] + // goto + if (!tryCatch.emitCatch()) { + return false; + } + + // Emit the lexical scope and catch body. + if (!emitTree(catchScope)) { + return false; + } + } + + // Emit the finally handler, if there is one. + if (finallyNode) { + if (!tryCatch.emitFinally(Some(finallyNode->pn_pos.begin))) { + return false; + } + + if (!emitTree(finallyNode)) { + return false; + } + } + + if (!tryCatch.emitEnd()) { + return false; + } + + return true; +} + +[[nodiscard]] bool BytecodeEmitter::emitJumpToFinally(JumpList* jump, + uint32_t idx) { + // Push the continuation index. + if (!emitNumberOp(idx)) { + return false; + } + + // Push |throwing|. + if (!emit1(JSOp::False)) { + return false; + } + + // Jump to the finally block. + if (!emitJumpNoFallthrough(JSOp::Goto, jump)) { + return false; + } + + return true; +} + +bool BytecodeEmitter::emitIf(TernaryNode* ifNode) { + IfEmitter ifThenElse(this); + + if (!ifThenElse.emitIf(Some(ifNode->kid1()->pn_pos.begin))) { + return false; + } + +if_again: + ParseNode* testNode = ifNode->kid1(); + auto conditionKind = IfEmitter::ConditionKind::Positive; + if (testNode->isKind(ParseNodeKind::NotExpr)) { + testNode = testNode->as().kid(); + conditionKind = IfEmitter::ConditionKind::Negative; + } + + if (!markStepBreakpoint()) { + return false; + } + + // Emit code for the condition before pushing stmtInfo. + // NOTE: NotExpr of testNode may be unwrapped, and in that case the negation + // is handled by conditionKind. + if (!emitTree(testNode)) { + return false; + } + + ParseNode* elseNode = ifNode->kid3(); + if (elseNode) { + if (!ifThenElse.emitThenElse(conditionKind)) { + return false; + } + } else { + if (!ifThenElse.emitThen(conditionKind)) { + return false; + } + } + + /* Emit code for the then part. */ + if (!emitTree(ifNode->kid2())) { + return false; + } + + if (elseNode) { + if (elseNode->isKind(ParseNodeKind::IfStmt)) { + ifNode = &elseNode->as(); + + if (!ifThenElse.emitElseIf(Some(ifNode->kid1()->pn_pos.begin))) { + return false; + } + + goto if_again; + } + + if (!ifThenElse.emitElse()) { + return false; + } + + /* Emit code for the else part. */ + if (!emitTree(elseNode)) { + return false; + } + } + + if (!ifThenElse.emitEnd()) { + return false; + } + + return true; +} + +bool BytecodeEmitter::emitHoistedFunctionsInList(ListNode* stmtList) { + MOZ_ASSERT(stmtList->hasTopLevelFunctionDeclarations()); + + // We can call this multiple times for sloppy eval scopes. + if (stmtList->emittedTopLevelFunctionDeclarations()) { + return true; + } + + stmtList->setEmittedTopLevelFunctionDeclarations(); + + for (ParseNode* stmt : stmtList->contents()) { + ParseNode* maybeFun = stmt; + + if (!sc->strict()) { + while (maybeFun->isKind(ParseNodeKind::LabelStmt)) { + maybeFun = maybeFun->as().statement(); + } + } + + if (maybeFun->is() && + maybeFun->as().functionIsHoisted()) { + if (!emitTree(maybeFun)) { + return false; + } + } + } + + return true; +} + +bool BytecodeEmitter::emitLexicalScopeBody( + ParseNode* body, EmitLineNumberNote emitLineNote /* = EMIT_LINENOTE */) { + if (body->isKind(ParseNodeKind::StatementList) && + body->as().hasTopLevelFunctionDeclarations()) { + // This block contains function statements whose definitions are + // hoisted to the top of the block. Emit these as a separate pass + // before the rest of the block. + if (!emitHoistedFunctionsInList(&body->as())) { + return false; + } + } + + // Line notes were updated by emitLexicalScope or emitScript. + return emitTree(body, ValueUsage::WantValue, emitLineNote); +} + +// Using MOZ_NEVER_INLINE in here is a workaround for llvm.org/pr14047. See +// the comment on emitSwitch. +MOZ_NEVER_INLINE bool BytecodeEmitter::emitLexicalScope( + LexicalScopeNode* lexicalScope) { + LexicalScopeEmitter lse(this); + + ParseNode* body = lexicalScope->scopeBody(); + if (lexicalScope->isEmptyScope()) { + if (!lse.emitEmptyScope()) { + return false; + } + + if (!emitLexicalScopeBody(body)) { + return false; + } + + if (!lse.emitEnd()) { + return false; + } + + return true; + } + + // We are about to emit some bytecode for what the spec calls "declaration + // instantiation". Assign these instructions to the opening `{` of the + // block. (Using the location of each declaration we're instantiating is + // too weird when stepping in the debugger.) + if (!ParseNodeRequiresSpecialLineNumberNotes(body)) { + if (!updateSourceCoordNotes(lexicalScope->pn_pos.begin)) { + return false; + } + } + + ScopeKind kind; + if (body->isKind(ParseNodeKind::Catch)) { + BinaryNode* catchNode = &body->as(); + kind = + (!catchNode->left() || catchNode->left()->isKind(ParseNodeKind::Name)) + ? ScopeKind::SimpleCatch + : ScopeKind::Catch; + } else { + kind = lexicalScope->kind(); + } + + if (!lse.emitScope(kind, lexicalScope->scopeBindings())) { + return false; + } + + if (body->isKind(ParseNodeKind::ForStmt)) { + // for loops need to emit {FRESHEN,RECREATE}LEXICALENV if there are + // lexical declarations in the head. Signal this by passing a + // non-nullptr lexical scope. + if (!emitFor(&body->as(), &lse.emitterScope())) { + return false; + } + } else { + if (!emitLexicalScopeBody(body, SUPPRESS_LINENOTE)) { + return false; + } + } + + if (!lse.emitEnd()) { + return false; + } + return true; +} + +bool BytecodeEmitter::emitWith(BinaryNode* withNode) { + // Ensure that the column of the 'with' is set properly. + if (!updateSourceCoordNotes(withNode->left()->pn_pos.begin)) { + return false; + } + + if (!markStepBreakpoint()) { + return false; + } + + if (!emitTree(withNode->left())) { + return false; + } + + EmitterScope emitterScope(this); + if (!emitterScope.enterWith(this)) { + return false; + } + + if (!emitTree(withNode->right())) { + return false; + } + + return emitterScope.leave(this); +} + +bool BytecodeEmitter::emitCopyDataProperties(CopyOption option) { + DebugOnly depth = bytecodeSection().stackDepth(); + + uint32_t argc; + if (option == CopyOption::Filtered) { + MOZ_ASSERT(depth > 2); + // [stack] TARGET SOURCE SET + argc = 3; + + if (!emitAtomOp(JSOp::GetIntrinsic, + TaggedParserAtomIndex::WellKnown::CopyDataProperties())) { + // [stack] TARGET SOURCE SET COPYDATAPROPERTIES + return false; + } + } else { + MOZ_ASSERT(depth > 1); + // [stack] TARGET SOURCE + argc = 2; + + if (!emitAtomOp( + JSOp::GetIntrinsic, + TaggedParserAtomIndex::WellKnown::CopyDataPropertiesUnfiltered())) { + // [stack] TARGET SOURCE COPYDATAPROPERTIES + return false; + } + } + + if (!emit1(JSOp::Undefined)) { + // [stack] TARGET SOURCE SET? COPYDATAPROPERTIES + // UNDEFINED + return false; + } + if (!emit2(JSOp::Pick, argc + 1)) { + // [stack] SOURCE SET? COPYDATAPROPERTIES UNDEFINED + // TARGET + return false; + } + if (!emit2(JSOp::Pick, argc + 1)) { + // [stack] SET? COPYDATAPROPERTIES UNDEFINED TARGET + // SOURCE + return false; + } + if (option == CopyOption::Filtered) { + if (!emit2(JSOp::Pick, argc + 1)) { + // [stack] COPYDATAPROPERTIES UNDEFINED TARGET SOURCE SET + return false; + } + } + // Callee is always self-hosted instrinsic, and cannot be content function. + if (!emitCall(JSOp::CallIgnoresRv, argc)) { + // [stack] IGNORED + return false; + } + + if (!emit1(JSOp::Pop)) { + // [stack] + return false; + } + + MOZ_ASSERT(depth - int(argc) == bytecodeSection().stackDepth()); + return true; +} + +bool BytecodeEmitter::emitBigIntOp(BigIntLiteral* bigint) { + GCThingIndex index; + if (!perScriptData().gcThingList().append(bigint, &index)) { + return false; + } + return emitGCIndexOp(JSOp::BigInt, index); +} + +bool BytecodeEmitter::emitIterator( + SelfHostedIter selfHostedIter /* = SelfHostedIter::Deny */, + bool isIteratorMethodOnStack /* = false */) { + MOZ_ASSERT(selfHostedIter == SelfHostedIter::Allow || + emitterMode != BytecodeEmitter::SelfHosting, + "[Symbol.iterator]() call is prohibited in self-hosted code " + "because it can run user-modifiable iteration code"); + + if (!isIteratorMethodOnStack) { + // [stack] OBJ + + // Convert iterable to iterator. + if (!emit1(JSOp::Dup)) { + // [stack] OBJ OBJ + return false; + } + if (!emit2(JSOp::Symbol, uint8_t(JS::SymbolCode::iterator))) { + // [stack] OBJ OBJ @@ITERATOR + return false; + } + if (!emitElemOpBase(JSOp::GetElem)) { + // [stack] OBJ ITERFN + return false; + } + } + + if (!emit1(JSOp::Swap)) { + // [stack] ITERFN OBJ + return false; + } + if (!emitCall(getIterCallOp(JSOp::CallIter, selfHostedIter), 0)) { + // [stack] ITER + return false; + } + if (!emitCheckIsObj(CheckIsObjectKind::GetIterator)) { + // [stack] ITER + return false; + } + if (!emit1(JSOp::Dup)) { + // [stack] ITER ITER + return false; + } + if (!emitAtomOp(JSOp::GetProp, TaggedParserAtomIndex::WellKnown::next())) { + // [stack] ITER NEXT + return false; + } + if (!emit1(JSOp::Swap)) { + // [stack] NEXT ITER + return false; + } + return true; +} + +bool BytecodeEmitter::emitAsyncIterator( + SelfHostedIter selfHostedIter /* = SelfHostedIter::Deny */, + bool isIteratorMethodOnStack /* = false */) { + MOZ_ASSERT(selfHostedIter == SelfHostedIter::Allow || + emitterMode != BytecodeEmitter::SelfHosting, + "[Symbol.asyncIterator]() call is prohibited in self-hosted code " + "because it can run user-modifiable iteration code"); + + if (!isIteratorMethodOnStack) { + // [stack] OBJ + + // Convert iterable to iterator. + if (!emit1(JSOp::Dup)) { + // [stack] OBJ OBJ + return false; + } + if (!emit2(JSOp::Symbol, uint8_t(JS::SymbolCode::asyncIterator))) { + // [stack] OBJ OBJ @@ASYNCITERATOR + return false; + } + if (!emitElemOpBase(JSOp::GetElem)) { + // [stack] OBJ ASYNC_ITERFN + return false; + } + } else { + // [stack] OBJ ASYNC_ITERFN SYNC_ITERFN + + if (!emitElemOpBase(JSOp::Swap)) { + // [stack] OBJ SYNC_ITERFN ASYNC_ITERFN + return false; + } + } + + InternalIfEmitter ifAsyncIterIsUndefined(this); + if (!emit1(JSOp::IsNullOrUndefined)) { + // [stack] OBJ SYNC_ITERFN? ASYNC_ITERFN NULL-OR-UNDEF + return false; + } + if (!ifAsyncIterIsUndefined.emitThenElse()) { + // [stack] OBJ SYNC_ITERFN? ASYNC_ITERFN + return false; + } + + if (!emit1(JSOp::Pop)) { + // [stack] OBJ SYNC_ITERFN? + return false; + } + + if (!isIteratorMethodOnStack) { + if (!emit1(JSOp::Dup)) { + // [stack] OBJ OBJ + return false; + } + if (!emit2(JSOp::Symbol, uint8_t(JS::SymbolCode::iterator))) { + // [stack] OBJ OBJ @@ITERATOR + return false; + } + if (!emitElemOpBase(JSOp::GetElem)) { + // [stack] OBJ SYNC_ITERFN + return false; + } + } else { + // [stack] OBJ SYNC_ITERFN + } + + if (!emit1(JSOp::Swap)) { + // [stack] SYNC_ITERFN OBJ + return false; + } + if (!emitCall(getIterCallOp(JSOp::CallIter, selfHostedIter), 0)) { + // [stack] ITER + return false; + } + if (!emitCheckIsObj(CheckIsObjectKind::GetIterator)) { + // [stack] ITER + return false; + } + + if (!emit1(JSOp::Dup)) { + // [stack] ITER ITER + return false; + } + if (!emitAtomOp(JSOp::GetProp, TaggedParserAtomIndex::WellKnown::next())) { + // [stack] ITER SYNCNEXT + return false; + } + + if (!emit1(JSOp::ToAsyncIter)) { + // [stack] ITER + return false; + } + + if (!ifAsyncIterIsUndefined.emitElse()) { + // [stack] OBJ SYNC_ITERFN? ASYNC_ITERFN + return false; + } + + if (isIteratorMethodOnStack) { + if (!emit1(JSOp::Swap)) { + // [stack] OBJ ASYNC_ITERFN SYNC_ITERFN + return false; + } + if (!emit1(JSOp::Pop)) { + // [stack] OBJ ASYNC_ITERFN + return false; + } + } + + if (!emit1(JSOp::Swap)) { + // [stack] ASYNC_ITERFN OBJ + return false; + } + if (!emitCall(getIterCallOp(JSOp::CallIter, selfHostedIter), 0)) { + // [stack] ITER + return false; + } + if (!emitCheckIsObj(CheckIsObjectKind::GetAsyncIterator)) { + // [stack] ITER + return false; + } + + if (!ifAsyncIterIsUndefined.emitEnd()) { + // [stack] ITER + return false; + } + + if (!emit1(JSOp::Dup)) { + // [stack] ITER ITER + return false; + } + if (!emitAtomOp(JSOp::GetProp, TaggedParserAtomIndex::WellKnown::next())) { + // [stack] ITER NEXT + return false; + } + if (!emit1(JSOp::Swap)) { + // [stack] NEXT ITER + return false; + } + + return true; +} + +bool BytecodeEmitter::emitSpread(SelfHostedIter selfHostedIter) { + // [stack] NEXT ITER ARR I + return emitSpread(selfHostedIter, 2, JSOp::InitElemInc); + // [stack] ARR FINAL_INDEX +} + +bool BytecodeEmitter::emitSpread(SelfHostedIter selfHostedIter, + int spreadeeStackItems, JSOp storeElementOp) { + LoopControl loopInfo(this, StatementKind::Spread); + // In the [stack] annotations, (spreadee) can be "ARR I" (when spreading + // into an array or into call parameters, or "TUPLE" (when spreading into a + // tuple) + + if (!loopInfo.emitLoopHead(this, Nothing())) { + // [stack] NEXT ITER (spreadee) + return false; + } + + { +#ifdef DEBUG + auto loopDepth = bytecodeSection().stackDepth(); +#endif + + // Spread operations can't contain |continue|, so don't bother setting loop + // and enclosing "update" offsets, as we do with for-loops. + + if (!emitDupAt(spreadeeStackItems + 1, 2)) { + // [stack] NEXT ITER (spreadee) NEXT ITER + return false; + } + if (!emitIteratorNext(Nothing(), IteratorKind::Sync, selfHostedIter)) { + // [stack] NEXT ITER (spreadee) RESULT + return false; + } + if (!emit1(JSOp::Dup)) { + // [stack] NEXT ITER (spreadee) RESULT RESULT + return false; + } + if (!emitAtomOp(JSOp::GetProp, TaggedParserAtomIndex::WellKnown::done())) { + // [stack] NEXT ITER (spreadee) RESULT DONE + return false; + } + if (!emitJump(JSOp::JumpIfTrue, &loopInfo.breaks)) { + // [stack] NEXT ITER (spreadee) RESULT + return false; + } + + // Emit code to assign result.value to the iteration variable. + if (!emitAtomOp(JSOp::GetProp, TaggedParserAtomIndex::WellKnown::value())) { + // [stack] NEXT ITER (spreadee) VALUE + return false; + } + if (!emit1(storeElementOp)) { + // [stack] NEXT ITER (spreadee) + return false; + } + + if (!loopInfo.emitLoopEnd(this, JSOp::Goto, TryNoteKind::ForOf)) { + // [stack] NEXT ITER (spreadee) + return false; + } + + MOZ_ASSERT(bytecodeSection().stackDepth() == loopDepth); + } + + // When we leave the loop body and jump to this point, the result value is + // still on the stack. Account for that by updating the stack depth + // manually. + bytecodeSection().setStackDepth(bytecodeSection().stackDepth() + 1); + + // No continues should occur in spreads. + MOZ_ASSERT(!loopInfo.continues.offset.valid()); + + if (!emit2(JSOp::Pick, spreadeeStackItems + 2)) { + // [stack] ITER (spreadee) RESULT NEXT + return false; + } + if (!emit2(JSOp::Pick, spreadeeStackItems + 2)) { + // [stack] (spreadee) RESULT NEXT ITER + return false; + } + + return emitPopN(3); + // [stack] (spreadee) +} + +bool BytecodeEmitter::emitInitializeForInOrOfTarget(TernaryNode* forHead) { + MOZ_ASSERT(forHead->isKind(ParseNodeKind::ForIn) || + forHead->isKind(ParseNodeKind::ForOf)); + + MOZ_ASSERT(bytecodeSection().stackDepth() >= 1, + "must have a per-iteration value for initializing"); + + ParseNode* target = forHead->kid1(); + MOZ_ASSERT(!forHead->kid2()); + + // If the for-in/of loop didn't have a variable declaration, per-loop + // initialization is just assigning the iteration value to a target + // expression. + if (!target->is()) { + return emitAssignmentOrInit(ParseNodeKind::AssignExpr, target, nullptr); + // [stack] ... ITERVAL + } + + // Otherwise, per-loop initialization is (possibly) declaration + // initialization. If the declaration is a lexical declaration, it must be + // initialized. If the declaration is a variable declaration, an + // assignment to that name (which does *not* necessarily assign to the + // variable!) must be generated. + + auto* declarationList = &target->as(); + if (!updateSourceCoordNotes(declarationList->pn_pos.begin)) { + return false; + } + + target = declarationList->singleBinding(); + + NameNode* nameNode = nullptr; + if (target->isKind(ParseNodeKind::Name)) { + nameNode = &target->as(); + } else if (target->isKind(ParseNodeKind::AssignExpr)) { + BinaryNode* assignNode = &target->as(); + if (assignNode->left()->is()) { + nameNode = &assignNode->left()->as(); + } + } + + if (nameNode) { + auto nameAtom = nameNode->name(); + NameOpEmitter noe(this, nameAtom, NameOpEmitter::Kind::Initialize); + if (!noe.prepareForRhs()) { + return false; + } + if (noe.emittedBindOp()) { + // Per-iteration initialization in for-in/of loops computes the + // iteration value *before* initializing. Thus the initializing + // value may be buried under a bind-specific value on the stack. + // Swap it to the top of the stack. + MOZ_ASSERT(bytecodeSection().stackDepth() >= 2); + if (!emit1(JSOp::Swap)) { + return false; + } + } else { + // In cases of emitting a frame slot or environment slot, + // nothing needs be done. + MOZ_ASSERT(bytecodeSection().stackDepth() >= 1); + } + if (!noe.emitAssignment()) { + return false; + } + + // The caller handles removing the iteration value from the stack. + return true; + } + + MOZ_ASSERT( + !target->isKind(ParseNodeKind::AssignExpr), + "for-in/of loop destructuring declarations can't have initializers"); + + MOZ_ASSERT(target->isKind(ParseNodeKind::ArrayExpr) || + target->isKind(ParseNodeKind::ObjectExpr)); + return emitDestructuringOps(&target->as(), + DestructuringFlavor::Declaration); +} + +bool BytecodeEmitter::emitForOf(ForNode* forOfLoop, + const EmitterScope* headLexicalEmitterScope) { + MOZ_ASSERT(forOfLoop->isKind(ParseNodeKind::ForStmt)); + + TernaryNode* forOfHead = forOfLoop->head(); + MOZ_ASSERT(forOfHead->isKind(ParseNodeKind::ForOf)); + + unsigned iflags = forOfLoop->iflags(); + IteratorKind iterKind = + (iflags & JSITER_FORAWAITOF) ? IteratorKind::Async : IteratorKind::Sync; + MOZ_ASSERT_IF(iterKind == IteratorKind::Async, sc->isSuspendableContext()); + MOZ_ASSERT_IF(iterKind == IteratorKind::Async, + sc->asSuspendableContext()->isAsync()); + + ParseNode* forHeadExpr = forOfHead->kid3(); + + // Certain builtins (e.g. Array.from) are implemented in self-hosting + // as for-of loops. + ForOfEmitter forOf(this, headLexicalEmitterScope, + getSelfHostedIterFor(forHeadExpr), iterKind); + + if (!forOf.emitIterated()) { + // [stack] + return false; + } + + if (!updateSourceCoordNotes(forHeadExpr->pn_pos.begin)) { + return false; + } + if (!markStepBreakpoint()) { + return false; + } + if (!emitTree(forHeadExpr)) { + // [stack] ITERABLE + return false; + } + + if (headLexicalEmitterScope) { + DebugOnly forOfTarget = forOfHead->kid1(); + MOZ_ASSERT(forOfTarget->isKind(ParseNodeKind::LetDecl) || + forOfTarget->isKind(ParseNodeKind::ConstDecl)); + } + + bool isIteratorMethodOnStack = false; + if (emitterMode == BytecodeEmitter::SelfHosting && + forHeadExpr->isKind(ParseNodeKind::CallExpr) && + forHeadExpr->as().left()->isName( + TaggedParserAtomIndex::WellKnown::allowContentIterWith())) { + // This is the following case: + // + // for (const nextValue of allowContentIterWith(items, usingIterator)) { + // + // `items` is emitted by `emitTree(forHeadExpr)` above, and the result + // is on the stack as ITERABLE. + // `usingIterator` is the value of `items[Symbol.iterator]`, that's already + // retrieved. + ListNode* argsList = &forHeadExpr->as().right()->as(); + MOZ_ASSERT_IF(iterKind == IteratorKind::Sync, argsList->count() == 2); + MOZ_ASSERT_IF(iterKind == IteratorKind::Async, argsList->count() == 3); + + if (!emitTree(argsList->head()->pn_next)) { + // [stack] ITERABLE ITERFN + return false; + } + + // Async iterator has two possible iterators: An async iterator and a sync + // iterator. + if (iterKind == IteratorKind::Async) { + if (!emitTree(argsList->head()->pn_next->pn_next)) { + // [stack] ITERABLE ASYNC_ITERFN SYNC_ITERFN + return false; + } + } + + isIteratorMethodOnStack = true; + } + + if (!forOf.emitInitialize(forOfHead->pn_pos.begin, isIteratorMethodOnStack)) { + // [stack] NEXT ITER VALUE + return false; + } + + if (!emitInitializeForInOrOfTarget(forOfHead)) { + // [stack] NEXT ITER VALUE + return false; + } + + if (!forOf.emitBody()) { + // [stack] NEXT ITER UNDEF + return false; + } + + // Perform the loop body. + ParseNode* forBody = forOfLoop->body(); + if (!emitTree(forBody)) { + // [stack] NEXT ITER UNDEF + return false; + } + + if (!forOf.emitEnd(forHeadExpr->pn_pos.begin)) { + // [stack] + return false; + } + + return true; +} + +bool BytecodeEmitter::emitForIn(ForNode* forInLoop, + const EmitterScope* headLexicalEmitterScope) { + TernaryNode* forInHead = forInLoop->head(); + MOZ_ASSERT(forInHead->isKind(ParseNodeKind::ForIn)); + + ForInEmitter forIn(this, headLexicalEmitterScope); + + // Annex B: Evaluate the var-initializer expression if present. + // |for (var i = initializer in expr) { ... }| + ParseNode* forInTarget = forInHead->kid1(); + if (forInTarget->is()) { + auto* declarationList = &forInTarget->as(); + + ParseNode* decl = declarationList->singleBinding(); + if (decl->isKind(ParseNodeKind::AssignExpr)) { + BinaryNode* assignNode = &decl->as(); + if (assignNode->left()->is()) { + NameNode* nameNode = &assignNode->left()->as(); + ParseNode* initializer = assignNode->right(); + MOZ_ASSERT( + forInTarget->isKind(ParseNodeKind::VarStmt), + "for-in initializers are only permitted for |var| declarations"); + + if (!updateSourceCoordNotes(decl->pn_pos.begin)) { + return false; + } + + auto nameAtom = nameNode->name(); + NameOpEmitter noe(this, nameAtom, NameOpEmitter::Kind::Initialize); + if (!noe.prepareForRhs()) { + return false; + } + if (!emitInitializer(initializer, nameNode)) { + return false; + } + if (!noe.emitAssignment()) { + return false; + } + + // Pop the initializer. + if (!emit1(JSOp::Pop)) { + return false; + } + } + } + } + + if (!forIn.emitIterated()) { + // [stack] + return false; + } + + // Evaluate the expression being iterated. + ParseNode* expr = forInHead->kid3(); + + if (!updateSourceCoordNotes(expr->pn_pos.begin)) { + return false; + } + if (!markStepBreakpoint()) { + return false; + } + if (!emitTree(expr)) { + // [stack] EXPR + return false; + } + + MOZ_ASSERT(forInLoop->iflags() == 0); + + MOZ_ASSERT_IF(headLexicalEmitterScope, + forInTarget->isKind(ParseNodeKind::LetDecl) || + forInTarget->isKind(ParseNodeKind::ConstDecl)); + + if (!forIn.emitInitialize()) { + // [stack] ITER ITERVAL + return false; + } + + if (!emitInitializeForInOrOfTarget(forInHead)) { + // [stack] ITER ITERVAL + return false; + } + + if (!forIn.emitBody()) { + // [stack] ITER ITERVAL + return false; + } + + // Perform the loop body. + ParseNode* forBody = forInLoop->body(); + if (!emitTree(forBody)) { + // [stack] ITER ITERVAL + return false; + } + + if (!forIn.emitEnd(forInHead->pn_pos.begin)) { + // [stack] + return false; + } + + return true; +} + +/* C-style `for (init; cond; update) ...` loop. */ +bool BytecodeEmitter::emitCStyleFor( + ForNode* forNode, const EmitterScope* headLexicalEmitterScope) { + TernaryNode* forHead = forNode->head(); + ParseNode* forBody = forNode->body(); + ParseNode* init = forHead->kid1(); + ParseNode* cond = forHead->kid2(); + ParseNode* update = forHead->kid3(); + bool isLet = init && init->isKind(ParseNodeKind::LetDecl); + + CForEmitter cfor(this, isLet ? headLexicalEmitterScope : nullptr); + + if (!cfor.emitInit(init ? Some(init->pn_pos.begin) : Nothing())) { + // [stack] + return false; + } + + // If the head of this for-loop declared any lexical variables, the parser + // wrapped this ParseNodeKind::For node in a ParseNodeKind::LexicalScope + // representing the implicit scope of those variables. By the time we get + // here, we have already entered that scope. So far, so good. + if (init) { + // Emit the `init` clause, whether it's an expression or a variable + // declaration. (The loop variables were hoisted into an enclosing + // scope, but we still need to emit code for the initializers.) + if (init->is()) { + MOZ_ASSERT(!init->as().empty()); + + if (!emitTree(init)) { + // [stack] + return false; + } + } else { + if (!updateSourceCoordNotes(init->pn_pos.begin)) { + return false; + } + if (!markStepBreakpoint()) { + return false; + } + + // 'init' is an expression, not a declaration. emitTree left its + // value on the stack. + if (!emitTree(init, ValueUsage::IgnoreValue)) { + // [stack] VAL + return false; + } + if (!emit1(JSOp::Pop)) { + // [stack] + return false; + } + } + } + + if (!cfor.emitCond(cond ? Some(cond->pn_pos.begin) : Nothing())) { + // [stack] + return false; + } + + if (cond) { + if (!updateSourceCoordNotes(cond->pn_pos.begin)) { + return false; + } + if (!markStepBreakpoint()) { + return false; + } + if (!emitTree(cond)) { + // [stack] VAL + return false; + } + } + + if (!cfor.emitBody(cond ? CForEmitter::Cond::Present + : CForEmitter::Cond::Missing)) { + // [stack] + return false; + } + + if (!emitTree(forBody)) { + // [stack] + return false; + } + + if (!cfor.emitUpdate( + update ? CForEmitter::Update::Present : CForEmitter::Update::Missing, + update ? Some(update->pn_pos.begin) : Nothing())) { + // [stack] + return false; + } + + // Check for update code to do before the condition (if any). + if (update) { + if (!updateSourceCoordNotes(update->pn_pos.begin)) { + return false; + } + if (!markStepBreakpoint()) { + return false; + } + if (!emitTree(update, ValueUsage::IgnoreValue)) { + // [stack] VAL + return false; + } + } + + if (!cfor.emitEnd(forNode->pn_pos.begin)) { + // [stack] + return false; + } + + return true; +} + +bool BytecodeEmitter::emitFor(ForNode* forNode, + const EmitterScope* headLexicalEmitterScope) { + if (forNode->head()->isKind(ParseNodeKind::ForHead)) { + return emitCStyleFor(forNode, headLexicalEmitterScope); + } + + if (!updateLineNumberNotes(forNode->pn_pos.begin)) { + return false; + } + + if (forNode->head()->isKind(ParseNodeKind::ForIn)) { + return emitForIn(forNode, headLexicalEmitterScope); + } + + MOZ_ASSERT(forNode->head()->isKind(ParseNodeKind::ForOf)); + return emitForOf(forNode, headLexicalEmitterScope); +} + +MOZ_NEVER_INLINE bool BytecodeEmitter::emitFunction( + FunctionNode* funNode, bool needsProto /* = false */) { + FunctionBox* funbox = funNode->funbox(); + + // [stack] + + FunctionEmitter fe(this, funbox, funNode->syntaxKind(), + funNode->functionIsHoisted() + ? FunctionEmitter::IsHoisted::Yes + : FunctionEmitter::IsHoisted::No); + + // |wasEmittedByEnclosingScript| flag is set to true once the function has + // been emitted. Function definitions that need hoisting to the top of the + // function will be seen by emitFunction in two places. + if (funbox->wasEmittedByEnclosingScript()) { + if (!fe.emitAgain()) { + // [stack] + return false; + } + MOZ_ASSERT(funNode->functionIsHoisted()); + } else if (funbox->isInterpreted()) { + if (!funbox->emitBytecode) { + return fe.emitLazy(); + // [stack] FUN? + } + + if (!fe.prepareForNonLazy()) { + // [stack] + return false; + } + + BytecodeEmitter bce2(this, funbox); + if (!bce2.init(funNode->pn_pos)) { + return false; + } + + /* We measured the max scope depth when we parsed the function. */ + if (!bce2.emitFunctionScript(funNode)) { + return false; + } + + if (!fe.emitNonLazyEnd()) { + // [stack] FUN? + return false; + } + } else { + if (!fe.emitAsmJSModule()) { + // [stack] + return false; + } + } + + // Track the last emitted top-level self-hosted function, so that intrinsics + // can adjust attributes at parse time. + // + // NOTE: We also disallow lambda functions in the top-level body. This is done + // to simplify handling of the self-hosted stencil. Within normal function + // declarations there are no such restrictions. + if (emitterMode == EmitterMode::SelfHosting) { + if (sc->isTopLevelContext()) { + MOZ_ASSERT(!funbox->isLambda()); + MOZ_ASSERT(funbox->explicitName()); + prevSelfHostedTopLevelFunction = funbox; + } + } + + return true; +} + +bool BytecodeEmitter::emitDo(BinaryNode* doNode) { + ParseNode* bodyNode = doNode->left(); + + DoWhileEmitter doWhile(this); + if (!doWhile.emitBody(doNode->pn_pos.begin, getOffsetForLoop(bodyNode))) { + return false; + } + + if (!emitTree(bodyNode)) { + return false; + } + + if (!doWhile.emitCond()) { + return false; + } + + ParseNode* condNode = doNode->right(); + if (!updateSourceCoordNotes(condNode->pn_pos.begin)) { + return false; + } + if (!markStepBreakpoint()) { + return false; + } + if (!emitTree(condNode)) { + return false; + } + + if (!doWhile.emitEnd()) { + return false; + } + + return true; +} + +bool BytecodeEmitter::emitWhile(BinaryNode* whileNode) { + ParseNode* bodyNode = whileNode->right(); + + WhileEmitter wh(this); + + ParseNode* condNode = whileNode->left(); + if (!wh.emitCond(whileNode->pn_pos.begin, getOffsetForLoop(condNode), + whileNode->pn_pos.end)) { + return false; + } + + if (!updateSourceCoordNotes(condNode->pn_pos.begin)) { + return false; + } + if (!markStepBreakpoint()) { + return false; + } + if (!emitTree(condNode)) { + return false; + } + + if (!wh.emitBody()) { + return false; + } + if (!emitTree(bodyNode)) { + return false; + } + + if (!wh.emitEnd()) { + return false; + } + + return true; +} + +bool BytecodeEmitter::emitBreak(TaggedParserAtomIndex label) { + BreakableControl* target; + if (label) { + // Any statement with the matching label may be the break target. + auto hasSameLabel = [label](LabelControl* labelControl) { + return labelControl->label() == label; + }; + target = findInnermostNestableControl(hasSameLabel); + } else { + auto isNotLabel = [](BreakableControl* control) { + return !control->is(); + }; + target = findInnermostNestableControl(isNotLabel); + } + + return emitGoto(target, GotoKind::Break); +} + +bool BytecodeEmitter::emitContinue(TaggedParserAtomIndex label) { + LoopControl* target = nullptr; + if (label) { + // Find the loop statement enclosed by the matching label. + NestableControl* control = innermostNestableControl; + while (!control->is() || + control->as().label() != label) { + if (control->is()) { + target = &control->as(); + } + control = control->enclosing(); + } + } else { + target = findInnermostNestableControl(); + } + return emitGoto(target, GotoKind::Continue); +} + +bool BytecodeEmitter::emitGetFunctionThis(NameNode* thisName) { + MOZ_ASSERT(sc->hasFunctionThisBinding()); + MOZ_ASSERT(thisName->isName(TaggedParserAtomIndex::WellKnown::dotThis())); + + if (!updateLineNumberNotes(thisName->pn_pos.begin)) { + return false; + } + + if (!emitGetName(TaggedParserAtomIndex::WellKnown::dotThis())) { + // [stack] THIS + return false; + } + if (sc->needsThisTDZChecks()) { + if (!emit1(JSOp::CheckThis)) { + // [stack] THIS + return false; + } + } + + return true; +} + +bool BytecodeEmitter::emitGetThisForSuperBase(UnaryNode* superBase) { + MOZ_ASSERT(superBase->isKind(ParseNodeKind::SuperBase)); + NameNode* nameNode = &superBase->kid()->as(); + return emitGetFunctionThis(nameNode); + // [stack] THIS +} + +bool BytecodeEmitter::emitThisLiteral(ThisLiteral* pn) { + if (ParseNode* kid = pn->kid()) { + NameNode* thisName = &kid->as(); + return emitGetFunctionThis(thisName); + // [stack] THIS + } + + if (sc->thisBinding() == ThisBinding::Module) { + return emit1(JSOp::Undefined); + // [stack] UNDEF + } + + MOZ_ASSERT(sc->thisBinding() == ThisBinding::Global); + + MOZ_ASSERT(outermostScope().hasNonSyntacticScopeOnChain() == + sc->hasNonSyntacticScope()); + if (sc->hasNonSyntacticScope()) { + return emit1(JSOp::NonSyntacticGlobalThis); + // [stack] THIS + } + + return emit1(JSOp::GlobalThis); + // [stack] THIS +} + +bool BytecodeEmitter::emitCheckDerivedClassConstructorReturn() { + MOZ_ASSERT( + lookupName(TaggedParserAtomIndex::WellKnown::dotThis()).hasKnownSlot()); + if (!emitGetName(TaggedParserAtomIndex::WellKnown::dotThis())) { + return false; + } + if (!emit1(JSOp::CheckReturn)) { + return false; + } + if (!emit1(JSOp::SetRval)) { + return false; + } + return true; +} + +bool BytecodeEmitter::emitNewTarget() { + MOZ_ASSERT(sc->allowNewTarget()); + + if (!emitGetName(TaggedParserAtomIndex::WellKnown::dotNewTarget())) { + // [stack] NEW.TARGET + return false; + } + return true; +} + +bool BytecodeEmitter::emitNewTarget(NewTargetNode* pn) { + MOZ_ASSERT(pn->newTargetName()->isName( + TaggedParserAtomIndex::WellKnown::dotNewTarget())); + + return emitNewTarget(); +} + +bool BytecodeEmitter::emitNewTarget(CallNode* pn) { + MOZ_ASSERT(pn->callOp() == JSOp::SuperCall || + pn->callOp() == JSOp::SpreadSuperCall); + + // The parser is responsible for marking the "new.target" binding as being + // implicitly used in super() calls. + return emitNewTarget(); +} + +bool BytecodeEmitter::emitReturn(UnaryNode* returnNode) { + if (!updateSourceCoordNotes(returnNode->pn_pos.begin)) { + return false; + } + + if (!markStepBreakpoint()) { + return false; + } + + /* Push a return value */ + if (ParseNode* expr = returnNode->kid()) { + if (!emitTree(expr)) { + return false; + } + + if (sc->asSuspendableContext()->isAsync() && + sc->asSuspendableContext()->isGenerator()) { + if (!emitAwaitInInnermostScope()) { + return false; + } + } + } else { + /* No explicit return value provided */ + if (!emit1(JSOp::Undefined)) { + return false; + } + } + + // We know functionBodyEndPos is set because "return" is only + // valid in a function, and so we've passed through + // emitFunctionScript. + if (!updateSourceCoordNotes(*functionBodyEndPos)) { + return false; + } + + /* + * The return value is currently on the stack. We would like to + * generate JSOp::Return, but if we have work to do before returning, + * we will instead generate JSOp::SetRval / JSOp::RetRval. + * + * We don't know whether we will need fixup code until after calling + * prepareForNonLocalJumpToOutermost, so we start by generating + * JSOp::SetRval, then mutate it to JSOp::Return in finishReturn if it + * wasn't needed. + */ + BytecodeOffset setRvalOffset = bytecodeSection().offset(); + if (!emit1(JSOp::SetRval)) { + return false; + } + + NonLocalExitControl nle(this, NonLocalExitKind::Return); + return nle.emitReturn(setRvalOffset); +} + +bool BytecodeEmitter::finishReturn(BytecodeOffset setRvalOffset) { + // The return value is currently in rval. Depending on the current function, + // we may have to do additional work before returning: + // - Derived class constructors must check if the return value is an object. + // - Generators and async functions must do a final yield. + // - Non-async generators must return the value as an iterator result: + // { value: , done: true } + // - Non-generator async functions must resolve the function's result promise + // with the value. + // + // If we have not generated any code since the SetRval that stored the return + // value, we can also optimize the bytecode by rewriting that SetRval as a + // JSOp::Return. See |emitReturn| above. + + bool isDerivedClassConstructor = + sc->isFunctionBox() && sc->asFunctionBox()->isDerivedClassConstructor(); + bool needsFinalYield = + sc->isFunctionBox() && sc->asFunctionBox()->needsFinalYield(); + bool isSimpleReturn = + setRvalOffset.valid() && + setRvalOffset + BytecodeOffsetDiff(JSOpLength_SetRval) == + bytecodeSection().offset(); + + if (isDerivedClassConstructor) { + MOZ_ASSERT(!needsFinalYield); + if (!emitJump(JSOp::Goto, &endOfDerivedClassConstructorBody)) { + return false; + } + return true; + } + + if (needsFinalYield) { + if (!emitJump(JSOp::Goto, &finalYields)) { + return false; + } + return true; + } + + if (isSimpleReturn) { + MOZ_ASSERT(JSOp(bytecodeSection().code()[setRvalOffset.value()]) == + JSOp::SetRval); + bytecodeSection().code()[setRvalOffset.value()] = jsbytecode(JSOp::Return); + return true; + } + + // Nothing special needs to be done. + return emitReturnRval(); +} + +bool BytecodeEmitter::emitGetDotGeneratorInScope(EmitterScope& currentScope) { + if (!sc->isFunction() && sc->isModuleContext() && + sc->asModuleContext()->isAsync()) { + NameLocation loc = *locationOfNameBoundInScopeType( + TaggedParserAtomIndex::WellKnown::dotGenerator(), ¤tScope); + return emitGetNameAtLocation( + TaggedParserAtomIndex::WellKnown::dotGenerator(), loc); + } + NameLocation loc = *locationOfNameBoundInScopeType( + TaggedParserAtomIndex::WellKnown::dotGenerator(), ¤tScope); + return emitGetNameAtLocation(TaggedParserAtomIndex::WellKnown::dotGenerator(), + loc); +} + +bool BytecodeEmitter::emitInitialYield(UnaryNode* yieldNode) { + if (!emitTree(yieldNode->kid())) { + return false; + } + + if (!emitYieldOp(JSOp::InitialYield)) { + // [stack] RVAL GENERATOR RESUMEKIND + return false; + } + if (!emit1(JSOp::CheckResumeKind)) { + // [stack] RVAL + return false; + } + if (!emit1(JSOp::Pop)) { + // [stack] + return false; + } + + return true; +} + +bool BytecodeEmitter::emitYield(UnaryNode* yieldNode) { + MOZ_ASSERT(sc->isFunctionBox()); + MOZ_ASSERT(sc->asFunctionBox()->isGenerator()); + MOZ_ASSERT(yieldNode->isKind(ParseNodeKind::YieldExpr)); + + bool needsIteratorResult = sc->asFunctionBox()->needsIteratorResult(); + if (needsIteratorResult) { + if (!emitPrepareIteratorResult()) { + // [stack] ITEROBJ + return false; + } + } + if (ParseNode* expr = yieldNode->kid()) { + if (!emitTree(expr)) { + // [stack] ITEROBJ? VAL + return false; + } + } else { + if (!emit1(JSOp::Undefined)) { + // [stack] ITEROBJ? UNDEFINED + return false; + } + } + + if (sc->asSuspendableContext()->isAsync()) { + MOZ_ASSERT(!needsIteratorResult); + if (!emitAwaitInInnermostScope()) { + // [stack] RESULT + return false; + } + } + + if (needsIteratorResult) { + if (!emitFinishIteratorResult(false)) { + // [stack] ITEROBJ + return false; + } + } + + if (!emitGetDotGeneratorInInnermostScope()) { + // [stack] # if needsIteratorResult + // [stack] ITEROBJ .GENERATOR + // [stack] # else + // [stack] RESULT .GENERATOR + return false; + } + + if (!emitYieldOp(JSOp::Yield)) { + // [stack] YIELDRESULT GENERATOR RESUMEKIND + return false; + } + + if (!emit1(JSOp::CheckResumeKind)) { + // [stack] YIELDRESULT + return false; + } + + return true; +} + +bool BytecodeEmitter::emitAwaitInInnermostScope(UnaryNode* awaitNode) { + MOZ_ASSERT(sc->isSuspendableContext()); + MOZ_ASSERT(awaitNode->isKind(ParseNodeKind::AwaitExpr)); + + if (!emitTree(awaitNode->kid())) { + return false; + } + return emitAwaitInInnermostScope(); +} + +bool BytecodeEmitter::emitAwaitInScope(EmitterScope& currentScope) { + if (!emit1(JSOp::CanSkipAwait)) { + // [stack] VALUE CANSKIP + return false; + } + + if (!emit1(JSOp::MaybeExtractAwaitValue)) { + // [stack] VALUE_OR_RESOLVED CANSKIP + return false; + } + + InternalIfEmitter ifCanSkip(this); + if (!ifCanSkip.emitThen(IfEmitter::ConditionKind::Negative)) { + // [stack] VALUE_OR_RESOLVED + return false; + } + + if (sc->asSuspendableContext()->needsPromiseResult()) { + if (!emitGetDotGeneratorInScope(currentScope)) { + // [stack] VALUE GENERATOR + return false; + } + if (!emit1(JSOp::AsyncAwait)) { + // [stack] PROMISE + return false; + } + } + + if (!emitGetDotGeneratorInScope(currentScope)) { + // [stack] VALUE|PROMISE GENERATOR + return false; + } + if (!emitYieldOp(JSOp::Await)) { + // [stack] RESOLVED GENERATOR RESUMEKIND + return false; + } + if (!emit1(JSOp::CheckResumeKind)) { + // [stack] RESOLVED + return false; + } + + if (!ifCanSkip.emitEnd()) { + return false; + } + + MOZ_ASSERT(ifCanSkip.popped() == 0); + + return true; +} + +// ES2019 draft rev 49b781ec80117b60f73327ef3054703a3111e40c +// 14.4.14 Runtime Semantics: Evaluation +// YieldExpression : yield* AssignmentExpression +bool BytecodeEmitter::emitYieldStar(ParseNode* iter) { + MOZ_ASSERT(emitterMode != BytecodeEmitter::SelfHosting, + "yield* is prohibited in self-hosted code because it can run " + "user-modifiable iteration code"); + + MOZ_ASSERT(sc->isSuspendableContext()); + MOZ_ASSERT(sc->asSuspendableContext()->isGenerator()); + + // Step 1. + IteratorKind iterKind = sc->asSuspendableContext()->isAsync() + ? IteratorKind::Async + : IteratorKind::Sync; + bool needsIteratorResult = sc->asSuspendableContext()->needsIteratorResult(); + + // Steps 2-5. + if (!emitTree(iter)) { + // [stack] ITERABLE + return false; + } + if (iterKind == IteratorKind::Async) { + if (!emitAsyncIterator()) { + // [stack] NEXT ITER + return false; + } + } else { + if (!emitIterator()) { + // [stack] NEXT ITER + return false; + } + } + + // Step 6. + // Start with NormalCompletion(undefined). + if (!emit1(JSOp::Undefined)) { + // [stack] NEXT ITER RECEIVED + return false; + } + if (!emitPushResumeKind(GeneratorResumeKind::Next)) { + // [stack] NEXT ITER RECEIVED RESUMEKIND + return false; + } + + const int32_t startDepth = bytecodeSection().stackDepth(); + MOZ_ASSERT(startDepth >= 4); + + // Step 7 is a loop. + LoopControl loopInfo(this, StatementKind::YieldStar); + if (!loopInfo.emitLoopHead(this, Nothing())) { + // [stack] NEXT ITER RECEIVED RESUMEKIND + return false; + } + + // Step 7.a. Check for Normal completion. + if (!emit1(JSOp::Dup)) { + // [stack] NEXT ITER RECEIVED RESUMEKIND RESUMEKIND + return false; + } + if (!emitPushResumeKind(GeneratorResumeKind::Next)) { + // [stack] NEXT ITER RECEIVED RESUMEKIND RESUMEKIND NORMAL + return false; + } + if (!emit1(JSOp::StrictEq)) { + // [stack] NEXT ITER RECEIVED RESUMEKIND IS_NORMAL + return false; + } + + InternalIfEmitter ifKind(this); + if (!ifKind.emitThenElse()) { + // [stack] NEXT ITER RECEIVED RESUMEKIND + return false; + } + { + if (!emit1(JSOp::Pop)) { + // [stack] NEXT ITER RECEIVED + return false; + } + + // Step 7.a.i. + // result = iter.next(received) + if (!emit2(JSOp::Unpick, 2)) { + // [stack] RECEIVED NEXT ITER + return false; + } + if (!emit1(JSOp::Dup2)) { + // [stack] RECEIVED NEXT ITER NEXT ITER + return false; + } + if (!emit2(JSOp::Pick, 4)) { + // [stack] NEXT ITER NEXT ITER RECEIVED + return false; + } + if (!emitCall(JSOp::Call, 1, iter)) { + // [stack] NEXT ITER RESULT + return false; + } + + // Step 7.a.ii. + if (iterKind == IteratorKind::Async) { + if (!emitAwaitInInnermostScope()) { + // [stack] NEXT ITER RESULT + return false; + } + } + + // Step 7.a.iii. + if (!emitCheckIsObj(CheckIsObjectKind::IteratorNext)) { + // [stack] NEXT ITER RESULT + return false; + } + + // Bytecode for steps 7.a.iv-vii is emitted after the ifKind if-else because + // it's shared with other branches. + } + + // Step 7.b. Check for Throw completion. + if (!ifKind.emitElseIf(Nothing())) { + // [stack] NEXT ITER RECEIVED RESUMEKIND + return false; + } + if (!emit1(JSOp::Dup)) { + // [stack] NEXT ITER RECEIVED RESUMEKIND RESUMEKIND + return false; + } + if (!emitPushResumeKind(GeneratorResumeKind::Throw)) { + // [stack] NEXT ITER RECEIVED RESUMEKIND RESUMEKIND THROW + return false; + } + if (!emit1(JSOp::StrictEq)) { + // [stack] NEXT ITER RECEIVED RESUMEKIND IS_THROW + return false; + } + if (!ifKind.emitThenElse()) { + // [stack] NEXT ITER RECEIVED RESUMEKIND + return false; + } + { + if (!emit1(JSOp::Pop)) { + // [stack] NEXT ITER RECEIVED + return false; + } + // Step 7.b.i. + if (!emitDupAt(1)) { + // [stack] NEXT ITER RECEIVED ITER + return false; + } + if (!emit1(JSOp::Dup)) { + // [stack] NEXT ITER RECEIVED ITER ITER + return false; + } + if (!emitAtomOp(JSOp::GetProp, + TaggedParserAtomIndex::WellKnown::throw_())) { + // [stack] NEXT ITER RECEIVED ITER THROW + return false; + } + + // Step 7.b.ii. + InternalIfEmitter ifThrowMethodIsNotDefined(this); + if (!emit1(JSOp::IsNullOrUndefined)) { + // [stack] NEXT ITER RECEIVED ITER THROW NULL-OR-UNDEF + return false; + } + + if (!ifThrowMethodIsNotDefined.emitThenElse( + IfEmitter::ConditionKind::Negative)) { + // [stack] NEXT ITER RECEIVED ITER THROW + return false; + } + + // Step 7.b.ii.1. + // RESULT = ITER.throw(EXCEPTION) + if (!emit1(JSOp::Swap)) { + // [stack] NEXT ITER RECEIVED THROW ITER + return false; + } + if (!emit2(JSOp::Pick, 2)) { + // [stack] NEXT ITER THROW ITER RECEIVED + return false; + } + if (!emitCall(JSOp::Call, 1, iter)) { + // [stack] NEXT ITER RESULT + return false; + } + + // Step 7.b.ii.2. + if (iterKind == IteratorKind::Async) { + if (!emitAwaitInInnermostScope()) { + // [stack] NEXT ITER RESULT + return false; + } + } + + // Step 7.b.ii.4. + if (!emitCheckIsObj(CheckIsObjectKind::IteratorThrow)) { + // [stack] NEXT ITER RESULT + return false; + } + + // Bytecode for steps 7.b.ii.5-8 is emitted after the ifKind if-else because + // it's shared with other branches. + + // Step 7.b.iii. + if (!ifThrowMethodIsNotDefined.emitElse()) { + // [stack] NEXT ITER RECEIVED ITER THROW + return false; + } + if (!emit1(JSOp::Pop)) { + // [stack] NEXT ITER RECEIVED ITER + return false; + } + + // Steps 7.b.iii.1-4. + // + // If the iterator does not have a "throw" method, it calls IteratorClose + // and then throws a TypeError. + if (!emitIteratorCloseInInnermostScope(iterKind, CompletionKind::Normal, + getSelfHostedIterFor(iter))) { + // [stack] NEXT ITER RECEIVED ITER + return false; + } + // Steps 7.b.iii.5-6. + if (!emit2(JSOp::ThrowMsg, uint8_t(ThrowMsgKind::IteratorNoThrow))) { + // [stack] NEXT ITER RECEIVED ITER + // [stack] # throw + return false; + } + + if (!ifThrowMethodIsNotDefined.emitEnd()) { + return false; + } + } + + // Step 7.c. It must be a Return completion. + if (!ifKind.emitElse()) { + // [stack] NEXT ITER RECEIVED RESUMEKIND + return false; + } + { + if (!emit1(JSOp::Pop)) { + // [stack] NEXT ITER RECEIVED + return false; + } + + // Step 7.c.i. + // + // Call iterator.return() for receiving a "forced return" completion from + // the generator. + + // Step 7.c.ii. + // + // Get the "return" method. + if (!emitDupAt(1)) { + // [stack] NEXT ITER RECEIVED ITER + return false; + } + if (!emit1(JSOp::Dup)) { + // [stack] NEXT ITER RECEIVED ITER ITER + return false; + } + if (!emitAtomOp(JSOp::GetProp, + TaggedParserAtomIndex::WellKnown::return_())) { + // [stack] NEXT ITER RECEIVED ITER RET + return false; + } + + // Step 7.c.iii. + // + // Do nothing if "return" is undefined or null. + InternalIfEmitter ifReturnMethodIsDefined(this); + if (!emit1(JSOp::IsNullOrUndefined)) { + // [stack] NEXT ITER RECEIVED ITER RET NULL-OR-UNDEF + return false; + } + + // Step 7.c.iv. + // + // Call "return" with the argument passed to Generator.prototype.return. + if (!ifReturnMethodIsDefined.emitThenElse( + IfEmitter::ConditionKind::Negative)) { + // [stack] NEXT ITER RECEIVED ITER RET + return false; + } + if (!emit1(JSOp::Swap)) { + // [stack] NEXT ITER RECEIVED RET ITER + return false; + } + if (!emit2(JSOp::Pick, 2)) { + // [stack] NEXT ITER RET ITER RECEIVED + return false; + } + if (needsIteratorResult) { + if (!emitAtomOp(JSOp::GetProp, + TaggedParserAtomIndex::WellKnown::value())) { + // [stack] NEXT ITER RET ITER VAL + return false; + } + } + if (!emitCall(JSOp::Call, 1)) { + // [stack] NEXT ITER RESULT + return false; + } + + // Step 7.c.v. + if (iterKind == IteratorKind::Async) { + if (!emitAwaitInInnermostScope()) { + // [stack] NEXT ITER RESULT + return false; + } + } + + // Step 7.c.vi. + if (!emitCheckIsObj(CheckIsObjectKind::IteratorReturn)) { + // [stack] NEXT ITER RESULT + return false; + } + + // Check if the returned object from iterator.return() is done. If not, + // continue yielding. + + // Steps 7.c.vii-viii. + InternalIfEmitter ifReturnDone(this); + if (!emit1(JSOp::Dup)) { + // [stack] NEXT ITER RESULT RESULT + return false; + } + if (!emitAtomOp(JSOp::GetProp, TaggedParserAtomIndex::WellKnown::done())) { + // [stack] NEXT ITER RESULT DONE + return false; + } + if (!ifReturnDone.emitThenElse()) { + // [stack] NEXT ITER RESULT + return false; + } + + // Step 7.c.viii.1. + if (!emitAtomOp(JSOp::GetProp, TaggedParserAtomIndex::WellKnown::value())) { + // [stack] NEXT ITER VALUE + return false; + } + if (needsIteratorResult) { + if (!emitPrepareIteratorResult()) { + // [stack] NEXT ITER VALUE RESULT + return false; + } + if (!emit1(JSOp::Swap)) { + // [stack] NEXT ITER RESULT VALUE + return false; + } + if (!emitFinishIteratorResult(true)) { + // [stack] NEXT ITER RESULT + return false; + } + } + + if (!ifReturnDone.emitElse()) { + // [stack] NEXT ITER RESULT + return false; + } + + // Jump to continue label for steps 7.c.ix-x. + if (!emitJump(JSOp::Goto, &loopInfo.continues)) { + // [stack] NEXT ITER RESULT + return false; + } + + if (!ifReturnDone.emitEnd()) { + // [stack] NEXT ITER RESULT + return false; + } + + // Step 7.c.iii. + if (!ifReturnMethodIsDefined.emitElse()) { + // [stack] NEXT ITER RECEIVED ITER RET + return false; + } + if (!emitPopN(2)) { + // [stack] NEXT ITER RECEIVED + return false; + } + if (iterKind == IteratorKind::Async) { + // Step 7.c.iii.1. + if (!emitAwaitInInnermostScope()) { + // [stack] NEXT ITER RECEIVED + return false; + } + } + if (!ifReturnMethodIsDefined.emitEnd()) { + // [stack] NEXT ITER RECEIVED + return false; + } + + // Perform a "forced generator return". + // + // Step 7.c.iii.2. + // Step 7.c.viii.2. + if (!emitGetDotGeneratorInInnermostScope()) { + // [stack] NEXT ITER RESULT GENOBJ + return false; + } + if (!emitPushResumeKind(GeneratorResumeKind::Return)) { + // [stack] NEXT ITER RESULT GENOBJ RESUMEKIND + return false; + } + if (!emit1(JSOp::CheckResumeKind)) { + // [stack] NEXT ITER RESULT GENOBJ RESUMEKIND + return false; + } + } + + if (!ifKind.emitEnd()) { + // [stack] NEXT ITER RESULT + return false; + } + + // Shared tail for Normal/Throw completions. + // + // Steps 7.a.iv-v. + // Steps 7.b.ii.5-6. + // + // [stack] NEXT ITER RESULT + + // if (result.done) break; + if (!emit1(JSOp::Dup)) { + // [stack] NEXT ITER RESULT RESULT + return false; + } + if (!emitAtomOp(JSOp::GetProp, TaggedParserAtomIndex::WellKnown::done())) { + // [stack] NEXT ITER RESULT DONE + return false; + } + if (!emitJump(JSOp::JumpIfTrue, &loopInfo.breaks)) { + // [stack] NEXT ITER RESULT + return false; + } + + // Steps 7.a.vi-vii. + // Steps 7.b.ii.7-8. + // Steps 7.c.ix-x. + if (!loopInfo.emitContinueTarget(this)) { + // [stack] NEXT ITER RESULT + return false; + } + if (iterKind == IteratorKind::Async) { + if (!emitAtomOp(JSOp::GetProp, TaggedParserAtomIndex::WellKnown::value())) { + // [stack] NEXT ITER RESULT + return false; + } + } + if (!emitGetDotGeneratorInInnermostScope()) { + // [stack] NEXT ITER RESULT GENOBJ + return false; + } + if (!emitYieldOp(JSOp::Yield)) { + // [stack] NEXT ITER RVAL GENOBJ RESUMEKIND + return false; + } + if (!emit1(JSOp::Swap)) { + // [stack] NEXT ITER RVAL RESUMEKIND GENOBJ + return false; + } + if (!emit1(JSOp::Pop)) { + // [stack] NEXT ITER RVAL RESUMEKIND + return false; + } + if (!loopInfo.emitLoopEnd(this, JSOp::Goto, TryNoteKind::Loop)) { + // [stack] NEXT ITER RVAL RESUMEKIND + return false; + } + + // Jumps to this point have 3 (instead of 4) values on the stack. + MOZ_ASSERT(bytecodeSection().stackDepth() == startDepth); + bytecodeSection().setStackDepth(startDepth - 1); + + // [stack] NEXT ITER RESULT + + // Step 7.a.v.1. + // Step 7.b.ii.6.a. + // + // result.value + if (!emit2(JSOp::Unpick, 2)) { + // [stack] RESULT NEXT ITER + return false; + } + if (!emitPopN(2)) { + // [stack] RESULT + return false; + } + if (!emitAtomOp(JSOp::GetProp, TaggedParserAtomIndex::WellKnown::value())) { + // [stack] VALUE + return false; + } + + MOZ_ASSERT(bytecodeSection().stackDepth() == startDepth - 3); + + return true; +} + +bool BytecodeEmitter::emitStatementList(ListNode* stmtList) { + for (ParseNode* stmt : stmtList->contents()) { + if (!emitTree(stmt)) { + return false; + } + } + return true; +} + +bool BytecodeEmitter::emitExpressionStatement(UnaryNode* exprStmt) { + MOZ_ASSERT(exprStmt->isKind(ParseNodeKind::ExpressionStmt)); + + /* + * Top-level or called-from-a-native JS_Execute/EvaluateScript, + * debugger, and eval frames may need the value of the ultimate + * expression statement as the script's result, despite the fact + * that it appears useless to the compiler. + * + * API users may also set the JSOPTION_NO_SCRIPT_RVAL option when + * calling JS_Compile* to suppress JSOp::SetRval. + */ + bool wantval = false; + bool useful = false; + if (sc->isTopLevelContext()) { + useful = wantval = !sc->noScriptRval(); + } + + /* Don't eliminate expressions with side effects. */ + ParseNode* expr = exprStmt->kid(); + if (!useful) { + if (!checkSideEffects(expr, &useful)) { + return false; + } + + /* + * Don't eliminate apparently useless expressions if they are labeled + * expression statements. The startOffset() test catches the case + * where we are nesting in emitTree for a labeled compound statement. + */ + if (innermostNestableControl && + innermostNestableControl->is() && + innermostNestableControl->as().startOffset() >= + bytecodeSection().offset()) { + useful = true; + } + } + + if (useful) { + ValueUsage valueUsage = + wantval ? ValueUsage::WantValue : ValueUsage::IgnoreValue; + ExpressionStatementEmitter ese(this, valueUsage); + if (!ese.prepareForExpr(exprStmt->pn_pos.begin)) { + return false; + } + if (!markStepBreakpoint()) { + return false; + } + if (!emitTree(expr, valueUsage)) { + return false; + } + if (!ese.emitEnd()) { + return false; + } + } + + return true; +} + +bool BytecodeEmitter::emitDeleteName(UnaryNode* deleteNode) { + MOZ_ASSERT(deleteNode->isKind(ParseNodeKind::DeleteNameExpr)); + + NameNode* nameExpr = &deleteNode->kid()->as(); + MOZ_ASSERT(nameExpr->isKind(ParseNodeKind::Name)); + + return emitAtomOp(JSOp::DelName, nameExpr->atom()); +} + +bool BytecodeEmitter::emitDeleteProperty(UnaryNode* deleteNode) { + MOZ_ASSERT(deleteNode->isKind(ParseNodeKind::DeletePropExpr)); + + PropertyAccess* propExpr = &deleteNode->kid()->as(); + PropOpEmitter poe(this, PropOpEmitter::Kind::Delete, + propExpr->as().isSuper() + ? PropOpEmitter::ObjKind::Super + : PropOpEmitter::ObjKind::Other); + if (propExpr->isSuper()) { + // The expression |delete super.foo;| has to evaluate |super.foo|, + // which could throw if |this| hasn't yet been set by a |super(...)| + // call or the super-base is not an object, before throwing a + // ReferenceError for attempting to delete a super-reference. + UnaryNode* base = &propExpr->expression().as(); + if (!emitGetThisForSuperBase(base)) { + // [stack] THIS + return false; + } + } else { + if (!poe.prepareForObj()) { + return false; + } + if (!emitPropLHS(propExpr)) { + // [stack] OBJ + return false; + } + } + + if (!poe.emitDelete(propExpr->key().atom())) { + // [stack] # if Super + // [stack] THIS + // [stack] # otherwise + // [stack] SUCCEEDED + return false; + } + + return true; +} + +bool BytecodeEmitter::emitDeleteElement(UnaryNode* deleteNode) { + MOZ_ASSERT(deleteNode->isKind(ParseNodeKind::DeleteElemExpr)); + + PropertyByValue* elemExpr = &deleteNode->kid()->as(); + bool isSuper = elemExpr->isSuper(); + DebugOnly isPrivate = + elemExpr->key().isKind(ParseNodeKind::PrivateName); + MOZ_ASSERT(!isPrivate); + ElemOpEmitter eoe( + this, ElemOpEmitter::Kind::Delete, + isSuper ? ElemOpEmitter::ObjKind::Super : ElemOpEmitter::ObjKind::Other); + if (isSuper) { + // The expression |delete super[foo];| has to evaluate |super[foo]|, + // which could throw if |this| hasn't yet been set by a |super(...)| + // call, or trigger side-effects when evaluating ToPropertyKey(foo), + // or also throw when the super-base is not an object, before throwing + // a ReferenceError for attempting to delete a super-reference. + if (!eoe.prepareForObj()) { + // [stack] + return false; + } + + UnaryNode* base = &elemExpr->expression().as(); + if (!emitGetThisForSuperBase(base)) { + // [stack] THIS + return false; + } + if (!eoe.prepareForKey()) { + // [stack] THIS + return false; + } + if (!emitTree(&elemExpr->key())) { + // [stack] THIS KEY + return false; + } + } else { + if (!emitElemObjAndKey(elemExpr, false, eoe)) { + // [stack] OBJ KEY + return false; + } + } + if (!eoe.emitDelete()) { + // [stack] # if Super + // [stack] THIS + // [stack] # otherwise + // [stack] SUCCEEDED + return false; + } + + return true; +} + +bool BytecodeEmitter::emitDeleteExpression(UnaryNode* deleteNode) { + MOZ_ASSERT(deleteNode->isKind(ParseNodeKind::DeleteExpr)); + + ParseNode* expression = deleteNode->kid(); + + // If useless, just emit JSOp::True; otherwise convert |delete | to + // effectively |, true|. + bool useful = false; + if (!checkSideEffects(expression, &useful)) { + return false; + } + + if (useful) { + if (!emitTree(expression)) { + return false; + } + if (!emit1(JSOp::Pop)) { + return false; + } + } + + return emit1(JSOp::True); +} + +bool BytecodeEmitter::emitDeleteOptionalChain(UnaryNode* deleteNode) { + MOZ_ASSERT(deleteNode->isKind(ParseNodeKind::DeleteOptionalChainExpr)); + + OptionalEmitter oe(this, bytecodeSection().stackDepth()); + + ParseNode* kid = deleteNode->kid(); + switch (kid->getKind()) { + case ParseNodeKind::ElemExpr: + case ParseNodeKind::OptionalElemExpr: { + auto* elemExpr = &kid->as(); + if (!emitDeleteElementInOptChain(elemExpr, oe)) { + // [stack] # If shortcircuit + // [stack] UNDEFINED-OR-NULL + // [stack] # otherwise + // [stack] SUCCEEDED + return false; + } + + break; + } + case ParseNodeKind::DotExpr: + case ParseNodeKind::OptionalDotExpr: { + auto* propExpr = &kid->as(); + if (!emitDeletePropertyInOptChain(propExpr, oe)) { + // [stack] # If shortcircuit + // [stack] UNDEFINED-OR-NULL + // [stack] # otherwise + // [stack] SUCCEEDED + return false; + } + break; + } + default: + MOZ_ASSERT_UNREACHABLE("Unrecognized optional delete ParseNodeKind"); + } + + if (!oe.emitOptionalJumpTarget(JSOp::True)) { + // [stack] # If shortcircuit + // [stack] TRUE + // [stack] # otherwise + // [stack] SUCCEEDED + return false; + } + + return true; +} + +bool BytecodeEmitter::emitDeletePropertyInOptChain(PropertyAccessBase* propExpr, + OptionalEmitter& oe) { + MOZ_ASSERT_IF(propExpr->is(), + !propExpr->as().isSuper()); + PropOpEmitter poe(this, PropOpEmitter::Kind::Delete, + PropOpEmitter::ObjKind::Other); + + if (!poe.prepareForObj()) { + // [stack] + return false; + } + if (!emitOptionalTree(&propExpr->expression(), oe)) { + // [stack] OBJ + return false; + } + if (propExpr->isKind(ParseNodeKind::OptionalDotExpr)) { + if (!oe.emitJumpShortCircuit()) { + // [stack] # if Jump + // [stack] UNDEFINED-OR-NULL + // [stack] # otherwise + // [stack] OBJ + return false; + } + } + + if (!poe.emitDelete(propExpr->key().atom())) { + // [stack] SUCCEEDED + return false; + } + + return true; +} + +bool BytecodeEmitter::emitDeleteElementInOptChain(PropertyByValueBase* elemExpr, + OptionalEmitter& oe) { + MOZ_ASSERT_IF(elemExpr->is(), + !elemExpr->as().isSuper()); + ElemOpEmitter eoe(this, ElemOpEmitter::Kind::Delete, + ElemOpEmitter::ObjKind::Other); + + if (!eoe.prepareForObj()) { + // [stack] + return false; + } + + if (!emitOptionalTree(&elemExpr->expression(), oe)) { + // [stack] OBJ + return false; + } + + if (elemExpr->isKind(ParseNodeKind::OptionalElemExpr)) { + if (!oe.emitJumpShortCircuit()) { + // [stack] # if Jump + // [stack] UNDEFINED-OR-NULL + // [stack] # otherwise + // [stack] OBJ + return false; + } + } + + if (!eoe.prepareForKey()) { + // [stack] OBJ + return false; + } + + if (!emitTree(&elemExpr->key())) { + // [stack] OBJ KEY + return false; + } + + if (!eoe.emitDelete()) { + // [stack] SUCCEEDED + return false; + } + + return true; +} + +bool BytecodeEmitter::emitDebugCheckSelfHosted() { + // [stack] CALLEE + +#ifdef DEBUG + if (!emit1(JSOp::DebugCheckSelfHosted)) { + // [stack] CALLEE + return false; + } +#endif + + return true; +} + +bool BytecodeEmitter::emitSelfHostedCallFunction(CallNode* callNode, JSOp op) { + // Special-casing of callFunction to emit bytecode that directly + // invokes the callee with the correct |this| object and arguments. + // callFunction(fun, thisArg, arg0, arg1) thus becomes: + // - emit lookup for fun + // - emit lookup for thisArg + // - emit lookups for arg0, arg1 + // + // argc is set to the amount of actually emitted args and the + // emitting of args below is disabled by setting emitArgs to false. + NameNode* calleeNode = &callNode->left()->as(); + ListNode* argsList = &callNode->right()->as(); + + MOZ_ASSERT(argsList->count() >= 2); + + MOZ_ASSERT(callNode->callOp() == JSOp::Call); + + bool constructing = + calleeNode->name() == + TaggedParserAtomIndex::WellKnown::constructContentFunction(); + ParseNode* funNode = argsList->head(); + + if (!emitTree(funNode)) { + // [stack] CALLEE + return false; + } + +#ifdef DEBUG + MOZ_ASSERT(op == JSOp::Call || op == JSOp::CallContent || + op == JSOp::NewContent); + if (op == JSOp::Call) { + if (!emitDebugCheckSelfHosted()) { + // [stack] CALLEE + return false; + } + } +#endif + + ParseNode* thisOrNewTarget = funNode->pn_next; + if (constructing) { + // Save off the new.target value, but here emit a proper |this| for a + // constructing call. + if (!emit1(JSOp::IsConstructing)) { + // [stack] CALLEE IS_CONSTRUCTING + return false; + } + } else { + // It's |this|, emit it. + if (!emitTree(thisOrNewTarget)) { + // [stack] CALLEE THIS + return false; + } + } + + for (ParseNode* argpn : argsList->contentsFrom(thisOrNewTarget->pn_next)) { + if (!emitTree(argpn)) { + // [stack] CALLEE ... ARGS... + return false; + } + } + + if (constructing) { + if (!emitTree(thisOrNewTarget)) { + // [stack] CALLEE IS_CONSTRUCTING ARGS... NEW.TARGET + return false; + } + } + + uint32_t argc = argsList->count() - 2; + if (!emitCall(op, argc)) { + // [stack] RVAL + return false; + } + + return true; +} + +bool BytecodeEmitter::emitSelfHostedResumeGenerator(CallNode* callNode) { + ListNode* argsList = &callNode->right()->as(); + + // Syntax: resumeGenerator(gen, value, 'next'|'throw'|'return') + MOZ_ASSERT(argsList->count() == 3); + + ParseNode* genNode = argsList->head(); + if (!emitTree(genNode)) { + // [stack] GENERATOR + return false; + } + + ParseNode* valNode = genNode->pn_next; + if (!emitTree(valNode)) { + // [stack] GENERATOR VALUE + return false; + } + + ParseNode* kindNode = valNode->pn_next; + MOZ_ASSERT(kindNode->isKind(ParseNodeKind::StringExpr)); + GeneratorResumeKind kind = + ParserAtomToResumeKind(kindNode->as().atom()); + MOZ_ASSERT(!kindNode->pn_next); + + if (!emitPushResumeKind(kind)) { + // [stack] GENERATOR VALUE RESUMEKIND + return false; + } + + if (!emit1(JSOp::Resume)) { + // [stack] RVAL + return false; + } + + return true; +} + +bool BytecodeEmitter::emitSelfHostedForceInterpreter() { + // JSScript::hasForceInterpreterOp() relies on JSOp::ForceInterpreter being + // the first bytecode op in the script. + MOZ_ASSERT(bytecodeSection().code().empty()); + + if (!emit1(JSOp::ForceInterpreter)) { + return false; + } + if (!emit1(JSOp::Undefined)) { + return false; + } + + return true; +} + +bool BytecodeEmitter::emitSelfHostedAllowContentIter(CallNode* callNode) { + ListNode* argsList = &callNode->right()->as(); + + MOZ_ASSERT(argsList->count() == 1); + + // We're just here as a sentinel. Pass the value through directly. + return emitTree(argsList->head()); +} + +bool BytecodeEmitter::emitSelfHostedAllowContentIterWith(CallNode* callNode) { + ListNode* argsList = &callNode->right()->as(); + + MOZ_ASSERT(argsList->count() == 2 || argsList->count() == 3); + + // We're just here as a sentinel. Pass the value through directly. + return emitTree(argsList->head()); +} + +bool BytecodeEmitter::emitSelfHostedDefineDataProperty(CallNode* callNode) { + ListNode* argsList = &callNode->right()->as(); + + // Only optimize when 3 arguments are passed. + MOZ_ASSERT(argsList->count() == 3); + + ParseNode* objNode = argsList->head(); + if (!emitTree(objNode)) { + return false; + } + + ParseNode* idNode = objNode->pn_next; + if (!emitTree(idNode)) { + return false; + } + + ParseNode* valNode = idNode->pn_next; + if (!emitTree(valNode)) { + return false; + } + + // This will leave the object on the stack instead of pushing |undefined|, + // but that's fine because the self-hosted code doesn't use the return + // value. + return emit1(JSOp::InitElem); +} + +bool BytecodeEmitter::emitSelfHostedHasOwn(CallNode* callNode) { + ListNode* argsList = &callNode->right()->as(); + + MOZ_ASSERT(argsList->count() == 2); + + ParseNode* idNode = argsList->head(); + if (!emitTree(idNode)) { + return false; + } + + ParseNode* objNode = idNode->pn_next; + if (!emitTree(objNode)) { + return false; + } + + return emit1(JSOp::HasOwn); +} + +bool BytecodeEmitter::emitSelfHostedGetPropertySuper(CallNode* callNode) { + ListNode* argsList = &callNode->right()->as(); + + MOZ_ASSERT(argsList->count() == 3); + + ParseNode* objNode = argsList->head(); + ParseNode* idNode = objNode->pn_next; + ParseNode* receiverNode = idNode->pn_next; + + if (!emitTree(receiverNode)) { + return false; + } + + if (!emitTree(idNode)) { + return false; + } + + if (!emitTree(objNode)) { + return false; + } + + return emitElemOpBase(JSOp::GetElemSuper); +} + +bool BytecodeEmitter::emitSelfHostedToNumeric(CallNode* callNode) { + ListNode* argsList = &callNode->right()->as(); + + MOZ_ASSERT(argsList->count() == 1); + + ParseNode* argNode = argsList->head(); + + if (!emitTree(argNode)) { + return false; + } + + return emit1(JSOp::ToNumeric); +} + +bool BytecodeEmitter::emitSelfHostedToString(CallNode* callNode) { + ListNode* argsList = &callNode->right()->as(); + + MOZ_ASSERT(argsList->count() == 1); + + ParseNode* argNode = argsList->head(); + + if (!emitTree(argNode)) { + return false; + } + + return emit1(JSOp::ToString); +} + +bool BytecodeEmitter::emitSelfHostedIsNullOrUndefined(CallNode* callNode) { + ListNode* argsList = &callNode->right()->as(); + + MOZ_ASSERT(argsList->count() == 1); + + ParseNode* argNode = argsList->head(); + + if (!emitTree(argNode)) { + // [stack] ARG + return false; + } + if (!emit1(JSOp::IsNullOrUndefined)) { + // [stack] ARG IS_NULL_OR_UNDEF + return false; + } + if (!emit1(JSOp::Swap)) { + // [stack] IS_NULL_OR_UNDEF ARG + return false; + } + if (!emit1(JSOp::Pop)) { + // [stack] IS_NULL_OR_UNDEF + return false; + } + return true; +} + +bool BytecodeEmitter::emitSelfHostedGetBuiltinConstructorOrPrototype( + CallNode* callNode, bool isConstructor) { + ListNode* argsList = &callNode->right()->as(); + + MOZ_ASSERT(argsList->count() == 1); + + ParseNode* argNode = argsList->head(); + + if (!argNode->isKind(ParseNodeKind::StringExpr)) { + reportError(callNode, JSMSG_UNEXPECTED_TYPE, "built-in name", + "not a string constant"); + return false; + } + + auto name = argNode->as().atom(); + + BuiltinObjectKind kind; + if (isConstructor) { + kind = BuiltinConstructorForName(name); + } else { + kind = BuiltinPrototypeForName(name); + } + + if (kind == BuiltinObjectKind::None) { + reportError(callNode, JSMSG_UNEXPECTED_TYPE, "built-in name", + "not a valid built-in"); + return false; + } + + return emitBuiltinObject(kind); +} + +bool BytecodeEmitter::emitSelfHostedGetBuiltinConstructor(CallNode* callNode) { + return emitSelfHostedGetBuiltinConstructorOrPrototype( + callNode, /* isConstructor = */ true); +} + +bool BytecodeEmitter::emitSelfHostedGetBuiltinPrototype(CallNode* callNode) { + return emitSelfHostedGetBuiltinConstructorOrPrototype( + callNode, /* isConstructor = */ false); +} + +JS::SymbolCode ParserAtomToSymbolCode(TaggedParserAtomIndex atom) { + // NOTE: This is a linear search, but the set of entries is quite small and + // this is only used for initial self-hosted parse. +#define MATCH_WELL_KNOWN_SYMBOL(NAME) \ + if (atom == TaggedParserAtomIndex::WellKnown::NAME()) { \ + return JS::SymbolCode::NAME; \ + } + JS_FOR_EACH_WELL_KNOWN_SYMBOL(MATCH_WELL_KNOWN_SYMBOL) +#undef MATCH_WELL_KNOWN_SYMBOL + + return JS::SymbolCode::Limit; +} + +bool BytecodeEmitter::emitSelfHostedGetBuiltinSymbol(CallNode* callNode) { + ListNode* argsList = &callNode->right()->as(); + + MOZ_ASSERT(argsList->count() == 1); + + ParseNode* argNode = argsList->head(); + + if (!argNode->isKind(ParseNodeKind::StringExpr)) { + reportError(callNode, JSMSG_UNEXPECTED_TYPE, "built-in name", + "not a string constant"); + return false; + } + + auto name = argNode->as().atom(); + + JS::SymbolCode code = ParserAtomToSymbolCode(name); + if (code == JS::SymbolCode::Limit) { + reportError(callNode, JSMSG_UNEXPECTED_TYPE, "built-in name", + "not a valid built-in"); + return false; + } + + return emit2(JSOp::Symbol, uint8_t(code)); +} + +bool BytecodeEmitter::emitSelfHostedArgumentsLength(CallNode* callNode) { + MOZ_ASSERT(!sc->asFunctionBox()->needsArgsObj()); + sc->asFunctionBox()->setUsesArgumentsIntrinsics(); + + MOZ_ASSERT(callNode->right()->as().count() == 0); + + return emit1(JSOp::ArgumentsLength); +} + +bool BytecodeEmitter::emitSelfHostedGetArgument(CallNode* callNode) { + MOZ_ASSERT(!sc->asFunctionBox()->needsArgsObj()); + sc->asFunctionBox()->setUsesArgumentsIntrinsics(); + + ListNode* argsList = &callNode->right()->as(); + MOZ_ASSERT(argsList->count() == 1); + + ParseNode* argNode = argsList->head(); + if (!emitTree(argNode)) { + return false; + } + + return emit1(JSOp::GetActualArg); +} + +#ifdef DEBUG +void BytecodeEmitter::assertSelfHostedExpectedTopLevel(ParseNode* node) { + // The function argument is expected to be a simple binding/function name. + // Eg. `function foo() { }; SpecialIntrinsic(foo)` + MOZ_ASSERT(node->isKind(ParseNodeKind::Name), + "argument must be a function name"); + TaggedParserAtomIndex targetName = node->as().name(); + + // The special intrinsics must follow the target functions definition. A + // simple assert is fine here since any hoisted function will cause a non-null + // value to be set here. + MOZ_ASSERT(prevSelfHostedTopLevelFunction); + + // The target function must match the most recently defined top-level + // self-hosted function. + MOZ_ASSERT(prevSelfHostedTopLevelFunction->explicitName() == targetName, + "selfhost decorator must immediately follow target function"); +} +#endif + +bool BytecodeEmitter::emitSelfHostedSetIsInlinableLargeFunction( + CallNode* callNode) { +#ifdef DEBUG + ListNode* argsList = &callNode->right()->as(); + + MOZ_ASSERT(argsList->count() == 1); + + assertSelfHostedExpectedTopLevel(argsList->head()); +#endif + + MOZ_ASSERT(prevSelfHostedTopLevelFunction->isInitialCompilation); + prevSelfHostedTopLevelFunction->setIsInlinableLargeFunction(); + + // This is still a call node, so we must generate a stack value. + return emit1(JSOp::Undefined); +} + +bool BytecodeEmitter::emitSelfHostedSetCanonicalName(CallNode* callNode) { + ListNode* argsList = &callNode->right()->as(); + + MOZ_ASSERT(argsList->count() == 2); + +#ifdef DEBUG + assertSelfHostedExpectedTopLevel(argsList->head()); +#endif + + ParseNode* nameNode = argsList->last(); + MOZ_ASSERT(nameNode->isKind(ParseNodeKind::StringExpr)); + TaggedParserAtomIndex specName = nameNode->as().atom(); + // Canonical name must be atomized. + compilationState.parserAtoms.markUsedByStencil(specName, + ParserAtom::Atomize::Yes); + + // Store the canonical name for instantiation. + prevSelfHostedTopLevelFunction->functionStencil().setSelfHostedCanonicalName( + specName); + + return emit1(JSOp::Undefined); +} + +#ifdef DEBUG +void BytecodeEmitter::assertSelfHostedUnsafeGetReservedSlot( + ListNode* argsList) { + MOZ_ASSERT(argsList->count() == 2); + + ParseNode* objNode = argsList->head(); + ParseNode* slotNode = objNode->pn_next; + + // Ensure that the slot argument is fixed, this is required by the JITs. + MOZ_ASSERT(slotNode->isKind(ParseNodeKind::NumberExpr), + "slot argument must be a constant"); +} + +void BytecodeEmitter::assertSelfHostedUnsafeSetReservedSlot( + ListNode* argsList) { + MOZ_ASSERT(argsList->count() == 3); + + ParseNode* objNode = argsList->head(); + ParseNode* slotNode = objNode->pn_next; + + // Ensure that the slot argument is fixed, this is required by the JITs. + MOZ_ASSERT(slotNode->isKind(ParseNodeKind::NumberExpr), + "slot argument must be a constant"); +} +#endif + +/* A version of emitCalleeAndThis for the optional cases: + * * a?.() + * * a?.b() + * * a?.["b"]() + * * (a?.b)() + * * a?.#b() + * + * See emitCallOrNew and emitOptionalCall for more context. + */ +bool BytecodeEmitter::emitOptionalCalleeAndThis(ParseNode* callee, + CallNode* call, + CallOrNewEmitter& cone, + OptionalEmitter& oe) { + AutoCheckRecursionLimit recursion(fc); + if (!recursion.check(fc)) { + return false; + } + + switch (ParseNodeKind kind = callee->getKind()) { + case ParseNodeKind::Name: { + auto name = callee->as().name(); + if (!cone.emitNameCallee(name)) { + // [stack] CALLEE THIS + return false; + } + break; + } + + case ParseNodeKind::OptionalDotExpr: { + MOZ_ASSERT(emitterMode != BytecodeEmitter::SelfHosting); + OptionalPropertyAccess* prop = &callee->as(); + bool isSuper = false; + + PropOpEmitter& poe = cone.prepareForPropCallee(isSuper); + if (!emitOptionalDotExpression(prop, poe, isSuper, oe)) { + // [stack] CALLEE THIS + return false; + } + break; + } + case ParseNodeKind::DotExpr: { + MOZ_ASSERT(emitterMode != BytecodeEmitter::SelfHosting); + PropertyAccess* prop = &callee->as(); + bool isSuper = prop->isSuper(); + + PropOpEmitter& poe = cone.prepareForPropCallee(isSuper); + if (!emitOptionalDotExpression(prop, poe, isSuper, oe)) { + // [stack] CALLEE THIS + return false; + } + break; + } + + case ParseNodeKind::OptionalElemExpr: { + OptionalPropertyByValue* elem = &callee->as(); + bool isSuper = false; + MOZ_ASSERT(!elem->key().isKind(ParseNodeKind::PrivateName)); + ElemOpEmitter& eoe = cone.prepareForElemCallee(isSuper); + if (!emitOptionalElemExpression(elem, eoe, isSuper, oe)) { + // [stack] CALLEE THIS + return false; + } + break; + } + case ParseNodeKind::ElemExpr: { + PropertyByValue* elem = &callee->as(); + bool isSuper = elem->isSuper(); + MOZ_ASSERT(!elem->key().isKind(ParseNodeKind::PrivateName)); + ElemOpEmitter& eoe = cone.prepareForElemCallee(isSuper); + if (!emitOptionalElemExpression(elem, eoe, isSuper, oe)) { + // [stack] CALLEE THIS + return false; + } + break; + } + + case ParseNodeKind::PrivateMemberExpr: + case ParseNodeKind::OptionalPrivateMemberExpr: { + PrivateMemberAccessBase* privateExpr = + &callee->as(); + PrivateOpEmitter& xoe = + cone.prepareForPrivateCallee(privateExpr->privateName().name()); + if (!emitOptionalPrivateExpression(privateExpr, xoe, oe)) { + // [stack] CALLEE THIS + return false; + } + break; + } + + case ParseNodeKind::Function: + if (!cone.prepareForFunctionCallee()) { + return false; + } + if (!emitOptionalTree(callee, oe)) { + // [stack] CALLEE + return false; + } + break; + + case ParseNodeKind::OptionalChain: { + return emitCalleeAndThisForOptionalChain(&callee->as(), call, + cone); + } + + default: + MOZ_RELEASE_ASSERT(kind != ParseNodeKind::SuperBase); + + if (!cone.prepareForOtherCallee()) { + return false; + } + if (!emitOptionalTree(callee, oe)) { + // [stack] CALLEE + return false; + } + break; + } + + if (!cone.emitThis()) { + // [stack] CALLEE THIS + return false; + } + + return true; +} + +bool BytecodeEmitter::emitCalleeAndThis(ParseNode* callee, ParseNode* call, + CallOrNewEmitter& cone) { + switch (callee->getKind()) { + case ParseNodeKind::Name: { + auto name = callee->as().name(); + if (!cone.emitNameCallee(name)) { + // [stack] CALLEE THIS? + return false; + } + break; + } + case ParseNodeKind::DotExpr: { + MOZ_ASSERT(emitterMode != BytecodeEmitter::SelfHosting); + PropertyAccess* prop = &callee->as(); + bool isSuper = prop->isSuper(); + + PropOpEmitter& poe = cone.prepareForPropCallee(isSuper); + if (!poe.prepareForObj()) { + return false; + } + if (isSuper) { + UnaryNode* base = &prop->expression().as(); + if (!emitGetThisForSuperBase(base)) { + // [stack] THIS + return false; + } + } else { + if (!emitPropLHS(prop)) { + // [stack] OBJ + return false; + } + } + if (!poe.emitGet(prop->key().atom())) { + // [stack] CALLEE THIS? + return false; + } + + break; + } + case ParseNodeKind::ElemExpr: { + MOZ_ASSERT(emitterMode != BytecodeEmitter::SelfHosting); + PropertyByValue* elem = &callee->as(); + bool isSuper = elem->isSuper(); + MOZ_ASSERT(!elem->key().isKind(ParseNodeKind::PrivateName)); + ElemOpEmitter& eoe = cone.prepareForElemCallee(isSuper); + if (!emitElemObjAndKey(elem, isSuper, eoe)) { + // [stack] # if Super + // [stack] THIS? THIS KEY + // [stack] # otherwise + // [stack] OBJ? OBJ KEY + return false; + } + if (!eoe.emitGet()) { + // [stack] CALLEE THIS? + return false; + } + + break; + } + case ParseNodeKind::PrivateMemberExpr: { + MOZ_ASSERT(emitterMode != BytecodeEmitter::SelfHosting); + PrivateMemberAccessBase* privateExpr = + &callee->as(); + PrivateOpEmitter& xoe = + cone.prepareForPrivateCallee(privateExpr->privateName().name()); + if (!emitTree(&privateExpr->expression())) { + // [stack] OBJ + return false; + } + if (!xoe.emitReference()) { + // [stack] OBJ NAME + return false; + } + if (!xoe.emitGetForCallOrNew()) { + // [stack] CALLEE THIS + return false; + } + + break; + } + case ParseNodeKind::Function: + if (!cone.prepareForFunctionCallee()) { + return false; + } + if (!emitTree(callee)) { + // [stack] CALLEE + return false; + } + break; + case ParseNodeKind::SuperBase: + MOZ_ASSERT(call->isKind(ParseNodeKind::SuperCallExpr)); + MOZ_ASSERT(callee->isKind(ParseNodeKind::SuperBase)); + if (!cone.emitSuperCallee()) { + // [stack] CALLEE IsConstructing + return false; + } + break; + case ParseNodeKind::OptionalChain: { + return emitCalleeAndThisForOptionalChain(&callee->as(), + &call->as(), cone); + } + default: + if (!cone.prepareForOtherCallee()) { + return false; + } + if (!emitTree(callee)) { + return false; + } + break; + } + + if (!cone.emitThis()) { + // [stack] CALLEE THIS + return false; + } + + return true; +} + +ParseNode* BytecodeEmitter::getCoordNode(ParseNode* callNode, + ParseNode* calleeNode, JSOp op, + ListNode* argsList) { + ParseNode* coordNode = callNode; + if (op == JSOp::Call || op == JSOp::SpreadCall) { + // Default to using the location of the `(` itself. + // obj[expr]() // expression + // ^ // column coord + coordNode = argsList; + + switch (calleeNode->getKind()) { + case ParseNodeKind::DotExpr: + // Use the position of a property access identifier. + // + // obj().aprop() // expression + // ^ // column coord + // + // Note: Because of the constant folding logic in FoldElement, + // this case also applies for constant string properties. + // + // obj()['aprop']() // expression + // ^ // column coord + coordNode = &calleeNode->as().key(); + break; + case ParseNodeKind::Name: { + // Use the start of callee name unless it is at a separator + // or has no args. + // + // 2 + obj() // expression + // ^ // column coord + // + if (argsList->empty() || + !bytecodeSection().atSeparator(calleeNode->pn_pos.begin)) { + // Use the start of callee names. + coordNode = calleeNode; + } + break; + } + + default: + break; + } + } + return coordNode; +} + +bool BytecodeEmitter::emitArguments(ListNode* argsList, bool isCall, + bool isSpread, CallOrNewEmitter& cone) { + uint32_t argc = argsList->count(); + if (argc >= ARGC_LIMIT) { + reportError(argsList, + isCall ? JSMSG_TOO_MANY_FUN_ARGS : JSMSG_TOO_MANY_CON_ARGS); + return false; + } + if (!isSpread) { + if (!cone.prepareForNonSpreadArguments()) { + // [stack] CALLEE THIS + return false; + } + for (ParseNode* arg : argsList->contents()) { + if (!emitTree(arg)) { + // [stack] CALLEE THIS ARG* + return false; + } + } + } else if (cone.wantSpreadOperand()) { + auto* spreadNode = &argsList->head()->as(); + if (!emitTree(spreadNode->kid())) { + // [stack] CALLEE THIS ARG0 + return false; + } + + if (!cone.emitSpreadArgumentsTest()) { + // [stack] CALLEE THIS ARG0 + return false; + } + + if (cone.wantSpreadIteration()) { + if (!emitSpreadIntoArray(spreadNode)) { + // [stack] CALLEE THIS ARR + return false; + } + } + + if (!cone.emitSpreadArgumentsTestEnd()) { + // [stack] CALLEE THIS ARR + return false; + } + } else { + if (!cone.prepareForSpreadArguments()) { + // [stack] CALLEE THIS + return false; + } + if (!emitArray(argsList)) { + // [stack] CALLEE THIS ARR + return false; + } + } + + return true; +} + +bool BytecodeEmitter::emitOptionalCall(CallNode* callNode, OptionalEmitter& oe, + ValueUsage valueUsage) { + /* + * A modified version of emitCallOrNew that handles optional calls. + * + * These include the following: + * a?.() + * a.b?.() + * a.["b"]?.() + * (a?.b)?.() + * + * See CallOrNewEmitter for more context. + */ + ParseNode* calleeNode = callNode->left(); + ListNode* argsList = &callNode->right()->as(); + bool isSpread = IsSpreadOp(callNode->callOp()); + JSOp op = callNode->callOp(); + uint32_t argc = argsList->count(); + bool isOptimizableSpread = isSpread && argc == 1; + + CallOrNewEmitter cone(this, op, + isOptimizableSpread + ? CallOrNewEmitter::ArgumentsKind::SingleSpread + : CallOrNewEmitter::ArgumentsKind::Other, + valueUsage); + + ParseNode* coordNode = getCoordNode(callNode, calleeNode, op, argsList); + + if (!emitOptionalCalleeAndThis(calleeNode, callNode, cone, oe)) { + // [stack] CALLEE THIS + return false; + } + + if (callNode->isKind(ParseNodeKind::OptionalCallExpr)) { + if (!oe.emitJumpShortCircuitForCall()) { + // [stack] CALLEE THIS + return false; + } + } + + if (!emitArguments(argsList, /* isCall = */ true, isSpread, cone)) { + // [stack] CALLEE THIS ARGS... + return false; + } + + if (!cone.emitEnd(argc, coordNode->pn_pos.begin)) { + // [stack] RVAL + return false; + } + + return true; +} + +bool BytecodeEmitter::emitCallOrNew(CallNode* callNode, ValueUsage valueUsage) { + /* + * Emit callable invocation or operator new (constructor call) code. + * First, emit code for the left operand to evaluate the callable or + * constructable object expression. + * + * Then (or in a call case that has no explicit reference-base + * object) we emit JSOp::Undefined to produce the undefined |this| + * value required for calls (which non-strict mode functions + * will box into the global object). + */ + bool isCall = callNode->isKind(ParseNodeKind::CallExpr) || + callNode->isKind(ParseNodeKind::TaggedTemplateExpr); + ParseNode* calleeNode = callNode->left(); + ListNode* argsList = &callNode->right()->as(); + JSOp op = callNode->callOp(); + + if (calleeNode->isKind(ParseNodeKind::Name) && + emitterMode == BytecodeEmitter::SelfHosting && op == JSOp::Call) { + // Calls to "forceInterpreter", "callFunction", + // "callContentFunction", or "resumeGenerator" in self-hosted + // code generate inline bytecode. + // + // NOTE: The list of special instruction names has to be kept in sync with + // "js/src/builtin/.eslintrc.js". + auto calleeName = calleeNode->as().name(); + if (calleeName == TaggedParserAtomIndex::WellKnown::callFunction()) { + return emitSelfHostedCallFunction(callNode, JSOp::Call); + } + if (calleeName == TaggedParserAtomIndex::WellKnown::callContentFunction()) { + return emitSelfHostedCallFunction(callNode, JSOp::CallContent); + } + if (calleeName == + TaggedParserAtomIndex::WellKnown::constructContentFunction()) { + return emitSelfHostedCallFunction(callNode, JSOp::NewContent); + } + if (calleeName == TaggedParserAtomIndex::WellKnown::resumeGenerator()) { + return emitSelfHostedResumeGenerator(callNode); + } + if (calleeName == TaggedParserAtomIndex::WellKnown::forceInterpreter()) { + return emitSelfHostedForceInterpreter(); + } + if (calleeName == TaggedParserAtomIndex::WellKnown::allowContentIter()) { + return emitSelfHostedAllowContentIter(callNode); + } + if (calleeName == + TaggedParserAtomIndex::WellKnown::allowContentIterWith()) { + return emitSelfHostedAllowContentIterWith(callNode); + } + if (calleeName == + TaggedParserAtomIndex::WellKnown::defineDataPropertyIntrinsic() && + argsList->count() == 3) { + return emitSelfHostedDefineDataProperty(callNode); + } + if (calleeName == TaggedParserAtomIndex::WellKnown::hasOwn()) { + return emitSelfHostedHasOwn(callNode); + } + if (calleeName == TaggedParserAtomIndex::WellKnown::getPropertySuper()) { + return emitSelfHostedGetPropertySuper(callNode); + } + if (calleeName == TaggedParserAtomIndex::WellKnown::ToNumeric()) { + return emitSelfHostedToNumeric(callNode); + } + if (calleeName == TaggedParserAtomIndex::WellKnown::ToString()) { + return emitSelfHostedToString(callNode); + } + if (calleeName == + TaggedParserAtomIndex::WellKnown::GetBuiltinConstructor()) { + return emitSelfHostedGetBuiltinConstructor(callNode); + } + if (calleeName == TaggedParserAtomIndex::WellKnown::GetBuiltinPrototype()) { + return emitSelfHostedGetBuiltinPrototype(callNode); + } + if (calleeName == TaggedParserAtomIndex::WellKnown::GetBuiltinSymbol()) { + return emitSelfHostedGetBuiltinSymbol(callNode); + } + if (calleeName == TaggedParserAtomIndex::WellKnown::ArgumentsLength()) { + return emitSelfHostedArgumentsLength(callNode); + } + if (calleeName == TaggedParserAtomIndex::WellKnown::GetArgument()) { + return emitSelfHostedGetArgument(callNode); + } + if (calleeName == + TaggedParserAtomIndex::WellKnown::SetIsInlinableLargeFunction()) { + return emitSelfHostedSetIsInlinableLargeFunction(callNode); + } + if (calleeName == TaggedParserAtomIndex::WellKnown::SetCanonicalName()) { + return emitSelfHostedSetCanonicalName(callNode); + } + if (calleeName == TaggedParserAtomIndex::WellKnown::IsNullOrUndefined()) { + return emitSelfHostedIsNullOrUndefined(callNode); + } +#ifdef DEBUG + if (calleeName == + TaggedParserAtomIndex::WellKnown::UnsafeGetReservedSlot() || + calleeName == TaggedParserAtomIndex::WellKnown:: + UnsafeGetObjectFromReservedSlot() || + calleeName == TaggedParserAtomIndex::WellKnown:: + UnsafeGetInt32FromReservedSlot() || + calleeName == TaggedParserAtomIndex::WellKnown:: + UnsafeGetStringFromReservedSlot()) { + // Make sure that this call is correct, but don't emit any special code. + assertSelfHostedUnsafeGetReservedSlot(argsList); + } + if (calleeName == + TaggedParserAtomIndex::WellKnown::UnsafeSetReservedSlot()) { + // Make sure that this call is correct, but don't emit any special code. + assertSelfHostedUnsafeSetReservedSlot(argsList); + } +#endif + // Fall through + } + + uint32_t argc = argsList->count(); + bool isSpread = IsSpreadOp(op); + bool isOptimizableSpread = isSpread && argc == 1; + bool isDefaultDerivedClassConstructor = + sc->isFunctionBox() && sc->asFunctionBox()->isDerivedClassConstructor() && + sc->asFunctionBox()->isSyntheticFunction(); + MOZ_ASSERT_IF(isDefaultDerivedClassConstructor, isOptimizableSpread); + CallOrNewEmitter cone( + this, op, + isOptimizableSpread + ? isDefaultDerivedClassConstructor + ? CallOrNewEmitter::ArgumentsKind::PassthroughRest + : CallOrNewEmitter::ArgumentsKind::SingleSpread + : CallOrNewEmitter::ArgumentsKind::Other, + valueUsage); + + if (!emitCalleeAndThis(calleeNode, callNode, cone)) { + // [stack] CALLEE THIS + return false; + } + if (!emitArguments(argsList, isCall, isSpread, cone)) { + // [stack] CALLEE THIS ARGS... + return false; + } + + // Push new.target for construct calls. + if (IsConstructOp(op)) { + if (op == JSOp::SuperCall || op == JSOp::SpreadSuperCall) { + if (!emitNewTarget(callNode)) { + // [stack] CALLEE THIS ARGS.. NEW.TARGET + return false; + } + } else { + // Repush the callee as new.target + uint32_t effectiveArgc = isSpread ? 1 : argc; + if (!emitDupAt(effectiveArgc + 1)) { + // [stack] CALLEE THIS ARGS.. CALLEE + return false; + } + } + } + + ParseNode* coordNode = getCoordNode(callNode, calleeNode, op, argsList); + + if (!cone.emitEnd(argc, coordNode->pn_pos.begin)) { + // [stack] RVAL + return false; + } + + return true; +} + +// This list must be kept in the same order in several places: +// - The binary operators in ParseNode.h , +// - the binary operators in TokenKind.h +// - the precedence list in Parser.cpp +static const JSOp ParseNodeKindToJSOp[] = { + // Some binary ops require special code generation (PrivateIn); + // these should not use BinaryOpParseNodeKindToJSOp. This table fills those + // slots with Nops to make the rest of the table lookup work. + JSOp::Coalesce, JSOp::Or, JSOp::And, JSOp::BitOr, JSOp::BitXor, + JSOp::BitAnd, JSOp::StrictEq, JSOp::Eq, JSOp::StrictNe, JSOp::Ne, + JSOp::Lt, JSOp::Le, JSOp::Gt, JSOp::Ge, JSOp::Instanceof, + JSOp::In, JSOp::Nop, JSOp::Lsh, JSOp::Rsh, JSOp::Ursh, + JSOp::Add, JSOp::Sub, JSOp::Mul, JSOp::Div, JSOp::Mod, + JSOp::Pow}; + +static inline JSOp BinaryOpParseNodeKindToJSOp(ParseNodeKind pnk) { + MOZ_ASSERT(pnk >= ParseNodeKind::BinOpFirst); + MOZ_ASSERT(pnk <= ParseNodeKind::BinOpLast); + int parseNodeFirst = size_t(ParseNodeKind::BinOpFirst); +#ifdef DEBUG + int jsopArraySize = std::size(ParseNodeKindToJSOp); + int parseNodeKindListSize = + size_t(ParseNodeKind::BinOpLast) - parseNodeFirst + 1; + MOZ_ASSERT(jsopArraySize == parseNodeKindListSize); + // Ensure we don't use this to find an op for a parse node + // requiring special emission rules. + MOZ_ASSERT(ParseNodeKindToJSOp[size_t(pnk) - parseNodeFirst] != JSOp::Nop); +#endif + return ParseNodeKindToJSOp[size_t(pnk) - parseNodeFirst]; +} + +bool BytecodeEmitter::emitRightAssociative(ListNode* node) { + // ** is the only right-associative operator. + MOZ_ASSERT(node->isKind(ParseNodeKind::PowExpr)); + + // Right-associative operator chain. + for (ParseNode* subexpr : node->contents()) { + if (!emitTree(subexpr)) { + return false; + } + } + for (uint32_t i = 0; i < node->count() - 1; i++) { + if (!emit1(JSOp::Pow)) { + return false; + } + } + return true; +} + +bool BytecodeEmitter::emitLeftAssociative(ListNode* node) { + // Left-associative operator chain. + if (!emitTree(node->head())) { + return false; + } + JSOp op = BinaryOpParseNodeKindToJSOp(node->getKind()); + ParseNode* nextExpr = node->head()->pn_next; + do { + if (!emitTree(nextExpr)) { + return false; + } + if (!emit1(op)) { + return false; + } + } while ((nextExpr = nextExpr->pn_next)); + return true; +} + +bool BytecodeEmitter::emitPrivateInExpr(ListNode* node) { + MOZ_ASSERT(node->head()->isKind(ParseNodeKind::PrivateName)); + + NameNode& privateNameNode = node->head()->as(); + TaggedParserAtomIndex privateName = privateNameNode.name(); + + PrivateOpEmitter xoe(this, PrivateOpEmitter::Kind::ErgonomicBrandCheck, + privateName); + + ParseNode* valueNode = node->head()->pn_next; + MOZ_ASSERT(valueNode->pn_next == nullptr); + + if (!emitTree(valueNode)) { + // [stack] OBJ + return false; + } + + if (!xoe.emitReference()) { + // [stack] OBJ BRAND if private method + // [stack] OBJ NAME if private field or accessor. + return false; + } + + if (!xoe.emitBrandCheck()) { + // [stack] OBJ BRAND BOOL if private method + // [stack] OBJ NAME BOOL if private field or accessor. + return false; + } + + if (!emitUnpickN(2)) { + // [stack] BOOL OBJ BRAND if private method + // [stack] BOOL OBJ NAME if private field or accessor. + return false; + } + + if (!emitPopN(2)) { + // [stack] BOOL + return false; + } + + return true; +} + +/* + * Special `emitTree` for Optional Chaining case. + * Examples of this are `emitOptionalChain`, `emitDeleteOptionalChain` and + * `emitCalleeAndThisForOptionalChain`. + */ +bool BytecodeEmitter::emitOptionalTree( + ParseNode* pn, OptionalEmitter& oe, + ValueUsage valueUsage /* = ValueUsage::WantValue */) { + AutoCheckRecursionLimit recursion(fc); + if (!recursion.check(fc)) { + return false; + } + ParseNodeKind kind = pn->getKind(); + switch (kind) { + case ParseNodeKind::OptionalDotExpr: { + OptionalPropertyAccess* prop = &pn->as(); + bool isSuper = false; + PropOpEmitter poe(this, PropOpEmitter::Kind::Get, + PropOpEmitter::ObjKind::Other); + if (!emitOptionalDotExpression(prop, poe, isSuper, oe)) { + return false; + } + break; + } + case ParseNodeKind::DotExpr: { + PropertyAccess* prop = &pn->as(); + bool isSuper = prop->isSuper(); + PropOpEmitter poe(this, PropOpEmitter::Kind::Get, + isSuper ? PropOpEmitter::ObjKind::Super + : PropOpEmitter::ObjKind::Other); + if (!emitOptionalDotExpression(prop, poe, isSuper, oe)) { + return false; + } + break; + } + + case ParseNodeKind::OptionalElemExpr: { + OptionalPropertyByValue* elem = &pn->as(); + bool isSuper = false; + MOZ_ASSERT(!elem->key().isKind(ParseNodeKind::PrivateName)); + ElemOpEmitter eoe(this, ElemOpEmitter::Kind::Get, + ElemOpEmitter::ObjKind::Other); + + if (!emitOptionalElemExpression(elem, eoe, isSuper, oe)) { + return false; + } + break; + } + case ParseNodeKind::ElemExpr: { + PropertyByValue* elem = &pn->as(); + bool isSuper = elem->isSuper(); + MOZ_ASSERT(!elem->key().isKind(ParseNodeKind::PrivateName)); + ElemOpEmitter eoe(this, ElemOpEmitter::Kind::Get, + isSuper ? ElemOpEmitter::ObjKind::Super + : ElemOpEmitter::ObjKind::Other); + + if (!emitOptionalElemExpression(elem, eoe, isSuper, oe)) { + return false; + } + break; + } + case ParseNodeKind::PrivateMemberExpr: + case ParseNodeKind::OptionalPrivateMemberExpr: { + PrivateMemberAccessBase* privateExpr = &pn->as(); + PrivateOpEmitter xoe(this, PrivateOpEmitter::Kind::Get, + privateExpr->privateName().name()); + if (!emitOptionalPrivateExpression(privateExpr, xoe, oe)) { + return false; + } + break; + } + case ParseNodeKind::CallExpr: + case ParseNodeKind::OptionalCallExpr: + if (!emitOptionalCall(&pn->as(), oe, valueUsage)) { + return false; + } + break; + // List of accepted ParseNodeKinds that might appear only at the beginning + // of an Optional Chain. + // For example, a taggedTemplateExpr node might occur if we have + // `test`?.b, with `test` as the taggedTemplateExpr ParseNode. + default: +#ifdef DEBUG + // https://tc39.es/ecma262/#sec-primary-expression + bool isPrimaryExpression = + kind == ParseNodeKind::ThisExpr || kind == ParseNodeKind::Name || + kind == ParseNodeKind::PrivateName || + kind == ParseNodeKind::NullExpr || kind == ParseNodeKind::TrueExpr || + kind == ParseNodeKind::FalseExpr || + kind == ParseNodeKind::NumberExpr || + kind == ParseNodeKind::BigIntExpr || + kind == ParseNodeKind::StringExpr || + kind == ParseNodeKind::ArrayExpr || + kind == ParseNodeKind::ObjectExpr || + kind == ParseNodeKind::Function || kind == ParseNodeKind::ClassDecl || + kind == ParseNodeKind::RegExpExpr || + kind == ParseNodeKind::TemplateStringExpr || + kind == ParseNodeKind::TemplateStringListExpr || + kind == ParseNodeKind::RawUndefinedExpr || pn->isInParens(); + + // https://tc39.es/ecma262/#sec-left-hand-side-expressions + bool isMemberExpression = isPrimaryExpression || + kind == ParseNodeKind::TaggedTemplateExpr || + kind == ParseNodeKind::NewExpr || + kind == ParseNodeKind::NewTargetExpr || + kind == ParseNodeKind::ImportMetaExpr; + + bool isCallExpression = kind == ParseNodeKind::SetThis || + kind == ParseNodeKind::CallImportExpr; + + MOZ_ASSERT(isMemberExpression || isCallExpression, + "Unknown ParseNodeKind for OptionalChain"); +#endif + return emitTree(pn); + } + return true; +} + +// Handle the case of a call made on a OptionalChainParseNode. +// For example `(a?.b)()` and `(a?.b)?.()`. +bool BytecodeEmitter::emitCalleeAndThisForOptionalChain( + UnaryNode* optionalChain, CallNode* callNode, CallOrNewEmitter& cone) { + ParseNode* calleeNode = optionalChain->kid(); + + // Create a new OptionalEmitter, in order to emit the right bytecode + // in isolation. + OptionalEmitter oe(this, bytecodeSection().stackDepth()); + + if (!emitOptionalCalleeAndThis(calleeNode, callNode, cone, oe)) { + // [stack] CALLEE THIS + return false; + } + + // complete the jump if necessary. This will set both the "this" value + // and the "callee" value to undefined, if the callee is undefined. It + // does not matter much what the this value is, the function call will + // fail if it is not optional, and be set to undefined otherwise. + if (!oe.emitOptionalJumpTarget(JSOp::Undefined, + OptionalEmitter::Kind::Reference)) { + // [stack] # If shortcircuit + // [stack] UNDEFINED UNDEFINED + // [stack] # otherwise + // [stack] CALLEE THIS + return false; + } + return true; +} + +bool BytecodeEmitter::emitOptionalChain(UnaryNode* optionalChain, + ValueUsage valueUsage) { + ParseNode* expr = optionalChain->kid(); + + OptionalEmitter oe(this, bytecodeSection().stackDepth()); + + if (!emitOptionalTree(expr, oe, valueUsage)) { + // [stack] VAL + return false; + } + + if (!oe.emitOptionalJumpTarget(JSOp::Undefined)) { + // [stack] # If shortcircuit + // [stack] UNDEFINED + // [stack] # otherwise + // [stack] VAL + return false; + } + + return true; +} + +bool BytecodeEmitter::emitOptionalDotExpression(PropertyAccessBase* prop, + PropOpEmitter& poe, + bool isSuper, + OptionalEmitter& oe) { + if (!poe.prepareForObj()) { + // [stack] + return false; + } + + if (isSuper) { + UnaryNode* base = &prop->expression().as(); + if (!emitGetThisForSuperBase(base)) { + // [stack] OBJ + return false; + } + } else { + if (!emitOptionalTree(&prop->expression(), oe)) { + // [stack] OBJ + return false; + } + } + + if (prop->isKind(ParseNodeKind::OptionalDotExpr)) { + MOZ_ASSERT(!isSuper); + if (!oe.emitJumpShortCircuit()) { + // [stack] # if Jump + // [stack] UNDEFINED-OR-NULL + // [stack] # otherwise + // [stack] OBJ + return false; + } + } + + if (!poe.emitGet(prop->key().atom())) { + // [stack] PROP + return false; + } + + return true; +} + +bool BytecodeEmitter::emitOptionalElemExpression(PropertyByValueBase* elem, + ElemOpEmitter& eoe, + bool isSuper, + OptionalEmitter& oe) { + if (!eoe.prepareForObj()) { + // [stack] + return false; + } + + if (isSuper) { + UnaryNode* base = &elem->expression().as(); + if (!emitGetThisForSuperBase(base)) { + // [stack] OBJ + return false; + } + } else { + if (!emitOptionalTree(&elem->expression(), oe)) { + // [stack] OBJ + return false; + } + } + + if (elem->isKind(ParseNodeKind::OptionalElemExpr)) { + MOZ_ASSERT(!isSuper); + if (!oe.emitJumpShortCircuit()) { + // [stack] # if Jump + // [stack] UNDEFINED-OR-NULL + // [stack] # otherwise + // [stack] OBJ + return false; + } + } + + if (!eoe.prepareForKey()) { + // [stack] OBJ? OBJ + return false; + } + + if (!emitTree(&elem->key())) { + // [stack] OBJ? OBJ KEY + return false; + } + + if (!eoe.emitGet()) { + // [stack] ELEM + return false; + } + + return true; +} + +bool BytecodeEmitter::emitOptionalPrivateExpression( + PrivateMemberAccessBase* privateExpr, PrivateOpEmitter& xoe, + OptionalEmitter& oe) { + if (!emitOptionalTree(&privateExpr->expression(), oe)) { + // [stack] OBJ + return false; + } + + if (privateExpr->isKind(ParseNodeKind::OptionalPrivateMemberExpr)) { + if (!oe.emitJumpShortCircuit()) { + // [stack] # if Jump + // [stack] UNDEFINED-OR-NULL + // [stack] # otherwise + // [stack] OBJ + return false; + } + } + + if (!xoe.emitReference()) { + // [stack] OBJ NAME + return false; + } + if (!xoe.emitGet()) { + // [stack] CALLEE THIS # if call + // [stack] VALUE # otherwise + return false; + } + + return true; +} + +bool BytecodeEmitter::emitShortCircuit(ListNode* node, ValueUsage valueUsage) { + MOZ_ASSERT(node->isKind(ParseNodeKind::OrExpr) || + node->isKind(ParseNodeKind::CoalesceExpr) || + node->isKind(ParseNodeKind::AndExpr)); + + /* + * JSOp::Or converts the operand on the stack to boolean, leaves the original + * value on the stack and jumps if true; otherwise it falls into the next + * bytecode, which pops the left operand and then evaluates the right operand. + * The jump goes around the right operand evaluation. + * + * JSOp::And converts the operand on the stack to boolean and jumps if false; + * otherwise it falls into the right operand's bytecode. + */ + + TDZCheckCache tdzCache(this); + + JSOp op; + switch (node->getKind()) { + case ParseNodeKind::OrExpr: + op = JSOp::Or; + break; + case ParseNodeKind::CoalesceExpr: + op = JSOp::Coalesce; + break; + case ParseNodeKind::AndExpr: + op = JSOp::And; + break; + default: + MOZ_CRASH("Unexpected ParseNodeKind"); + } + + JumpList jump; + + // Left-associative operator chain: avoid too much recursion. + // + // Emit all nodes but the last. + for (ParseNode* expr : node->contentsTo(node->last())) { + if (!emitTree(expr)) { + return false; + } + if (!emitJump(op, &jump)) { + return false; + } + if (!emit1(JSOp::Pop)) { + return false; + } + } + + // Emit the last node + if (!emitTree(node->last(), valueUsage)) { + return false; + } + + if (!emitJumpTargetAndPatch(jump)) { + return false; + } + return true; +} + +bool BytecodeEmitter::emitSequenceExpr(ListNode* node, ValueUsage valueUsage) { + for (ParseNode* child : node->contentsTo(node->last())) { + if (!updateSourceCoordNotes(child->pn_pos.begin)) { + return false; + } + if (!emitTree(child, ValueUsage::IgnoreValue)) { + return false; + } + if (!emit1(JSOp::Pop)) { + return false; + } + } + + ParseNode* child = node->last(); + if (!updateSourceCoordNotes(child->pn_pos.begin)) { + return false; + } + if (!emitTree(child, valueUsage)) { + return false; + } + return true; +} + +// Using MOZ_NEVER_INLINE in here is a workaround for llvm.org/pr14047. See +// the comment on emitSwitch. +MOZ_NEVER_INLINE bool BytecodeEmitter::emitIncOrDec(UnaryNode* incDec, + ValueUsage valueUsage) { + switch (incDec->kid()->getKind()) { + case ParseNodeKind::DotExpr: + return emitPropIncDec(incDec, valueUsage); + case ParseNodeKind::ElemExpr: + return emitElemIncDec(incDec, valueUsage); + case ParseNodeKind::PrivateMemberExpr: + return emitPrivateIncDec(incDec, valueUsage); + case ParseNodeKind::CallExpr: + return emitCallIncDec(incDec); + default: + return emitNameIncDec(incDec, valueUsage); + } +} + +// Using MOZ_NEVER_INLINE in here is a workaround for llvm.org/pr14047. See +// the comment on emitSwitch. +MOZ_NEVER_INLINE bool BytecodeEmitter::emitLabeledStatement( + const LabeledStatement* labeledStmt) { + auto name = labeledStmt->label(); + LabelEmitter label(this); + + label.emitLabel(name); + + if (!emitTree(labeledStmt->statement())) { + return false; + } + if (!label.emitEnd()) { + return false; + } + + return true; +} + +bool BytecodeEmitter::emitConditionalExpression( + ConditionalExpression& conditional, ValueUsage valueUsage) { + CondEmitter cond(this); + if (!cond.emitCond()) { + return false; + } + + ParseNode* conditionNode = &conditional.condition(); + auto conditionKind = IfEmitter::ConditionKind::Positive; + if (conditionNode->isKind(ParseNodeKind::NotExpr)) { + conditionNode = conditionNode->as().kid(); + conditionKind = IfEmitter::ConditionKind::Negative; + } + + // NOTE: NotExpr of conditionNode may be unwrapped, and in that case the + // negation is handled by conditionKind. + if (!emitTree(conditionNode)) { + return false; + } + + if (!cond.emitThenElse(conditionKind)) { + return false; + } + + if (!emitTree(&conditional.thenExpression(), valueUsage)) { + return false; + } + + if (!cond.emitElse()) { + return false; + } + + if (!emitTree(&conditional.elseExpression(), valueUsage)) { + return false; + } + + if (!cond.emitEnd()) { + return false; + } + MOZ_ASSERT(cond.pushed() == 1); + + return true; +} + +// Check for an object-literal property list that can be handled by the +// ObjLiteral writer. We ensure that for each `prop: value` pair, the key is a +// constant name or numeric index, there is no accessor specified, and the value +// can be encoded by an ObjLiteral instruction (constant number, string, +// boolean, null/undefined). +void BytecodeEmitter::isPropertyListObjLiteralCompatible(ListNode* obj, + bool* withValues, + bool* withoutValues) { + bool keysOK = true; + bool valuesOK = true; + uint32_t propCount = 0; + + for (ParseNode* propdef : obj->contents()) { + if (!propdef->is()) { + keysOK = false; + break; + } + propCount++; + + BinaryNode* prop = &propdef->as(); + ParseNode* key = prop->left(); + ParseNode* value = prop->right(); + + // Computed keys not OK (ObjLiteral data stores constant keys). + if (key->isKind(ParseNodeKind::ComputedName)) { + keysOK = false; + break; + } + + // BigIntExprs should have been lowered to computed names at parse + // time, and so should be excluded above. + MOZ_ASSERT(!key->isKind(ParseNodeKind::BigIntExpr)); + + // Numeric keys OK as long as they are integers and in range. + if (key->isKind(ParseNodeKind::NumberExpr)) { + double numValue = key->as().value(); + int32_t i = 0; + if (!NumberIsInt32(numValue, &i)) { + keysOK = false; + break; + } + if (!ObjLiteralWriter::arrayIndexInRange(i)) { + keysOK = false; + break; + } + } + + MOZ_ASSERT(key->isKind(ParseNodeKind::ObjectPropertyName) || + key->isKind(ParseNodeKind::StringExpr) || + key->isKind(ParseNodeKind::NumberExpr)); + + AccessorType accessorType = + prop->is() + ? prop->as().accessorType() + : AccessorType::None; + if (accessorType != AccessorType::None) { + keysOK = false; + break; + } + + if (!isRHSObjLiteralCompatible(value)) { + valuesOK = false; + } + } + + if (propCount > SharedPropMap::MaxPropsForNonDictionary) { + // JSOp::NewObject cannot accept dictionary-mode objects. + keysOK = false; + } + + *withValues = keysOK && valuesOK; + *withoutValues = keysOK; +} + +bool BytecodeEmitter::isArrayObjLiteralCompatible(ListNode* array) { + for (ParseNode* elem : array->contents()) { + if (elem->isKind(ParseNodeKind::Spread)) { + return false; + } + if (!isRHSObjLiteralCompatible(elem)) { + return false; + } + } + return true; +} + +bool BytecodeEmitter::emitPropertyList(ListNode* obj, PropertyEmitter& pe, + PropListType type) { + // [stack] CTOR? OBJ + + size_t curFieldKeyIndex = 0; + size_t curStaticFieldKeyIndex = 0; + for (ParseNode* propdef : obj->contents()) { + if (propdef->is()) { + MOZ_ASSERT(type == ClassBody); + // Only handle computing field keys here: the .initializers lambda array + // is created elsewhere. + ClassField* field = &propdef->as(); + if (field->name().getKind() == ParseNodeKind::ComputedName) { + auto fieldKeys = + field->isStatic() + ? TaggedParserAtomIndex::WellKnown::dotStaticFieldKeys() + : TaggedParserAtomIndex::WellKnown::dotFieldKeys(); + if (!emitGetName(fieldKeys)) { + // [stack] CTOR OBJ ARRAY + return false; + } + + ParseNode* nameExpr = field->name().as().kid(); + + if (!emitTree(nameExpr, ValueUsage::WantValue)) { + // [stack] CTOR OBJ ARRAY KEY + return false; + } + + if (!emit1(JSOp::ToPropertyKey)) { + // [stack] CTOR OBJ ARRAY KEY + return false; + } + + size_t fieldKeysIndex; + if (field->isStatic()) { + fieldKeysIndex = curStaticFieldKeyIndex++; + } else { + fieldKeysIndex = curFieldKeyIndex++; + } + + if (!emitUint32Operand(JSOp::InitElemArray, fieldKeysIndex)) { + // [stack] CTOR OBJ ARRAY + return false; + } + + if (!emit1(JSOp::Pop)) { + // [stack] CTOR OBJ + return false; + } + } + continue; + } + + if (propdef->isKind(ParseNodeKind::StaticClassBlock)) { + // Static class blocks are emitted as part of + // emitCreateMemberInitializers. + continue; + } + + if (propdef->is()) { + // Constructors are sometimes wrapped in LexicalScopeNodes. As we + // already handled emitting the constructor, skip it. + MOZ_ASSERT( + propdef->as().scopeBody()->is()); + continue; + } + + // Handle __proto__: v specially because *only* this form, and no other + // involving "__proto__", performs [[Prototype]] mutation. + if (propdef->isKind(ParseNodeKind::MutateProto)) { + // [stack] OBJ + MOZ_ASSERT(type == ObjectLiteral); + if (!pe.prepareForProtoValue(propdef->pn_pos.begin)) { + // [stack] OBJ + return false; + } + if (!emitTree(propdef->as().kid())) { + // [stack] OBJ PROTO + return false; + } + if (!pe.emitMutateProto()) { + // [stack] OBJ + return false; + } + continue; + } + + if (propdef->isKind(ParseNodeKind::Spread)) { + MOZ_ASSERT(type == ObjectLiteral); + // [stack] OBJ + if (!pe.prepareForSpreadOperand(propdef->pn_pos.begin)) { + // [stack] OBJ OBJ + return false; + } + if (!emitTree(propdef->as().kid())) { + // [stack] OBJ OBJ VAL + return false; + } + if (!pe.emitSpread()) { + // [stack] OBJ + return false; + } + continue; + } + + BinaryNode* prop = &propdef->as(); + + ParseNode* key = prop->left(); + AccessorType accessorType; + if (prop->is()) { + ClassMethod& method = prop->as(); + accessorType = method.accessorType(); + + if (!method.isStatic() && key->isKind(ParseNodeKind::PrivateName) && + accessorType != AccessorType::None) { + // Private non-static accessors are stamped onto instances from + // initializers; see emitCreateMemberInitializers. + continue; + } + } else if (prop->is()) { + accessorType = prop->as().accessorType(); + } else { + accessorType = AccessorType::None; + } + + auto emitValue = [this, &key, &prop, accessorType, &pe]() { + // [stack] CTOR? OBJ CTOR? KEY? + + ParseNode* propVal = prop->right(); + if (propVal->isDirectRHSAnonFunction()) { + // The following branches except for the last `else` clause emit the + // cases handled in NameResolver::resolveFun (see NameFunctions.cpp) + if (key->isKind(ParseNodeKind::ObjectPropertyName) || + key->isKind(ParseNodeKind::StringExpr)) { + auto keyAtom = key->as().atom(); + if (!emitAnonymousFunctionWithName(propVal, keyAtom)) { + // [stack] CTOR? OBJ CTOR? VAL + return false; + } + } else if (key->isKind(ParseNodeKind::NumberExpr)) { + MOZ_ASSERT(accessorType == AccessorType::None); + + auto keyAtom = key->as().toAtom(fc, parserAtoms()); + if (!keyAtom) { + return false; + } + if (!emitAnonymousFunctionWithName(propVal, keyAtom)) { + // [stack] CTOR? OBJ CTOR? KEY VAL + return false; + } + } else if (key->isKind(ParseNodeKind::ComputedName) && + (key->as().kid()->isKind( + ParseNodeKind::NumberExpr) || + key->as().kid()->isKind( + ParseNodeKind::StringExpr)) && + accessorType == AccessorType::None) { + ParseNode* keyKid = key->as().kid(); + if (keyKid->isKind(ParseNodeKind::NumberExpr)) { + auto keyAtom = + keyKid->as().toAtom(fc, parserAtoms()); + if (!keyAtom) { + return false; + } + if (!emitAnonymousFunctionWithName(propVal, keyAtom)) { + // [stack] CTOR? OBJ CTOR? KEY VAL + return false; + } + } else { + MOZ_ASSERT(keyKid->isKind(ParseNodeKind::StringExpr)); + auto keyAtom = keyKid->as().atom(); + if (!emitAnonymousFunctionWithName(propVal, keyAtom)) { + // [stack] CTOR? OBJ CTOR? KEY VAL + return false; + } + } + } else { + // Either a proper computed property name or a synthetic computed + // property name for BigInt keys. + MOZ_ASSERT(key->isKind(ParseNodeKind::ComputedName)); + + FunctionPrefixKind prefix = + accessorType == AccessorType::None ? FunctionPrefixKind::None + : accessorType == AccessorType::Getter ? FunctionPrefixKind::Get + : FunctionPrefixKind::Set; + + if (!emitAnonymousFunctionWithComputedName(propVal, prefix)) { + // [stack] CTOR? OBJ CTOR? KEY VAL + return false; + } + } + } else { + if (!emitTree(propVal)) { + // [stack] CTOR? OBJ CTOR? KEY? VAL + return false; + } + } + + if (propVal->is() && + propVal->as().funbox()->needsHomeObject()) { + if (!pe.emitInitHomeObject()) { + // [stack] CTOR? OBJ CTOR? KEY? FUN + return false; + } + } + +#ifdef ENABLE_DECORATORS + if (prop->is()) { + ClassMethod& method = prop->as(); + if (method.decorators() && !method.decorators()->empty()) { + DecoratorEmitter::Kind kind; + switch (method.accessorType()) { + case AccessorType::Getter: + kind = DecoratorEmitter::Getter; + break; + case AccessorType::Setter: + kind = DecoratorEmitter::Setter; + break; + case AccessorType::None: + kind = DecoratorEmitter::Method; + break; + } + + // The decorators are applied to the current value on the stack, + // possibly replacing it. + DecoratorEmitter de(this); + if (!de.emitApplyDecoratorsToElementDefinition( + kind, key, method.decorators(), method.isStatic())) { + // [stack] CTOR? OBJ CTOR? KEY? VAL + return false; + } + } + } +#endif + + return true; + }; + + PropertyEmitter::Kind kind = + (type == ClassBody && propdef->as().isStatic()) + ? PropertyEmitter::Kind::Static + : PropertyEmitter::Kind::Prototype; + if (key->isKind(ParseNodeKind::ObjectPropertyName) || + key->isKind(ParseNodeKind::StringExpr)) { + // [stack] CTOR? OBJ + + auto keyAtom = key->as().atom(); + + // emitClass took care of constructor already. + if (type == ClassBody && + keyAtom == TaggedParserAtomIndex::WellKnown::constructor() && + !propdef->as().isStatic()) { + continue; + } + + if (!pe.prepareForPropValue(propdef->pn_pos.begin, kind)) { + // [stack] CTOR? OBJ CTOR? + return false; + } + + if (!emitValue()) { + // [stack] CTOR? OBJ CTOR? VAL + return false; + } + + if (!pe.emitInit(accessorType, keyAtom)) { + // [stack] CTOR? OBJ + return false; + } + + continue; + } + + if (key->isKind(ParseNodeKind::NumberExpr)) { + // [stack] CTOR? OBJ + if (!pe.prepareForIndexPropKey(propdef->pn_pos.begin, kind)) { + // [stack] CTOR? OBJ CTOR? + return false; + } + if (!emitNumberOp(key->as().value())) { + // [stack] CTOR? OBJ CTOR? KEY + return false; + } + if (!pe.prepareForIndexPropValue()) { + // [stack] CTOR? OBJ CTOR? KEY + return false; + } + if (!emitValue()) { + // [stack] CTOR? OBJ CTOR? KEY VAL + return false; + } + + if (!pe.emitInitIndexOrComputed(accessorType)) { + // [stack] CTOR? OBJ + return false; + } + + continue; + } + + if (key->isKind(ParseNodeKind::ComputedName)) { + // Either a proper computed property name or a synthetic computed property + // name for BigInt keys. + + // [stack] CTOR? OBJ + + if (!pe.prepareForComputedPropKey(propdef->pn_pos.begin, kind)) { + // [stack] CTOR? OBJ CTOR? + return false; + } + if (!emitTree(key->as().kid())) { + // [stack] CTOR? OBJ CTOR? KEY + return false; + } + if (!pe.prepareForComputedPropValue()) { + // [stack] CTOR? OBJ CTOR? KEY + return false; + } + if (!emitValue()) { + // [stack] CTOR? OBJ CTOR? KEY VAL + return false; + } + + if (!pe.emitInitIndexOrComputed(accessorType)) { + // [stack] CTOR? OBJ + return false; + } + + continue; + } + + MOZ_ASSERT(key->isKind(ParseNodeKind::PrivateName)); + MOZ_ASSERT(type == ClassBody); + + auto* privateName = &key->as(); + + if (kind == PropertyEmitter::Kind::Prototype) { + MOZ_ASSERT(accessorType == AccessorType::None); + if (!pe.prepareForPrivateMethod()) { + // [stack] CTOR OBJ + return false; + } + NameOpEmitter noe(this, privateName->atom(), + NameOpEmitter::Kind::SimpleAssignment); + + // Ensure the NameOp emitter doesn't push an environment onto the stack, + // because that would change the stack location of the home object. + MOZ_ASSERT(noe.loc().kind() == NameLocation::Kind::FrameSlot || + noe.loc().kind() == NameLocation::Kind::EnvironmentCoordinate); + + if (!noe.prepareForRhs()) { + // [stack] CTOR OBJ + return false; + } + if (!emitValue()) { + // [stack] CTOR OBJ METHOD + return false; + } + if (!noe.emitAssignment()) { + // [stack] CTOR OBJ METHOD + return false; + } + if (!emit1(JSOp::Pop)) { + // [stack] CTOR OBJ + return false; + } + if (!pe.skipInit()) { + // [stack] CTOR OBJ + return false; + } + continue; + } + + MOZ_ASSERT(kind == PropertyEmitter::Kind::Static); + + // [stack] CTOR OBJ + + if (!pe.prepareForPrivateStaticMethod(propdef->pn_pos.begin)) { + // [stack] CTOR OBJ CTOR + return false; + } + if (!emitGetPrivateName(privateName)) { + // [stack] CTOR OBJ CTOR KEY + return false; + } + if (!emitValue()) { + // [stack] CTOR OBJ CTOR KEY VAL + return false; + } + + if (!pe.emitPrivateStaticMethod(accessorType)) { + // [stack] CTOR OBJ + return false; + } + + if (privateName->privateNameKind() == PrivateNameKind::Setter) { + if (!emitDupAt(1)) { + // [stack] CTOR OBJ CTOR + return false; + } + if (!emitGetPrivateName(privateName)) { + // [stack] CTOR OBJ CTOR NAME + return false; + } + if (!emitAtomOp(JSOp::GetIntrinsic, + TaggedParserAtomIndex::WellKnown::NoPrivateGetter())) { + // [stack] CTOR OBJ CTOR NAME FUN + return false; + } + if (!emit1(JSOp::InitHiddenElemGetter)) { + // [stack] CTOR OBJ CTOR + return false; + } + if (!emit1(JSOp::Pop)) { + // [stack] CTOR OBJ + return false; + } + } + } + + return true; +} + +bool BytecodeEmitter::emitPropertyListObjLiteral(ListNode* obj, JSOp op, + bool useObjLiteralValues) { + ObjLiteralWriter writer; + +#ifdef DEBUG + // In self-hosted JS, we check duplication only on debug build. + mozilla::Maybe> + selfHostedPropNames; + if (emitterMode == BytecodeEmitter::SelfHosting) { + selfHostedPropNames.emplace(); + } +#endif + + if (op == JSOp::Object) { + writer.beginObject(op); + } else { + MOZ_ASSERT(op == JSOp::NewObject); + writer.beginShape(op); + } + + for (ParseNode* propdef : obj->contents()) { + BinaryNode* prop = &propdef->as(); + ParseNode* key = prop->left(); + + if (key->is()) { + if (emitterMode == BytecodeEmitter::SelfHosting) { + auto propName = key->as().atom(); +#ifdef DEBUG + // Self-hosted JS shouldn't contain duplicate properties. + auto p = selfHostedPropNames->lookupForAdd(propName); + MOZ_ASSERT(!p); + if (!selfHostedPropNames->add(p, propName)) { + js::ReportOutOfMemory(fc); + return false; + } +#endif + writer.setPropNameNoDuplicateCheck(parserAtoms(), propName); + } else { + if (!writer.setPropName(parserAtoms(), key->as().atom())) { + return false; + } + } + } else { + double numValue = key->as().value(); + int32_t i = 0; + DebugOnly numIsInt = + NumberIsInt32(numValue, &i); // checked previously. + MOZ_ASSERT(numIsInt); + MOZ_ASSERT( + ObjLiteralWriter::arrayIndexInRange(i)); // checked previously. + + // Ignore indexed properties if we're not storing property values, and + // rely on InitElem ops to define those. These properties will be either + // dense elements (not possible to represent in the literal's shape) or + // sparse elements (enumerated separately, so this doesn't affect property + // iteration order). + if (!useObjLiteralValues) { + continue; + } + + writer.setPropIndex(i); + } + + if (useObjLiteralValues) { + MOZ_ASSERT(op == JSOp::Object); + ParseNode* value = prop->right(); + if (!emitObjLiteralValue(writer, value)) { + return false; + } + } else { + if (!writer.propWithUndefinedValue(fc)) { + return false; + } + } + } + + GCThingIndex index; + if (!addObjLiteralData(writer, &index)) { + return false; + } + + // JSOp::Object may only be used by (top-level) run-once scripts. + MOZ_ASSERT_IF(op == JSOp::Object, + sc->isTopLevelContext() && sc->treatAsRunOnce()); + + if (!emitGCIndexOp(op, index)) { + // [stack] OBJ + return false; + } + + return true; +} + +bool BytecodeEmitter::emitDestructuringRestExclusionSetObjLiteral( + ListNode* pattern) { + // Note: if we want to squeeze out a little more performance, we could switch + // to the `JSOp::Object` opcode, because the exclusion set object is never + // exposed to the user, so it's safe to bake the object into the bytecode. + constexpr JSOp op = JSOp::NewObject; + + ObjLiteralWriter writer; + writer.beginShape(op); + + for (ParseNode* member : pattern->contents()) { + if (member->isKind(ParseNodeKind::Spread)) { + MOZ_ASSERT(!member->pn_next, "unexpected trailing element after spread"); + break; + } + + TaggedParserAtomIndex atom; + if (member->isKind(ParseNodeKind::MutateProto)) { + atom = TaggedParserAtomIndex::WellKnown::proto(); + } else { + ParseNode* key = member->as().left(); + atom = key->as().atom(); + } + + if (!writer.setPropName(parserAtoms(), atom)) { + return false; + } + + if (!writer.propWithUndefinedValue(fc)) { + return false; + } + } + + GCThingIndex index; + if (!addObjLiteralData(writer, &index)) { + return false; + } + + if (!emitGCIndexOp(op, index)) { + // [stack] OBJ + return false; + } + + return true; +} + +bool BytecodeEmitter::emitObjLiteralArray(ListNode* array) { + MOZ_ASSERT(checkSingletonContext()); + + constexpr JSOp op = JSOp::Object; + + ObjLiteralWriter writer; + writer.beginArray(op); + + writer.beginDenseArrayElements(); + for (ParseNode* elem : array->contents()) { + if (!emitObjLiteralValue(writer, elem)) { + return false; + } + } + + GCThingIndex index; + if (!addObjLiteralData(writer, &index)) { + return false; + } + + if (!emitGCIndexOp(op, index)) { + // [stack] OBJ + return false; + } + + return true; +} + +bool BytecodeEmitter::isRHSObjLiteralCompatible(ParseNode* value) { + return value->isKind(ParseNodeKind::NumberExpr) || + value->isKind(ParseNodeKind::TrueExpr) || + value->isKind(ParseNodeKind::FalseExpr) || + value->isKind(ParseNodeKind::NullExpr) || + value->isKind(ParseNodeKind::RawUndefinedExpr) || + value->isKind(ParseNodeKind::StringExpr) || + value->isKind(ParseNodeKind::TemplateStringExpr); +} + +bool BytecodeEmitter::emitObjLiteralValue(ObjLiteralWriter& writer, + ParseNode* value) { + MOZ_ASSERT(isRHSObjLiteralCompatible(value)); + if (value->isKind(ParseNodeKind::NumberExpr)) { + double numValue = value->as().value(); + int32_t i = 0; + js::Value v; + if (NumberIsInt32(numValue, &i)) { + v.setInt32(i); + } else { + v.setDouble(numValue); + } + if (!writer.propWithConstNumericValue(fc, v)) { + return false; + } + } else if (value->isKind(ParseNodeKind::TrueExpr)) { + if (!writer.propWithTrueValue(fc)) { + return false; + } + } else if (value->isKind(ParseNodeKind::FalseExpr)) { + if (!writer.propWithFalseValue(fc)) { + return false; + } + } else if (value->isKind(ParseNodeKind::NullExpr)) { + if (!writer.propWithNullValue(fc)) { + return false; + } + } else if (value->isKind(ParseNodeKind::RawUndefinedExpr)) { + if (!writer.propWithUndefinedValue(fc)) { + return false; + } + } else if (value->isKind(ParseNodeKind::StringExpr) || + value->isKind(ParseNodeKind::TemplateStringExpr)) { + if (!writer.propWithAtomValue(fc, parserAtoms(), + value->as().atom())) { + return false; + } + } else { + MOZ_CRASH("Unexpected parse node"); + } + return true; +} + +static bool NeedsPrivateBrand(ParseNode* member) { + return member->is() && + member->as().name().isKind(ParseNodeKind::PrivateName) && + !member->as().isStatic(); +} + +mozilla::Maybe BytecodeEmitter::setupMemberInitializers( + ListNode* classMembers, FieldPlacement placement) { + bool isStatic = placement == FieldPlacement::Static; + + size_t numFields = 0; + size_t numPrivateInitializers = 0; + bool hasPrivateBrand = false; + for (ParseNode* member : classMembers->contents()) { + if (NeedsFieldInitializer(member, isStatic)) { + numFields++; + } else if (NeedsAccessorInitializer(member, isStatic)) { + numPrivateInitializers++; + hasPrivateBrand = true; + } else if (NeedsPrivateBrand(member)) { + hasPrivateBrand = true; + } + } + + // If there are more initializers than can be represented, return invalid. + if (numFields + numPrivateInitializers > + MemberInitializers::MaxInitializers) { + return Nothing(); + } + return Some( + MemberInitializers(hasPrivateBrand, numFields + numPrivateInitializers)); +} + +// Purpose of .fieldKeys: +// Computed field names (`["x"] = 2;`) must be ran at class-evaluation time, +// not object construction time. The transformation to do so is roughly as +// follows: +// +// class C { +// [keyExpr] = valueExpr; +// } +// --> +// let .fieldKeys = [keyExpr]; +// let .initializers = [ +// () => { +// this[.fieldKeys[0]] = valueExpr; +// } +// ]; +// class C { +// constructor() { +// .initializers[0](); +// } +// } +// +// BytecodeEmitter::emitCreateFieldKeys does `let .fieldKeys = [...];` +// BytecodeEmitter::emitPropertyList fills in the elements of the array. +// See GeneralParser::fieldInitializer for the `this[.fieldKeys[0]]` part. +bool BytecodeEmitter::emitCreateFieldKeys(ListNode* obj, + FieldPlacement placement) { + bool isStatic = placement == FieldPlacement::Static; + auto isFieldWithComputedName = [isStatic](ParseNode* propdef) { + return propdef->is() && + propdef->as().isStatic() == isStatic && + propdef->as().name().getKind() == + ParseNodeKind::ComputedName; + }; + + size_t numFieldKeys = std::count_if( + obj->contents().begin(), obj->contents().end(), isFieldWithComputedName); + if (numFieldKeys == 0) { + return true; + } + + auto fieldKeys = isStatic + ? TaggedParserAtomIndex::WellKnown::dotStaticFieldKeys() + : TaggedParserAtomIndex::WellKnown::dotFieldKeys(); + NameOpEmitter noe(this, fieldKeys, NameOpEmitter::Kind::Initialize); + if (!noe.prepareForRhs()) { + return false; + } + + if (!emitUint32Operand(JSOp::NewArray, numFieldKeys)) { + // [stack] ARRAY + return false; + } + + if (!noe.emitAssignment()) { + // [stack] ARRAY + return false; + } + + if (!emit1(JSOp::Pop)) { + // [stack] + return false; + } + + return true; +} + +static bool HasInitializer(ParseNode* node, bool isStaticContext) { + return (node->is() && + node->as().isStatic() == isStaticContext) || + (isStaticContext && node->is()); +} + +static FunctionNode* GetInitializer(ParseNode* node, bool isStaticContext) { + MOZ_ASSERT(HasInitializer(node, isStaticContext)); + MOZ_ASSERT_IF(!node->is(), isStaticContext); + return node->is() ? node->as().initializer() + : node->as().function(); +} + +bool BytecodeEmitter::emitCreateMemberInitializers(ClassEmitter& ce, + ListNode* obj, + FieldPlacement placement) { + // FieldPlacement::Instance + // [stack] HOMEOBJ HERITAGE? + // + // FieldPlacement::Static + // [stack] CTOR HOMEOBJ + mozilla::Maybe memberInitializers = + setupMemberInitializers(obj, placement); + if (!memberInitializers) { + ReportAllocationOverflow(fc); + return false; + } + + size_t numInitializers = memberInitializers->numMemberInitializers; + if (numInitializers == 0) { + return true; + } + + bool isStatic = placement == FieldPlacement::Static; + if (!ce.prepareForMemberInitializers(numInitializers, isStatic)) { + // [stack] HOMEOBJ HERITAGE? ARRAY + // or: + // [stack] CTOR HOMEOBJ ARRAY + return false; + } + + // Private accessors could be used in the field initializers, so make sure + // accessor initializers appear earlier in the .initializers array so they + // run first. Static private methods are not initialized using initializers + // (emitPropertyList emits bytecode to stamp them onto the constructor), so + // skip this step if isStatic. + if (!isStatic) { + if (!emitPrivateMethodInitializers(ce, obj)) { + return false; + } + } + + for (ParseNode* propdef : obj->contents()) { + if (!HasInitializer(propdef, isStatic)) { + continue; + } + + FunctionNode* initializer = GetInitializer(propdef, isStatic); + + if (!ce.prepareForMemberInitializer()) { + return false; + } + if (!emitTree(initializer)) { + // [stack] HOMEOBJ HERITAGE? ARRAY LAMBDA + // or: + // [stack] CTOR HOMEOBJ ARRAY LAMBDA + return false; + } + if (initializer->funbox()->needsHomeObject()) { + MOZ_ASSERT(initializer->funbox()->allowSuperProperty()); + if (!ce.emitMemberInitializerHomeObject(isStatic)) { + // [stack] HOMEOBJ HERITAGE? ARRAY LAMBDA + // or: + // [stack] CTOR HOMEOBJ ARRAY LAMBDA + return false; + } + } + if (!ce.emitStoreMemberInitializer()) { + // [stack] HOMEOBJ HERITAGE? ARRAY + // or: + // [stack] CTOR HOMEOBJ ARRAY + return false; + } + } + +#ifdef ENABLE_DECORATORS + // Index to use to append new initializers returned by decorators to the array + if (!emitNumberOp(numInitializers)) { + // [stack] HOMEOBJ HERITAGE? ARRAY INDEX + // or: + // [stack] CTOR HOMEOBJ ARRAY INDEX + return false; + } + + for (ParseNode* propdef : obj->contents()) { + if (!propdef->is()) { + continue; + } + ClassField* field = &propdef->as(); + if (placement == FieldPlacement::Static && !field->isStatic()) { + continue; + } + if (field->decorators() && !field->decorators()->empty()) { + DecoratorEmitter de(this); + if (!de.emitApplyDecoratorsToFieldDefinition( + &field->name(), field->decorators(), field->isStatic())) { + // [stack] HOMEOBJ HERITAGE? ARRAY INDEX INITIALIZERS + // or: + // [stack] CTOR HOMEOBJ ARRAY INDEX INITIALIZERS + return false; + } + + if (!emit1(JSOp::InitElemInc)) { + // [stack] HOMEOBJ HERITAGE? ARRAY INDEX + // or: + // [stack] CTOR HOMEOBJ ARRAY INDEX + return false; + } + } + } + + // Pop INDEX + if (!emitPopN(1)) { + // [stack] HOMEOBJ HERITAGE? ARRAY + // or: + // [stack] CTOR HOMEOBJ ARRAY + return false; + } +#endif + + if (!ce.emitMemberInitializersEnd()) { + // [stack] HOMEOBJ HERITAGE? + // or: + // [stack] CTOR HOMEOBJ + return false; + } + + return true; +} + +static bool IsPrivateInstanceAccessor(const ClassMethod* classMethod) { + return !classMethod->isStatic() && + classMethod->name().isKind(ParseNodeKind::PrivateName) && + classMethod->accessorType() != AccessorType::None; +} + +bool BytecodeEmitter::emitPrivateMethodInitializers(ClassEmitter& ce, + ListNode* obj) { + for (ParseNode* propdef : obj->contents()) { + if (!propdef->is()) { + continue; + } + auto* classMethod = &propdef->as(); + + // Skip over anything which isn't a private instance accessor. + if (!IsPrivateInstanceAccessor(classMethod)) { + continue; + } + + if (!ce.prepareForMemberInitializer()) { + // [stack] HOMEOBJ HERITAGE? ARRAY + // or: + // [stack] CTOR HOMEOBJ ARRAY + return false; + } + + // Synthesize a name for the lexical variable that will store the + // private method body. + TaggedParserAtomIndex name = classMethod->name().as().atom(); + AccessorType accessorType = classMethod->accessorType(); + StringBuffer storedMethodName(fc); + if (!storedMethodName.append(parserAtoms(), name)) { + return false; + } + if (!storedMethodName.append( + accessorType == AccessorType::Getter ? ".getter" : ".setter")) { + return false; + } + auto storedMethodAtom = + storedMethodName.finishParserAtom(parserAtoms(), fc); + + // Emit the private method body and store it as a lexical var. + if (!emitFunction(&classMethod->method())) { + // [stack] HOMEOBJ HERITAGE? ARRAY METHOD + // or: + // [stack] CTOR HOMEOBJ ARRAY METHOD + return false; + } + // The private method body needs to access the home object, + // and the CE knows where that is on the stack. + if (classMethod->method().funbox()->needsHomeObject()) { + if (!ce.emitMemberInitializerHomeObject(false)) { + // [stack] HOMEOBJ HERITAGE? ARRAY METHOD + // or: + // [stack] CTOR HOMEOBJ ARRAY METHOD + return false; + } + } + if (!emitLexicalInitialization(storedMethodAtom)) { + // [stack] HOMEOBJ HERITAGE? ARRAY METHOD + // or: + // [stack] CTOR HOMEOBJ ARRAY METHOD + return false; + } + if (!emit1(JSOp::Pop)) { + // [stack] HOMEOBJ HERITAGE? ARRAY + // or: + // [stack] CTOR HOMEOBJ ARRAY + return false; + } + + if (!emitPrivateMethodInitializer(classMethod, storedMethodAtom)) { + // [stack] HOMEOBJ HERITAGE? ARRAY + // or: + // [stack] CTOR HOMEOBJ ARRAY + return false; + } + + // Store the emitted initializer function into the .initializers array. + if (!ce.emitStoreMemberInitializer()) { + // [stack] HOMEOBJ HERITAGE? ARRAY + // or: + // [stack] CTOR HOMEOBJ ARRAY + return false; + } + } + + return true; +} + +bool BytecodeEmitter::emitPrivateMethodInitializer( + ClassMethod* classMethod, TaggedParserAtomIndex storedMethodAtom) { + MOZ_ASSERT(IsPrivateInstanceAccessor(classMethod)); + + auto* name = &classMethod->name().as(); + + // Emit the synthesized initializer function. + FunctionNode* funNode = classMethod->initializerIfPrivate(); + MOZ_ASSERT(funNode); + FunctionBox* funbox = funNode->funbox(); + FunctionEmitter fe(this, funbox, funNode->syntaxKind(), + FunctionEmitter::IsHoisted::No); + if (!fe.prepareForNonLazy()) { + // [stack] + return false; + } + + BytecodeEmitter bce2(this, funbox); + if (!bce2.init(funNode->pn_pos)) { + return false; + } + ParamsBodyNode* paramsBody = funNode->body(); + FunctionScriptEmitter fse(&bce2, funbox, Nothing(), Nothing()); + if (!fse.prepareForParameters()) { + // [stack] + return false; + } + if (!bce2.emitFunctionFormalParameters(paramsBody)) { + // [stack] + return false; + } + if (!fse.prepareForBody()) { + // [stack] + return false; + } + + if (!bce2.emit1(JSOp::FunctionThis)) { + // [stack] THIS + return false; + } + if (!bce2.emitGetPrivateName(name)) { + // [stack] THIS NAME + return false; + } + if (!bce2.emitGetName(storedMethodAtom)) { + // [stack] THIS NAME METHOD + return false; + } + + switch (name->privateNameKind()) { + case PrivateNameKind::Setter: + if (!bce2.emit1(JSOp::InitHiddenElemSetter)) { + // [stack] THIS + return false; + } + if (!bce2.emitGetPrivateName(name)) { + // [stack] THIS NAME + return false; + } + if (!bce2.emitAtomOp( + JSOp::GetIntrinsic, + TaggedParserAtomIndex::WellKnown::NoPrivateGetter())) { + // [stack] THIS NAME FUN + return false; + } + if (!bce2.emit1(JSOp::InitHiddenElemGetter)) { + // [stack] THIS + return false; + } + break; + case PrivateNameKind::Getter: + case PrivateNameKind::GetterSetter: + if (classMethod->accessorType() == AccessorType::Getter) { + if (!bce2.emit1(JSOp::InitHiddenElemGetter)) { + // [stack] THIS + return false; + } + } else { + if (!bce2.emit1(JSOp::InitHiddenElemSetter)) { + // [stack] THIS + return false; + } + } + break; + default: + MOZ_CRASH("Invalid op"); + } + + // Pop remaining THIS. + if (!bce2.emit1(JSOp::Pop)) { + // [stack] + return false; + } + + if (!fse.emitEndBody()) { + // [stack] + return false; + } + if (!fse.intoStencil()) { + return false; + } + + if (!fe.emitNonLazyEnd()) { + // [stack] HOMEOBJ HERITAGE? ARRAY FUN + // or: + // [stack] CTOR HOMEOBJ ARRAY FUN + return false; + } + + return true; +} + +const MemberInitializers& BytecodeEmitter::findMemberInitializersForCall() { + for (BytecodeEmitter* current = this; current; current = current->parent) { + if (current->sc->isFunctionBox()) { + FunctionBox* funbox = current->sc->asFunctionBox(); + + if (funbox->isArrow()) { + continue; + } + + // If we found a non-arrow / non-constructor we were never allowed to + // expect fields in the first place. + MOZ_RELEASE_ASSERT(funbox->isClassConstructor()); + + return funbox->useMemberInitializers() ? funbox->memberInitializers() + : MemberInitializers::Empty(); + } + } + + MOZ_RELEASE_ASSERT(compilationState.scopeContext.memberInitializers); + return *compilationState.scopeContext.memberInitializers; +} + +bool BytecodeEmitter::emitInitializeInstanceMembers( + bool isDerivedClassConstructor) { + const MemberInitializers& memberInitializers = + findMemberInitializersForCall(); + MOZ_ASSERT(memberInitializers.valid); + + if (memberInitializers.hasPrivateBrand) { + // This is guaranteed to run after super(), so we don't need TDZ checks. + if (!emitGetName(TaggedParserAtomIndex::WellKnown::dotThis())) { + // [stack] THIS + return false; + } + if (!emitGetName(TaggedParserAtomIndex::WellKnown::dotPrivateBrand())) { + // [stack] THIS BRAND + return false; + } + if (isDerivedClassConstructor) { + if (!emitCheckPrivateField(ThrowCondition::ThrowHas, + ThrowMsgKind::PrivateBrandDoubleInit)) { + // [stack] THIS BRAND BOOL + return false; + } + if (!emit1(JSOp::Pop)) { + // [stack] THIS BRAND + return false; + } + } + if (!emit1(JSOp::Null)) { + // [stack] THIS BRAND NULL + return false; + } + if (!emit1(JSOp::InitHiddenElem)) { + // [stack] THIS + return false; + } + if (!emit1(JSOp::Pop)) { + // [stack] + return false; + } + } + + size_t numInitializers = memberInitializers.numMemberInitializers; + if (numInitializers > 0) { + if (!emitGetName(TaggedParserAtomIndex::WellKnown::dotInitializers())) { + // [stack] ARRAY + return false; + } + + for (size_t index = 0; index < numInitializers; index++) { + if (index < numInitializers - 1) { + // We Dup to keep the array around (it is consumed in the bytecode + // below) for next iterations of this loop, except for the last + // iteration, which avoids an extra Pop at the end of the loop. + if (!emit1(JSOp::Dup)) { + // [stack] ARRAY ARRAY + return false; + } + } + + if (!emitNumberOp(index)) { + // [stack] ARRAY? ARRAY INDEX + return false; + } + + if (!emit1(JSOp::GetElem)) { + // [stack] ARRAY? FUNC + return false; + } + + // This is guaranteed to run after super(), so we don't need TDZ checks. + if (!emitGetName(TaggedParserAtomIndex::WellKnown::dotThis())) { + // [stack] ARRAY? FUNC THIS + return false; + } + + // Callee is always internal function. + if (!emitCall(JSOp::CallIgnoresRv, 0)) { + // [stack] ARRAY? RVAL + return false; + } + + if (!emit1(JSOp::Pop)) { + // [stack] ARRAY? + return false; + } + } +#ifdef ENABLE_DECORATORS + // Decorators Proposal + // https://arai-a.github.io/ecma262-compare/?pr=2417&id=sec-initializeinstanceelements + // 4. For each element e of elements, do + // 4.a. If elementRecord.[[Kind]] is field or accessor, then + // 4.a.i. Perform ? InitializeFieldOrAccessor(O, elementRecord). + // + + // TODO: (See Bug 1817993) At the moment, we're applying the initialization + // logic in two steps. The pre-decorator initialization code runs, stores + // the initial value, and then we retrieve it here and apply the + // initializers added by decorators. We should unify these two steps. + if (!emitGetName(TaggedParserAtomIndex::WellKnown::dotInitializers())) { + // [stack] ARRAY + return false; + } + + if (!emit1(JSOp::Dup)) { + // [stack] ARRAY ARRAY + return false; + } + + if (!emitAtomOp(JSOp::GetProp, + TaggedParserAtomIndex::WellKnown::length())) { + // [stack] ARRAY LENGTH + return false; + } + + if (!emitNumberOp(static_cast(numInitializers))) { + // [stack] ARRAY LENGTH INDEX + return false; + } + + WhileEmitter wh(this); + // At this point, we have no context to determine offsets in the + // code for this while statement. Ideally, it would correspond to + // the field we're initializing. + if (!wh.emitCond(0, 0, 0)) { + // [stack] ARRAY LENGTH INDEX + return false; + } + + if (!emit1(JSOp::Dup)) { + // [stack] ARRAY LENGTH INDEX INDEX + return false; + } + + if (!emitDupAt(2)) { + // [stack] ARRAY LENGTH INDEX INDEX LENGTH + return false; + } + + if (!emit1(JSOp::Lt)) { + // [stack] ARRAY LENGTH INDEX BOOL + return false; + } + + if (!wh.emitBody()) { + // [stack] ARRAY LENGTH INDEX + return false; + } + + if (!emitDupAt(2)) { + // [stack] ARRAY LENGTH INDEX ARRAY + return false; + } + + if (!emitDupAt(1)) { + // [stack] ARRAY LENGTH INDEX ARRAY INDEX + return false; + } + + // Retrieve initializers for this field + if (!emit1(JSOp::GetElem)) { + // [stack] ARRAY LENGTH INDEX INITIALIZERS + return false; + } + + // This is guaranteed to run after super(), so we don't need TDZ checks. + if (!emitGetName(TaggedParserAtomIndex::WellKnown::dotThis())) { + // [stack] ARRAY LENGTH INDEX INITIALIZERS THIS + return false; + } + + if (!emit1(JSOp::Swap)) { + // [stack] ARRAY LENGTH INDEX THIS INITIALIZERS + return false; + } + + DecoratorEmitter de(this); + if (!de.emitInitializeFieldOrAccessor()) { + // [stack] ARRAY LENGTH INDEX + return false; + } + + if (!emit1(JSOp::Inc)) { + // [stack] ARRAY LENGTH INDEX + return false; + } + + if (!wh.emitEnd()) { + // [stack] ARRAY LENGTH INDEX + return false; + } + + if (!emitPopN(3)) { + // [stack] + return false; + } + // 5. Return unused. +#endif + } + return true; +} + +bool BytecodeEmitter::emitInitializeStaticFields(ListNode* classMembers) { + auto isStaticField = [](ParseNode* propdef) { + return HasInitializer(propdef, true); + }; + size_t numFields = + std::count_if(classMembers->contents().begin(), + classMembers->contents().end(), isStaticField); + + if (numFields == 0) { + return true; + } + + if (!emitGetName(TaggedParserAtomIndex::WellKnown::dotStaticInitializers())) { + // [stack] CTOR ARRAY + return false; + } + + for (size_t fieldIndex = 0; fieldIndex < numFields; fieldIndex++) { + bool hasNext = fieldIndex < numFields - 1; + if (hasNext) { + // We Dup to keep the array around (it is consumed in the bytecode below) + // for next iterations of this loop, except for the last iteration, which + // avoids an extra Pop at the end of the loop. + if (!emit1(JSOp::Dup)) { + // [stack] CTOR ARRAY ARRAY + return false; + } + } + + if (!emitNumberOp(fieldIndex)) { + // [stack] CTOR ARRAY? ARRAY INDEX + return false; + } + + if (!emit1(JSOp::GetElem)) { + // [stack] CTOR ARRAY? FUNC + return false; + } + + if (!emitDupAt(1 + hasNext)) { + // [stack] CTOR ARRAY? FUNC CTOR + return false; + } + + // Callee is always internal function. + if (!emitCall(JSOp::CallIgnoresRv, 0)) { + // [stack] CTOR ARRAY? RVAL + return false; + } + + if (!emit1(JSOp::Pop)) { + // [stack] CTOR ARRAY? + return false; + } + } + + // Overwrite |.staticInitializers| and |.staticFieldKeys| with undefined to + // avoid keeping the arrays alive indefinitely. + auto clearStaticFieldSlot = [&](TaggedParserAtomIndex name) { + NameOpEmitter noe(this, name, NameOpEmitter::Kind::SimpleAssignment); + if (!noe.prepareForRhs()) { + // [stack] ENV? VAL? + return false; + } + + if (!emit1(JSOp::Undefined)) { + // [stack] ENV? VAL? UNDEFINED + return false; + } + + if (!noe.emitAssignment()) { + // [stack] VAL + return false; + } + + if (!emit1(JSOp::Pop)) { + // [stack] + return false; + } + + return true; + }; + + if (!clearStaticFieldSlot( + TaggedParserAtomIndex::WellKnown::dotStaticInitializers())) { + return false; + } + + auto isStaticFieldWithComputedName = [](ParseNode* propdef) { + return propdef->is() && propdef->as().isStatic() && + propdef->as().name().getKind() == + ParseNodeKind::ComputedName; + }; + + if (std::any_of(classMembers->contents().begin(), + classMembers->contents().end(), + isStaticFieldWithComputedName)) { + if (!clearStaticFieldSlot( + TaggedParserAtomIndex::WellKnown::dotStaticFieldKeys())) { + return false; + } + } + + return true; +} + +// Using MOZ_NEVER_INLINE in here is a workaround for llvm.org/pr14047. See +// the comment on emitSwitch. +MOZ_NEVER_INLINE bool BytecodeEmitter::emitObject(ListNode* objNode) { + // Note: this method uses the ObjLiteralWriter and emits ObjLiteralStencil + // objects into the GCThingList, which will evaluate them into real GC objects + // or shapes during JSScript::fullyInitFromEmitter. Eventually we want + // OBJLITERAL to be a real opcode, but for now, performance constraints limit + // us to evaluating object literals at the end of parse, when we're allowed to + // allocate GC things. + // + // There are four cases here, in descending order of preference: + // + // 1. The list of property names is "normal" and constant (no computed + // values, no integer indices), the values are all simple constants + // (numbers, booleans, strings), *and* this occurs in a run-once + // (singleton) context. In this case, we can emit ObjLiteral + // instructions to build an object with values, and the object will be + // attached to a JSOp::Object opcode, whose semantics are for the backend + // to simply steal the object from the script. + // + // 2. The list of property names is "normal" and constant as above, *and* this + // occurs in a run-once (singleton) context, but some values are complex + // (computed expressions, sub-objects, functions, etc.). In this case, we + // can still use JSOp::Object (because singleton context), but the object + // has |undefined| property values and InitProp ops are emitted to set the + // values. + // + // 3. The list of property names is "normal" and constant as above, but this + // occurs in a non-run-once (non-singleton) context. In this case, we can + // use the ObjLiteral functionality to describe an *empty* object (all + // values left undefined) with the right fields, which will become a + // JSOp::NewObject opcode using the object's shape to speed up the creation + // of the object each time it executes. The emitted bytecode still needs + // InitProp ops to set the values in this case. + // + // 4. Any other case. As a fallback, we use NewInit to create a new, empty + // object (i.e., `{}`) and then emit bytecode to initialize its properties + // one-by-one. + + bool useObjLiteral = false; + bool useObjLiteralValues = false; + isPropertyListObjLiteralCompatible(objNode, &useObjLiteralValues, + &useObjLiteral); + + // [stack] + // + ObjectEmitter oe(this); + if (useObjLiteral) { + bool singleton = checkSingletonContext() && + !objNode->hasNonConstInitializer() && objNode->head(); + JSOp op; + if (singleton) { + // Case 1 or 2. + op = JSOp::Object; + } else { + // Case 3. + useObjLiteralValues = false; + op = JSOp::NewObject; + } + + // Use an ObjLiteral op. This will record ObjLiteral insns in the + // objLiteralWriter's buffer and add a fixup to the list of ObjLiteral + // fixups so that at GC-publish time at the end of parse, the full object + // (case 1 or 2) or shape (case 3) can be allocated and the bytecode can be + // patched to refer to it. + if (!emitPropertyListObjLiteral(objNode, op, useObjLiteralValues)) { + // [stack] OBJ + return false; + } + // Put the ObjectEmitter in the right state. This tells it that there will + // already be an object on the stack as a result of the (eventual) + // NewObject or Object op, and prepares it to emit values if needed. + if (!oe.emitObjectWithTemplateOnStack()) { + // [stack] OBJ + return false; + } + if (!useObjLiteralValues) { + // Case 2 or 3 above: we still need to emit bytecode to fill in the + // object's property values. + if (!emitPropertyList(objNode, oe, ObjectLiteral)) { + // [stack] OBJ + return false; + } + } + } else { + // Case 4 above: no ObjLiteral use, just bytecode to build the object from + // scratch. + if (!oe.emitObject(objNode->count())) { + // [stack] OBJ + return false; + } + if (!emitPropertyList(objNode, oe, ObjectLiteral)) { + // [stack] OBJ + return false; + } + } + + if (!oe.emitEnd()) { + // [stack] OBJ + return false; + } + + return true; +} + +bool BytecodeEmitter::emitArrayLiteral(ListNode* array) { + // Emit JSOp::Object if the array consists entirely of primitive values and we + // are in a singleton context. + if (checkSingletonContext() && !array->hasNonConstInitializer() && + !array->empty() && isArrayObjLiteralCompatible(array)) { + return emitObjLiteralArray(array); + } + + return emitArray(array); +} + +bool BytecodeEmitter::emitArray(ListNode* array) { + /* + * Emit code for [a, b, c] that is equivalent to constructing a new + * array and in source order evaluating each element value and adding + * it to the array, without invoking latent setters. We use the + * JSOp::NewInit and JSOp::InitElemArray bytecodes to ignore setters and + * to avoid dup'ing and popping the array as each element is added, as + * JSOp::SetElem/JSOp::SetProp would do. + */ + + uint32_t nspread = 0; + for (ParseNode* elem : array->contents()) { + if (elem->isKind(ParseNodeKind::Spread)) { + nspread++; + } + } + + // Array literal's length is limited to NELEMENTS_LIMIT in parser. + static_assert(NativeObject::MAX_DENSE_ELEMENTS_COUNT <= INT32_MAX, + "array literals' maximum length must not exceed limits " + "required by BaselineCompiler::emit_NewArray, " + "BaselineCompiler::emit_InitElemArray, " + "and DoSetElemFallback's handling of JSOp::InitElemArray"); + + uint32_t count = array->count(); + MOZ_ASSERT(count >= nspread); + MOZ_ASSERT(count <= NativeObject::MAX_DENSE_ELEMENTS_COUNT, + "the parser must throw an error if the array exceeds maximum " + "length"); + + // For arrays with spread, this is a very pessimistic allocation, the + // minimum possible final size. + if (!emitUint32Operand(JSOp::NewArray, count - nspread)) { + // [stack] ARRAY + return false; + } + + uint32_t index = 0; + bool afterSpread = false; + for (ParseNode* elem : array->contents()) { + if (elem->isKind(ParseNodeKind::Spread)) { + if (!afterSpread) { + afterSpread = true; + if (!emitNumberOp(index)) { + // [stack] ARRAY INDEX + return false; + } + } + + ParseNode* expr = elem->as().kid(); + SelfHostedIter selfHostedIter = getSelfHostedIterFor(expr); + + if (!updateSourceCoordNotes(elem->pn_pos.begin)) { + return false; + } + if (!emitTree(expr, ValueUsage::WantValue)) { + // [stack] ARRAY INDEX VALUE + return false; + } + if (!emitIterator(selfHostedIter)) { + // [stack] ARRAY INDEX NEXT ITER + return false; + } + if (!emit2(JSOp::Pick, 3)) { + // [stack] INDEX NEXT ITER ARRAY + return false; + } + if (!emit2(JSOp::Pick, 3)) { + // [stack] NEXT ITER ARRAY INDEX + return false; + } + if (!emitSpread(selfHostedIter)) { + // [stack] ARRAY INDEX + return false; + } + } else { + if (!updateSourceCoordNotes(elem->pn_pos.begin)) { + return false; + } + if (elem->isKind(ParseNodeKind::Elision)) { + if (!emit1(JSOp::Hole)) { + return false; + } + } else { + if (!emitTree(elem, ValueUsage::WantValue)) { + // [stack] ARRAY INDEX? VALUE + return false; + } + } + + if (afterSpread) { + if (!emit1(JSOp::InitElemInc)) { + // [stack] ARRAY (INDEX+1) + return false; + } + } else { + if (!emitUint32Operand(JSOp::InitElemArray, index)) { + // [stack] ARRAY + return false; + } + } + } + + index++; + } + MOZ_ASSERT(index == count); + if (afterSpread) { + if (!emit1(JSOp::Pop)) { + // [stack] ARRAY + return false; + } + } + return true; +} + +bool BytecodeEmitter::emitSpreadIntoArray(UnaryNode* elem) { + MOZ_ASSERT(elem->isKind(ParseNodeKind::Spread)); + + if (!updateSourceCoordNotes(elem->pn_pos.begin)) { + // [stack] VALUE + return false; + } + + SelfHostedIter selfHostedIter = getSelfHostedIterFor(elem->kid()); + if (!emitIterator(selfHostedIter)) { + // [stack] NEXT ITER + return false; + } + + if (!emitUint32Operand(JSOp::NewArray, 0)) { + // [stack] NEXT ITER ARRAY + return false; + } + + if (!emitNumberOp(0)) { + // [stack] NEXT ITER ARRAY INDEX + return false; + } + + if (!emitSpread(selfHostedIter)) { + // [stack] ARRAY INDEX + return false; + } + + if (!emit1(JSOp::Pop)) { + // [stack] ARRAY + return false; + } + return true; +} + +#ifdef ENABLE_RECORD_TUPLE +bool BytecodeEmitter::emitRecordLiteral(ListNode* record) { + if (!emitUint32Operand(JSOp::InitRecord, record->count())) { + // [stack] RECORD + return false; + } + + for (ParseNode* propdef : record->contents()) { + if (propdef->isKind(ParseNodeKind::Spread)) { + if (!emitTree(propdef->as().kid())) { + // [stack] RECORD SPREADEE + return false; + } + if (!emit1(JSOp::AddRecordSpread)) { + // [stack] RECORD + return false; + } + } else { + BinaryNode* prop = &propdef->as(); + + ParseNode* key = prop->left(); + ParseNode* value = prop->right(); + + switch (key->getKind()) { + case ParseNodeKind::ObjectPropertyName: + if (!emitStringOp(JSOp::String, key->as().atom())) { + return false; + } + break; + case ParseNodeKind::ComputedName: + if (!emitTree(key->as().kid())) { + return false; + } + break; + default: + MOZ_ASSERT(key->isKind(ParseNodeKind::StringExpr) || + key->isKind(ParseNodeKind::NumberExpr) || + key->isKind(ParseNodeKind::BigIntExpr)); + if (!emitTree(key)) { + return false; + } + break; + } + // [stack] RECORD KEY + + if (!emitTree(value)) { + // [stack] RECORD KEY VALUE + return false; + } + + if (!emit1(JSOp::AddRecordProperty)) { + // [stack] RECORD + return false; + } + } + } + + if (!emit1(JSOp::FinishRecord)) { + // [stack] RECORD + return false; + } + + return true; +} + +bool BytecodeEmitter::emitTupleLiteral(ListNode* tuple) { + if (!emitUint32Operand(JSOp::InitTuple, tuple->count())) { + // [stack] TUPLE + return false; + } + + for (ParseNode* elt : tuple->contents()) { + if (elt->isKind(ParseNodeKind::Spread)) { + ParseNode* expr = elt->as().kid(); + + if (!emitTree(expr)) { + // [stack] TUPLE VALUE + return false; + } + if (!emitIterator()) { + // [stack] TUPLE NEXT ITER + return false; + } + if (!emit2(JSOp::Pick, 2)) { + // [stack] NEXT ITER TUPLE + return false; + } + if (!emitSpread(getSelfHostedIterFor(expr), /* spreadeeStackItems = */ 1, + JSOp::AddTupleElement)) { + // [stack] TUPLE + return false; + } + } else { + if (!emitTree(elt)) { + // [stack] TUPLE VALUE + return false; + } + + // Update location to throw errors about non-primitive elements + // in the correct position. + if (!updateSourceCoordNotes(elt->pn_pos.begin)) { + return false; + } + + if (!emit1(JSOp::AddTupleElement)) { + // [stack] TUPLE + return false; + } + } + } + + if (!emit1(JSOp::FinishTuple)) { + // [stack] TUPLE + return false; + } + + return true; +} +#endif + +static inline JSOp UnaryOpParseNodeKindToJSOp(ParseNodeKind pnk) { + switch (pnk) { + case ParseNodeKind::ThrowStmt: + return JSOp::Throw; + case ParseNodeKind::VoidExpr: + return JSOp::Void; + case ParseNodeKind::NotExpr: + return JSOp::Not; + case ParseNodeKind::BitNotExpr: + return JSOp::BitNot; + case ParseNodeKind::PosExpr: + return JSOp::Pos; + case ParseNodeKind::NegExpr: + return JSOp::Neg; + default: + MOZ_CRASH("unexpected unary op"); + } +} + +bool BytecodeEmitter::emitUnary(UnaryNode* unaryNode) { + if (!updateSourceCoordNotes(unaryNode->pn_pos.begin)) { + return false; + } + + JSOp op = UnaryOpParseNodeKindToJSOp(unaryNode->getKind()); + ValueUsage valueUsage = + op == JSOp::Void ? ValueUsage::IgnoreValue : ValueUsage::WantValue; + if (!emitTree(unaryNode->kid(), valueUsage)) { + return false; + } + return emit1(op); +} + +bool BytecodeEmitter::emitTypeof(UnaryNode* typeofNode, JSOp op) { + MOZ_ASSERT(op == JSOp::Typeof || op == JSOp::TypeofExpr); + + if (!updateSourceCoordNotes(typeofNode->pn_pos.begin)) { + return false; + } + + if (!emitTree(typeofNode->kid())) { + return false; + } + + return emit1(op); +} + +bool BytecodeEmitter::emitFunctionFormalParameters(ParamsBodyNode* paramsBody) { + FunctionBox* funbox = sc->asFunctionBox(); + + bool hasRest = funbox->hasRest(); + + FunctionParamsEmitter fpe(this, funbox); + for (ParseNode* arg : paramsBody->parameters()) { + ParseNode* bindingElement = arg; + ParseNode* initializer = nullptr; + if (arg->isKind(ParseNodeKind::AssignExpr)) { + bindingElement = arg->as().left(); + initializer = arg->as().right(); + } + bool hasInitializer = !!initializer; + bool isRest = + hasRest && arg->pn_next == *std::end(paramsBody->parameters()); + bool isDestructuring = !bindingElement->isKind(ParseNodeKind::Name); + + // Left-hand sides are either simple names or destructuring patterns. + MOZ_ASSERT(bindingElement->isKind(ParseNodeKind::Name) || + bindingElement->isKind(ParseNodeKind::ArrayExpr) || + bindingElement->isKind(ParseNodeKind::ObjectExpr)); + + auto emitDefaultInitializer = [this, &initializer, &bindingElement]() { + // [stack] + + if (!this->emitInitializer(initializer, bindingElement)) { + // [stack] DEFAULT + return false; + } + return true; + }; + + auto emitDestructuring = [this, &bindingElement]() { + // [stack] ARG + + if (!this->emitDestructuringOps(&bindingElement->as(), + DestructuringFlavor::Declaration)) { + // [stack] ARG + return false; + } + + return true; + }; + + if (isRest) { + if (isDestructuring) { + if (!fpe.prepareForDestructuringRest()) { + // [stack] + return false; + } + if (!emitDestructuring()) { + // [stack] + return false; + } + if (!fpe.emitDestructuringRestEnd()) { + // [stack] + return false; + } + } else { + auto paramName = bindingElement->as().name(); + if (!fpe.emitRest(paramName)) { + // [stack] + return false; + } + } + + continue; + } + + if (isDestructuring) { + if (hasInitializer) { + if (!fpe.prepareForDestructuringDefaultInitializer()) { + // [stack] + return false; + } + if (!emitDefaultInitializer()) { + // [stack] + return false; + } + if (!fpe.prepareForDestructuringDefault()) { + // [stack] + return false; + } + if (!emitDestructuring()) { + // [stack] + return false; + } + if (!fpe.emitDestructuringDefaultEnd()) { + // [stack] + return false; + } + } else { + if (!fpe.prepareForDestructuring()) { + // [stack] + return false; + } + if (!emitDestructuring()) { + // [stack] + return false; + } + if (!fpe.emitDestructuringEnd()) { + // [stack] + return false; + } + } + + continue; + } + + if (hasInitializer) { + if (!fpe.prepareForDefault()) { + // [stack] + return false; + } + if (!emitDefaultInitializer()) { + // [stack] + return false; + } + auto paramName = bindingElement->as().name(); + if (!fpe.emitDefaultEnd(paramName)) { + // [stack] + return false; + } + + continue; + } + + auto paramName = bindingElement->as().name(); + if (!fpe.emitSimple(paramName)) { + // [stack] + return false; + } + } + + return true; +} + +bool BytecodeEmitter::emitInitializeFunctionSpecialNames() { + FunctionBox* funbox = sc->asFunctionBox(); + + // [stack] + + auto emitInitializeFunctionSpecialName = + [](BytecodeEmitter* bce, TaggedParserAtomIndex name, JSOp op) { + // A special name must be slotful, either on the frame or on the + // call environment. + MOZ_ASSERT(bce->lookupName(name).hasKnownSlot()); + + NameOpEmitter noe(bce, name, NameOpEmitter::Kind::Initialize); + if (!noe.prepareForRhs()) { + // [stack] + return false; + } + if (!bce->emit1(op)) { + // [stack] THIS/ARGUMENTS/NEW.TARGET + return false; + } + if (!noe.emitAssignment()) { + // [stack] THIS/ARGUMENTS/NEW.TARGET + return false; + } + if (!bce->emit1(JSOp::Pop)) { + // [stack] + return false; + } + + return true; + }; + + // Do nothing if the function doesn't have an arguments binding. + if (funbox->needsArgsObj()) { + // Self-hosted code should use the more efficient ArgumentsLength and + // GetArgument intrinsics instead of `arguments`. + MOZ_ASSERT(emitterMode != BytecodeEmitter::SelfHosting); + if (!emitInitializeFunctionSpecialName( + this, TaggedParserAtomIndex::WellKnown::arguments(), + JSOp::Arguments)) { + // [stack] + return false; + } + } + + // Do nothing if the function doesn't have a this-binding (this + // happens for instance if it doesn't use this/eval or if it's an + // arrow function). + if (funbox->functionHasThisBinding()) { + if (!emitInitializeFunctionSpecialName( + this, TaggedParserAtomIndex::WellKnown::dotThis(), + JSOp::FunctionThis)) { + return false; + } + } + + // Do nothing if the function doesn't have a new.target-binding (this happens + // for instance if it doesn't use new.target/eval or if it's an arrow + // function). + if (funbox->functionHasNewTargetBinding()) { + if (!emitInitializeFunctionSpecialName( + this, TaggedParserAtomIndex::WellKnown::dotNewTarget(), + JSOp::NewTarget)) { + return false; + } + } + + // Do nothing if the function doesn't implicitly return a promise result. + if (funbox->needsPromiseResult()) { + if (!emitInitializeFunctionSpecialName( + this, TaggedParserAtomIndex::WellKnown::dotGenerator(), + JSOp::Generator)) { + // [stack] + return false; + } + } + return true; +} + +bool BytecodeEmitter::emitLexicalInitialization(NameNode* name) { + return emitLexicalInitialization(name->name()); +} + +bool BytecodeEmitter::emitLexicalInitialization(TaggedParserAtomIndex name) { + NameOpEmitter noe(this, name, NameOpEmitter::Kind::Initialize); + if (!noe.prepareForRhs()) { + return false; + } + + // The caller has pushed the RHS to the top of the stack. Assert that the + // binding can be initialized without a binding object on the stack, and that + // no BIND[G]NAME ops were emitted. + MOZ_ASSERT(noe.loc().isLexical() || noe.loc().isSynthetic() || + noe.loc().isPrivateMethod()); + MOZ_ASSERT(!noe.emittedBindOp()); + + if (!noe.emitAssignment()) { + return false; + } + + return true; +} + +static MOZ_ALWAYS_INLINE ParseNode* FindConstructor(ListNode* classMethods) { + for (ParseNode* classElement : classMethods->contents()) { + ParseNode* unwrappedElement = classElement; + if (unwrappedElement->is()) { + unwrappedElement = unwrappedElement->as().scopeBody(); + } + if (unwrappedElement->is()) { + ClassMethod& method = unwrappedElement->as(); + ParseNode& methodName = method.name(); + if (!method.isStatic() && + (methodName.isKind(ParseNodeKind::ObjectPropertyName) || + methodName.isKind(ParseNodeKind::StringExpr)) && + methodName.as().atom() == + TaggedParserAtomIndex::WellKnown::constructor()) { + return classElement; + } + } + } + return nullptr; +} + +bool BytecodeEmitter::emitNewPrivateName(TaggedParserAtomIndex bindingName, + TaggedParserAtomIndex symbolName) { + if (!emitAtomOp(JSOp::NewPrivateName, symbolName)) { + // [stack] HERITAGE PRIVATENAME + return false; + } + + // Add a binding for #name => privatename + if (!emitLexicalInitialization(bindingName)) { + // [stack] HERITAGE PRIVATENAME + return false; + } + + // Pop Private name off the stack. + if (!emit1(JSOp::Pop)) { + // [stack] HERITAGE + return false; + } + + return true; +} + +bool BytecodeEmitter::emitNewPrivateNames( + TaggedParserAtomIndex privateBrandName, ListNode* classMembers) { + bool hasPrivateBrand = false; + + for (ParseNode* classElement : classMembers->contents()) { + ParseNode* elementName; + if (classElement->is()) { + elementName = &classElement->as().name(); + } else if (classElement->is()) { + elementName = &classElement->as().name(); + } else { + continue; + } + + if (!elementName->isKind(ParseNodeKind::PrivateName)) { + continue; + } + + // Non-static private methods' private names are optimized away. + bool isOptimized = false; + if (classElement->is() && + !classElement->as().isStatic()) { + hasPrivateBrand = true; + if (classElement->as().accessorType() == + AccessorType::None) { + isOptimized = true; + } + } + + if (!isOptimized) { + auto privateName = elementName->as().name(); + if (!emitNewPrivateName(privateName, privateName)) { + return false; + } + } + } + + if (hasPrivateBrand) { + // We don't make a private name for every optimized method, but we need one + // private name per class, the `.privateBrand`. + if (!emitNewPrivateName(TaggedParserAtomIndex::WellKnown::dotPrivateBrand(), + privateBrandName)) { + return false; + } + } + return true; +} + +// This follows ES6 14.5.14 (ClassDefinitionEvaluation) and ES6 14.5.15 +// (BindingClassDeclarationEvaluation). +bool BytecodeEmitter::emitClass( + ClassNode* classNode, + ClassNameKind nameKind /* = ClassNameKind::BindingName */, + TaggedParserAtomIndex + nameForAnonymousClass /* = TaggedParserAtomIndex::null() */) { + MOZ_ASSERT((nameKind == ClassNameKind::InferredName) == + bool(nameForAnonymousClass)); + + ParseNode* heritageExpression = classNode->heritage(); + ListNode* classMembers = classNode->memberList(); + ParseNode* constructor = FindConstructor(classMembers); + + // If |nameKind != ClassNameKind::ComputedName| + // [stack] + // Else + // [stack] NAME + + ClassEmitter ce(this); + TaggedParserAtomIndex innerName; + ClassEmitter::Kind kind = ClassEmitter::Kind::Expression; + if (ClassNames* names = classNode->names()) { + MOZ_ASSERT(nameKind == ClassNameKind::BindingName); + innerName = names->innerBinding()->name(); + MOZ_ASSERT(innerName); + + if (names->outerBinding()) { + MOZ_ASSERT(names->outerBinding()->name()); + MOZ_ASSERT(names->outerBinding()->name() == innerName); + kind = ClassEmitter::Kind::Declaration; + } + } + + if (LexicalScopeNode* scopeBindings = classNode->scopeBindings()) { + if (!ce.emitScope(scopeBindings->scopeBindings())) { + // [stack] + return false; + } + } + + bool isDerived = !!heritageExpression; + if (isDerived) { + if (!updateSourceCoordNotes(classNode->pn_pos.begin)) { + return false; + } + if (!markStepBreakpoint()) { + return false; + } + if (!emitTree(heritageExpression)) { + // [stack] HERITAGE + return false; + } + } + + // The class body scope holds any private names. Those mustn't be visible in + // the heritage expression and hence the scope must be emitted after the + // heritage expression. + if (ClassBodyScopeNode* bodyScopeBindings = classNode->bodyScopeBindings()) { + if (!ce.emitBodyScope(bodyScopeBindings->scopeBindings())) { + // [stack] HERITAGE + return false; + } + + // The spec does not say anything about private brands being symbols. It's + // an implementation detail. So we can give the special private brand + // symbol any description we want and users won't normally see it. For + // debugging, use the class name. + auto privateBrandName = innerName; + if (!innerName) { + privateBrandName = nameForAnonymousClass + ? nameForAnonymousClass + : TaggedParserAtomIndex::WellKnown::anonymous(); + } + if (!emitNewPrivateNames(privateBrandName, classMembers)) { + return false; + } + } + + bool hasNameOnStack = nameKind == ClassNameKind::ComputedName; + if (isDerived) { + if (!ce.emitDerivedClass(innerName, nameForAnonymousClass, + hasNameOnStack)) { + // [stack] HERITAGE HOMEOBJ + return false; + } + } else { + if (!ce.emitClass(innerName, nameForAnonymousClass, hasNameOnStack)) { + // [stack] HOMEOBJ + return false; + } + } + + // Stack currently has HOMEOBJ followed by optional HERITAGE. When HERITAGE + // is not used, an implicit value of %FunctionPrototype% is implied. + + // See |Parser::classMember(...)| for the reason why |.initializers| is + // created within its own scope. + Maybe lse; + FunctionNode* ctor; + if (constructor->is()) { + LexicalScopeNode* constructorScope = &constructor->as(); + + // The constructor scope should only contain the |.initializers| binding. + MOZ_ASSERT(!constructorScope->isEmptyScope()); + MOZ_ASSERT(constructorScope->scopeBindings()->length == 1); + MOZ_ASSERT(GetScopeDataTrailingNames(constructorScope->scopeBindings())[0] + .name() == + TaggedParserAtomIndex::WellKnown::dotInitializers()); + + auto needsInitializer = [](ParseNode* propdef) { + return NeedsFieldInitializer(propdef, false) || + NeedsAccessorInitializer(propdef, false); + }; + + // As an optimization omit the |.initializers| binding when no instance + // fields or private methods are present. + bool needsInitializers = + std::any_of(classMembers->contents().begin(), + classMembers->contents().end(), needsInitializer); + if (needsInitializers) { + lse.emplace(this); + if (!lse->emitScope(ScopeKind::Lexical, + constructorScope->scopeBindings())) { + return false; + } + + // Any class with field initializers will have a constructor + if (!emitCreateMemberInitializers(ce, classMembers, + FieldPlacement::Instance)) { + return false; + } + } + + ctor = &constructorScope->scopeBody()->as().method(); + } else { + // The |.initializers| binding is never emitted when in self-hosting mode. + MOZ_ASSERT(emitterMode == BytecodeEmitter::SelfHosting); + ctor = &constructor->as().method(); + } + + bool needsHomeObject = ctor->funbox()->needsHomeObject(); + // HERITAGE is consumed inside emitFunction. + if (nameKind == ClassNameKind::InferredName) { + if (!setFunName(ctor->funbox(), nameForAnonymousClass)) { + return false; + } + } + if (!emitFunction(ctor, isDerived)) { + // [stack] HOMEOBJ CTOR + return false; + } + if (lse.isSome()) { + if (!lse->emitEnd()) { + return false; + } + lse.reset(); + } + if (!ce.emitInitConstructor(needsHomeObject)) { + // [stack] CTOR HOMEOBJ + return false; + } + + if (!emitCreateFieldKeys(classMembers, FieldPlacement::Instance)) { + return false; + } + + if (!emitCreateMemberInitializers(ce, classMembers, FieldPlacement::Static)) { + return false; + } + + if (!emitCreateFieldKeys(classMembers, FieldPlacement::Static)) { + return false; + } + + if (!emitPropertyList(classMembers, ce, ClassBody)) { + // [stack] CTOR HOMEOBJ + return false; + } + + if (!ce.emitBinding()) { + // [stack] CTOR + return false; + } + + if (!emitInitializeStaticFields(classMembers)) { + // [stack] CTOR + return false; + } + + if (!ce.emitEnd(kind)) { + // [stack] # class declaration + // [stack] + // [stack] # class expression + // [stack] CTOR + return false; + } + + return true; +} + +bool BytecodeEmitter::emitExportDefault(BinaryNode* exportNode) { + MOZ_ASSERT(exportNode->isKind(ParseNodeKind::ExportDefaultStmt)); + + ParseNode* valueNode = exportNode->left(); + if (valueNode->isDirectRHSAnonFunction()) { + MOZ_ASSERT(exportNode->right()); + + if (!emitAnonymousFunctionWithName( + valueNode, TaggedParserAtomIndex::WellKnown::default_())) { + return false; + } + } else { + if (!emitTree(valueNode)) { + return false; + } + } + + if (ParseNode* binding = exportNode->right()) { + if (!emitLexicalInitialization(&binding->as())) { + return false; + } + + if (!emit1(JSOp::Pop)) { + return false; + } + } + + return true; +} + +bool BytecodeEmitter::emitTree( + ParseNode* pn, ValueUsage valueUsage /* = ValueUsage::WantValue */, + EmitLineNumberNote emitLineNote /* = EMIT_LINENOTE */) { + AutoCheckRecursionLimit recursion(fc); + if (!recursion.check(fc)) { + return false; + } + + /* Emit notes to tell the current bytecode's source line number. + However, a couple trees require special treatment; see the + relevant emitter functions for details. */ + if (emitLineNote == EMIT_LINENOTE && + !ParseNodeRequiresSpecialLineNumberNotes(pn)) { + if (!updateLineNumberNotes(pn->pn_pos.begin)) { + return false; + } + } + + switch (pn->getKind()) { + case ParseNodeKind::Function: + if (!emitFunction(&pn->as())) { + return false; + } + break; + + case ParseNodeKind::ParamsBody: + MOZ_ASSERT_UNREACHABLE( + "ParamsBody should be handled in emitFunctionScript."); + break; + + case ParseNodeKind::IfStmt: + if (!emitIf(&pn->as())) { + return false; + } + break; + + case ParseNodeKind::SwitchStmt: + if (!emitSwitch(&pn->as())) { + return false; + } + break; + + case ParseNodeKind::WhileStmt: + if (!emitWhile(&pn->as())) { + return false; + } + break; + + case ParseNodeKind::DoWhileStmt: + if (!emitDo(&pn->as())) { + return false; + } + break; + + case ParseNodeKind::ForStmt: + if (!emitFor(&pn->as())) { + return false; + } + break; + + case ParseNodeKind::BreakStmt: + // Ensure that the column of the 'break' is set properly. + if (!updateSourceCoordNotes(pn->pn_pos.begin)) { + return false; + } + if (!markStepBreakpoint()) { + return false; + } + + if (!emitBreak(pn->as().label())) { + return false; + } + break; + + case ParseNodeKind::ContinueStmt: + // Ensure that the column of the 'continue' is set properly. + if (!updateSourceCoordNotes(pn->pn_pos.begin)) { + return false; + } + if (!markStepBreakpoint()) { + return false; + } + + if (!emitContinue(pn->as().label())) { + return false; + } + break; + + case ParseNodeKind::WithStmt: + if (!emitWith(&pn->as())) { + return false; + } + break; + + case ParseNodeKind::TryStmt: + if (!emitTry(&pn->as())) { + return false; + } + break; + + case ParseNodeKind::Catch: + if (!emitCatch(&pn->as())) { + return false; + } + break; + + case ParseNodeKind::VarStmt: + if (!emitDeclarationList(&pn->as())) { + return false; + } + break; + + case ParseNodeKind::ReturnStmt: + if (!emitReturn(&pn->as())) { + return false; + } + break; + + case ParseNodeKind::YieldStarExpr: + if (!emitYieldStar(pn->as().kid())) { + return false; + } + break; + + case ParseNodeKind::Generator: + if (!emit1(JSOp::Generator)) { + return false; + } + break; + + case ParseNodeKind::InitialYield: + if (!emitInitialYield(&pn->as())) { + return false; + } + break; + + case ParseNodeKind::YieldExpr: + if (!emitYield(&pn->as())) { + return false; + } + break; + + case ParseNodeKind::AwaitExpr: + if (!emitAwaitInInnermostScope(&pn->as())) { + return false; + } + break; + + case ParseNodeKind::StatementList: + if (!emitStatementList(&pn->as())) { + return false; + } + break; + + case ParseNodeKind::EmptyStmt: + break; + + case ParseNodeKind::ExpressionStmt: + if (!emitExpressionStatement(&pn->as())) { + return false; + } + break; + + case ParseNodeKind::LabelStmt: + if (!emitLabeledStatement(&pn->as())) { + return false; + } + break; + + case ParseNodeKind::CommaExpr: + if (!emitSequenceExpr(&pn->as(), valueUsage)) { + return false; + } + break; + + case ParseNodeKind::InitExpr: + case ParseNodeKind::AssignExpr: + case ParseNodeKind::AddAssignExpr: + case ParseNodeKind::SubAssignExpr: + case ParseNodeKind::BitOrAssignExpr: + case ParseNodeKind::BitXorAssignExpr: + case ParseNodeKind::BitAndAssignExpr: + case ParseNodeKind::LshAssignExpr: + case ParseNodeKind::RshAssignExpr: + case ParseNodeKind::UrshAssignExpr: + case ParseNodeKind::MulAssignExpr: + case ParseNodeKind::DivAssignExpr: + case ParseNodeKind::ModAssignExpr: + case ParseNodeKind::PowAssignExpr: { + BinaryNode* assignNode = &pn->as(); + if (!emitAssignmentOrInit(assignNode->getKind(), assignNode->left(), + assignNode->right())) { + return false; + } + break; + } + + case ParseNodeKind::CoalesceAssignExpr: + case ParseNodeKind::OrAssignExpr: + case ParseNodeKind::AndAssignExpr: + if (!emitShortCircuitAssignment(&pn->as())) { + return false; + } + break; + + case ParseNodeKind::ConditionalExpr: + if (!emitConditionalExpression(pn->as(), + valueUsage)) { + return false; + } + break; + + case ParseNodeKind::OrExpr: + case ParseNodeKind::CoalesceExpr: + case ParseNodeKind::AndExpr: + if (!emitShortCircuit(&pn->as(), valueUsage)) { + return false; + } + break; + + case ParseNodeKind::AddExpr: + case ParseNodeKind::SubExpr: + case ParseNodeKind::BitOrExpr: + case ParseNodeKind::BitXorExpr: + case ParseNodeKind::BitAndExpr: + case ParseNodeKind::StrictEqExpr: + case ParseNodeKind::EqExpr: + case ParseNodeKind::StrictNeExpr: + case ParseNodeKind::NeExpr: + case ParseNodeKind::LtExpr: + case ParseNodeKind::LeExpr: + case ParseNodeKind::GtExpr: + case ParseNodeKind::GeExpr: + case ParseNodeKind::InExpr: + case ParseNodeKind::InstanceOfExpr: + case ParseNodeKind::LshExpr: + case ParseNodeKind::RshExpr: + case ParseNodeKind::UrshExpr: + case ParseNodeKind::MulExpr: + case ParseNodeKind::DivExpr: + case ParseNodeKind::ModExpr: + if (!emitLeftAssociative(&pn->as())) { + return false; + } + break; + + case ParseNodeKind::PrivateInExpr: + if (!emitPrivateInExpr(&pn->as())) { + return false; + } + break; + + case ParseNodeKind::PowExpr: + if (!emitRightAssociative(&pn->as())) { + return false; + } + break; + + case ParseNodeKind::TypeOfNameExpr: + if (!emitTypeof(&pn->as(), JSOp::Typeof)) { + return false; + } + break; + + case ParseNodeKind::TypeOfExpr: + if (!emitTypeof(&pn->as(), JSOp::TypeofExpr)) { + return false; + } + break; + + case ParseNodeKind::ThrowStmt: + if (!updateSourceCoordNotes(pn->pn_pos.begin)) { + return false; + } + if (!markStepBreakpoint()) { + return false; + } + [[fallthrough]]; + case ParseNodeKind::VoidExpr: + case ParseNodeKind::NotExpr: + case ParseNodeKind::BitNotExpr: + case ParseNodeKind::PosExpr: + case ParseNodeKind::NegExpr: + if (!emitUnary(&pn->as())) { + return false; + } + break; + + case ParseNodeKind::PreIncrementExpr: + case ParseNodeKind::PreDecrementExpr: + case ParseNodeKind::PostIncrementExpr: + case ParseNodeKind::PostDecrementExpr: + if (!emitIncOrDec(&pn->as(), valueUsage)) { + return false; + } + break; + + case ParseNodeKind::DeleteNameExpr: + if (!emitDeleteName(&pn->as())) { + return false; + } + break; + + case ParseNodeKind::DeletePropExpr: + if (!emitDeleteProperty(&pn->as())) { + return false; + } + break; + + case ParseNodeKind::DeleteElemExpr: + if (!emitDeleteElement(&pn->as())) { + return false; + } + break; + + case ParseNodeKind::DeleteExpr: + if (!emitDeleteExpression(&pn->as())) { + return false; + } + break; + + case ParseNodeKind::DeleteOptionalChainExpr: + if (!emitDeleteOptionalChain(&pn->as())) { + return false; + } + break; + + case ParseNodeKind::OptionalChain: + if (!emitOptionalChain(&pn->as(), valueUsage)) { + return false; + } + break; + + case ParseNodeKind::DotExpr: { + PropertyAccess* prop = &pn->as(); + bool isSuper = prop->isSuper(); + PropOpEmitter poe(this, PropOpEmitter::Kind::Get, + isSuper ? PropOpEmitter::ObjKind::Super + : PropOpEmitter::ObjKind::Other); + if (!poe.prepareForObj()) { + return false; + } + if (isSuper) { + UnaryNode* base = &prop->expression().as(); + if (!emitGetThisForSuperBase(base)) { + // [stack] THIS + return false; + } + } else { + if (!emitPropLHS(prop)) { + // [stack] OBJ + return false; + } + } + if (!poe.emitGet(prop->key().atom())) { + // [stack] PROP + return false; + } + break; + } + + case ParseNodeKind::ElemExpr: { + PropertyByValue* elem = &pn->as(); + bool isSuper = elem->isSuper(); + MOZ_ASSERT(!elem->key().isKind(ParseNodeKind::PrivateName)); + ElemOpEmitter eoe(this, ElemOpEmitter::Kind::Get, + isSuper ? ElemOpEmitter::ObjKind::Super + : ElemOpEmitter::ObjKind::Other); + if (!emitElemObjAndKey(elem, isSuper, eoe)) { + // [stack] # if Super + // [stack] THIS KEY + // [stack] # otherwise + // [stack] OBJ KEY + return false; + } + if (!eoe.emitGet()) { + // [stack] ELEM + return false; + } + + break; + } + + case ParseNodeKind::PrivateMemberExpr: { + PrivateMemberAccess* privateExpr = &pn->as(); + PrivateOpEmitter xoe(this, PrivateOpEmitter::Kind::Get, + privateExpr->privateName().name()); + if (!emitTree(&privateExpr->expression())) { + // [stack] OBJ + return false; + } + if (!xoe.emitReference()) { + // [stack] OBJ NAME + return false; + } + if (!xoe.emitGet()) { + // [stack] VALUE + return false; + } + + break; + } + + case ParseNodeKind::NewExpr: + case ParseNodeKind::TaggedTemplateExpr: + case ParseNodeKind::CallExpr: + case ParseNodeKind::SuperCallExpr: + if (!emitCallOrNew(&pn->as(), valueUsage)) { + return false; + } + break; + + case ParseNodeKind::LexicalScope: + if (!emitLexicalScope(&pn->as())) { + return false; + } + break; + + case ParseNodeKind::ConstDecl: + case ParseNodeKind::LetDecl: + if (!emitDeclarationList(&pn->as())) { + return false; + } + break; + + case ParseNodeKind::ImportDecl: + MOZ_ASSERT(sc->isModuleContext()); + break; + + case ParseNodeKind::ExportStmt: { + MOZ_ASSERT(sc->isModuleContext()); + UnaryNode* node = &pn->as(); + ParseNode* decl = node->kid(); + if (decl->getKind() != ParseNodeKind::ExportSpecList) { + if (!emitTree(decl)) { + return false; + } + } + break; + } + + case ParseNodeKind::ExportDefaultStmt: + MOZ_ASSERT(sc->isModuleContext()); + if (!emitExportDefault(&pn->as())) { + return false; + } + break; + + case ParseNodeKind::ExportFromStmt: + MOZ_ASSERT(sc->isModuleContext()); + break; + + case ParseNodeKind::CallSiteObj: + if (!emitCallSiteObject(&pn->as())) { + return false; + } + break; + + case ParseNodeKind::ArrayExpr: + if (!emitArrayLiteral(&pn->as())) { + return false; + } + break; + + case ParseNodeKind::ObjectExpr: + if (!emitObject(&pn->as())) { + return false; + } + break; + + case ParseNodeKind::Name: + if (!emitGetName(&pn->as())) { + return false; + } + break; + + case ParseNodeKind::PrivateName: + if (!emitGetPrivateName(&pn->as())) { + return false; + } + break; + + case ParseNodeKind::TemplateStringListExpr: + if (!emitTemplateString(&pn->as())) { + return false; + } + break; + + case ParseNodeKind::TemplateStringExpr: + case ParseNodeKind::StringExpr: + if (!emitStringOp(JSOp::String, pn->as().atom())) { + return false; + } + break; + + case ParseNodeKind::NumberExpr: + if (!emitNumberOp(pn->as().value())) { + return false; + } + break; + + case ParseNodeKind::BigIntExpr: + if (!emitBigIntOp(&pn->as())) { + return false; + } + break; + + case ParseNodeKind::RegExpExpr: { + GCThingIndex index; + if (!perScriptData().gcThingList().append(&pn->as(), + &index)) { + return false; + } + if (!emitRegExp(index)) { + return false; + } + break; + } + + case ParseNodeKind::TrueExpr: + if (!emit1(JSOp::True)) { + return false; + } + break; + case ParseNodeKind::FalseExpr: + if (!emit1(JSOp::False)) { + return false; + } + break; + case ParseNodeKind::NullExpr: + if (!emit1(JSOp::Null)) { + return false; + } + break; + case ParseNodeKind::RawUndefinedExpr: + if (!emit1(JSOp::Undefined)) { + return false; + } + break; + + case ParseNodeKind::ThisExpr: + if (!emitThisLiteral(&pn->as())) { + return false; + } + break; + + case ParseNodeKind::DebuggerStmt: + if (!updateSourceCoordNotes(pn->pn_pos.begin)) { + return false; + } + if (!markStepBreakpoint()) { + return false; + } + if (!emit1(JSOp::Debugger)) { + return false; + } + break; + + case ParseNodeKind::ClassDecl: + if (!emitClass(&pn->as())) { + return false; + } + break; + + case ParseNodeKind::NewTargetExpr: + if (!emitNewTarget(&pn->as())) { + return false; + } + break; + + case ParseNodeKind::ImportMetaExpr: + if (!emit1(JSOp::ImportMeta)) { + return false; + } + break; + + case ParseNodeKind::CallImportExpr: { + BinaryNode* spec = &pn->as().right()->as(); + + if (!emitTree(spec->left())) { + // [stack] specifier + return false; + } + + if (!spec->right()->isKind(ParseNodeKind::PosHolder)) { + // [stack] specifier options + if (!emitTree(spec->right())) { + return false; + } + } else { + // [stack] specifier undefined + if (!emit1(JSOp::Undefined)) { + return false; + } + } + + if (!emit1(JSOp::DynamicImport)) { + return false; + } + + break; + } + + case ParseNodeKind::SetThis: + if (!emitSetThis(&pn->as())) { + return false; + } + break; + +#ifdef ENABLE_RECORD_TUPLE + case ParseNodeKind::RecordExpr: + if (!emitRecordLiteral(&pn->as())) { + return false; + } + break; + + case ParseNodeKind::TupleExpr: + if (!emitTupleLiteral(&pn->as())) { + return false; + } + break; +#endif + + case ParseNodeKind::PropertyNameExpr: + case ParseNodeKind::PosHolder: + MOZ_FALLTHROUGH_ASSERT( + "Should never try to emit ParseNodeKind::PosHolder or ::Property"); + + default: + MOZ_ASSERT(0); + } + + return true; +} + +static bool AllocSrcNote(FrontendContext* fc, SrcNotesVector& notes, + unsigned size, unsigned* index) { + size_t oldLength = notes.length(); + + if (MOZ_UNLIKELY(oldLength + size > MaxSrcNotesLength)) { + ReportAllocationOverflow(fc); + return false; + } + + if (!notes.growByUninitialized(size)) { + return false; + } + + *index = oldLength; + return true; +} + +bool BytecodeEmitter::addTryNote(TryNoteKind kind, uint32_t stackDepth, + BytecodeOffset start, BytecodeOffset end) { + MOZ_ASSERT(!inPrologue()); + return bytecodeSection().tryNoteList().append(kind, stackDepth, start, end); +} + +bool BytecodeEmitter::newSrcNote(SrcNoteType type, unsigned* indexp) { + // Non-gettable source notes such as column/lineno and debugger should not be + // emitted for prologue / self-hosted. + MOZ_ASSERT_IF(skipLocationSrcNotes() || skipBreakpointSrcNotes(), + type <= SrcNoteType::LastGettable); + + SrcNotesVector& notes = bytecodeSection().notes(); + unsigned index; + + /* + * Compute delta from the last annotated bytecode's offset. If it's too + * big to fit in sn, allocate one or more xdelta notes and reset sn. + */ + BytecodeOffset offset = bytecodeSection().offset(); + ptrdiff_t delta = (offset - bytecodeSection().lastNoteOffset()).value(); + bytecodeSection().setLastNoteOffset(offset); + + auto allocator = [&](unsigned size) -> SrcNote* { + if (!AllocSrcNote(fc, notes, size, &index)) { + return nullptr; + } + return ¬es[index]; + }; + + if (!SrcNoteWriter::writeNote(type, delta, allocator)) { + return false; + } + + if (indexp) { + *indexp = index; + } + return true; +} + +bool BytecodeEmitter::newSrcNote2(SrcNoteType type, ptrdiff_t offset, + unsigned* indexp) { + unsigned index; + if (!newSrcNote(type, &index)) { + return false; + } + if (!newSrcNoteOperand(offset)) { + return false; + } + if (indexp) { + *indexp = index; + } + return true; +} + +bool BytecodeEmitter::newSrcNoteOperand(ptrdiff_t operand) { + if (!SrcNote::isRepresentableOperand(operand)) { + reportError(nullptr, JSMSG_NEED_DIET, js_script_str); + return false; + } + + SrcNotesVector& notes = bytecodeSection().notes(); + + auto allocator = [&](unsigned size) -> SrcNote* { + unsigned index; + if (!AllocSrcNote(fc, notes, size, &index)) { + return nullptr; + } + return ¬es[index]; + }; + + return SrcNoteWriter::writeOperand(operand, allocator); +} + +bool BytecodeEmitter::intoScriptStencil(ScriptIndex scriptIndex) { + js::UniquePtr immutableScriptData = + createImmutableScriptData(); + if (!immutableScriptData) { + return false; + } + + MOZ_ASSERT(outermostScope().hasNonSyntacticScopeOnChain() == + sc->hasNonSyntacticScope()); + + auto& things = perScriptData().gcThingList().objects(); + if (!compilationState.appendGCThings(fc, scriptIndex, things)) { + return false; + } + + // Hand over the ImmutableScriptData instance generated by BCE. + auto* sharedData = + SharedImmutableScriptData::createWith(fc, std::move(immutableScriptData)); + if (!sharedData) { + return false; + } + + // De-duplicate the bytecode within the runtime. + if (!compilationState.sharedData.addAndShare(fc, scriptIndex, sharedData)) { + return false; + } + + ScriptStencil& script = compilationState.scriptData[scriptIndex]; + script.setHasSharedData(); + + // Update flags specific to functions. + if (sc->isFunctionBox()) { + FunctionBox* funbox = sc->asFunctionBox(); + MOZ_ASSERT(&script == &funbox->functionStencil()); + funbox->copyUpdatedImmutableFlags(); + MOZ_ASSERT(script.isFunction()); + } else { + ScriptStencilExtra& scriptExtra = compilationState.scriptExtra[scriptIndex]; + sc->copyScriptExtraFields(scriptExtra); + } + + return true; +} + +SelfHostedIter BytecodeEmitter::getSelfHostedIterFor(ParseNode* parseNode) { + if (emitterMode == BytecodeEmitter::SelfHosting && + parseNode->isKind(ParseNodeKind::CallExpr) && + (parseNode->as().left()->isName( + TaggedParserAtomIndex::WellKnown::allowContentIter()) || + parseNode->as().left()->isName( + TaggedParserAtomIndex::WellKnown::allowContentIterWith()))) { + return SelfHostedIter::Allow; + } + + return SelfHostedIter::Deny; +} + +#if defined(DEBUG) || defined(JS_JITSPEW) +void BytecodeEmitter::dumpAtom(TaggedParserAtomIndex index) const { + parserAtoms().dump(index); +} +#endif diff --git a/js/src/frontend/BytecodeEmitter.h b/js/src/frontend/BytecodeEmitter.h new file mode 100644 index 0000000000..6d9c6eed0a --- /dev/null +++ b/js/src/frontend/BytecodeEmitter.h @@ -0,0 +1,1095 @@ +/* -*- 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 bytecode generation. */ + +#ifndef frontend_BytecodeEmitter_h +#define frontend_BytecodeEmitter_h + +#include "mozilla/Assertions.h" // MOZ_ASSERT +#include "mozilla/Attributes.h" // MOZ_STACK_CLASS, MOZ_ALWAYS_INLINE, MOZ_NEVER_INLINE, MOZ_RAII +#include "mozilla/Maybe.h" // mozilla::Maybe, mozilla::Some +#include "mozilla/Saturate.h" // mozilla::SaturateUint8 +#include "mozilla/Span.h" // mozilla::Span + +#include // ptrdiff_t +#include // uint16_t, uint32_t + +#include "frontend/AbstractScopePtr.h" // ScopeIndex +#include "frontend/BytecodeSection.h" // BytecodeSection, PerScriptData, CGScopeList +#include "frontend/DestructuringFlavor.h" // DestructuringFlavor +#include "frontend/EitherParser.h" // EitherParser +#include "frontend/IteratorKind.h" // IteratorKind +#include "frontend/JumpList.h" // JumpList, JumpTarget +#include "frontend/NameAnalysisTypes.h" // NameLocation +#include "frontend/NameCollections.h" // AtomIndexMap +#include "frontend/ParseNode.h" // ParseNode and subclasses +#include "frontend/Parser.h" // Parser, PropListType +#include "frontend/ParserAtom.h" // TaggedParserAtomIndex, ParserAtom +#include "frontend/ScriptIndex.h" // ScriptIndex +#include "frontend/SelfHostedIter.h" // SelfHostedIter +#include "frontend/SourceNotes.h" // SrcNoteType +#include "frontend/ValueUsage.h" // ValueUsage +#include "js/AllocPolicy.h" // ReportOutOfMemory +#include "js/TypeDecls.h" // jsbytecode +#include "vm/BuiltinObjectKind.h" // BuiltinObjectKind +#include "vm/CheckIsObjectKind.h" // CheckIsObjectKind +#include "vm/CompletionKind.h" // CompletionKind +#include "vm/FunctionPrefixKind.h" // FunctionPrefixKind +#include "vm/GeneratorResumeKind.h" // GeneratorResumeKind +#include "vm/Opcodes.h" // JSOp +#include "vm/SharedStencil.h" // GCThingIndex, MemberInitializers +#include "vm/StencilEnums.h" // TryNoteKind +#include "vm/ThrowMsgKind.h" // ThrowMsgKind, ThrowCondition + +namespace js { + +class FrontendContext; + +namespace frontend { + +class BytecodeOffset; +class CallOrNewEmitter; +class ClassEmitter; +class ElemOpEmitter; +class EmitterScope; +class ErrorReporter; +class FullParseHandler; +class NestableControl; +class PrivateOpEmitter; +class PropertyEmitter; +class PropOpEmitter; +class OptionalEmitter; +class SharedContext; +class TDZCheckCache; +class TryEmitter; + +struct TokenPos; + +enum class ValueIsOnStack { Yes, No }; + +// [SMDOC] Bytecode emission +// +// Bytecode emitter class and helper classes for generating bytecode and related +// stencil data from AST generated by JS parser. +// +// +// BytecodeEmitter +// --------------- +// +// BytecodeEmitter receives an AST, and utilizes helper classes to generate the +// bytecode sequence, and related stencil data. +// +// BytecodeEmitter can be nested, in order to emit inner non-lazy function +// scripts. +// +// +// Bytecode structures +// ------------------- +// +// While bytecode is being emitted, it is separated into 2 parts, the prologue +// and the main part. The prologue part contains instantiation of the declared +// variables, functions, and special names in function. The main part contains +// the remaining part of the bytecode. +// +// The generated bytecode is stored into the following 2 classes, before +// converting them into stencil data (See ImmutableScriptData and +// BytecodeEmitter::createImmutableScriptData): +// +// * BytecodeSection +// * PerScriptData +// +// BytecodeSection stores the bytecode sequence and data directly associated +// with opcode or index inside the bytecode sequence. +// +// PerScriptData contains data referred from the bytecode, that is mostly the +// list of GC things. +// +// +// Bindings +// -------- +// +// # Scope and bindings +// +// When emitting AST node that's associated with a given scope, EmitterScope is +// allocated to store/cache the bindings information. +// +// This information is used when emitting an opcode that accesses bindings, to +// determine where the binding is stored, and how the binding should be +// accessed, including which opcode to use and what operand to use for it. +// +// +// # Temporal Dead Zone (TDZ) check cache +// +// The spec requires TDZ check for all lexical variable access, but emitting +// TDZ check for all operation increases the bytecode size and affects the +// performance. TDZCheckCache is a cache to optimize away unnecessary TDZ check +// operations. +// +// See comments for TDZCheckCache for more details. +// +// +// Control structures +// ------------------ +// +// # Jump list +// +// When emitting jump-related bytecode (if-else, break/continue, try-catch), +// forward jump is tracked by JumpList class, in order to patch the jump +// after the jump target is emitted. +// +// See the comment above JumpList class for mode details. +// +// +// # Loop and label +// +// Control structure related to break/continue is handled by NestableControl and +// its subclasses. Those classes handle jump with labelled and un-labelled +// break/continue, stack balancing around them, TDZ check cache for the +// loop's basic block, and association between the control and the scope. +// +// +// Emitter helpers +// --------------- +// +// Bytecode sequence or structure specific to certain syntax (e.g. if, for, try) +// are handled by emitter helper classes. +// +// Each emitter helper is defined in *Emitter.{cpp,h} in this directory. +// +// Emitter helpers should meet the following requirements: +// * helper classes should be ParseNode-agnostic +// * helper classes shouldn't contain `JS::Rooted` field, given they can be +// held in `mozilla::Maybe` in the consumer or other helper classes +// * instantiation (ctor/dtor) of the emitter helper class shouldn't +// modify BytecodeEmitter, except for nestable controls +// * instantiation (ctor/dtor) of the emitter helper class shouldn't +// read BytecodeEmitter field that can change before the first method call. +// Such data should be explicitly passed as parameter, or be accessed inside +// the method +// * methods that emits bytecode should be named `emit*` or `prepareFor*` +// * methods and their names shouldn't require the consumer knowing the +// details of the bytecode sequence/structure that the helper emits +// * implicit branch or scope/control handling should be hidden from the +// consumer +// * If there are multiple operations between bytecode that the consumer +// emits, they should be wrapped into single `emit*` or `prepareFor*` +// method +// e.g. +// // Bad! +// helper.emitJumpAroundA(); +// helper.allocateScopeForA(); +// ... // emit bytecode for A here +// helper.deallocateScopeForA(); +// helper.emitJumpAroundB(); +// helper.allocateScopeForB(); +// ... // emit bytecode for B here +// helper.deallocateScopeForB(); +// helper.emitJumpTarget(); +// +// // Good! +// helper.prepareForA(); +// ... // emit bytecode for A here +// helper.prepareForB(); +// ... // emit bytecode for B here +// helper.emitEnd(); +// * helper classes should track state transition and assert it in each +// method call, to avoid misuse +// * it's recommended to defer receiving parameter until the parameter value +// is actually used in the method, instead of receiving and storing them +// into instance fields +// +// See comment block above each helper class for more details and example usage. + +struct MOZ_STACK_CLASS BytecodeEmitter { + // Context shared between parsing and bytecode generation. + SharedContext* const sc = nullptr; + + FrontendContext* const fc = nullptr; + + // Enclosing function or global context. + BytecodeEmitter* const parent = nullptr; + + BytecodeSection bytecodeSection_; + + public: + BytecodeSection& bytecodeSection() { return bytecodeSection_; } + const BytecodeSection& bytecodeSection() const { return bytecodeSection_; } + + private: + PerScriptData perScriptData_; + + public: + PerScriptData& perScriptData() { return perScriptData_; } + const PerScriptData& perScriptData() const { return perScriptData_; } + + private: + // switchToMain sets this to the bytecode offset of the main section. + mozilla::Maybe mainOffset_ = {}; + + // Private storage for parser wrapper. DO NOT REFERENCE INTERNALLY. May not be + // initialized. + mozilla::Maybe ep_ = {}; + + const ErrorReporter& errorReporter_; + + public: + CompilationState& compilationState; + + uint32_t maxFixedSlots = 0; /* maximum number of fixed frame slots so far */ + + // Index into scopeList of the body scope. + GCThingIndex bodyScopeIndex = ScopeNote::NoScopeIndex; + + EmitterScope* varEmitterScope = nullptr; + NestableControl* innermostNestableControl = nullptr; + EmitterScope* innermostEmitterScope_ = nullptr; + TDZCheckCache* innermostTDZCheckCache = nullptr; + + // When compiling in self-hosted mode, we have special intrinsics that act as + // decorators for exported functions. To keeps things simple, we only allow + // these to target the last top-level function emitted. This field tracks that + // function. + FunctionBox* prevSelfHostedTopLevelFunction = nullptr; + +#ifdef DEBUG + bool unstableEmitterScope = false; + + friend class AutoCheckUnstableEmitterScope; +#endif + + const ErrorReporter& errorReporter() const { return errorReporter_; } + + ParserAtomsTable& parserAtoms() { return compilationState.parserAtoms; } + const ParserAtomsTable& parserAtoms() const { + return compilationState.parserAtoms; + } + + EmitterScope* innermostEmitterScope() const { + MOZ_ASSERT(!unstableEmitterScope); + return innermostEmitterScopeNoCheck(); + } + EmitterScope* innermostEmitterScopeNoCheck() const { + return innermostEmitterScope_; + } + + // When parsing internal code such as self-hosted functions or synthetic + // class constructors, we do not emit breakpoint and srcnote data since there + // is no direcly corresponding user-visible sources. + const bool suppressBreakpointsAndSourceNotes = false; + + // Script contains finally block. + bool hasTryFinally = false; + + enum EmitterMode { + Normal, + + // Emit JSOp::GetIntrinsic instead of JSOp::GetName and assert that + // JSOp::GetName and JSOp::*GName don't ever get emitted. See the comment + // for the field |selfHostingMode| in Parser.h for details. + SelfHosting, + + // Check the static scope chain of the root function for resolving free + // variable accesses in the script. + LazyFunction + }; + + const EmitterMode emitterMode = Normal; + + mozilla::Maybe scriptStartOffset = {}; + + // The end location of a function body that is being emitted. + mozilla::Maybe functionBodyEndPos = {}; + + // Jump target just before the final CheckReturn opcode in a derived class + // constructor body. + JumpList endOfDerivedClassConstructorBody = {}; + + // Jump target just before the final yield in a generator or async function. + JumpList finalYields = {}; + + // In order to heuristically determine the size of the allocation if this is a + // constructor function, we track expressions which add properties in the + // constructor. + mozilla::SaturateUint8 propertyAdditionEstimate = {}; + + /* + * Note that BytecodeEmitters are magic: they own the arena "top-of-stack" + * space above their tempMark points. This means that you cannot alloc from + * tempLifoAlloc and save the pointer beyond the next BytecodeEmitter + * destruction. + */ + private: + // Internal constructor, for delegation use only. + BytecodeEmitter(BytecodeEmitter* parent, FrontendContext* fc, + SharedContext* sc, const ErrorReporter& errorReporter, + CompilationState& compilationState, EmitterMode emitterMode); + + BytecodeEmitter(BytecodeEmitter* parent, SharedContext* sc); + + void initFromBodyPosition(TokenPos bodyPosition); + + public: + BytecodeEmitter(FrontendContext* fc, const EitherParser& parser, + SharedContext* sc, CompilationState& compilationState, + EmitterMode emitterMode = Normal); + + template + BytecodeEmitter(FrontendContext* fc, Parser* parser, + SharedContext* sc, CompilationState& compilationState, + EmitterMode emitterMode = Normal) + : BytecodeEmitter(fc, EitherParser(parser), sc, compilationState, + emitterMode) {} + + [[nodiscard]] bool init(); + [[nodiscard]] bool init(TokenPos bodyPosition); + + template + T* findInnermostNestableControl() const; + + template bool */> + T* findInnermostNestableControl(Predicate predicate) const; + + NameLocation lookupName(TaggedParserAtomIndex name); + + // See EmitterScope::lookupPrivate for details around brandLoc + void lookupPrivate(TaggedParserAtomIndex name, NameLocation& loc, + mozilla::Maybe& brandLoc); + + // To implement Annex B and the formal parameter defaults scope semantics + // requires accessing names that would otherwise be shadowed. This method + // returns the access location of a name that is known to be bound in a + // target scope. + mozilla::Maybe locationOfNameBoundInScope( + TaggedParserAtomIndex name, EmitterScope* target); + + // Get the location of a name known to be bound in a given scope, + // starting at the source scope. + template + mozilla::Maybe locationOfNameBoundInScopeType( + TaggedParserAtomIndex name, EmitterScope* source); + + // Get the location of a name known to be bound in the function scope, + // starting at the source scope. + mozilla::Maybe locationOfNameBoundInFunctionScope( + TaggedParserAtomIndex name) { + return locationOfNameBoundInScopeType( + name, innermostEmitterScope()); + } + + void setVarEmitterScope(EmitterScope* emitterScope) { + MOZ_ASSERT(emitterScope); + MOZ_ASSERT(!varEmitterScope); + varEmitterScope = emitterScope; + } + + AbstractScopePtr outermostScope() const { + return perScriptData().gcThingList().firstScope(); + } + AbstractScopePtr innermostScope() const; + ScopeIndex innermostScopeIndex() const; + + [[nodiscard]] MOZ_ALWAYS_INLINE bool makeAtomIndex( + TaggedParserAtomIndex atom, ParserAtom::Atomize atomize, + GCThingIndex* indexp) { + MOZ_ASSERT(perScriptData().atomIndices()); + AtomIndexMap::AddPtr p = perScriptData().atomIndices()->lookupForAdd(atom); + if (p) { + compilationState.parserAtoms.markAtomize(atom, atomize); + *indexp = GCThingIndex(p->value()); + return true; + } + + GCThingIndex index; + if (!perScriptData().gcThingList().append(atom, atomize, &index)) { + return false; + } + + // `atomIndices()` uses uint32_t instead of GCThingIndex, because + // GCThingIndex isn't trivial type. + if (!perScriptData().atomIndices()->add(p, atom, index.index)) { + ReportOutOfMemory(fc); + return false; + } + + *indexp = index; + return true; + } + + bool isInLoop(); + [[nodiscard]] bool checkSingletonContext(); + + bool needsImplicitThis(); + + size_t countThisEnvironmentHops(); + [[nodiscard]] bool emitThisEnvironmentCallee(); + [[nodiscard]] bool emitSuperBase(); + + uint32_t mainOffset() const { return *mainOffset_; } + + bool inPrologue() const { return mainOffset_.isNothing(); } + + void switchToMain() { + MOZ_ASSERT(inPrologue()); + mainOffset_.emplace(bytecodeSection().code().length()); + } + + void setFunctionBodyEndPos(uint32_t pos) { + functionBodyEndPos = mozilla::Some(pos); + } + + void setScriptStartOffsetIfUnset(uint32_t pos) { + if (scriptStartOffset.isNothing()) { + scriptStartOffset = mozilla::Some(pos); + } + } + + void reportError(ParseNode* pn, unsigned errorNumber, ...); + void reportError(uint32_t offset, unsigned errorNumber, ...); + + // Fill in a ScriptStencil using this BCE data. + bool intoScriptStencil(ScriptIndex scriptIndex); + + // If pn contains a useful expression, return true with *answer set to true. + // If pn contains a useless expression, return true with *answer set to + // false. Return false on error. + // + // The caller should initialize *answer to false and invoke this function on + // an expression statement or similar subtree to decide whether the tree + // could produce code that has any side effects. For an expression + // statement, we define useless code as code with no side effects, because + // the main effect, the value left on the stack after the code executes, + // will be discarded by a pop bytecode. + [[nodiscard]] bool checkSideEffects(ParseNode* pn, bool* answer); + +#ifdef DEBUG + [[nodiscard]] bool checkStrictOrSloppy(JSOp op); +#endif + + // Add TryNote to the tryNoteList array. The start and end offset are + // relative to current section. + [[nodiscard]] bool addTryNote(TryNoteKind kind, uint32_t stackDepth, + BytecodeOffset start, BytecodeOffset end); + + // Indicates the emitter should not generate location or debugger source + // notes. This lets us avoid generating notes for non-user code. + bool skipLocationSrcNotes() const { + return inPrologue() || suppressBreakpointsAndSourceNotes; + } + bool skipBreakpointSrcNotes() const { + return inPrologue() || suppressBreakpointsAndSourceNotes; + } + + // Append a new source note of the given type (and therefore size) to the + // notes dynamic array, updating noteCount. Return the new note's index + // within the array pointed at by current->notes as outparam. + [[nodiscard]] bool newSrcNote(SrcNoteType type, unsigned* indexp = nullptr); + [[nodiscard]] bool newSrcNote2(SrcNoteType type, ptrdiff_t operand, + unsigned* indexp = nullptr); + + [[nodiscard]] bool newSrcNoteOperand(ptrdiff_t operand); + + // Control whether emitTree emits a line number note. + enum EmitLineNumberNote { EMIT_LINENOTE, SUPPRESS_LINENOTE }; + + // Emit code for the tree rooted at pn. + [[nodiscard]] bool emitTree(ParseNode* pn, + ValueUsage valueUsage = ValueUsage::WantValue, + EmitLineNumberNote emitLineNote = EMIT_LINENOTE); + + [[nodiscard]] bool emitOptionalTree( + ParseNode* pn, OptionalEmitter& oe, + ValueUsage valueUsage = ValueUsage::WantValue); + + [[nodiscard]] bool emitDeclarationInstantiation(ParseNode* body); + + // Emit global, eval, or module code for tree rooted at body. Always + // encompasses the entire source. + [[nodiscard]] bool emitScript(ParseNode* body); + + // Calculate the `nslots` value for BCEScriptStencil constructor parameter. + // Fails if it overflows. + [[nodiscard]] bool getNslots(uint32_t* nslots); + + // Emit function code for the tree rooted at body. + [[nodiscard]] bool emitFunctionScript(FunctionNode* funNode); + + [[nodiscard]] bool markStepBreakpoint(); + [[nodiscard]] bool markSimpleBreakpoint(); + [[nodiscard]] bool updateLineNumberNotes(uint32_t offset); + [[nodiscard]] bool updateSourceCoordNotes(uint32_t offset); + + JSOp strictifySetNameOp(JSOp op); + + [[nodiscard]] bool emitCheck(JSOp op, ptrdiff_t delta, + BytecodeOffset* offset); + + // Emit one bytecode. + [[nodiscard]] bool emit1(JSOp op); + + // Emit two bytecodes, an opcode (op) with a byte of immediate operand + // (op1). + [[nodiscard]] bool emit2(JSOp op, uint8_t op1); + + // Emit three bytecodes, an opcode with two bytes of immediate operands. + [[nodiscard]] bool emit3(JSOp op, jsbytecode op1, jsbytecode op2); + + // Helper to duplicate one or more stack values. |slotFromTop| is the value's + // depth on the JS stack, as measured from the top. |count| is the number of + // values to duplicate, in theiro original order. + [[nodiscard]] bool emitDupAt(unsigned slotFromTop, unsigned count = 1); + + // Helper to emit JSOp::Pop or JSOp::PopN. + [[nodiscard]] bool emitPopN(unsigned n); + + // Helper to emit JSOp::Swap or JSOp::Pick. + [[nodiscard]] bool emitPickN(uint8_t n); + + // Helper to emit JSOp::Swap or JSOp::Unpick. + [[nodiscard]] bool emitUnpickN(uint8_t n); + + // Helper to emit JSOp::CheckIsObj. + [[nodiscard]] bool emitCheckIsObj(CheckIsObjectKind kind); + + // Helper to emit JSOp::BuiltinObject. + [[nodiscard]] bool emitBuiltinObject(BuiltinObjectKind kind); + + // Emit a bytecode followed by an uint16 immediate operand stored in + // big-endian order. + [[nodiscard]] bool emitUint16Operand(JSOp op, uint32_t operand); + + // Emit a bytecode followed by an uint32 immediate operand. + [[nodiscard]] bool emitUint32Operand(JSOp op, uint32_t operand); + + // Emit (1 + extra) bytecodes, for N bytes of op and its immediate operand. + [[nodiscard]] bool emitN(JSOp op, size_t extra, + BytecodeOffset* offset = nullptr); + + [[nodiscard]] bool emitDouble(double dval); + [[nodiscard]] bool emitNumberOp(double dval); + + [[nodiscard]] bool emitBigIntOp(BigIntLiteral* bigint); + + [[nodiscard]] bool emitThisLiteral(ThisLiteral* pn); + [[nodiscard]] bool emitGetFunctionThis(NameNode* thisName); + [[nodiscard]] bool emitGetThisForSuperBase(UnaryNode* superBase); + [[nodiscard]] bool emitSetThis(BinaryNode* setThisNode); + [[nodiscard]] bool emitCheckDerivedClassConstructorReturn(); + + private: + [[nodiscard]] bool emitNewTarget(); + + public: + [[nodiscard]] bool emitNewTarget(NewTargetNode* pn); + [[nodiscard]] bool emitNewTarget(CallNode* pn); + + // Handle jump opcodes and jump targets. + [[nodiscard]] bool emitJumpTargetOp(JSOp op, BytecodeOffset* off); + [[nodiscard]] bool emitJumpTarget(JumpTarget* target); + [[nodiscard]] bool emitJumpNoFallthrough(JSOp op, JumpList* jump); + [[nodiscard]] bool emitJump(JSOp op, JumpList* jump); + void patchJumpsToTarget(JumpList jump, JumpTarget target); + [[nodiscard]] bool emitJumpTargetAndPatch(JumpList jump); + + [[nodiscard]] bool emitCall( + JSOp op, uint16_t argc, + const mozilla::Maybe& sourceCoordOffset); + [[nodiscard]] bool emitCall(JSOp op, uint16_t argc, ParseNode* pn = nullptr); + [[nodiscard]] bool emitCallIncDec(UnaryNode* incDec); + + uint32_t getOffsetForLoop(ParseNode* nextpn); + + enum class GotoKind { Break, Continue }; + [[nodiscard]] bool emitGoto(NestableControl* target, GotoKind kind); + + [[nodiscard]] bool emitGCIndexOp(JSOp op, GCThingIndex index); + + [[nodiscard]] bool emitAtomOp(JSOp op, TaggedParserAtomIndex atom); + [[nodiscard]] bool emitAtomOp(JSOp op, GCThingIndex atomIndex); + + [[nodiscard]] bool emitStringOp(JSOp op, TaggedParserAtomIndex atom); + [[nodiscard]] bool emitStringOp(JSOp op, GCThingIndex atomIndex); + + [[nodiscard]] bool emitArrayLiteral(ListNode* array); + [[nodiscard]] bool emitArray(ListNode* array); + [[nodiscard]] bool emitSpreadIntoArray(UnaryNode* elem); + + [[nodiscard]] bool emitInternedScopeOp(GCThingIndex index, JSOp op); + [[nodiscard]] bool emitInternedObjectOp(GCThingIndex index, JSOp op); + [[nodiscard]] bool emitRegExp(GCThingIndex index); + + [[nodiscard]] MOZ_NEVER_INLINE bool emitFunction(FunctionNode* funNode, + bool needsProto = false); + [[nodiscard]] MOZ_NEVER_INLINE bool emitObject(ListNode* objNode); + + [[nodiscard]] bool emitHoistedFunctionsInList(ListNode* stmtList); + + // Can we use the object-literal writer either in singleton-object mode (with + // values) or in template mode (field names only, no values) for the property + // list? + void isPropertyListObjLiteralCompatible(ListNode* obj, bool* withValues, + bool* withoutValues); + bool isArrayObjLiteralCompatible(ListNode* array); + + [[nodiscard]] bool emitPropertyList(ListNode* obj, PropertyEmitter& pe, + PropListType type); + + [[nodiscard]] bool emitPropertyListObjLiteral(ListNode* obj, JSOp op, + bool useObjLiteralValues); + + [[nodiscard]] bool emitDestructuringRestExclusionSetObjLiteral( + ListNode* pattern); + + [[nodiscard]] bool emitObjLiteralArray(ListNode* array); + + // Is a field value OBJLITERAL-compatible? + [[nodiscard]] bool isRHSObjLiteralCompatible(ParseNode* value); + + [[nodiscard]] bool emitObjLiteralValue(ObjLiteralWriter& writer, + ParseNode* value); + + mozilla::Maybe setupMemberInitializers( + ListNode* classMembers, FieldPlacement placement); + [[nodiscard]] bool emitCreateFieldKeys(ListNode* obj, + FieldPlacement placement); + [[nodiscard]] bool emitCreateMemberInitializers(ClassEmitter& ce, + ListNode* obj, + FieldPlacement placement); + const MemberInitializers& findMemberInitializersForCall(); + [[nodiscard]] bool emitInitializeInstanceMembers( + bool isDerivedClassConstructor); + [[nodiscard]] bool emitInitializeStaticFields(ListNode* classMembers); + + [[nodiscard]] bool emitPrivateMethodInitializers(ClassEmitter& ce, + ListNode* obj); + [[nodiscard]] bool emitPrivateMethodInitializer( + ClassMethod* classMethod, TaggedParserAtomIndex storedMethodAtom); + + // To catch accidental misuse, emitUint16Operand/emit3 assert that they are + // not used to unconditionally emit JSOp::GetLocal. Variable access should + // instead be emitted using EmitVarOp. In special cases, when the caller + // definitely knows that a given local slot is unaliased, this function may be + // used as a non-asserting version of emitUint16Operand. + [[nodiscard]] bool emitLocalOp(JSOp op, uint32_t slot); + + [[nodiscard]] bool emitArgOp(JSOp op, uint16_t slot); + [[nodiscard]] bool emitEnvCoordOp(JSOp op, EnvironmentCoordinate ec); + + [[nodiscard]] bool emitGetNameAtLocation(TaggedParserAtomIndex name, + const NameLocation& loc); + [[nodiscard]] bool emitGetName(TaggedParserAtomIndex name) { + return emitGetNameAtLocation(name, lookupName(name)); + } + [[nodiscard]] bool emitGetName(NameNode* name); + [[nodiscard]] bool emitGetPrivateName(NameNode* name); + [[nodiscard]] bool emitGetPrivateName(TaggedParserAtomIndex name); + + [[nodiscard]] bool emitTDZCheckIfNeeded(TaggedParserAtomIndex name, + const NameLocation& loc, + ValueIsOnStack isOnStack); + + [[nodiscard]] bool emitNameIncDec(UnaryNode* incDec, ValueUsage valueUsage); + + [[nodiscard]] bool emitDeclarationList(ListNode* declList); + [[nodiscard]] bool emitSingleDeclaration(ListNode* declList, NameNode* decl, + ParseNode* initializer); + [[nodiscard]] bool emitAssignmentRhs(ParseNode* rhs, + TaggedParserAtomIndex anonFunctionName); + [[nodiscard]] bool emitAssignmentRhs(uint8_t offset); + + [[nodiscard]] bool emitPrepareIteratorResult(); + [[nodiscard]] bool emitFinishIteratorResult(bool done); + + // Convert and add `writer` data to stencil. + // Iff it suceeds, `outIndex` out parameter is initialized to the index of the + // object in GC things vector. + [[nodiscard]] bool addObjLiteralData(ObjLiteralWriter& writer, + GCThingIndex* outIndex); + + [[nodiscard]] bool emitGetDotGeneratorInInnermostScope() { + return emitGetDotGeneratorInScope(*innermostEmitterScope()); + } + [[nodiscard]] bool emitGetDotGeneratorInScope(EmitterScope& currentScope); + + [[nodiscard]] bool allocateResumeIndex(BytecodeOffset offset, + uint32_t* resumeIndex); + [[nodiscard]] bool allocateResumeIndexRange( + mozilla::Span offsets, uint32_t* firstResumeIndex); + + [[nodiscard]] bool emitInitialYield(UnaryNode* yieldNode); + [[nodiscard]] bool emitYield(UnaryNode* yieldNode); + [[nodiscard]] bool emitYieldOp(JSOp op); + [[nodiscard]] bool emitYieldStar(ParseNode* iter); + [[nodiscard]] bool emitAwaitInInnermostScope() { + return emitAwaitInScope(*innermostEmitterScope()); + } + [[nodiscard]] bool emitAwaitInInnermostScope(UnaryNode* awaitNode); + [[nodiscard]] bool emitAwaitInScope(EmitterScope& currentScope); + + [[nodiscard]] bool emitPushResumeKind(GeneratorResumeKind kind); + + [[nodiscard]] bool emitPropLHS(PropertyAccess* prop); + [[nodiscard]] bool emitPropIncDec(UnaryNode* incDec, ValueUsage valueUsage); + + [[nodiscard]] bool emitComputedPropertyName(UnaryNode* computedPropName); + + [[nodiscard]] bool emitObjAndKey(ParseNode* exprOrSuper, ParseNode* key, + ElemOpEmitter& eoe); + + // Emit bytecode to put operands for a JSOp::GetElem/CallElem/SetElem/DelElem + // opcode onto the stack in the right order. In the case of SetElem, the + // value to be assigned must already be pushed. + enum class EmitElemOption { Get, Call, IncDec, CompoundAssign, Ref }; + [[nodiscard]] bool emitElemOperands(PropertyByValue* elem, + EmitElemOption opts); + + [[nodiscard]] bool emitElemObjAndKey(PropertyByValue* elem, bool isSuper, + ElemOpEmitter& eoe); + [[nodiscard]] bool emitElemOpBase(JSOp op); + + [[nodiscard]] bool emitElemIncDec(UnaryNode* incDec, ValueUsage valueUsage); + [[nodiscard]] bool emitObjAndPrivateName(PrivateMemberAccess* elem, + ElemOpEmitter& eoe); + [[nodiscard]] bool emitPrivateIncDec(UnaryNode* incDec, + ValueUsage valueUsage); + + [[nodiscard]] bool emitCatch(BinaryNode* catchClause); + [[nodiscard]] bool emitIf(TernaryNode* ifNode); + [[nodiscard]] bool emitWith(BinaryNode* withNode); + + [[nodiscard]] MOZ_NEVER_INLINE bool emitLabeledStatement( + const LabeledStatement* labeledStmt); + [[nodiscard]] MOZ_NEVER_INLINE bool emitLexicalScope( + LexicalScopeNode* lexicalScope); + [[nodiscard]] bool emitLexicalScopeBody( + ParseNode* body, EmitLineNumberNote emitLineNote = EMIT_LINENOTE); + [[nodiscard]] MOZ_NEVER_INLINE bool emitSwitch(SwitchStatement* switchStmt); + [[nodiscard]] MOZ_NEVER_INLINE bool emitTry(TryNode* tryNode); + + [[nodiscard]] bool emitJumpToFinally(JumpList* jump, uint32_t idx); + + // emitDestructuringLHSRef emits the lhs expression's reference. + // If the lhs expression is object property |OBJ.prop|, it emits |OBJ|. + // If it's object element |OBJ[ELEM]|, it emits |OBJ| and |ELEM|. + // If there's nothing to evaluate for the reference, it emits nothing. + // |emitted| parameter receives the number of values pushed onto the stack. + [[nodiscard]] bool emitDestructuringLHSRef(ParseNode* target, + size_t* emitted); + + // emitSetOrInitializeDestructuring assumes the lhs expression's reference + // and the to-be-destructured value has been pushed on the stack. It emits + // code to destructure a single lhs expression (either a name or a compound + // []/{} expression). + [[nodiscard]] bool emitSetOrInitializeDestructuring(ParseNode* target, + DestructuringFlavor flav); + + // emitDestructuringObjRestExclusionSet emits the property exclusion set + // for the rest-property in an object pattern. + [[nodiscard]] bool emitDestructuringObjRestExclusionSet(ListNode* pattern); + + // emitDestructuringOps assumes the to-be-destructured value has been + // pushed on the stack and emits code to destructure each part of a [] or + // {} lhs expression. + [[nodiscard]] bool emitDestructuringOps(ListNode* pattern, + DestructuringFlavor flav); + [[nodiscard]] bool emitDestructuringOpsArray(ListNode* pattern, + DestructuringFlavor flav); + [[nodiscard]] bool emitDestructuringOpsObject(ListNode* pattern, + DestructuringFlavor flav); + + enum class CopyOption { Filtered, Unfiltered }; + + // Calls either the |CopyDataProperties| or the + // |CopyDataPropertiesUnfiltered| intrinsic function, consumes three (or + // two in the latter case) elements from the stack. + [[nodiscard]] bool emitCopyDataProperties(CopyOption option); + + JSOp getIterCallOp(JSOp callOp, SelfHostedIter selfHostedIter); + + // emitIterator expects the iterable to already be on the stack. + // It will replace that stack value with the corresponding iterator + [[nodiscard]] bool emitIterator( + SelfHostedIter selfHostedIter = SelfHostedIter::Deny, + bool isIteratorMethodOnStack = false); + + [[nodiscard]] bool emitAsyncIterator( + SelfHostedIter selfHostedIter = SelfHostedIter::Deny, + bool isIteratorMethodOnStack = false); + + // Pops iterator from the top of the stack. Pushes the result of |.next()| + // onto the stack. + [[nodiscard]] bool emitIteratorNext( + const mozilla::Maybe& callSourceCoordOffset, + IteratorKind kind = IteratorKind::Sync, + SelfHostedIter selfHostedIter = SelfHostedIter::Deny); + [[nodiscard]] bool emitIteratorCloseInScope( + EmitterScope& currentScope, IteratorKind iterKind = IteratorKind::Sync, + CompletionKind completionKind = CompletionKind::Normal, + SelfHostedIter selfHostedIter = SelfHostedIter::Deny); + [[nodiscard]] bool emitIteratorCloseInInnermostScope( + IteratorKind iterKind = IteratorKind::Sync, + CompletionKind completionKind = CompletionKind::Normal, + SelfHostedIter selfHostedIter = SelfHostedIter::Deny) { + return emitIteratorCloseInScope(*innermostEmitterScope(), iterKind, + completionKind, selfHostedIter); + } + + template + [[nodiscard]] bool wrapWithDestructuringTryNote(int32_t iterDepth, + InnerEmitter emitter); + + [[nodiscard]] bool defineHoistedTopLevelFunctions(ParseNode* body); + + // Check if the value on top of the stack is "undefined". If so, replace + // that value on the stack with the value defined by |defaultExpr|. + // |pattern| is a lhs node of the default expression. If it's an + // identifier and |defaultExpr| is an anonymous function, |SetFunctionName| + // is called at compile time. + [[nodiscard]] bool emitDefault(ParseNode* defaultExpr, ParseNode* pattern); + + [[nodiscard]] bool emitAnonymousFunctionWithName(ParseNode* node, + TaggedParserAtomIndex name); + + [[nodiscard]] bool emitAnonymousFunctionWithComputedName( + ParseNode* node, FunctionPrefixKind prefixKind); + + [[nodiscard]] bool setFunName(FunctionBox* fun, TaggedParserAtomIndex name); + [[nodiscard]] bool emitInitializer(ParseNode* initializer, + ParseNode* pattern); + + [[nodiscard]] bool emitCallSiteObjectArray(ObjLiteralWriter& writer, + ListNode* cookedOrRaw, + ParseNode* head, uint32_t count); + [[nodiscard]] bool emitCallSiteObject(CallSiteNode* callSiteObj); + [[nodiscard]] bool emitTemplateString(ListNode* templateString); + [[nodiscard]] bool emitAssignmentOrInit(ParseNodeKind kind, ParseNode* lhs, + ParseNode* rhs); + [[nodiscard]] bool emitShortCircuitAssignment(AssignmentNode* node); + + [[nodiscard]] bool emitReturn(UnaryNode* returnNode); + [[nodiscard]] bool finishReturn(BytecodeOffset setRvalOffset); + + [[nodiscard]] bool emitExpressionStatement(UnaryNode* exprStmt); + [[nodiscard]] bool emitStatementList(ListNode* stmtList); + + [[nodiscard]] bool emitDeleteName(UnaryNode* deleteNode); + [[nodiscard]] bool emitDeleteProperty(UnaryNode* deleteNode); + [[nodiscard]] bool emitDeleteElement(UnaryNode* deleteNode); + [[nodiscard]] bool emitDeleteExpression(UnaryNode* deleteNode); + + // Optional methods which emit Optional Jump Target + [[nodiscard]] bool emitOptionalChain(UnaryNode* expr, ValueUsage valueUsage); + [[nodiscard]] bool emitCalleeAndThisForOptionalChain(UnaryNode* expr, + CallNode* callNode, + CallOrNewEmitter& cone); + [[nodiscard]] bool emitDeleteOptionalChain(UnaryNode* deleteNode); + + // Optional methods which emit a shortCircuit jump. They need to be called by + // a method which emits an Optional Jump Target, see below. + [[nodiscard]] bool emitOptionalDotExpression(PropertyAccessBase* expr, + PropOpEmitter& poe, bool isSuper, + OptionalEmitter& oe); + [[nodiscard]] bool emitOptionalElemExpression(PropertyByValueBase* elem, + ElemOpEmitter& eoe, + bool isSuper, + OptionalEmitter& oe); + [[nodiscard]] bool emitOptionalPrivateExpression( + PrivateMemberAccessBase* privateExpr, PrivateOpEmitter& xoe, + OptionalEmitter& oe); + [[nodiscard]] bool emitOptionalCall(CallNode* callNode, OptionalEmitter& oe, + ValueUsage valueUsage); + [[nodiscard]] bool emitDeletePropertyInOptChain(PropertyAccessBase* propExpr, + OptionalEmitter& oe); + [[nodiscard]] bool emitDeleteElementInOptChain(PropertyByValueBase* elemExpr, + OptionalEmitter& oe); + + // |op| must be JSOp::Typeof or JSOp::TypeofExpr. + [[nodiscard]] bool emitTypeof(UnaryNode* typeofNode, JSOp op); + + [[nodiscard]] bool emitUnary(UnaryNode* unaryNode); + [[nodiscard]] bool emitRightAssociative(ListNode* node); + [[nodiscard]] bool emitLeftAssociative(ListNode* node); + [[nodiscard]] bool emitPrivateInExpr(ListNode* node); + [[nodiscard]] bool emitShortCircuit(ListNode* node, ValueUsage valueUsage); + [[nodiscard]] bool emitSequenceExpr(ListNode* node, ValueUsage valueUsage); + + [[nodiscard]] MOZ_NEVER_INLINE bool emitIncOrDec(UnaryNode* incDec, + ValueUsage valueUsage); + + [[nodiscard]] bool emitConditionalExpression( + ConditionalExpression& conditional, ValueUsage valueUsage); + + [[nodiscard]] ParseNode* getCoordNode(ParseNode* callNode, + ParseNode* calleeNode, JSOp op, + ListNode* argsList); + + [[nodiscard]] bool emitArguments(ListNode* argsList, bool isCall, + bool isSpread, CallOrNewEmitter& cone); + [[nodiscard]] bool emitCallOrNew(CallNode* callNode, ValueUsage valueUsage); + [[nodiscard]] bool emitDebugCheckSelfHosted(); + [[nodiscard]] bool emitSelfHostedCallFunction(CallNode* callNode, JSOp op); + [[nodiscard]] bool emitSelfHostedResumeGenerator(CallNode* callNode); + [[nodiscard]] bool emitSelfHostedForceInterpreter(); + [[nodiscard]] bool emitSelfHostedAllowContentIter(CallNode* callNode); + [[nodiscard]] bool emitSelfHostedAllowContentIterWith(CallNode* callNode); + [[nodiscard]] bool emitSelfHostedDefineDataProperty(CallNode* callNode); + [[nodiscard]] bool emitSelfHostedGetPropertySuper(CallNode* callNode); + [[nodiscard]] bool emitSelfHostedHasOwn(CallNode* callNode); + [[nodiscard]] bool emitSelfHostedToNumeric(CallNode* callNode); + [[nodiscard]] bool emitSelfHostedToString(CallNode* callNode); + [[nodiscard]] bool emitSelfHostedIsNullOrUndefined(CallNode* callNode); + [[nodiscard]] bool emitSelfHostedGetBuiltinConstructor(CallNode* callNode); + [[nodiscard]] bool emitSelfHostedGetBuiltinPrototype(CallNode* callNode); + [[nodiscard]] bool emitSelfHostedGetBuiltinSymbol(CallNode* callNode); + [[nodiscard]] bool emitSelfHostedSetIsInlinableLargeFunction( + CallNode* callNode); + [[nodiscard]] bool emitSelfHostedSetCanonicalName(CallNode* callNode); + [[nodiscard]] bool emitSelfHostedArgumentsLength(CallNode* callNode); + [[nodiscard]] bool emitSelfHostedGetArgument(CallNode* callNode); +#ifdef DEBUG + void assertSelfHostedExpectedTopLevel(ParseNode* node); + void assertSelfHostedUnsafeGetReservedSlot(ListNode* argsList); + void assertSelfHostedUnsafeSetReservedSlot(ListNode* argsList); +#endif + + [[nodiscard]] bool emitDo(BinaryNode* doNode); + [[nodiscard]] bool emitWhile(BinaryNode* whileNode); + + [[nodiscard]] bool emitFor( + ForNode* forNode, const EmitterScope* headLexicalEmitterScope = nullptr); + [[nodiscard]] bool emitCStyleFor(ForNode* forNode, + const EmitterScope* headLexicalEmitterScope); + [[nodiscard]] bool emitForIn(ForNode* forNode, + const EmitterScope* headLexicalEmitterScope); + [[nodiscard]] bool emitForOf(ForNode* forNode, + const EmitterScope* headLexicalEmitterScope); + + [[nodiscard]] bool emitInitializeForInOrOfTarget(TernaryNode* forHead); + + [[nodiscard]] bool emitBreak(TaggedParserAtomIndex label); + [[nodiscard]] bool emitContinue(TaggedParserAtomIndex label); + + [[nodiscard]] bool emitFunctionFormalParameters(ParamsBodyNode* paramsBody); + [[nodiscard]] bool emitInitializeFunctionSpecialNames(); + [[nodiscard]] bool emitLexicalInitialization(NameNode* name); + [[nodiscard]] bool emitLexicalInitialization(TaggedParserAtomIndex name); + + // Emit bytecode for the spread operator. + // + // emitSpread expects some values representing the spread target (an array or + // a tuple), the iterator and it's next() method to be on the stack in that + // order (iterator's next() on the bottom). + // The number of values representing the spread target is + // `spreadeeStackItems`: it's 2 for arrays (one for the array and one for the + // index) and 1 for tuples (the tuple itself). + // Since arrays and tuples use different opcodes to initialize new elements, + // it must be specified using `storeElementOp`. + // When emitSpread() finishes, the stack only contains the values representing + // the spread target. + [[nodiscard]] bool emitSpread(SelfHostedIter selfHostedIter, + int spreadeeStackItems, JSOp storeElementOp); + // This shortcut can be used when spreading into arrays, as it assumes + // `spreadeeStackItems = 2` (|ARRAY INDEX|) and `storeElementOp = + // JSOp::InitElemInc` + [[nodiscard]] bool emitSpread( + SelfHostedIter selfHostedIter = SelfHostedIter::Deny); + + enum class ClassNameKind { + // The class name is defined through its BindingIdentifier, if present. + BindingName, + + // The class is anonymous and has a statically inferred name. + InferredName, + + // The class is anonymous and has a dynamically computed name. + ComputedName + }; + + [[nodiscard]] bool emitClass( + ClassNode* classNode, ClassNameKind nameKind = ClassNameKind::BindingName, + TaggedParserAtomIndex nameForAnonymousClass = + TaggedParserAtomIndex::null()); + + [[nodiscard]] bool emitSuperElemOperands( + PropertyByValue* elem, EmitElemOption opts = EmitElemOption::Get); + [[nodiscard]] bool emitSuperGetElem(PropertyByValue* elem, + bool isCall = false); + + [[nodiscard]] bool emitCalleeAndThis(ParseNode* callee, ParseNode* call, + CallOrNewEmitter& cone); + + [[nodiscard]] bool emitOptionalCalleeAndThis(ParseNode* callee, + CallNode* call, + CallOrNewEmitter& cone, + OptionalEmitter& oe); + +#ifdef ENABLE_RECORD_TUPLE + [[nodiscard]] bool emitRecordLiteral(ListNode* record); + [[nodiscard]] bool emitTupleLiteral(ListNode* tuple); +#endif + + [[nodiscard]] bool emitExportDefault(BinaryNode* exportNode); + + [[nodiscard]] bool emitReturnRval() { return emit1(JSOp::RetRval); } + + [[nodiscard]] bool emitCheckPrivateField(ThrowCondition throwCondition, + ThrowMsgKind msgKind) { + return emit3(JSOp::CheckPrivateField, uint8_t(throwCondition), + uint8_t(msgKind)); + } + + [[nodiscard]] bool emitNewPrivateName(TaggedParserAtomIndex bindingName, + TaggedParserAtomIndex symbolName); + + template + [[nodiscard]] bool emitNewPrivateNames(ListNode* classMembers); + + [[nodiscard]] bool emitNewPrivateNames(TaggedParserAtomIndex privateBrandName, + ListNode* classMembers); + + [[nodiscard]] js::UniquePtr createImmutableScriptData(); + + private: + [[nodiscard]] SelfHostedIter getSelfHostedIterFor(ParseNode* parseNode); + + [[nodiscard]] bool emitSelfHostedGetBuiltinConstructorOrPrototype( + CallNode* callNode, bool isConstructor); + + public: +#if defined(DEBUG) || defined(JS_JITSPEW) + void dumpAtom(TaggedParserAtomIndex index) const; +#endif +}; + +class MOZ_RAII AutoCheckUnstableEmitterScope { +#ifdef DEBUG + bool prev_; + BytecodeEmitter* bce_; +#endif + + public: + AutoCheckUnstableEmitterScope() = delete; + explicit AutoCheckUnstableEmitterScope(BytecodeEmitter* bce) +#ifdef DEBUG + : bce_(bce) +#endif + { +#ifdef DEBUG + prev_ = bce_->unstableEmitterScope; + bce_->unstableEmitterScope = true; +#endif + } + ~AutoCheckUnstableEmitterScope() { +#ifdef DEBUG + bce_->unstableEmitterScope = prev_; +#endif + } +}; + +} /* namespace frontend */ +} /* namespace js */ + +#endif /* frontend_BytecodeEmitter_h */ diff --git a/js/src/frontend/BytecodeOffset.h b/js/src/frontend/BytecodeOffset.h new file mode 100644 index 0000000000..cb13e9e79a --- /dev/null +++ b/js/src/frontend/BytecodeOffset.h @@ -0,0 +1,153 @@ +/* -*- 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/. */ + +#ifndef frontend_BytecodeOffset_h +#define frontend_BytecodeOffset_h + +#include "mozilla/Assertions.h" // MOZ_ASSERT +#include "mozilla/CheckedInt.h" // mozilla::CheckedInt + +#include // ptrdiff_t + +namespace js { +namespace frontend { + +class BytecodeOffsetDiff; + +// The offset inside bytecode. +class BytecodeOffset { + private: + static const ptrdiff_t INVALID_OFFSET = -1; + + ptrdiff_t value_ = 0; + + struct Invalid {}; + explicit constexpr BytecodeOffset(Invalid) : value_(INVALID_OFFSET) {} + + public: + constexpr BytecodeOffset() = default; + + explicit BytecodeOffset(ptrdiff_t value) : value_(value) { + MOZ_ASSERT(value >= 0); + } + + static constexpr BytecodeOffset invalidOffset() { + return BytecodeOffset(Invalid()); + } + + bool operator==(const BytecodeOffset& rhs) const { + return value_ == rhs.value_; + } + + bool operator!=(const BytecodeOffset& rhs) const { return !(*this == rhs); } + + inline BytecodeOffsetDiff operator-(const BytecodeOffset& rhs) const; + inline BytecodeOffset operator+(const BytecodeOffsetDiff& diff) const; + + inline BytecodeOffset& operator+=(const BytecodeOffsetDiff& diff); + inline BytecodeOffset& operator-=(const BytecodeOffsetDiff& diff); + + bool operator<(const BytecodeOffset& rhs) const { + MOZ_ASSERT(valid()); + MOZ_ASSERT(rhs.valid()); + return value_ < rhs.value_; + } + bool operator<=(const BytecodeOffset& rhs) const { + MOZ_ASSERT(valid()); + MOZ_ASSERT(rhs.valid()); + return value_ <= rhs.value_; + } + bool operator>(const BytecodeOffset& rhs) const { + MOZ_ASSERT(valid()); + MOZ_ASSERT(rhs.valid()); + return value_ > rhs.value_; + } + bool operator>=(const BytecodeOffset& rhs) const { + MOZ_ASSERT(valid()); + MOZ_ASSERT(rhs.valid()); + return value_ >= rhs.value_; + } + + ptrdiff_t value() const { return value_; } + uint32_t toUint32() const { + MOZ_ASSERT(size_t(uint32_t(value_)) == size_t(value_)); + return uint32_t(value_); + } + + bool valid() const { return value_ != INVALID_OFFSET; } +}; + +class BytecodeOffsetDiff { + private: + friend class BytecodeOffset; + + ptrdiff_t value_ = 0; + + public: + constexpr BytecodeOffsetDiff() = default; + + explicit constexpr BytecodeOffsetDiff(ptrdiff_t value_) : value_(value_) {} + + bool operator==(const BytecodeOffsetDiff& rhs) const { + return value_ == rhs.value_; + } + + bool operator!=(const BytecodeOffsetDiff& rhs) const { + return !(*this == rhs); + } + + BytecodeOffsetDiff operator+(const BytecodeOffsetDiff& rhs) const { + mozilla::CheckedInt result = value_; + result += rhs.value_; + return BytecodeOffsetDiff(result.value()); + } + + ptrdiff_t value() const { return value_; } + uint32_t toUint32() const { + MOZ_ASSERT(size_t(uint32_t(value_)) == size_t(value_)); + return uint32_t(value_); + } +}; + +inline BytecodeOffsetDiff BytecodeOffset::operator-( + const BytecodeOffset& rhs) const { + MOZ_ASSERT(valid()); + MOZ_ASSERT(rhs.valid()); + mozilla::CheckedInt result = value_; + result -= rhs.value_; + return BytecodeOffsetDiff(result.value()); +} + +inline BytecodeOffset BytecodeOffset::operator+( + const BytecodeOffsetDiff& diff) const { + MOZ_ASSERT(valid()); + mozilla::CheckedInt result = value_; + result += diff.value_; + return BytecodeOffset(result.value()); +} + +inline BytecodeOffset& BytecodeOffset::operator+=( + const BytecodeOffsetDiff& diff) { + MOZ_ASSERT(valid()); + mozilla::CheckedInt result = value_; + result += diff.value_; + value_ = result.value(); + return *this; +} + +inline BytecodeOffset& BytecodeOffset::operator-=( + const BytecodeOffsetDiff& diff) { + MOZ_ASSERT(valid()); + mozilla::CheckedInt result = value_; + result -= diff.value_; + value_ = result.value(); + return *this; +} + +} /* namespace frontend */ +} /* namespace js */ + +#endif /* frontend_BytecodeOffset_h */ diff --git a/js/src/frontend/BytecodeSection.cpp b/js/src/frontend/BytecodeSection.cpp new file mode 100644 index 0000000000..95ba28566d --- /dev/null +++ b/js/src/frontend/BytecodeSection.cpp @@ -0,0 +1,195 @@ +/* -*- 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/BytecodeSection.h" + +#include "mozilla/Assertions.h" // MOZ_ASSERT + +#include "frontend/AbstractScopePtr.h" // ScopeIndex +#include "frontend/CompilationStencil.h" // CompilationStencil +#include "frontend/FrontendContext.h" // FrontendContext +#include "frontend/SharedContext.h" // FunctionBox +#include "vm/BytecodeUtil.h" // INDEX_LIMIT, StackUses, StackDefs +#include "vm/GlobalObject.h" +#include "vm/JSContext.h" // JSContext +#include "vm/RegExpObject.h" // RegexpObject +#include "vm/Scope.h" // GlobalScope + +using namespace js; +using namespace js::frontend; + +bool GCThingList::append(FunctionBox* funbox, GCThingIndex* index) { + // Append the function to the vector and return the index in *index. + *index = GCThingIndex(vector.length()); + + if (!vector.emplaceBack(funbox->index())) { + return false; + } + return true; +} + +AbstractScopePtr GCThingList::getScope(size_t index) const { + const TaggedScriptThingIndex& elem = vector[index]; + if (elem.isEmptyGlobalScope()) { + // The empty enclosing scope should be stored by + // CompilationInput::initForSelfHostingGlobal. + return AbstractScopePtr::compilationEnclosingScope(compilationState); + } + return AbstractScopePtr(compilationState, elem.toScope()); +} + +mozilla::Maybe GCThingList::getScopeIndex(size_t index) const { + const TaggedScriptThingIndex& elem = vector[index]; + if (elem.isEmptyGlobalScope()) { + return mozilla::Nothing(); + } + return mozilla::Some(vector[index].toScope()); +} + +TaggedParserAtomIndex GCThingList::getAtom(size_t index) const { + const TaggedScriptThingIndex& elem = vector[index]; + return elem.toAtom(); +} + +bool js::frontend::EmitScriptThingsVector( + JSContext* cx, const CompilationAtomCache& atomCache, + const CompilationStencil& stencil, CompilationGCOutput& gcOutput, + mozilla::Span things, + mozilla::Span output) { + MOZ_ASSERT(things.size() <= INDEX_LIMIT); + MOZ_ASSERT(things.size() == output.size()); + + for (uint32_t i = 0; i < things.size(); i++) { + const auto& thing = things[i]; + switch (thing.tag()) { + case TaggedScriptThingIndex::Kind::ParserAtomIndex: + case TaggedScriptThingIndex::Kind::WellKnown: { + JSString* str = atomCache.getExistingStringAt(cx, thing.toAtom()); + MOZ_ASSERT(str); + output[i] = JS::GCCellPtr(str); + break; + } + case TaggedScriptThingIndex::Kind::Null: + output[i] = JS::GCCellPtr(nullptr); + break; + case TaggedScriptThingIndex::Kind::BigInt: { + const BigIntStencil& data = stencil.bigIntData[thing.toBigInt()]; + BigInt* bi = data.createBigInt(cx); + if (!bi) { + return false; + } + output[i] = JS::GCCellPtr(bi); + break; + } + case TaggedScriptThingIndex::Kind::ObjLiteral: { + const ObjLiteralStencil& data = + stencil.objLiteralData[thing.toObjLiteral()]; + JS::GCCellPtr ptr = data.create(cx, atomCache); + if (!ptr) { + return false; + } + output[i] = ptr; + break; + } + case TaggedScriptThingIndex::Kind::RegExp: { + RegExpStencil& data = stencil.regExpData[thing.toRegExp()]; + RegExpObject* regexp = data.createRegExp(cx, atomCache); + if (!regexp) { + return false; + } + output[i] = JS::GCCellPtr(regexp); + break; + } + case TaggedScriptThingIndex::Kind::Scope: + output[i] = JS::GCCellPtr(gcOutput.getScope(thing.toScope())); + break; + case TaggedScriptThingIndex::Kind::Function: + output[i] = JS::GCCellPtr(gcOutput.getFunction(thing.toFunction())); + break; + case TaggedScriptThingIndex::Kind::EmptyGlobalScope: { + Scope* scope = &cx->global()->emptyGlobalScope(); + output[i] = JS::GCCellPtr(scope); + break; + } + } + } + + return true; +} + +bool CGTryNoteList::append(TryNoteKind kind, uint32_t stackDepth, + BytecodeOffset start, BytecodeOffset end) { + MOZ_ASSERT(start <= end); + + // Offsets are given relative to sections, but we only expect main-section + // to have TryNotes. In finish() we will fixup base offset. + + TryNote note(uint32_t(kind), stackDepth, start.toUint32(), + (end - start).toUint32()); + + return list.append(note); +} + +bool CGScopeNoteList::append(GCThingIndex scopeIndex, BytecodeOffset offset, + uint32_t parent) { + ScopeNote note; + note.index = scopeIndex; + note.start = offset.toUint32(); + note.length = 0; + note.parent = parent; + + return list.append(note); +} + +void CGScopeNoteList::recordEnd(uint32_t index, BytecodeOffset offset) { + recordEndImpl(index, offset.toUint32()); +} + +void CGScopeNoteList::recordEndFunctionBodyVar(uint32_t index) { + recordEndImpl(index, UINT32_MAX); +} + +void CGScopeNoteList::recordEndImpl(uint32_t index, uint32_t offset) { + MOZ_ASSERT(index < length()); + MOZ_ASSERT(list[index].length == 0); + MOZ_ASSERT(offset >= list[index].start); + list[index].length = offset - list[index].start; +} + +BytecodeSection::BytecodeSection(FrontendContext* fc, uint32_t lineNum, + uint32_t column) + : code_(fc), + notes_(fc), + lastNoteOffset_(0), + tryNoteList_(fc), + scopeNoteList_(fc), + resumeOffsetList_(fc), + currentLine_(lineNum), + lastColumn_(column) {} + +void BytecodeSection::updateDepth(JSOp op, BytecodeOffset target) { + jsbytecode* pc = code(target); + + int nuses = StackUses(op, pc); + int ndefs = StackDefs(op); + + stackDepth_ -= nuses; + MOZ_ASSERT(stackDepth_ >= 0); + stackDepth_ += ndefs; + + if (uint32_t(stackDepth_) > maxStackDepth_) { + maxStackDepth_ = stackDepth_; + } +} + +PerScriptData::PerScriptData(FrontendContext* fc, + frontend::CompilationState& compilationState) + : gcThingList_(fc, compilationState), + atomIndices_(fc->nameCollectionPool()) {} + +bool PerScriptData::init(FrontendContext* fc) { + return atomIndices_.acquire(fc); +} diff --git a/js/src/frontend/BytecodeSection.h b/js/src/frontend/BytecodeSection.h new file mode 100644 index 0000000000..955d6fc748 --- /dev/null +++ b/js/src/frontend/BytecodeSection.h @@ -0,0 +1,393 @@ +/* -*- 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/. */ + +#ifndef frontend_BytecodeSection_h +#define frontend_BytecodeSection_h + +#include "mozilla/Attributes.h" // MOZ_STACK_CLASS +#include "mozilla/Maybe.h" // mozilla::Maybe +#include "mozilla/Span.h" // mozilla::Span + +#include // ptrdiff_t, size_t +#include // uint16_t, int32_t, uint32_t + +#include "frontend/AbstractScopePtr.h" // AbstractScopePtr, ScopeIndex +#include "frontend/BytecodeOffset.h" // BytecodeOffset +#include "frontend/CompilationStencil.h" // CompilationStencil, CompilationGCOutput, CompilationAtomCache +#include "frontend/FrontendContext.h" // FrontendContext +#include "frontend/JumpList.h" // JumpTarget +#include "frontend/NameCollections.h" // AtomIndexMap, PooledMapPtr +#include "frontend/ParseNode.h" // BigIntLiteral +#include "frontend/ParserAtom.h" // ParserAtomsTable, TaggedParserAtomIndex, ParserAtom +#include "frontend/SourceNotes.h" // SrcNote +#include "frontend/Stencil.h" // Stencils +#include "js/TypeDecls.h" // jsbytecode, JSContext +#include "js/Vector.h" // Vector +#include "vm/SharedStencil.h" // TryNote, ScopeNote, GCThingIndex +#include "vm/StencilEnums.h" // TryNoteKind + +namespace js { +namespace frontend { + +class FunctionBox; + +struct MOZ_STACK_CLASS GCThingList { + // The BCE accumulates TaggedScriptThingIndex items so use a vector type. We + // reserve some stack slots to avoid allocating for most small scripts. + using ScriptThingsStackVector = Vector; + + CompilationState& compilationState; + ScriptThingsStackVector vector; + + // Index of the first scope in the vector. + mozilla::Maybe firstScopeIndex; + + explicit GCThingList(FrontendContext* fc, CompilationState& compilationState) + : compilationState(compilationState), vector(fc) {} + + [[nodiscard]] bool append(TaggedParserAtomIndex atom, + ParserAtom::Atomize atomize, GCThingIndex* index) { + *index = GCThingIndex(vector.length()); + compilationState.parserAtoms.markUsedByStencil(atom, atomize); + if (!vector.emplaceBack(atom)) { + return false; + } + return true; + } + [[nodiscard]] bool append(ScopeIndex scope, GCThingIndex* index) { + *index = GCThingIndex(vector.length()); + if (!vector.emplaceBack(scope)) { + return false; + } + if (!firstScopeIndex) { + firstScopeIndex.emplace(*index); + } + return true; + } + [[nodiscard]] bool append(BigIntLiteral* literal, GCThingIndex* index) { + *index = GCThingIndex(vector.length()); + if (!vector.emplaceBack(literal->index())) { + return false; + } + return true; + } + [[nodiscard]] bool append(RegExpLiteral* literal, GCThingIndex* index) { + *index = GCThingIndex(vector.length()); + if (!vector.emplaceBack(literal->index())) { + return false; + } + return true; + } + [[nodiscard]] bool append(ObjLiteralIndex objlit, GCThingIndex* index) { + *index = GCThingIndex(vector.length()); + if (!vector.emplaceBack(objlit)) { + return false; + } + return true; + } + [[nodiscard]] bool append(FunctionBox* funbox, GCThingIndex* index); + + [[nodiscard]] bool appendEmptyGlobalScope(GCThingIndex* index) { + *index = GCThingIndex(vector.length()); + EmptyGlobalScopeType emptyGlobalScope; + if (!vector.emplaceBack(emptyGlobalScope)) { + return false; + } + if (!firstScopeIndex) { + firstScopeIndex.emplace(*index); + } + return true; + } + + uint32_t length() const { return vector.length(); } + + const ScriptThingsStackVector& objects() { return vector; } + + AbstractScopePtr getScope(size_t index) const; + + // Index of scope within CompilationStencil or Nothing is the scope is + // EmptyGlobalScopeType. + mozilla::Maybe getScopeIndex(size_t index) const; + + TaggedParserAtomIndex getAtom(size_t index) const; + + AbstractScopePtr firstScope() const { + MOZ_ASSERT(firstScopeIndex.isSome()); + return getScope(*firstScopeIndex); + } +}; + +[[nodiscard]] bool EmitScriptThingsVector( + JSContext* cx, const CompilationAtomCache& atomCache, + const CompilationStencil& stencil, CompilationGCOutput& gcOutput, + mozilla::Span things, + mozilla::Span output); + +struct CGTryNoteList { + Vector list; + explicit CGTryNoteList(FrontendContext* fc) : list(fc) {} + + [[nodiscard]] bool append(TryNoteKind kind, uint32_t stackDepth, + BytecodeOffset start, BytecodeOffset end); + mozilla::Span span() const { + return {list.begin(), list.length()}; + } + size_t length() const { return list.length(); } +}; + +struct CGScopeNoteList { + Vector list; + explicit CGScopeNoteList(FrontendContext* fc) : list(fc) {} + + [[nodiscard]] bool append(GCThingIndex scopeIndex, BytecodeOffset offset, + uint32_t parent); + void recordEnd(uint32_t index, BytecodeOffset offset); + void recordEndFunctionBodyVar(uint32_t index); + mozilla::Span span() const { + return {list.begin(), list.length()}; + } + size_t length() const { return list.length(); } + + private: + void recordEndImpl(uint32_t index, uint32_t offset); +}; + +struct CGResumeOffsetList { + Vector list; + explicit CGResumeOffsetList(FrontendContext* fc) : list(fc) {} + + [[nodiscard]] bool append(uint32_t offset) { return list.append(offset); } + mozilla::Span span() const { + return {list.begin(), list.length()}; + } + size_t length() const { return list.length(); } +}; + +static constexpr size_t MaxBytecodeLength = INT32_MAX; +static constexpr size_t MaxSrcNotesLength = INT32_MAX; + +// Have a few inline elements, so as to avoid heap allocation for tiny +// sequences. See bug 1390526. +typedef Vector BytecodeVector; +typedef Vector SrcNotesVector; + +// Bytecode and all data directly associated with specific opcode/index inside +// bytecode is stored in this class. +class BytecodeSection { + public: + BytecodeSection(FrontendContext* fc, uint32_t lineNum, uint32_t column); + + // ---- Bytecode ---- + + BytecodeVector& code() { return code_; } + const BytecodeVector& code() const { return code_; } + + jsbytecode* code(BytecodeOffset offset) { + return code_.begin() + offset.value(); + } + BytecodeOffset offset() const { + return BytecodeOffset(code_.end() - code_.begin()); + } + + // ---- Source notes ---- + + SrcNotesVector& notes() { return notes_; } + const SrcNotesVector& notes() const { return notes_; } + + BytecodeOffset lastNoteOffset() const { return lastNoteOffset_; } + void setLastNoteOffset(BytecodeOffset offset) { lastNoteOffset_ = offset; } + + // ---- Jump ---- + + BytecodeOffset lastTargetOffset() const { return lastTarget_.offset; } + void setLastTargetOffset(BytecodeOffset offset) { + lastTarget_.offset = offset; + } + + // ---- Stack ---- + + int32_t stackDepth() const { return stackDepth_; } + void setStackDepth(int32_t depth) { stackDepth_ = depth; } + + uint32_t maxStackDepth() const { return maxStackDepth_; } + + void updateDepth(JSOp op, BytecodeOffset target); + + // ---- Try notes ---- + + CGTryNoteList& tryNoteList() { return tryNoteList_; }; + const CGTryNoteList& tryNoteList() const { return tryNoteList_; }; + + // ---- Scope ---- + + CGScopeNoteList& scopeNoteList() { return scopeNoteList_; }; + const CGScopeNoteList& scopeNoteList() const { return scopeNoteList_; }; + + // ---- Generator ---- + + CGResumeOffsetList& resumeOffsetList() { return resumeOffsetList_; } + const CGResumeOffsetList& resumeOffsetList() const { + return resumeOffsetList_; + } + + uint32_t numYields() const { return numYields_; } + void addNumYields() { numYields_++; } + + // ---- Line and column ---- + + uint32_t currentLine() const { return currentLine_; } + uint32_t lastColumn() const { return lastColumn_; } + void setCurrentLine(uint32_t line, uint32_t sourceOffset) { + currentLine_ = line; + lastColumn_ = 0; + lastSourceOffset_ = sourceOffset; + } + + void setLastColumn(uint32_t column, uint32_t offset) { + lastColumn_ = column; + lastSourceOffset_ = offset; + } + + void updateSeparatorPosition() { + lastSeparatorCodeOffset_ = code().length(); + lastSeparatorSourceOffset_ = lastSourceOffset_; + lastSeparatorLine_ = currentLine_; + lastSeparatorColumn_ = lastColumn_; + } + + void updateSeparatorPositionIfPresent() { + if (lastSeparatorCodeOffset_ == code().length()) { + lastSeparatorSourceOffset_ = lastSourceOffset_; + lastSeparatorLine_ = currentLine_; + lastSeparatorColumn_ = lastColumn_; + } + } + + bool isDuplicateLocation() const { + return lastSeparatorLine_ == currentLine_ && + lastSeparatorColumn_ == lastColumn_; + } + + bool atSeparator(uint32_t offset) const { + return lastSeparatorSourceOffset_ == offset; + } + + // ---- JIT ---- + + uint32_t numICEntries() const { return numICEntries_; } + void incrementNumICEntries() { + MOZ_ASSERT(numICEntries_ != UINT32_MAX, "Shouldn't overflow"); + numICEntries_++; + } + void setNumICEntries(uint32_t entries) { numICEntries_ = entries; } + + private: + // ---- Bytecode ---- + + // Bytecode. + BytecodeVector code_; + + // ---- Source notes ---- + + // Source notes + SrcNotesVector notes_; + + // Code offset for last source note + BytecodeOffset lastNoteOffset_; + + // ---- Jump ---- + + // Last jump target emitted. + JumpTarget lastTarget_; + + // ---- Stack ---- + + // Maximum number of expression stack slots so far. + uint32_t maxStackDepth_ = 0; + + // Current stack depth in script frame. + int32_t stackDepth_ = 0; + + // ---- Try notes ---- + + // List of emitted try notes. + CGTryNoteList tryNoteList_; + + // ---- Scope ---- + + // List of emitted block scope notes. + CGScopeNoteList scopeNoteList_; + + // ---- Generator ---- + + // Certain ops (yield, await) have an entry in the script's resumeOffsets + // list. This can be used to map from the op's resumeIndex to the bytecode + // offset of the next pc. This indirection makes it easy to resume in the JIT + // (because BaselineScript stores a resumeIndex => native code array). + CGResumeOffsetList resumeOffsetList_; + + // Number of yield instructions emitted. Does not include JSOp::Await. + uint32_t numYields_ = 0; + + // ---- Line and column ---- + + // Line number for srcnotes. + // + // WARNING: If this becomes out of sync with already-emitted srcnotes, + // we can get undefined behavior. + uint32_t currentLine_; + + // Zero-based column index on currentLine_ of last + // SrcNoteType::ColSpan-annotated opcode. + // + // WARNING: If this becomes out of sync with already-emitted srcnotes, + // we can get undefined behavior. + uint32_t lastColumn_ = 0; + + // The last code unit used for srcnotes. + uint32_t lastSourceOffset_ = 0; + + // The offset, line and column numbers of the last opcode for the + // breakpoint for step execution. + uint32_t lastSeparatorCodeOffset_ = 0; + uint32_t lastSeparatorSourceOffset_ = 0; + uint32_t lastSeparatorLine_ = 0; + uint32_t lastSeparatorColumn_ = 0; + + // ---- JIT ---- + + // Number of ICEntries in the script. There's one ICEntry for each JOF_IC op + // and, if the script is a function, for |this| and each formal argument. + uint32_t numICEntries_ = 0; +}; + +// Data that is not directly associated with specific opcode/index inside +// bytecode, but referred from bytecode is stored in this class. +class PerScriptData { + public: + PerScriptData(FrontendContext* fc, + frontend::CompilationState& compilationState); + + [[nodiscard]] bool init(FrontendContext* fc); + + GCThingList& gcThingList() { return gcThingList_; } + const GCThingList& gcThingList() const { return gcThingList_; } + + PooledMapPtr& atomIndices() { return atomIndices_; } + const PooledMapPtr& atomIndices() const { return atomIndices_; } + + private: + // List of emitted scopes/objects/bigints. + GCThingList gcThingList_; + + // Map from atom to index. + PooledMapPtr atomIndices_; +}; + +} /* namespace frontend */ +} /* namespace js */ + +#endif /* frontend_BytecodeSection_h */ diff --git a/js/src/frontend/CForEmitter.cpp b/js/src/frontend/CForEmitter.cpp new file mode 100644 index 0000000000..4fccde0e88 --- /dev/null +++ b/js/src/frontend/CForEmitter.cpp @@ -0,0 +1,179 @@ +/* -*- 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/CForEmitter.h" + +#include "frontend/BytecodeEmitter.h" // BytecodeEmitter +#include "frontend/EmitterScope.h" // EmitterScope +#include "vm/Opcodes.h" // JSOp +#include "vm/ScopeKind.h" // ScopeKind +#include "vm/StencilEnums.h" // TryNoteKind + +using namespace js; +using namespace js::frontend; + +using mozilla::Maybe; + +CForEmitter::CForEmitter(BytecodeEmitter* bce, + const EmitterScope* headLexicalEmitterScopeForLet) + : bce_(bce), + headLexicalEmitterScopeForLet_(headLexicalEmitterScopeForLet) {} + +bool CForEmitter::emitInit(const Maybe& initPos) { + MOZ_ASSERT(state_ == State::Start); + + loopInfo_.emplace(bce_, StatementKind::ForLoop); + + if (initPos) { + if (!bce_->updateSourceCoordNotes(*initPos)) { + return false; + } + } + +#ifdef DEBUG + state_ = State::Init; +#endif + return true; +} + +bool CForEmitter::emitCond(const Maybe& condPos) { + MOZ_ASSERT(state_ == State::Init); + + // ES 13.7.4.8 step 2. The initial freshening. + // + // If an initializer let-declaration may be captured during loop + // iteration, the current scope has an environment. If so, freshen the + // current environment to expose distinct bindings for each loop + // iteration. + if (headLexicalEmitterScopeForLet_) { + // The environment chain only includes an environment for the + // for(;;) loop head's let-declaration *if* a scope binding is + // captured, thus requiring a fresh environment each iteration. If + // a lexical scope exists for the head, it must be the innermost + // one. If that scope has closed-over bindings inducing an + // environment, recreate the current environment. + MOZ_ASSERT(headLexicalEmitterScopeForLet_ == bce_->innermostEmitterScope()); + MOZ_ASSERT(headLexicalEmitterScopeForLet_->scope(bce_).kind() == + ScopeKind::Lexical); + + if (headLexicalEmitterScopeForLet_->hasEnvironment()) { + if (!bce_->emitInternedScopeOp(headLexicalEmitterScopeForLet_->index(), + JSOp::FreshenLexicalEnv)) { + return false; + } + } + } + + if (!loopInfo_->emitLoopHead(bce_, condPos)) { + // [stack] + return false; + } + +#ifdef DEBUG + state_ = State::Cond; +#endif + return true; +} + +bool CForEmitter::emitBody(Cond cond) { + MOZ_ASSERT(state_ == State::Cond); + cond_ = cond; + + if (cond_ == Cond::Present) { + if (!bce_->emitJump(JSOp::JumpIfFalse, &loopInfo_->breaks)) { + return false; + } + } + + tdzCache_.emplace(bce_); + +#ifdef DEBUG + state_ = State::Body; +#endif + return true; +} + +bool CForEmitter::emitUpdate(Update update, const Maybe& updatePos) { + MOZ_ASSERT(state_ == State::Body); + update_ = update; + tdzCache_.reset(); + + // Set loop and enclosing "update" offsets, for continue. Note that we + // continue to immediately *before* the block-freshening: continuing must + // refresh the block. + if (!loopInfo_->emitContinueTarget(bce_)) { + return false; + } + + // ES 13.7.4.8 step 3.e. The per-iteration freshening. + if (headLexicalEmitterScopeForLet_) { + MOZ_ASSERT(headLexicalEmitterScopeForLet_ == bce_->innermostEmitterScope()); + MOZ_ASSERT(headLexicalEmitterScopeForLet_->scope(bce_).kind() == + ScopeKind::Lexical); + + if (headLexicalEmitterScopeForLet_->hasEnvironment()) { + if (!bce_->emitInternedScopeOp(headLexicalEmitterScopeForLet_->index(), + JSOp::FreshenLexicalEnv)) { + return false; + } + } + } + + // The update code may not be executed at all; it needs its own TDZ + // cache. + if (update_ == Update::Present) { + tdzCache_.emplace(bce_); + + if (updatePos) { + if (!bce_->updateSourceCoordNotes(*updatePos)) { + return false; + } + } + } + +#ifdef DEBUG + state_ = State::Update; +#endif + return true; +} + +bool CForEmitter::emitEnd(uint32_t forPos) { + MOZ_ASSERT(state_ == State::Update); + + if (update_ == Update::Present) { + tdzCache_.reset(); + + // [stack] UPDATE + + if (!bce_->emit1(JSOp::Pop)) { + // [stack] + return false; + } + } + + if (cond_ == Cond::Missing && update_ == Update::Missing) { + // If there is no condition clause and no update clause, mark + // the loop-ending "goto" with the location of the "for". + // This ensures that the debugger will stop on each loop + // iteration. + if (!bce_->updateSourceCoordNotes(forPos)) { + return false; + } + } + + // Emit the loop-closing jump. + if (!loopInfo_->emitLoopEnd(bce_, JSOp::Goto, TryNoteKind::Loop)) { + // [stack] + return false; + } + + loopInfo_.reset(); + +#ifdef DEBUG + state_ = State::End; +#endif + return true; +} diff --git a/js/src/frontend/CForEmitter.h b/js/src/frontend/CForEmitter.h new file mode 100644 index 0000000000..231dd318be --- /dev/null +++ b/js/src/frontend/CForEmitter.h @@ -0,0 +1,175 @@ +/* -*- 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/. */ + +#ifndef frontend_CForEmitter_h +#define frontend_CForEmitter_h + +#include "mozilla/Attributes.h" // MOZ_STACK_CLASS +#include "mozilla/Maybe.h" // mozilla::Maybe + +#include // uint32_t + +#include "frontend/BytecodeControlStructures.h" // LoopControl +#include "frontend/TDZCheckCache.h" // TDZCheckCache + +namespace js { +namespace frontend { + +struct BytecodeEmitter; +class EmitterScope; + +// Class for emitting bytecode for c-style for block. +// +// Usage: (check for the return value is omitted for simplicity) +// +// `for (init; cond; update) body` +// CForEmitter cfor(this, headLexicalEmitterScopeForLet or nullptr); +// cfor.emitInit(Some(offset_of_init)); +// emit(init); // without pushing value +// cfor.emitCond(Some(offset_of_cond)); +// emit(cond); +// cfor.emitBody(CForEmitter::Cond::Present); +// emit(body); +// cfor.emitUpdate(CForEmitter::Update::Present, Some(offset_of_update))); +// emit(update); +// cfor.emitEnd(offset_of_for); +// +// `for (;;) body` +// CForEmitter cfor(this, nullptr); +// cfor.emitInit(Nothing()); +// cfor.emitCond(Nothing()); +// cfor.emitBody(CForEmitter::Cond::Missing); +// emit(body); +// cfor.emitUpdate(CForEmitter::Update::Missing, Nothing()); +// cfor.emitEnd(offset_of_for); +// +class MOZ_STACK_CLASS CForEmitter { + // Basic structure of the bytecode (not complete). + // + // If `cond` is not empty: + // {init} + // loop: + // JSOp::LoopHead + // {cond} + // JSOp::JumpIfFalse break + // {body} + // continue: + // {update} + // JSOp::Goto loop + // break: + // + // If `cond` is empty: + // {init} + // loop: + // JSOp::LoopHead + // {body} + // continue: + // {update} + // JSOp::Goto loop + // break: + // + public: + enum class Cond { Missing, Present }; + enum class Update { Missing, Present }; + + private: + BytecodeEmitter* bce_; + + // Whether the c-style for loop has `cond` and `update`. + Cond cond_ = Cond::Missing; + Update update_ = Update::Missing; + + mozilla::Maybe loopInfo_; + + // The lexical scope to be freshened for each iteration. + // See the comment in `emitCond` for more details. + // + // ### Scope freshening + // + // Each iteration of a `for (let V...)` loop creates a fresh loop variable + // binding for V, even if the loop is a C-style `for(;;)` loop: + // + // var funcs = []; + // for (let i = 0; i < 2; i++) + // funcs.push(function() { return i; }); + // assertEq(funcs[0](), 0); // the two closures capture... + // assertEq(funcs[1](), 1); // ...two different `i` bindings + // + // This is implemented by "freshening" the implicit block -- changing the + // scope chain to a fresh clone of the instantaneous block object -- each + // iteration, just before evaluating the "update" in for(;;) loops. + // + // ECMAScript doesn't freshen in `for (const ...;;)`. Lack of freshening + // isn't directly observable in-language because `const`s can't be mutated, + // but it *can* be observed in the Debugger API. + const EmitterScope* headLexicalEmitterScopeForLet_; + + mozilla::Maybe tdzCache_; + +#ifdef DEBUG + // The state of this emitter. + // + // +-------+ emitInit +------+ emitCond +------+ emitBody +------+ + // | Start |--------->| Init |--------->| Cond |--------->| Body |-+ + // +-------+ +------+ +------+ +------+ | + // | + // +-------------------------------------+ + // | + // | emitUpdate +--------+ emitEnd +-----+ + // +----------->| Update |-------->| End | + // +--------+ +-----+ + enum class State { + // The initial state. + Start, + + // After calling emitInit. + Init, + + // After calling emitCond. + Cond, + + // After calling emitBody. + Body, + + // After calling emitUpdate. + Update, + + // After calling emitEnd. + End + }; + State state_ = State::Start; +#endif + + public: + CForEmitter(BytecodeEmitter* bce, + const EmitterScope* headLexicalEmitterScopeForLet); + + // Parameters are the offset in the source code for each character below: + // + // for ( x = 10 ; x < 20 ; x ++ ) { f(x); } + // ^ ^ ^ ^ + // | | | | + // | | | updatePos + // | | | + // | | condPos + // | | + // | initPos + // | + // forPos + // + // Can be Nothing() if not available. + [[nodiscard]] bool emitInit(const mozilla::Maybe& initPos); + [[nodiscard]] bool emitCond(const mozilla::Maybe& condPos); + [[nodiscard]] bool emitBody(Cond cond); + [[nodiscard]] bool emitUpdate(Update update, + const mozilla::Maybe& updatePos); + [[nodiscard]] bool emitEnd(uint32_t forPos); +}; + +} /* namespace frontend */ +} /* namespace js */ + +#endif /* frontend_CForEmitter_h */ diff --git a/js/src/frontend/CallOrNewEmitter.cpp b/js/src/frontend/CallOrNewEmitter.cpp new file mode 100644 index 0000000000..fc4e449d56 --- /dev/null +++ b/js/src/frontend/CallOrNewEmitter.cpp @@ -0,0 +1,365 @@ +/* -*- 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/CallOrNewEmitter.h" + +#include "frontend/BytecodeEmitter.h" +#include "frontend/NameOpEmitter.h" +#include "vm/Opcodes.h" + +using namespace js; +using namespace js::frontend; + +CallOrNewEmitter::CallOrNewEmitter(BytecodeEmitter* bce, JSOp op, + ArgumentsKind argumentsKind, + ValueUsage valueUsage) + : bce_(bce), op_(op), argumentsKind_(argumentsKind) { + if (op_ == JSOp::Call && valueUsage == ValueUsage::IgnoreValue) { + op_ = JSOp::CallIgnoresRv; + } + + MOZ_ASSERT(isCall() || isNew() || isSuperCall()); +} + +bool CallOrNewEmitter::emitNameCallee(TaggedParserAtomIndex name) { + MOZ_ASSERT(state_ == State::Start); + + // [stack] + + NameOpEmitter noe( + bce_, name, + isCall() ? NameOpEmitter::Kind::Call : NameOpEmitter::Kind::Get); + if (!noe.emitGet()) { + // [stack] # if isCall() + // [stack] CALLEE THIS + // [stack] # if isNew() or isSuperCall() + // [stack] CALLEE + return false; + } + + state_ = State::NameCallee; + return true; +} + +[[nodiscard]] PropOpEmitter& CallOrNewEmitter::prepareForPropCallee( + bool isSuperProp) { + MOZ_ASSERT(state_ == State::Start); + MOZ_ASSERT(bce_->emitterMode != BytecodeEmitter::SelfHosting); + + // [stack] + + poe_.emplace(bce_, + isCall() ? PropOpEmitter::Kind::Call : PropOpEmitter::Kind::Get, + isSuperProp ? PropOpEmitter::ObjKind::Super + : PropOpEmitter::ObjKind::Other); + + state_ = State::PropCallee; + return *poe_; +} + +[[nodiscard]] ElemOpEmitter& CallOrNewEmitter::prepareForElemCallee( + bool isSuperElem) { + MOZ_ASSERT(state_ == State::Start); + MOZ_ASSERT(bce_->emitterMode != BytecodeEmitter::SelfHosting); + + // [stack] + + eoe_.emplace(bce_, + isCall() ? ElemOpEmitter::Kind::Call : ElemOpEmitter::Kind::Get, + isSuperElem ? ElemOpEmitter::ObjKind::Super + : ElemOpEmitter::ObjKind::Other); + + state_ = State::ElemCallee; + return *eoe_; +} + +PrivateOpEmitter& CallOrNewEmitter::prepareForPrivateCallee( + TaggedParserAtomIndex privateName) { + MOZ_ASSERT(state_ == State::Start); + MOZ_ASSERT(bce_->emitterMode != BytecodeEmitter::SelfHosting); + + // [stack] + + xoe_.emplace( + bce_, + isCall() ? PrivateOpEmitter::Kind::Call : PrivateOpEmitter::Kind::Get, + privateName); + state_ = State::PrivateCallee; + return *xoe_; +} + +bool CallOrNewEmitter::prepareForFunctionCallee() { + MOZ_ASSERT(state_ == State::Start); + MOZ_ASSERT(bce_->emitterMode != BytecodeEmitter::SelfHosting); + + // [stack] + + state_ = State::FunctionCallee; + return true; +} + +bool CallOrNewEmitter::emitSuperCallee() { + MOZ_ASSERT(state_ == State::Start); + MOZ_ASSERT(bce_->emitterMode != BytecodeEmitter::SelfHosting); + + // [stack] + + if (!bce_->emitThisEnvironmentCallee()) { + // [stack] CALLEE + return false; + } + if (!bce_->emit1(JSOp::SuperFun)) { + // [stack] SUPER_FUN + return false; + } + if (!bce_->emit1(JSOp::IsConstructing)) { + // [stack] SUPER_FUN IS_CONSTRUCTING + return false; + } + + state_ = State::SuperCallee; + return true; +} + +bool CallOrNewEmitter::prepareForOtherCallee() { + MOZ_ASSERT(state_ == State::Start); + MOZ_ASSERT(bce_->emitterMode != BytecodeEmitter::SelfHosting); + + // [stack] + + state_ = State::OtherCallee; + return true; +} + +bool CallOrNewEmitter::emitThis() { + MOZ_ASSERT(state_ == State::NameCallee || state_ == State::PropCallee || + state_ == State::ElemCallee || state_ == State::PrivateCallee || + state_ == State::FunctionCallee || state_ == State::SuperCallee || + state_ == State::OtherCallee); + + // [stack] # if isCall() + // [stack] CALLEE THIS? + // [stack] # if isNew() or isSuperCall() + // [stack] CALLEE + + bool needsThis = false; + switch (state_) { + case State::NameCallee: + if (!isCall()) { + needsThis = true; + } + break; + case State::PropCallee: + poe_.reset(); + if (!isCall()) { + needsThis = true; + } + break; + case State::ElemCallee: + eoe_.reset(); + if (!isCall()) { + needsThis = true; + } + break; + case State::PrivateCallee: + xoe_.reset(); + if (!isCall()) { + needsThis = true; + } + break; + case State::FunctionCallee: + needsThis = true; + break; + case State::SuperCallee: + break; + case State::OtherCallee: + needsThis = true; + break; + default:; + } + if (needsThis) { + if (isNew() || isSuperCall()) { + if (!bce_->emit1(JSOp::IsConstructing)) { + // [stack] CALLEE IS_CONSTRUCTING + return false; + } + } else { + if (!bce_->emit1(JSOp::Undefined)) { + // [stack] CALLEE THIS + return false; + } + } + } + + // [stack] CALLEE THIS + + state_ = State::This; + return true; +} + +bool CallOrNewEmitter::prepareForNonSpreadArguments() { + MOZ_ASSERT(state_ == State::This); + MOZ_ASSERT(!isSpread()); + + // [stack] CALLEE THIS + + state_ = State::Arguments; + return true; +} + +// See the usage in the comment at the top of the class. +bool CallOrNewEmitter::wantSpreadOperand() { + MOZ_ASSERT(state_ == State::This); + MOZ_ASSERT(isSpread()); + + // [stack] CALLEE THIS + + state_ = State::WantSpreadOperand; + return isSingleSpread() || isPassthroughRest(); +} + +bool CallOrNewEmitter::prepareForSpreadArguments() { + MOZ_ASSERT(state_ == State::WantSpreadOperand); + MOZ_ASSERT(isSpread()); + MOZ_ASSERT(!isSingleSpread() && !isPassthroughRest()); + + // [stack] CALLEE THIS + + state_ = State::Arguments; + return true; +} + +bool CallOrNewEmitter::emitSpreadArgumentsTest() { + // Caller should check wantSpreadOperand before this. + MOZ_ASSERT(state_ == State::WantSpreadOperand); + MOZ_ASSERT(isSpread()); + MOZ_ASSERT(isSingleSpread() || isPassthroughRest()); + + // [stack] CALLEE THIS ARG0 + + if (isSingleSpread()) { + // Emit a preparation code to optimize the spread call: + // + // g(...args); + // + // If the spread operand is a packed array, skip the spread + // operation and pass it directly to spread call operation. + // See the comment in OptimizeSpreadCall in Interpreter.cpp + // for the optimizable conditions. + // [stack] CALLEE THIS ARG0 + + ifNotOptimizable_.emplace(bce_); + if (!bce_->emit1(JSOp::Dup)) { + // [stack] CALLEE THIS ARG0 ARG0 + return false; + } + if (!bce_->emit1(JSOp::OptimizeSpreadCall)) { + // [stack] CALLEE THIS ARG0 ARRAY_OR_UNDEF + return false; + } + + if (!bce_->emit1(JSOp::Dup)) { + // [stack] CALLEE THIS ARG0 ARRAY_OR_UNDEF ARRAY_OR_UNDEF + return false; + } + if (!bce_->emit1(JSOp::Undefined)) { + // [stack] CALLEE THIS ARG0 ARRAY_OR_UNDEF ARRAY_OR_UNDEF UNDEF + return false; + } + if (!bce_->emit1(JSOp::StrictEq)) { + // [stack] CALLEE THIS ARG0 ARRAY_OR_UNDEF EQ + return false; + } + + if (!ifNotOptimizable_->emitThenElse()) { + // [stack] CALLEE THIS ARG0 ARRAY_OR_UNDEF + return false; + } + if (!bce_->emit1(JSOp::Pop)) { + // [stack] CALLEE THIS ARG0 + return false; + } + } + + state_ = State::SpreadArgumentsTest; + return true; +} + +bool CallOrNewEmitter::wantSpreadIteration() { + MOZ_ASSERT(state_ == State::SpreadArgumentsTest); + MOZ_ASSERT(isSpread()); + + state_ = State::SpreadIteration; + return !isPassthroughRest(); +} + +bool CallOrNewEmitter::emitSpreadArgumentsTestEnd() { + MOZ_ASSERT(state_ == State::SpreadIteration); + MOZ_ASSERT(isSpread()); + + if (isSingleSpread()) { + if (!ifNotOptimizable_->emitElse()) { + // [stack] CALLEE THIS ARG0 ARRAY_OR_UNDEF + return false; + } + if (!bce_->emit1(JSOp::Swap)) { + // [stack] CALLEE THIS ARRAY_OR_UNDEF ARG0 + return false; + } + if (!bce_->emit1(JSOp::Pop)) { + // [stack] CALLEE THIS ARRAY_OR_UNDEF + return false; + } + + if (!ifNotOptimizable_->emitEnd()) { + // [stack] CALLEE THIS ARR + return false; + } + + ifNotOptimizable_.reset(); + } + + state_ = State::Arguments; + return true; +} + +bool CallOrNewEmitter::emitEnd(uint32_t argc, uint32_t beginPos) { + MOZ_ASSERT(state_ == State::Arguments); + + // [stack] # if isCall() + // [stack] CALLEE THIS ARG0 ... ARGN + // [stack] # if isNew() or isSuperCall() + // [stack] CALLEE IS_CONSTRUCTING ARG0 ... ARGN NEW.TARGET? + + if (!bce_->updateSourceCoordNotes(beginPos)) { + return false; + } + if (!bce_->markSimpleBreakpoint()) { + return false; + } + if (!isSpread()) { + if (!bce_->emitCall(op_, argc)) { + // [stack] RVAL + return false; + } + } else { + if (!bce_->emit1(op_)) { + // [stack] RVAL + return false; + } + } + + if (isEval()) { + uint32_t lineNum = bce_->errorReporter().lineAt(beginPos); + if (!bce_->emitUint32Operand(JSOp::Lineno, lineNum)) { + // [stack] RVAL + return false; + } + } + + state_ = State::End; + return true; +} diff --git a/js/src/frontend/CallOrNewEmitter.h b/js/src/frontend/CallOrNewEmitter.h new file mode 100644 index 0000000000..5abf644b70 --- /dev/null +++ b/js/src/frontend/CallOrNewEmitter.h @@ -0,0 +1,352 @@ +/* -*- 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/. */ + +#ifndef frontend_CallOrNewEmitter_h +#define frontend_CallOrNewEmitter_h + +#include "mozilla/Attributes.h" +#include "mozilla/Maybe.h" + +#include + +#include "frontend/ElemOpEmitter.h" +#include "frontend/IfEmitter.h" +#include "frontend/PrivateOpEmitter.h" +#include "frontend/PropOpEmitter.h" +#include "frontend/ValueUsage.h" +#include "vm/BytecodeUtil.h" +#include "vm/Opcodes.h" + +namespace js { +namespace frontend { + +struct BytecodeEmitter; +class TaggedParserAtomIndex; + +// Class for emitting bytecode for call or new expression. +// +// Usage: (check for the return value is omitted for simplicity) +// +// `print(arg);` +// CallOrNewEmitter cone(this, JSOp::Call, +// CallOrNewEmitter::ArgumentsKind::Other, +// ValueUsage::WantValue); +// cone.emitNameCallee(print); +// cone.emitThis(); +// cone.prepareForNonSpreadArguments(); +// emit(arg); +// cone.emitEnd(1, offset_of_callee); +// +// `callee.prop(arg1, arg2);` +// CallOrNewEmitter cone(this, JSOp::Call, +// CallOrNewEmitter::ArgumentsKind::Other, +// ValueUsage::WantValue); +// PropOpEmitter& poe = cone.prepareForPropCallee(false); +// ... emit `callee.prop` with `poe` here... +// cone.emitThis(); +// cone.prepareForNonSpreadArguments(); +// emit(arg1); +// emit(arg2); +// cone.emitEnd(2, offset_of_callee); +// +// `callee[key](arg);` +// CallOrNewEmitter cone(this, JSOp::Call, +// CallOrNewEmitter::ArgumentsKind::Other, +// ValueUsage::WantValue); +// ElemOpEmitter& eoe = cone.prepareForElemCallee(false); +// ... emit `callee[key]` with `eoe` here... +// cone.emitThis(); +// cone.prepareForNonSpreadArguments(); +// emit(arg); +// cone.emitEnd(1, offset_of_callee); +// +// `callee.#method(arg);` +// CallOrNewEmitter cone(this, JSOp::Call, +// CallOrNewEmitter::ArgumentsKind::Other, +// ValueUsage::WantValue); +// PrivateOpEmitter& xoe = cone.prepareForPrivateCallee(); +// ... emit `callee.#method` with `xoe` here... +// cone.prepareForNonSpreadArguments(); +// emit(arg); +// cone.emitEnd(1, offset_of_callee); +// +// `(function() { ... })(arg);` +// CallOrNewEmitter cone(this, JSOp::Call, +// CallOrNewEmitter::ArgumentsKind::Other, +// ValueUsage::WantValue); +// cone.prepareForFunctionCallee(); +// emit(function); +// cone.emitThis(); +// cone.prepareForNonSpreadArguments(); +// emit(arg); +// cone.emitEnd(1, offset_of_callee); +// +// `super(arg);` +// CallOrNewEmitter cone(this, JSOp::Call, +// CallOrNewEmitter::ArgumentsKind::Other, +// ValueUsage::WantValue); +// cone.emitSuperCallee(); +// cone.emitThis(); +// cone.prepareForNonSpreadArguments(); +// emit(arg); +// cone.emitEnd(1, offset_of_callee); +// +// `(some_other_expression)(arg);` +// CallOrNewEmitter cone(this, JSOp::Call, +// CallOrNewEmitter::ArgumentsKind::Other, +// ValueUsage::WantValue); +// cone.prepareForOtherCallee(); +// emit(some_other_expression); +// cone.emitThis(); +// cone.prepareForNonSpreadArguments(); +// emit(arg); +// cone.emitEnd(1, offset_of_callee); +// +// `print(...arg);` +// CallOrNewEmitter cone(this, JSOp::SpreadCall, +// CallOrNewEmitter::ArgumentsKind::SingleSpread, +// ValueUsage::WantValue); +// cone.emitNameCallee(print); +// cone.emitThis(); +// if (cone.wantSpreadOperand()) { +// emit(arg) +// } +// cone.emitSpreadArgumentsTest(); +// emit([...arg]); +// cone.emitEnd(1, offset_of_callee); +// +// `new f(arg);` +// CallOrNewEmitter cone(this, JSOp::New, +// CallOrNewEmitter::ArgumentsKind::Other, +// ValueUsage::WantValue); +// cone.emitNameCallee(f); +// cone.emitThis(); +// cone.prepareForNonSpreadArguments(); +// emit(arg); +// cone.emitEnd(1, offset_of_callee); +// +class MOZ_STACK_CLASS CallOrNewEmitter { + public: + enum class ArgumentsKind { + Other, + + // Specify this for the following case: + // + // g(...input); + // + // This enables optimization to avoid allocating an intermediate array + // for spread operation. + // + // wantSpreadOperand() returns true when this is specified. + SingleSpread, + + // Used for default derived class constructors: + // + // constructor(...args) { + // super(...args); + // } + // + // The rest-parameter is directly passed through to the `super` call without + // using the iteration protocol. + PassthroughRest, + }; + + private: + BytecodeEmitter* bce_; + + // The opcode for the call or new. + JSOp op_; + + // Whether the call is a spread call with single parameter or not. + // See the comment in emitSpreadArgumentsTest for more details. + ArgumentsKind argumentsKind_; + + // The branch for spread call optimization. + mozilla::Maybe ifNotOptimizable_; + + mozilla::Maybe poe_; + mozilla::Maybe eoe_; + mozilla::Maybe xoe_; + + // The state of this emitter. + // + // +-------+ emitNameCallee +------------+ + // | Start |-+------------------------->| NameCallee |------+ + // +-------+ | +------------+ | + // | | + // | prepareForPropCallee +------------+ v + // +------------------------->| PropCallee |----->+ + // | +------------+ | + // | | + // | prepareForElemCallee +------------+ v + // +------------------------->| ElemCallee |----->+ + // | +------------+ | + // | | + // | prepareForPrivateCallee +---------------+ v + // +------------------------->| PrivateCallee |-->+ + // | +---------------+ | + // | | + // | prepareForFunctionCallee +----------------+ v + // +------------------------->| FunctionCallee |->+ + // | +----------------+ | + // | | + // | emitSuperCallee +-------------+ v + // +------------------------->| SuperCallee |---->+ + // | +-------------+ | + // | | + // | prepareForOtherCallee +-------------+ v + // +------------------------->| OtherCallee |---->+ + // +-------------+ | + // | + // +--------------------------------------------------------+ + // | + // | emitThis +------+ + // +--------->| This |-+ + // +------+ | + // | + // +-------------------+ + // | + // | [!isSpread] + // | prepareForNonSpreadArguments +-----------+ emitEnd +-----+ + // +------------------------------->+->| Arguments |-------->| End | + // | ^ +-----------+ +-----+ + // | | + // | +<------------------------------------+ + // | | | + // | | emitSpreadArgumentsTestEnd | + // | | | + // | | +-----------------+ | + // | +---------| SpreadIteration |------+ | + // | +-----------------+ | | + // | +----------------------------------+ | + // | | | + // | | wantSpreadIteration | + // | | | + // | | +---------------------+ | + // | +---------| SpreadArgumentsTest |--+ | + // | +---------------------+ | | + // | [isSpread] | | + // | wantSpreadOperand +-------------------+ emitSpreadArgumentsTest | | + // +-------------------->| WantSpreadOperand |-------------------------+ | + // | +-------------------+ | + // | | + // | | + // | | + // | [isSpread] | + // | prepareForSpreadArguments | + // +----------------------------------------------------------------------+ + enum class State { + // The initial state. + Start, + + // After calling emitNameCallee. + NameCallee, + + // After calling prepareForPropCallee. + PropCallee, + + // After calling prepareForElemCallee. + ElemCallee, + + // After calling prepareForPrivateCallee. + PrivateCallee, + + // After calling prepareForFunctionCallee. + FunctionCallee, + + // After calling emitSuperCallee. + SuperCallee, + + // After calling prepareForOtherCallee. + OtherCallee, + + // After calling emitThis. + This, + + // After calling wantSpreadOperand. + WantSpreadOperand, + + // After calling emitSpreadArgumentsTest. + SpreadArgumentsTest, + + // After calling wantSpreadIteration. + SpreadIteration, + + // After calling prepareForNonSpreadArguments. + Arguments, + + // After calling emitEnd. + End + }; + State state_ = State::Start; + + public: + CallOrNewEmitter(BytecodeEmitter* bce, JSOp op, ArgumentsKind argumentsKind, + ValueUsage valueUsage); + + private: + [[nodiscard]] bool isCall() const { + return op_ == JSOp::Call || op_ == JSOp::CallIgnoresRv || + op_ == JSOp::SpreadCall || isEval(); + } + + [[nodiscard]] bool isNew() const { + return op_ == JSOp::New || op_ == JSOp::SpreadNew; + } + + [[nodiscard]] bool isSuperCall() const { + return op_ == JSOp::SuperCall || op_ == JSOp::SpreadSuperCall; + } + + [[nodiscard]] bool isEval() const { + return op_ == JSOp::Eval || op_ == JSOp::StrictEval || + op_ == JSOp::SpreadEval || op_ == JSOp::StrictSpreadEval; + } + + [[nodiscard]] bool isSpread() const { return IsSpreadOp(op_); } + + [[nodiscard]] bool isSingleSpread() const { + return argumentsKind_ == ArgumentsKind::SingleSpread; + } + + [[nodiscard]] bool isPassthroughRest() const { + return argumentsKind_ == ArgumentsKind::PassthroughRest; + } + + public: + [[nodiscard]] bool emitNameCallee(TaggedParserAtomIndex name); + [[nodiscard]] PropOpEmitter& prepareForPropCallee(bool isSuperProp); + [[nodiscard]] ElemOpEmitter& prepareForElemCallee(bool isSuperElem); + [[nodiscard]] PrivateOpEmitter& prepareForPrivateCallee( + TaggedParserAtomIndex privateName); + [[nodiscard]] bool prepareForFunctionCallee(); + [[nodiscard]] bool emitSuperCallee(); + [[nodiscard]] bool prepareForOtherCallee(); + + [[nodiscard]] bool emitThis(); + + [[nodiscard]] bool prepareForNonSpreadArguments(); + [[nodiscard]] bool prepareForSpreadArguments(); + + // See the usage in the comment at the top of the class. + [[nodiscard]] bool wantSpreadOperand(); + [[nodiscard]] bool emitSpreadArgumentsTest(); + [[nodiscard]] bool emitSpreadArgumentsTestEnd(); + [[nodiscard]] bool wantSpreadIteration(); + + // Parameters are the offset in the source code for each character below: + // + // callee(arg); + // ^ + // | + // beginPos + [[nodiscard]] bool emitEnd(uint32_t argc, uint32_t beginPos); +}; + +} /* namespace frontend */ +} /* namespace js */ + +#endif /* frontend_CallOrNewEmitter_h */ diff --git a/js/src/frontend/CompilationStencil.h b/js/src/frontend/CompilationStencil.h new file mode 100644 index 0000000000..617a3dd487 --- /dev/null +++ b/js/src/frontend/CompilationStencil.h @@ -0,0 +1,1842 @@ +/* -*- 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/. */ + +#ifndef frontend_CompilationStencil_h +#define frontend_CompilationStencil_h + +#include "mozilla/AlreadyAddRefed.h" // already_AddRefed +#include "mozilla/Assertions.h" // MOZ_ASSERT +#include "mozilla/Atomics.h" // mozilla::Atomic +#include "mozilla/Attributes.h" // MOZ_RAII +#include "mozilla/HashTable.h" // mozilla::HashMap +#include "mozilla/Maybe.h" // mozilla::Maybe +#include "mozilla/MemoryReporting.h" // mozilla::MallocSizeOf +#include "mozilla/RefPtr.h" // RefPtr +#include "mozilla/Span.h" +#include "mozilla/Variant.h" // mozilla::Variant + +#include "ds/LifoAlloc.h" +#include "frontend/FrontendContext.h" // AutoReportFrontendContext +#include "frontend/NameAnalysisTypes.h" // EnvironmentCoordinate +#include "frontend/ParserAtom.h" // ParserAtomsTable, TaggedParserAtomIndex +#include "frontend/ScriptIndex.h" // ScriptIndex +#include "frontend/SharedContext.h" +#include "frontend/Stencil.h" +#include "frontend/TaggedParserAtomIndexHasher.h" // TaggedParserAtomIndexHasher +#include "frontend/UsedNameTracker.h" +#include "js/GCVector.h" +#include "js/HashTable.h" +#include "js/RefCounted.h" // AtomicRefCounted +#include "js/Transcoding.h" +#include "js/UniquePtr.h" // js::UniquePtr +#include "js/Vector.h" +#include "js/WasmModule.h" +#include "vm/GlobalObject.h" // GlobalObject +#include "vm/JSContext.h" +#include "vm/JSFunction.h" // JSFunction +#include "vm/JSScript.h" // SourceExtent +#include "vm/Realm.h" +#include "vm/ScopeKind.h" // ScopeKind +#include "vm/SharedStencil.h" // SharedImmutableScriptData + +class JSAtom; +class JSString; + +namespace JS { +class JS_PUBLIC_API ReadOnlyCompileOptions; +} + +namespace js { + +class AtomSet; +class JSONPrinter; +class ModuleObject; + +namespace frontend { + +struct CompilationInput; +struct CompilationStencil; +struct CompilationGCOutput; +class ScriptStencilIterable; +struct InputName; +class ScopeBindingCache; + +// Reference to a Scope within a CompilationStencil. +struct ScopeStencilRef { + const CompilationStencil& context_; + const ScopeIndex scopeIndex_; + + // Lookup the ScopeStencil referenced by this ScopeStencilRef. + inline const ScopeStencil& scope() const; +}; + +// Wraps a scope for a CompilationInput. The scope is either as a GC pointer to +// an instantiated scope, or as a reference to a CompilationStencil. +// +// Note: A scope reference may be nullptr/InvalidIndex if there is no such +// scope, such as the enclosingScope at the end of a scope chain. See `isNull` +// helper. +class InputScope { + using InputScopeStorage = mozilla::Variant; + InputScopeStorage scope_; + + public: + // Create an InputScope given an instantiated scope. + explicit InputScope(Scope* ptr) : scope_(ptr) {} + + // Create an InputScope given a CompilationStencil and the ScopeIndex which is + // an offset within the same CompilationStencil given as argument. + InputScope(const CompilationStencil& context, ScopeIndex scopeIndex) + : scope_(ScopeStencilRef{context, scopeIndex}) {} + + // Returns the variant used by the InputScope. This can be useful for complex + // cases where the following accessors are not enough. + const InputScopeStorage& variant() const { return scope_; } + InputScopeStorage& variant() { return scope_; } + + // This match function will unwrap the variant for each type, and will + // specialize and call the Matcher given as argument with the type and value + // of the stored pointer / reference. + // + // This is useful for cases where the code is literally identical despite + // having different specializations. This is achiveable by relying on + // function overloading when the usage differ between the 2 types. + // + // Example: + // inputScope.match([](auto& scope) { + // // scope is either a `Scope*` or a `ScopeStencilRef`. + // for (auto bi = InputBindingIter(scope); bi; bi++) { + // InputName name(scope, bi.name()); + // // ... + // } + // }); + template + decltype(auto) match(Matcher&& matcher) const& { + return scope_.match(std::forward(matcher)); + } + template + decltype(auto) match(Matcher&& matcher) & { + return scope_.match(std::forward(matcher)); + } + + bool isNull() const { + return scope_.match( + [](const Scope* ptr) { return !ptr; }, + [](const ScopeStencilRef& ref) { return !ref.scopeIndex_.isValid(); }); + } + + ScopeKind kind() const { + return scope_.match( + [](const Scope* ptr) { return ptr->kind(); }, + [](const ScopeStencilRef& ref) { return ref.scope().kind(); }); + }; + bool hasEnvironment() const { + return scope_.match([](const Scope* ptr) { return ptr->hasEnvironment(); }, + [](const ScopeStencilRef& ref) { + return ref.scope().hasEnvironment(); + }); + }; + inline InputScope enclosing() const; + bool hasOnChain(ScopeKind kind) const { + return scope_.match( + [=](const Scope* ptr) { return ptr->hasOnChain(kind); }, + [=](const ScopeStencilRef& ref) { + ScopeStencilRef it = ref; + while (true) { + const ScopeStencil& scope = it.scope(); + if (scope.kind() == kind) { + return true; + } + if (!scope.hasEnclosing()) { + break; + } + new (&it) ScopeStencilRef{ref.context_, scope.enclosing()}; + } + return false; + }); + } + uint32_t environmentChainLength() const { + return scope_.match( + [](const Scope* ptr) { return ptr->environmentChainLength(); }, + [](const ScopeStencilRef& ref) { + uint32_t length = 0; + ScopeStencilRef it = ref; + while (true) { + const ScopeStencil& scope = it.scope(); + if (scope.hasEnvironment() && + scope.kind() != ScopeKind::NonSyntactic) { + length++; + } + if (!scope.hasEnclosing()) { + break; + } + new (&it) ScopeStencilRef{ref.context_, scope.enclosing()}; + } + return length; + }); + } + void trace(JSTracer* trc); + bool isStencil() const { return scope_.is(); }; + + // Various accessors which are valid only when the InputScope is a + // FunctionScope. Some of these accessors are returning values associated with + // the canonical function. + private: + inline FunctionFlags functionFlags() const; + inline ImmutableScriptFlags immutableFlags() const; + + public: + inline MemberInitializers getMemberInitializers() const; + RO_IMMUTABLE_SCRIPT_FLAGS(immutableFlags()) + bool isArrow() const { return functionFlags().isArrow(); } + bool allowSuperProperty() const { + return functionFlags().allowSuperProperty(); + } + bool isClassConstructor() const { + return functionFlags().isClassConstructor(); + } +}; + +// Reference to a Script within a CompilationStencil. +struct ScriptStencilRef { + const CompilationStencil& context_; + const ScriptIndex scriptIndex_; + + inline const ScriptStencil& scriptData() const; + inline const ScriptStencilExtra& scriptExtra() const; +}; + +// Wraps a script for a CompilationInput. The script is either as a BaseScript +// pointer to an instantiated script, or as a reference to a CompilationStencil. +class InputScript { + using InputScriptStorage = mozilla::Variant; + InputScriptStorage script_; + + public: + // Create an InputScript given an instantiated BaseScript pointer. + explicit InputScript(BaseScript* ptr) : script_(ptr) {} + + // Create an InputScript given a CompilationStencil and the ScriptIndex which + // is an offset within the same CompilationStencil given as argument. + InputScript(const CompilationStencil& context, ScriptIndex scriptIndex) + : script_(ScriptStencilRef{context, scriptIndex}) {} + + const InputScriptStorage& raw() const { return script_; } + InputScriptStorage& raw() { return script_; } + + SourceExtent extent() const { + return script_.match( + [](const BaseScript* ptr) { return ptr->extent(); }, + [](const ScriptStencilRef& ref) { return ref.scriptExtra().extent; }); + } + ImmutableScriptFlags immutableFlags() const { + return script_.match( + [](const BaseScript* ptr) { return ptr->immutableFlags(); }, + [](const ScriptStencilRef& ref) { + return ref.scriptExtra().immutableFlags; + }); + } + RO_IMMUTABLE_SCRIPT_FLAGS(immutableFlags()) + FunctionFlags functionFlags() const { + return script_.match( + [](const BaseScript* ptr) { return ptr->function()->flags(); }, + [](const ScriptStencilRef& ref) { + return ref.scriptData().functionFlags; + }); + } + bool hasPrivateScriptData() const { + return script_.match( + [](const BaseScript* ptr) { return ptr->hasPrivateScriptData(); }, + [](const ScriptStencilRef& ref) { + // See BaseScript::CreateRawLazy. + return ref.scriptData().hasGCThings() || + ref.scriptExtra().useMemberInitializers(); + }); + } + InputScope enclosingScope() const { + return script_.match( + [](const BaseScript* ptr) { + return InputScope(ptr->function()->enclosingScope()); + }, + [](const ScriptStencilRef& ref) { + // The ScriptStencilRef only reference lazy Script, otherwise we + // should fetch the enclosing scope using the bodyScope field of the + // immutable data which is a reference to the vector of gc-things. + MOZ_RELEASE_ASSERT(!ref.scriptData().hasSharedData()); + MOZ_ASSERT(ref.scriptData().hasLazyFunctionEnclosingScopeIndex()); + auto scopeIndex = ref.scriptData().lazyFunctionEnclosingScopeIndex(); + return InputScope(ref.context_, scopeIndex); + }); + } + MemberInitializers getMemberInitializers() const { + return script_.match( + [](const BaseScript* ptr) { return ptr->getMemberInitializers(); }, + [](const ScriptStencilRef& ref) { + return ref.scriptExtra().memberInitializers(); + }); + } + + InputName displayAtom() const; + void trace(JSTracer* trc); + bool isNull() const { + return script_.match([](const BaseScript* ptr) { return !ptr; }, + [](const ScriptStencilRef& ref) { return false; }); + } + bool isStencil() const { + return script_.match([](const BaseScript* ptr) { return false; }, + [](const ScriptStencilRef&) { return true; }); + }; +}; + +// Iterator for walking the scope chain, this is identical to ScopeIter but +// accept an InputScope instead of a Scope pointer. +// +// It may be placed in GC containers; for example: +// +// for (Rooted si(cx, InputScopeIter(scope)); si; si++) { +// use(si); +// SomeMayGCOperation(); +// use(si); +// } +// +class MOZ_STACK_CLASS InputScopeIter { + InputScope scope_; + + public: + explicit InputScopeIter(const InputScope& scope) : scope_(scope) {} + + InputScope& scope() { + MOZ_ASSERT(!done()); + return scope_; + } + + const InputScope& scope() const { + MOZ_ASSERT(!done()); + return scope_; + } + + bool done() const { return scope_.isNull(); } + explicit operator bool() const { return !done(); } + void operator++(int) { scope_ = scope_.enclosing(); } + ScopeKind kind() const { return scope_.kind(); } + + // Returns whether this scope has a syntactic environment (i.e., an + // Environment that isn't a non-syntactic With or NonSyntacticVariables) + // on the environment chain. + bool hasSyntacticEnvironment() const { + return scope_.hasEnvironment() && scope_.kind() != ScopeKind::NonSyntactic; + } + + void trace(JSTracer* trc) { scope_.trace(trc); } +}; + +// Reference to a Binding Name within an existing CompilationStencil. +// TaggedParserAtomIndex are in some cases indexes in the parserAtomData of the +// CompilationStencil. +struct NameStencilRef { + const CompilationStencil& context_; + const TaggedParserAtomIndex atomIndex_; +}; + +// Wraps a name for a CompilationInput. The name is either as a GC pointer to +// a JSAtom, or a TaggedParserAtomIndex which might reference to a non-included. +// +// The constructor for this class are using an InputScope as argument. This +// InputScope is made to fetch back the CompilationStencil associated with the +// TaggedParserAtomIndex when using a Stencil as input. +struct InputName { + using InputNameStorage = mozilla::Variant; + InputNameStorage variant_; + + InputName(Scope*, JSAtom* ptr) : variant_(ptr) {} + InputName(const ScopeStencilRef& scope, TaggedParserAtomIndex index) + : variant_(NameStencilRef{scope.context_, index}) {} + InputName(BaseScript*, JSAtom* ptr) : variant_(ptr) {} + InputName(const ScriptStencilRef& script, TaggedParserAtomIndex index) + : variant_(NameStencilRef{script.context_, index}) {} + + // The InputName is either from an instantiated name, or from another + // CompilationStencil. This method interns the current name in the parser atom + // table of a CompilationState, which has a corresponding CompilationInput. + TaggedParserAtomIndex internInto(FrontendContext* fc, + ParserAtomsTable& parserAtoms, + CompilationAtomCache& atomCache); + + // Compare an InputName, which is not yet interned, with `other` is either an + // interned name or a well-known or static string. + // + // The `otherCached` argument should be a reference to a JSAtom*, initialized + // to nullptr, which is used to cache the JSAtom representation of the `other` + // argument if needed. If a different `other` parameter is provided, the + // `otherCached` argument should be reset to nullptr. + bool isEqualTo(FrontendContext* fc, ParserAtomsTable& parserAtoms, + CompilationAtomCache& atomCache, TaggedParserAtomIndex other, + JSAtom** otherCached) const; + + bool isNull() const { + return variant_.match( + [](JSAtom* ptr) { return !ptr; }, + [](const NameStencilRef& ref) { return !ref.atomIndex_; }); + } +}; + +// ScopeContext holds information derived from the scope and environment chains +// to try to avoid the parser needing to traverse VM structures directly. +struct ScopeContext { + // Cache: Scope -> (JSAtom/TaggedParserAtomIndex -> NameLocation) + // + // This cache maps the scope to a hash table which can lookup a name of the + // scope to the equivalent NameLocation. + ScopeBindingCache* scopeCache = nullptr; + + // Generation number of the `scopeCache` collected before filling the cache + // with enclosing scope information. + // + // The generation number, obtained from `scopeCache->getCurrentGeneration()` + // is incremented each time the GC invalidate the content of the cache. The + // `scopeCache` can only be used when the generation number collected before + // filling the cache is identical to the generation number seen when querying + // the cached content. + size_t scopeCacheGen = 0; + + // Class field initializer info if we are nested within a class constructor. + // We may be an combination of arrow and eval context within the constructor. + mozilla::Maybe memberInitializers = {}; + + enum class EnclosingLexicalBindingKind { + Let, + Const, + CatchParameter, + Synthetic, + PrivateMethod, + }; + + using EnclosingLexicalBindingCache = + mozilla::HashMap; + + // Cache of enclosing lexical bindings. + // Used only for eval. + mozilla::Maybe enclosingLexicalBindingCache_; + + // A map of private names to NameLocations used to allow evals to + // provide correct private name semantics (particularly around early + // errors and private brand lookup). + using EffectiveScopePrivateFieldCache = + mozilla::HashMap; + + // Cache of enclosing class's private fields. + // Used only for eval. + mozilla::Maybe + effectiveScopePrivateFieldCache_; + +#ifdef DEBUG + bool enclosingEnvironmentIsDebugProxy_ = false; +#endif + + // How many hops required to navigate from 'enclosingScope' to effective + // scope. + uint32_t effectiveScopeHops = 0; + + uint32_t enclosingScopeEnvironmentChainLength = 0; + + // Eval and arrow scripts also inherit the "this" environment -- used by + // `super` expressions -- from their enclosing script. We count the number of + // environment hops needed to get from enclosing scope to the nearest + // appropriate environment. This value is undefined if the script we are + // compiling is not an eval or arrow-function. + uint32_t enclosingThisEnvironmentHops = 0; + + // The kind of enclosing scope. + ScopeKind enclosingScopeKind = ScopeKind::Global; + + // The type of binding required for `this` of the top level context, as + // indicated by the enclosing scopes of this parse. + // + // NOTE: This is computed based on the effective scope (defined above). + ThisBinding thisBinding = ThisBinding::Global; + + // Eval and arrow scripts inherit certain syntax allowances from their + // enclosing scripts. + bool allowNewTarget = false; + bool allowSuperProperty = false; + bool allowSuperCall = false; + bool allowArguments = true; + + // Indicates there is a 'class' or 'with' scope on enclosing scope chain. + bool inClass = false; + bool inWith = false; + + // True if the enclosing scope is for FunctionScope of arrow function. + bool enclosingScopeIsArrow = false; + + // True if the enclosing scope has environment. + bool enclosingScopeHasEnvironment = false; + +#ifdef DEBUG + // True if the enclosing scope has non-syntactic scope on chain. + bool hasNonSyntacticScopeOnChain = false; + + // True if the enclosing scope has function scope where the function needs + // home object. + bool hasFunctionNeedsHomeObjectOnChain = false; +#endif + + bool init(FrontendContext* fc, CompilationInput& input, + ParserAtomsTable& parserAtoms, ScopeBindingCache* scopeCache, + InheritThis inheritThis, JSObject* enclosingEnv); + + mozilla::Maybe + lookupLexicalBindingInEnclosingScope(TaggedParserAtomIndex name); + + NameLocation searchInEnclosingScope(FrontendContext* fc, + CompilationInput& input, + ParserAtomsTable& parserAtoms, + TaggedParserAtomIndex name); + + bool effectiveScopePrivateFieldCacheHas(TaggedParserAtomIndex name); + mozilla::Maybe getPrivateFieldLocation( + TaggedParserAtomIndex name); + + private: + void computeThisBinding(const InputScope& scope); + void computeThisEnvironment(const InputScope& enclosingScope); + void computeInScope(const InputScope& enclosingScope); + void cacheEnclosingScope(const InputScope& enclosingScope); + NameLocation searchInEnclosingScopeWithCache(FrontendContext* fc, + CompilationInput& input, + ParserAtomsTable& parserAtoms, + TaggedParserAtomIndex name); + NameLocation searchInEnclosingScopeNoCache(FrontendContext* fc, + CompilationInput& input, + ParserAtomsTable& parserAtoms, + TaggedParserAtomIndex name); + + InputScope determineEffectiveScope(InputScope& scope, JSObject* environment); + + bool cachePrivateFieldsForEval(FrontendContext* fc, CompilationInput& input, + JSObject* enclosingEnvironment, + const InputScope& effectiveScope, + ParserAtomsTable& parserAtoms); + + bool cacheEnclosingScopeBindingForEval(FrontendContext* fc, + CompilationInput& input, + ParserAtomsTable& parserAtoms); + + bool addToEnclosingLexicalBindingCache(FrontendContext* fc, + ParserAtomsTable& parserAtoms, + CompilationAtomCache& atomCache, + InputName& name, + EnclosingLexicalBindingKind kind); +}; + +struct CompilationAtomCache { + public: + using AtomCacheVector = JS::GCVector; + + private: + // Atoms lowered into or converted from CompilationStencil.parserAtomData. + // + // This field is here instead of in CompilationGCOutput because atoms lowered + // from JSAtom is part of input (enclosing scope bindings, lazy function name, + // etc), and having 2 vectors in both input/output is error prone. + AtomCacheVector atoms_; + + public: + JSString* getExistingStringAt(ParserAtomIndex index) const; + JSString* getExistingStringAt(JSContext* cx, + TaggedParserAtomIndex taggedIndex) const; + JSString* getStringAt(ParserAtomIndex index) const; + + JSAtom* getExistingAtomAt(ParserAtomIndex index) const; + JSAtom* getExistingAtomAt(JSContext* cx, + TaggedParserAtomIndex taggedIndex) const; + JSAtom* getAtomAt(ParserAtomIndex index) const; + + bool hasAtomAt(ParserAtomIndex index) const; + bool setAtomAt(FrontendContext* fc, ParserAtomIndex index, JSString* atom); + bool allocate(FrontendContext* fc, size_t length); + + bool empty() const { return atoms_.empty(); } + size_t size() const { return atoms_.length(); } + + void stealBuffer(AtomCacheVector& atoms); + void releaseBuffer(AtomCacheVector& atoms); + + void trace(JSTracer* trc); + + size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const { + return atoms_.sizeOfExcludingThis(mallocSizeOf); + } +}; + +// Input of the compilation, including source and enclosing context. +struct CompilationInput { + enum class CompilationTarget { + Global, + SelfHosting, + StandaloneFunction, + StandaloneFunctionInNonSyntacticScope, + Eval, + Module, + Delazification, + }; + CompilationTarget target = CompilationTarget::Global; + + const JS::ReadOnlyCompileOptions& options; + + CompilationAtomCache atomCache; + + private: + InputScript lazy_ = InputScript(nullptr); + + public: + RefPtr source; + + // * If the target is Global, null. + // * If the target is SelfHosting, null. Instantiation code for self-hosting + // will ignore this and use the appropriate empty global scope instead. + // * If the target is StandaloneFunction, an empty global scope. + // * If the target is StandaloneFunctionInNonSyntacticScope, the non-null + // enclosing scope of the function + // * If the target is Eval, the non-null enclosing scope of the `eval`. + // * If the target is Module, null that means empty global scope + // (See EmitterScope::checkEnvironmentChainLength) + // * If the target is Delazification, the non-null enclosing scope of + // the function + InputScope enclosingScope = InputScope(nullptr); + + explicit CompilationInput(const JS::ReadOnlyCompileOptions& options) + : options(options) {} + + private: + bool initScriptSource(FrontendContext* fc); + + public: + bool initForGlobal(FrontendContext* fc) { + target = CompilationTarget::Global; + return initScriptSource(fc); + } + + bool initForSelfHostingGlobal(FrontendContext* fc) { + target = CompilationTarget::SelfHosting; + return initScriptSource(fc); + } + + bool initForStandaloneFunction(JSContext* cx, FrontendContext* fc) { + target = CompilationTarget::StandaloneFunction; + if (!initScriptSource(fc)) { + return false; + } + enclosingScope = InputScope(&cx->global()->emptyGlobalScope()); + return true; + } + + bool initForStandaloneFunctionInNonSyntacticScope( + FrontendContext* fc, Handle functionEnclosingScope); + + bool initForEval(FrontendContext* fc, Handle evalEnclosingScope) { + target = CompilationTarget::Eval; + if (!initScriptSource(fc)) { + return false; + } + enclosingScope = InputScope(evalEnclosingScope); + return true; + } + + bool initForModule(FrontendContext* fc) { + target = CompilationTarget::Module; + if (!initScriptSource(fc)) { + return false; + } + // The `enclosingScope` is the emptyGlobalScope. + return true; + } + + void initFromLazy(JSContext* cx, BaseScript* lazyScript, ScriptSource* ss) { + MOZ_ASSERT(cx->compartment() == lazyScript->compartment()); + + // We can only compile functions whose parents have previously been + // compiled, because compilation requires full information about the + // function's immediately enclosing scope. + MOZ_ASSERT(lazyScript->isReadyForDelazification()); + target = CompilationTarget::Delazification; + lazy_ = InputScript(lazyScript); + source = ss; + enclosingScope = lazy_.enclosingScope(); + } + + void initFromStencil(CompilationStencil& context, ScriptIndex scriptIndex, + ScriptSource* ss) { + target = CompilationTarget::Delazification; + lazy_ = InputScript(context, scriptIndex); + source = ss; + enclosingScope = lazy_.enclosingScope(); + } + + // Returns true if enclosingScope field is provided to init* function, + // instead of setting to empty global internally. + bool hasNonDefaultEnclosingScope() const { + return target == CompilationTarget::StandaloneFunctionInNonSyntacticScope || + target == CompilationTarget::Eval || + target == CompilationTarget::Delazification; + } + + // Returns the enclosing scope provided to init* function. + // nullptr otherwise. + InputScope maybeNonDefaultEnclosingScope() const { + if (hasNonDefaultEnclosingScope()) { + return enclosingScope; + } + return InputScope(nullptr); + } + + // The BaseScript* is needed when instantiating a lazy function. + // See InstantiateTopLevel and FunctionsFromExistingLazy. + InputScript lazyOuterScript() { return lazy_; } + BaseScript* lazyOuterBaseScript() { return lazy_.raw().as(); } + + // The JSFunction* is needed when instantiating a lazy function. + // See FunctionsFromExistingLazy. + JSFunction* function() const { + return lazy_.raw().as()->function(); + } + + // When compiling an inner function, we want to know the unique identifier + // which identify a function. This is computed from the source extend. + SourceExtent extent() const { return lazy_.extent(); } + + // See `BaseScript::immutableFlags_`. + ImmutableScriptFlags immutableFlags() const { return lazy_.immutableFlags(); } + + RO_IMMUTABLE_SCRIPT_FLAGS(immutableFlags()) + + FunctionFlags functionFlags() const { return lazy_.functionFlags(); } + + // When delazifying, return the kind of function which is defined. + FunctionSyntaxKind functionSyntaxKind() const; + + bool hasPrivateScriptData() const { + // This is equivalent to: ngcthings != 0 || useMemberInitializers() + // See BaseScript::CreateRawLazy. + return lazy_.hasPrivateScriptData(); + } + + // Whether this CompilationInput is parsing the top-level of a script, or + // false if we are parsing an inner function. + bool isInitialStencil() { return lazy_.isNull(); } + + // Whether this CompilationInput is parsing a specific function with already + // pre-parsed contextual information. + bool isDelazifying() { return target == CompilationTarget::Delazification; } + + void trace(JSTracer* trc); + + // Size of dynamic data. Note that GC data is counted by GC and not here. We + // also ignore ScriptSource which is a shared RefPtr. + size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const { + return atomCache.sizeOfExcludingThis(mallocSizeOf); + } + size_t sizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const { + return mallocSizeOf(this) + sizeOfExcludingThis(mallocSizeOf); + } + +#if defined(DEBUG) || defined(JS_JITSPEW) + void dump() const; + void dump(js::JSONPrinter& json) const; + void dumpFields(js::JSONPrinter& json) const; +#endif +}; + +// When compiling a function which was previously Syntaxly Parsed, we generated +// some information which made it possible to skip over some parsing phases, +// such as computing closed over bindings as well as parsing inner functions. +// This class contains all information which is generated by the SyntaxParse and +// reused in the FullParse. +class CompilationSyntaxParseCache { + // When delazifying, we should prepare an array which contains all + // stencil-like gc-things such that it can be used by the parser. + // + // When compiling from a Stencil, this will alias the existing Stencil. + mozilla::Span cachedGCThings_; + + // When delazifying, we should perpare an array which contains all + // stencil-like information about scripts, such that it can be used by the + // parser. + // + // When compiling from a Stencil, these will alias the existing Stencil. + mozilla::Span cachedScriptData_; + mozilla::Span cachedScriptExtra_; + + // When delazifying, we copy the atom, either from JSAtom, or from another + // Stencil into TaggedParserAtomIndex which are valid in this current + // CompilationState. + mozilla::Span closedOverBindings_; + + // Atom of the function being compiled. This atom index is valid in the + // current CompilationState. + TaggedParserAtomIndex displayAtom_; + + // Stencil-like data about the function which is being compiled. + ScriptStencilExtra funExtra_; + +#ifdef DEBUG + // Whether any of these data should be considered or not. + bool isInitialized = false; +#endif + + public: + // When doing a full-parse of an incomplete BaseScript*, we have to iterate + // over functions and closed-over bindings, to avoid costly recursive decent + // in inner functions. This function will clone the BaseScript* information to + // make it available as a stencil-like data to the full-parser. + mozilla::Span closedOverBindings() const { + MOZ_ASSERT(isInitialized); + return closedOverBindings_; + } + const ScriptStencil& scriptData(size_t functionIndex) const { + return cachedScriptData_[scriptIndex(functionIndex)]; + } + const ScriptStencilExtra& scriptExtra(size_t functionIndex) const { + return cachedScriptExtra_[scriptIndex(functionIndex)]; + } + + // Return the name of the function being delazified, if any. + TaggedParserAtomIndex displayAtom() const { + MOZ_ASSERT(isInitialized); + return displayAtom_; + } + + // Return the extra information about the function being delazified, if any. + const ScriptStencilExtra& funExtra() const { + MOZ_ASSERT(isInitialized); + return funExtra_; + } + + // Initialize the SynaxParse cache given a LifoAlloc. The JSContext is only + // used for reporting allocation errors. + [[nodiscard]] bool init(FrontendContext* fc, LifoAlloc& alloc, + ParserAtomsTable& parseAtoms, + CompilationAtomCache& atomCache, + const InputScript& lazy); + + private: + // Return the script index of a given inner function. + // + // WARNING: The ScriptIndex returned by this function corresponds to the index + // in the cachedScriptExtra_ and cachedScriptData_ spans. With the + // cachedGCThings_ span, these might be reference to an actual Stencil from + // another compilation. Thus, the ScriptIndex returned by this function should + // not be confused with any ScriptIndex from the CompilationState. + ScriptIndex scriptIndex(size_t functionIndex) const { + MOZ_ASSERT(isInitialized); + auto taggedScriptIndex = cachedGCThings_[functionIndex]; + MOZ_ASSERT(taggedScriptIndex.isFunction()); + return taggedScriptIndex.toFunction(); + } + + [[nodiscard]] bool copyFunctionInfo(FrontendContext* fc, + ParserAtomsTable& parseAtoms, + CompilationAtomCache& atomCache, + const InputScript& lazy); + [[nodiscard]] bool copyScriptInfo(FrontendContext* fc, LifoAlloc& alloc, + ParserAtomsTable& parseAtoms, + CompilationAtomCache& atomCache, + BaseScript* lazy); + [[nodiscard]] bool copyScriptInfo(FrontendContext* fc, LifoAlloc& alloc, + ParserAtomsTable& parseAtoms, + CompilationAtomCache& atomCache, + const ScriptStencilRef& lazy); + [[nodiscard]] bool copyClosedOverBindings(FrontendContext* fc, + LifoAlloc& alloc, + ParserAtomsTable& parseAtoms, + CompilationAtomCache& atomCache, + BaseScript* lazy); + [[nodiscard]] bool copyClosedOverBindings(FrontendContext* fc, + LifoAlloc& alloc, + ParserAtomsTable& parseAtoms, + CompilationAtomCache& atomCache, + const ScriptStencilRef& lazy); +}; + +// AsmJS scripts are very rare on-average, so we use a HashMap to associate +// data with a ScriptStencil. The ScriptStencil has a flag to indicate if we +// need to even do this lookup. +using StencilAsmJSMap = + HashMap, + mozilla::DefaultHasher, js::SystemAllocPolicy>; + +struct StencilAsmJSContainer + : public js::AtomicRefCounted { + StencilAsmJSMap moduleMap; + + StencilAsmJSContainer() = default; + + size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const { + return moduleMap.shallowSizeOfExcludingThis(mallocSizeOf); + } + size_t sizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const { + return mallocSizeOf(this) + sizeOfExcludingThis(mallocSizeOf); + } +}; + +// Store shared data for non-lazy script. +struct SharedDataContainer { + // NOTE: While stored, we must hold a ref-count and care must be taken when + // updating or clearing the pointer. + using SingleSharedDataPtr = SharedImmutableScriptData*; + + using SharedDataVector = + Vector, 0, js::SystemAllocPolicy>; + using SharedDataVectorPtr = SharedDataVector*; + + using SharedDataMap = + HashMap, + mozilla::DefaultHasher, js::SystemAllocPolicy>; + using SharedDataMapPtr = SharedDataMap*; + + private: + enum { + SingleTag = 0, + VectorTag = 1, + MapTag = 2, + BorrowTag = 3, + + TagMask = 3, + }; + + uintptr_t data_ = 0; + + public: + // Defaults to SingleSharedData. + SharedDataContainer() = default; + + SharedDataContainer(const SharedDataContainer&) = delete; + SharedDataContainer(SharedDataContainer&& other) noexcept { + std::swap(data_, other.data_); + MOZ_ASSERT(other.isEmpty()); + } + + SharedDataContainer& operator=(const SharedDataContainer&) = delete; + SharedDataContainer& operator=(SharedDataContainer&& other) noexcept { + std::swap(data_, other.data_); + MOZ_ASSERT(other.isEmpty()); + return *this; + } + + ~SharedDataContainer(); + + [[nodiscard]] bool initVector(FrontendContext* fc); + [[nodiscard]] bool initMap(FrontendContext* fc); + + private: + [[nodiscard]] bool convertFromSingleToMap(FrontendContext* fc); + + public: + bool isEmpty() const { return (data_) == SingleTag; } + bool isSingle() const { return (data_ & TagMask) == SingleTag; } + bool isVector() const { return (data_ & TagMask) == VectorTag; } + bool isMap() const { return (data_ & TagMask) == MapTag; } + bool isBorrow() const { return (data_ & TagMask) == BorrowTag; } + + void setSingle(already_AddRefed&& data) { + MOZ_ASSERT(isEmpty()); + data_ = reinterpret_cast(data.take()); + MOZ_ASSERT(isSingle()); + MOZ_ASSERT(!isEmpty()); + } + + void setBorrow(SharedDataContainer* sharedData) { + MOZ_ASSERT(isEmpty()); + data_ = reinterpret_cast(sharedData) | BorrowTag; + MOZ_ASSERT(isBorrow()); + } + + SingleSharedDataPtr asSingle() const { + MOZ_ASSERT(isSingle()); + MOZ_ASSERT(!isEmpty()); + static_assert(SingleTag == 0); + return reinterpret_cast(data_); + } + SharedDataVectorPtr asVector() const { + MOZ_ASSERT(isVector()); + return reinterpret_cast(data_ & ~TagMask); + } + SharedDataMapPtr asMap() const { + MOZ_ASSERT(isMap()); + return reinterpret_cast(data_ & ~TagMask); + } + SharedDataContainer* asBorrow() const { + MOZ_ASSERT(isBorrow()); + return reinterpret_cast(data_ & ~TagMask); + } + + [[nodiscard]] bool prepareStorageFor(FrontendContext* fc, + size_t nonLazyScriptCount, + size_t allScriptCount); + [[nodiscard]] bool cloneFrom(FrontendContext* fc, + const SharedDataContainer& other); + + // Returns index-th script's shared data, or nullptr if it doesn't have. + js::SharedImmutableScriptData* get(ScriptIndex index) const; + + // Add data for index-th script and share it with VM. + [[nodiscard]] bool addAndShare(FrontendContext* fc, ScriptIndex index, + js::SharedImmutableScriptData* data); + + // Add data for index-th script without sharing it with VM. + // The data should already be shared with VM. + // + // The data is supposed to be added from delazification. + [[nodiscard]] bool addExtraWithoutShare(FrontendContext* fc, + ScriptIndex index, + js::SharedImmutableScriptData* data); + + // Dynamic memory associated with this container. Does not include the + // SharedImmutableScriptData since we are not the unique owner of it. + size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const { + if (isVector()) { + return asVector()->sizeOfIncludingThis(mallocSizeOf); + } + if (isMap()) { + return asMap()->shallowSizeOfIncludingThis(mallocSizeOf); + } + MOZ_ASSERT(isSingle() || isBorrow()); + return 0; + } + +#if defined(DEBUG) || defined(JS_JITSPEW) + void dump() const; + void dump(js::JSONPrinter& json) const; + void dumpFields(js::JSONPrinter& json) const; +#endif +}; + +struct ExtensibleCompilationStencil; + +// The top level struct of stencil specialized for non-extensible case. +// Used as the compilation output, and also XDR decode output. +// +// In XDR decode output case, the span and not-owning pointer fields point +// the internal LifoAlloc and the external XDR buffer. +// +// In BorrowingCompilationStencil usage, span and not-owning pointer fields +// point the ExtensibleCompilationStencil and its LifoAlloc. +// +// The dependent XDR buffer or ExtensibleCompilationStencil must be kept +// alive manually. +// +// See SMDOC in Stencil.h for more info. +struct CompilationStencil { + friend struct ExtensibleCompilationStencil; + + static constexpr ScriptIndex TopLevelIndex = ScriptIndex(0); + + static constexpr size_t LifoAllocChunkSize = 512; + + // The lifetime of this CompilationStencil may be managed by stack allocation, + // UniquePtr, or RefPtr. If a RefPtr is used, this ref-count will track + // the lifetime, otherwise it is ignored. + // + // NOTE: Internal code and public APIs use a mix of these different allocation + // modes. + // + // See: JS::StencilAddRef/Release + mutable mozilla::Atomic refCount{0}; + + private: + // On-heap ExtensibleCompilationStencil that this CompilationStencil owns, + // and this CompilationStencil borrows each data from. + UniquePtr ownedBorrowStencil; + + public: + enum class StorageType { + // Pointers and spans point LifoAlloc or owned buffer. + Owned, + + // Pointers and spans point external data, such as XDR buffer, or not-owned + // ExtensibleCompilationStencil (see BorrowingCompilationStencil). + Borrowed, + + // Pointers and spans point data owned by ownedBorrowStencil. + OwnedExtensible, + }; + StorageType storageType = StorageType::Owned; + + // Value of CanLazilyParse(CompilationInput) on compilation. + // Used during instantiation. + bool canLazilyParse = false; + + // If this stencil is a delazification, this identifies location of the + // function in the source text. + using FunctionKey = SourceExtent::FunctionKey; + FunctionKey functionKey = SourceExtent::NullFunctionKey; + + // This holds allocations that do not require destructors to be run but are + // live until the stencil is released. + LifoAlloc alloc; + + // The source text holder for the script. This may be an empty placeholder if + // the code will fully parsed and options indicate the source will never be + // needed again. + RefPtr source; + + // Stencil for all function and non-function scripts. The TopLevelIndex is + // reserved for the top-level script. This top-level may or may not be a + // function. + mozilla::Span scriptData; + + // Immutable data computed during initial compilation and never updated during + // delazification. + mozilla::Span scriptExtra; + + mozilla::Span gcThingData; + + // scopeData and scopeNames have the same size, and i-th scopeNames contains + // the names for the bindings contained in the slot defined by i-th scopeData. + mozilla::Span scopeData; + mozilla::Span scopeNames; + + // Hold onto the RegExpStencil, BigIntStencil, and ObjLiteralStencil that are + // allocated during parse to ensure correct destruction. + mozilla::Span regExpData; + mozilla::Span bigIntData; + mozilla::Span objLiteralData; + + // List of parser atoms for this compilation. This may contain nullptr entries + // when round-tripping with XDR if the atom was generated in original parse + // but not used by stencil. + ParserAtomSpan parserAtomData; + + // Variable sized container for bytecode and other immutable data. A valid + // stencil always contains at least an entry for `TopLevelIndex` script. + SharedDataContainer sharedData; + + // Module metadata if this is a module compile. + RefPtr moduleMetadata; + + // AsmJS modules generated by parsing. These scripts are never lazy and + // therefore only generated during initial parse. + RefPtr asmJS; + + // End of fields. + + // Construct a CompilationStencil + explicit CompilationStencil(ScriptSource* source) + : alloc(LifoAllocChunkSize), source(source) {} + + // Take the ownership of on-heap ExtensibleCompilationStencil and + // borrow from it. + explicit CompilationStencil( + UniquePtr&& extensibleStencil); + + protected: + void borrowFromExtensibleCompilationStencil( + ExtensibleCompilationStencil& extensibleStencil); + +#ifdef DEBUG + void assertBorrowingFromExtensibleCompilationStencil( + const ExtensibleCompilationStencil& extensibleStencil) const; +#endif + + public: + bool isInitialStencil() const { + return functionKey == SourceExtent::NullFunctionKey; + } + + [[nodiscard]] static bool instantiateStencilAfterPreparation( + JSContext* cx, CompilationInput& input, const CompilationStencil& stencil, + CompilationGCOutput& gcOutput); + + [[nodiscard]] static bool prepareForInstantiate( + FrontendContext* fc, CompilationAtomCache& atomCache, + const CompilationStencil& stencil, CompilationGCOutput& gcOutput); + + [[nodiscard]] static bool instantiateStencils( + JSContext* cx, CompilationInput& input, const CompilationStencil& stencil, + CompilationGCOutput& gcOutput); + + // Decode the special self-hosted stencil + [[nodiscard]] bool instantiateSelfHostedAtoms( + JSContext* cx, AtomSet& atomSet, CompilationAtomCache& atomCache) const; + [[nodiscard]] JSScript* instantiateSelfHostedTopLevelForRealm( + JSContext* cx, CompilationInput& input); + [[nodiscard]] JSFunction* instantiateSelfHostedLazyFunction( + JSContext* cx, CompilationAtomCache& atomCache, ScriptIndex index, + Handle name); + [[nodiscard]] bool delazifySelfHostedFunction(JSContext* cx, + CompilationAtomCache& atomCache, + ScriptIndexRange range, + HandleFunction fun); + + [[nodiscard]] bool serializeStencils(JSContext* cx, CompilationInput& input, + JS::TranscodeBuffer& buf, + bool* succeededOut = nullptr) const; + [[nodiscard]] bool deserializeStencils( + FrontendContext* fc, const JS::ReadOnlyCompileOptions& options, + const JS::TranscodeRange& range, bool* succeededOut = nullptr); + + // To avoid any misuses, make sure this is neither copyable or assignable. + CompilationStencil(const CompilationStencil&) = delete; + CompilationStencil(CompilationStencil&&) = delete; + CompilationStencil& operator=(const CompilationStencil&) = delete; + CompilationStencil& operator=(CompilationStencil&&) = delete; +#ifdef DEBUG + ~CompilationStencil() { + // We can mix UniquePtr<..> and RefPtr<..>. This asserts that a UniquePtr + // does not delete a reference-counted stencil. + MOZ_ASSERT(!refCount); + } +#endif + + static inline ScriptStencilIterable functionScriptStencils( + const CompilationStencil& stencil, CompilationGCOutput& gcOutput); + + void setFunctionKey(BaseScript* lazy) { + functionKey = lazy->extent().toFunctionKey(); + } + + inline size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const; + size_t sizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const { + return mallocSizeOf(this) + sizeOfExcludingThis(mallocSizeOf); + } + + const ParserAtomSpan& parserAtomsSpan() const { return parserAtomData; } + + bool isModule() const; + + bool hasMultipleReference() const { return refCount > 1; } + + bool hasOwnedBorrow() const { + return storageType == StorageType::OwnedExtensible; + } + + ExtensibleCompilationStencil* takeOwnedBorrow() { + MOZ_ASSERT(!hasMultipleReference()); + MOZ_ASSERT(hasOwnedBorrow()); + return ownedBorrowStencil.release(); + } + +#ifdef DEBUG + void assertNoExternalDependency() const; +#endif + +#if defined(DEBUG) || defined(JS_JITSPEW) + void dump() const; + void dump(js::JSONPrinter& json) const; + void dumpFields(js::JSONPrinter& json) const; + + void dumpAtom(TaggedParserAtomIndex index) const; +#endif +}; + +// The top level struct of stencil specialized for extensible case. +// Used as the temporary storage during compilation, an the compilation output. +// +// All not-owning pointer fields point the internal LifoAlloc. +// +// See CompilationStencil for each field's description. +// +// Also see SMDOC in Stencil.h for more info. +struct ExtensibleCompilationStencil { + bool canLazilyParse = false; + + using FunctionKey = SourceExtent::FunctionKey; + + FunctionKey functionKey = SourceExtent::NullFunctionKey; + + // Data pointed by other fields are allocated in this LifoAlloc, + // and moved to `CompilationStencil.alloc`. + LifoAlloc alloc; + + RefPtr source; + + // NOTE: We reserve a modest amount of inline storage in order to reduce + // allocations in the most common delazification cases. These common + // cases have one script and scope, as well as a handful of gcthings. + // For complex pages this covers about 75% of delazifications. + + Vector scriptData; + Vector scriptExtra; + + Vector gcThingData; + + Vector scopeData; + Vector scopeNames; + + Vector regExpData; + Vector bigIntData; + Vector objLiteralData; + + // Table of parser atoms for this compilation. + ParserAtomsTable parserAtoms; + + SharedDataContainer sharedData; + + RefPtr moduleMetadata; + + RefPtr asmJS; + + explicit ExtensibleCompilationStencil(ScriptSource* source); + + explicit ExtensibleCompilationStencil(CompilationInput& input); + ExtensibleCompilationStencil(const JS::ReadOnlyCompileOptions& options, + RefPtr source); + + ExtensibleCompilationStencil(ExtensibleCompilationStencil&& other) noexcept + : canLazilyParse(other.canLazilyParse), + functionKey(other.functionKey), + alloc(CompilationStencil::LifoAllocChunkSize), + source(std::move(other.source)), + scriptData(std::move(other.scriptData)), + scriptExtra(std::move(other.scriptExtra)), + gcThingData(std::move(other.gcThingData)), + scopeData(std::move(other.scopeData)), + scopeNames(std::move(other.scopeNames)), + regExpData(std::move(other.regExpData)), + bigIntData(std::move(other.bigIntData)), + objLiteralData(std::move(other.objLiteralData)), + parserAtoms(std::move(other.parserAtoms)), + sharedData(std::move(other.sharedData)), + moduleMetadata(std::move(other.moduleMetadata)), + asmJS(std::move(other.asmJS)) { + alloc.steal(&other.alloc); + parserAtoms.fixupAlloc(alloc); + } + + ExtensibleCompilationStencil& operator=( + ExtensibleCompilationStencil&& other) noexcept { + MOZ_ASSERT(alloc.isEmpty()); + + canLazilyParse = other.canLazilyParse; + functionKey = other.functionKey; + source = std::move(other.source); + scriptData = std::move(other.scriptData); + scriptExtra = std::move(other.scriptExtra); + gcThingData = std::move(other.gcThingData); + scopeData = std::move(other.scopeData); + scopeNames = std::move(other.scopeNames); + regExpData = std::move(other.regExpData); + bigIntData = std::move(other.bigIntData); + objLiteralData = std::move(other.objLiteralData); + parserAtoms = std::move(other.parserAtoms); + sharedData = std::move(other.sharedData); + moduleMetadata = std::move(other.moduleMetadata); + asmJS = std::move(other.asmJS); + + alloc.steal(&other.alloc); + parserAtoms.fixupAlloc(alloc); + + return *this; + } + + void setFunctionKey(const SourceExtent& extent) { + functionKey = extent.toFunctionKey(); + } + + bool isInitialStencil() const { + return functionKey == SourceExtent::NullFunctionKey; + } + + // Steal CompilationStencil content. + [[nodiscard]] bool steal(FrontendContext* fc, + RefPtr&& other); + + // Clone ExtensibleCompilationStencil content. + [[nodiscard]] bool cloneFrom(FrontendContext* fc, + const CompilationStencil& other); + [[nodiscard]] bool cloneFrom(FrontendContext* fc, + const ExtensibleCompilationStencil& other); + + private: + template + [[nodiscard]] bool cloneFromImpl(FrontendContext* fc, const Stencil& other); + + public: + const ParserAtomVector& parserAtomsSpan() const { + return parserAtoms.entries(); + } + + bool isModule() const; + + inline size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const; + size_t sizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const { + return mallocSizeOf(this) + sizeOfExcludingThis(mallocSizeOf); + } + +#ifdef DEBUG + void assertNoExternalDependency() const; +#endif + +#if defined(DEBUG) || defined(JS_JITSPEW) + void dump(); + void dump(js::JSONPrinter& json); + void dumpFields(js::JSONPrinter& json); + + void dumpAtom(TaggedParserAtomIndex index); +#endif +}; + +// The internal state of the compilation. +struct MOZ_RAII CompilationState : public ExtensibleCompilationStencil { + Directives directives; + + ScopeContext scopeContext; + + UsedNameTracker usedNames; + + // LifoAlloc scope used by Parser for allocating AST etc. + // + // NOTE: This is not used for ExtensibleCompilationStencil.alloc. + LifoAllocScope& parserAllocScope; + + CompilationInput& input; + CompilationSyntaxParseCache previousParseCache; + + // The number of functions that *will* have bytecode. + // This doesn't count top-level non-function script. + // + // This should be counted while parsing, and should be passed to + // SharedDataContainer.prepareStorageFor *before* start emitting bytecode. + size_t nonLazyFunctionCount = 0; + + // End of fields. + + CompilationState(FrontendContext* fc, LifoAllocScope& parserAllocScope, + CompilationInput& input); + + bool init(FrontendContext* fc, ScopeBindingCache* scopeCache, + InheritThis inheritThis = InheritThis::No, + JSObject* enclosingEnv = nullptr) { + if (!scopeContext.init(fc, input, parserAtoms, scopeCache, inheritThis, + enclosingEnv)) { + return false; + } + + // gcThings is later used by the full parser initialization. + if (input.isDelazifying()) { + InputScript lazy = input.lazyOuterScript(); + auto& atomCache = input.atomCache; + if (!previousParseCache.init(fc, alloc, parserAtoms, atomCache, lazy)) { + return false; + } + } + + return true; + } + + // Track the state of key allocations and roll them back as parts of parsing + // get retried. This ensures iteration during stencil instantiation does not + // encounter discarded frontend state. + struct CompilationStatePosition { + // Temporarily share this token struct with CompilationState. + size_t scriptDataLength = 0; + + size_t asmJSCount = 0; + }; + + bool prepareSharedDataStorage(FrontendContext* fc); + + CompilationStatePosition getPosition(); + void rewind(const CompilationStatePosition& pos); + + // When parsing arrow function, parameter is parsed twice, and if there are + // functions inside parameter expression, stencils will be created for them. + // + // Those functions exist only for lazy parsing. + // Mark them "ghost", so that they don't affect other parts. + // + // See GHOST_FUNCTION in FunctionFlags.h for more details. + void markGhost(const CompilationStatePosition& pos); + + // Allocate space for `length` gcthings, and return the address of the + // first element to `cursor` to initialize on the caller. + bool allocateGCThingsUninitialized(FrontendContext* fc, + ScriptIndex scriptIndex, size_t length, + TaggedScriptThingIndex** cursor); + + bool appendScriptStencilAndData(FrontendContext* fc); + + bool appendGCThings(FrontendContext* fc, ScriptIndex scriptIndex, + mozilla::Span things); +}; + +// A temporary CompilationStencil instance that borrows +// ExtensibleCompilationStencil data. +// Ensure that this instance does not outlive the ExtensibleCompilationStencil. +class MOZ_STACK_CLASS BorrowingCompilationStencil : public CompilationStencil { + public: + explicit BorrowingCompilationStencil( + ExtensibleCompilationStencil& extensibleStencil); +}; + +// Size of dynamic data. Ignores Spans (unless their contents are in the +// LifoAlloc) and RefPtrs since we are not the unique owner. +inline size_t CompilationStencil::sizeOfExcludingThis( + mozilla::MallocSizeOf mallocSizeOf) const { + if (ownedBorrowStencil) { + return ownedBorrowStencil->sizeOfIncludingThis(mallocSizeOf); + } + + size_t moduleMetadataSize = + moduleMetadata ? moduleMetadata->sizeOfIncludingThis(mallocSizeOf) : 0; + size_t asmJSSize = asmJS ? asmJS->sizeOfIncludingThis(mallocSizeOf) : 0; + + return alloc.sizeOfExcludingThis(mallocSizeOf) + + sharedData.sizeOfExcludingThis(mallocSizeOf) + moduleMetadataSize + + asmJSSize; +} + +inline size_t ExtensibleCompilationStencil::sizeOfExcludingThis( + mozilla::MallocSizeOf mallocSizeOf) const { + size_t moduleMetadataSize = + moduleMetadata ? moduleMetadata->sizeOfIncludingThis(mallocSizeOf) : 0; + size_t asmJSSize = asmJS ? asmJS->sizeOfIncludingThis(mallocSizeOf) : 0; + + return alloc.sizeOfExcludingThis(mallocSizeOf) + + scriptData.sizeOfExcludingThis(mallocSizeOf) + + scriptExtra.sizeOfExcludingThis(mallocSizeOf) + + gcThingData.sizeOfExcludingThis(mallocSizeOf) + + scopeData.sizeOfExcludingThis(mallocSizeOf) + + scopeNames.sizeOfExcludingThis(mallocSizeOf) + + regExpData.sizeOfExcludingThis(mallocSizeOf) + + bigIntData.sizeOfExcludingThis(mallocSizeOf) + + objLiteralData.sizeOfExcludingThis(mallocSizeOf) + + parserAtoms.sizeOfExcludingThis(mallocSizeOf) + + sharedData.sizeOfExcludingThis(mallocSizeOf) + moduleMetadataSize + + asmJSSize; +} + +// The output of GC allocation from stencil. +struct CompilationGCOutput { + // The resulting outermost script for the compilation powered + // by this CompilationStencil. + JSScript* script = nullptr; + + // The resulting module object if there is one. + ModuleObject* module = nullptr; + + // A Rooted vector to handle tracing of JSFunction* and Atoms within. + // + // If the top level script isn't a function, the item at TopLevelIndex is + // nullptr. + JS::GCVector functions; + + // References to scopes are controlled via AbstractScopePtr, which holds onto + // an index (and CompilationStencil reference). + JS::GCVector scopes; + + // The result ScriptSourceObject. This is unused in delazifying parses. + ScriptSourceObject* sourceObject = nullptr; + + private: + // If we are only instantiating part of a stencil, we can reduce allocations + // by setting a base index and reserving only the vector capacity we need. + // This applies to both the `functions` and `scopes` arrays. These fields are + // initialized by `ensureReservedWithBaseIndex` which also reserves the vector + // sizes appropriately. + // + // Note: These are only used for self-hosted delazification currently. + ScriptIndex functionsBaseIndex{}; + ScopeIndex scopesBaseIndex{}; + + // End of fields. + + public: + CompilationGCOutput() = default; + + // Helper to access the `functions` vector. The NoBaseIndex version is used if + // the caller never uses a base index. + JSFunction*& getFunction(ScriptIndex index) { + return functions[index - functionsBaseIndex]; + } + JSFunction*& getFunctionNoBaseIndex(ScriptIndex index) { + MOZ_ASSERT(!functionsBaseIndex); + return functions[index]; + } + + // Helper accessors for the `scopes` vector. + js::Scope*& getScope(ScopeIndex index) { + return scopes[index - scopesBaseIndex]; + } + js::Scope*& getScopeNoBaseIndex(ScopeIndex index) { + MOZ_ASSERT(!scopesBaseIndex); + return scopes[index]; + } + js::Scope* getScopeNoBaseIndex(ScopeIndex index) const { + MOZ_ASSERT(!scopesBaseIndex); + return scopes[index]; + } + + // Reserve output vector capacity. This may be called before instantiate to do + // allocations ahead of time (off thread). The stencil instantiation code will + // also run this to ensure the vectors are ready. + [[nodiscard]] bool ensureReserved(FrontendContext* fc, + size_t scriptDataLength, + size_t scopeDataLength) { + if (!functions.reserve(scriptDataLength)) { + ReportOutOfMemory(fc); + return false; + } + if (!scopes.reserve(scopeDataLength)) { + ReportOutOfMemory(fc); + return false; + } + return true; + } + + // A variant of `ensureReserved` that sets a base index for the function and + // scope arrays. This is used when instantiating only a subset of the stencil. + // Currently this only applies to self-hosted delazification. The ranges + // include the start index and exclude the limit index. + [[nodiscard]] bool ensureReservedWithBaseIndex(FrontendContext* fc, + ScriptIndex scriptStart, + ScriptIndex scriptLimit, + ScopeIndex scopeStart, + ScopeIndex scopeLimit) { + this->functionsBaseIndex = scriptStart; + this->scopesBaseIndex = scopeStart; + + return ensureReserved(fc, scriptLimit - scriptStart, + scopeLimit - scopeStart); + } + + // Size of dynamic data. Note that GC data is counted by GC and not here. + size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const { + return functions.sizeOfExcludingThis(mallocSizeOf) + + scopes.sizeOfExcludingThis(mallocSizeOf); + } + + void trace(JSTracer* trc); +}; + +// Iterator over functions that make up a CompilationStencil. This abstracts +// over the parallel arrays in stencil and gc-output that use the same index +// system. +class ScriptStencilIterable { + public: + class ScriptAndFunction { + public: + const ScriptStencil& script; + const ScriptStencilExtra* scriptExtra; + JSFunction* function; + ScriptIndex index; + + ScriptAndFunction() = delete; + ScriptAndFunction(const ScriptStencil& script, + const ScriptStencilExtra* scriptExtra, + JSFunction* function, ScriptIndex index) + : script(script), + scriptExtra(scriptExtra), + function(function), + index(index) {} + }; + + class Iterator { + size_t index_ = 0; + const CompilationStencil& stencil_; + CompilationGCOutput& gcOutput_; + + Iterator(const CompilationStencil& stencil, CompilationGCOutput& gcOutput, + size_t index) + : index_(index), stencil_(stencil), gcOutput_(gcOutput) { + MOZ_ASSERT(index == stencil.scriptData.size()); + } + + public: + explicit Iterator(const CompilationStencil& stencil, + CompilationGCOutput& gcOutput) + : stencil_(stencil), gcOutput_(gcOutput) { + skipTopLevelNonFunction(); + } + + Iterator operator++() { + next(); + assertFunction(); + return *this; + } + + void next() { + MOZ_ASSERT(index_ < stencil_.scriptData.size()); + index_++; + } + + void assertFunction() { + if (index_ < stencil_.scriptData.size()) { + MOZ_ASSERT(stencil_.scriptData[index_].isFunction()); + } + } + + void skipTopLevelNonFunction() { + MOZ_ASSERT(index_ == 0); + if (stencil_.scriptData.size()) { + if (!stencil_.scriptData[0].isFunction()) { + next(); + assertFunction(); + } + } + } + + bool operator!=(const Iterator& other) const { + return index_ != other.index_; + } + + ScriptAndFunction operator*() { + ScriptIndex index = ScriptIndex(index_); + const ScriptStencil& script = stencil_.scriptData[index]; + const ScriptStencilExtra* scriptExtra = nullptr; + if (stencil_.isInitialStencil()) { + scriptExtra = &stencil_.scriptExtra[index]; + } + return ScriptAndFunction(script, scriptExtra, + gcOutput_.getFunctionNoBaseIndex(index), index); + } + + static Iterator end(const CompilationStencil& stencil, + CompilationGCOutput& gcOutput) { + return Iterator(stencil, gcOutput, stencil.scriptData.size()); + } + }; + + const CompilationStencil& stencil_; + CompilationGCOutput& gcOutput_; + + explicit ScriptStencilIterable(const CompilationStencil& stencil, + CompilationGCOutput& gcOutput) + : stencil_(stencil), gcOutput_(gcOutput) {} + + Iterator begin() const { return Iterator(stencil_, gcOutput_); } + + Iterator end() const { return Iterator::end(stencil_, gcOutput_); } +}; + +inline ScriptStencilIterable CompilationStencil::functionScriptStencils( + const CompilationStencil& stencil, CompilationGCOutput& gcOutput) { + return ScriptStencilIterable(stencil, gcOutput); +} + +// Merge CompilationStencil for delazification into initial +// ExtensibleCompilationStencil. +struct CompilationStencilMerger { + private: + using FunctionKey = SourceExtent::FunctionKey; + + // The stencil for the initial compilation. + // Delazifications are merged into this. + // + // If any failure happens during merge operation, this field is reset to + // nullptr. + UniquePtr initial_; + + // A Map from function key to the ScriptIndex in the initial stencil. + using FunctionKeyToScriptIndexMap = + HashMap, + js::SystemAllocPolicy>; + FunctionKeyToScriptIndexMap functionKeyToInitialScriptIndex_; + + [[nodiscard]] bool buildFunctionKeyToIndex(FrontendContext* fc); + + ScriptIndex getInitialScriptIndexFor( + const CompilationStencil& delazification) const; + + // A map from delazification's ParserAtomIndex to + // initial's TaggedParserAtomIndex + using AtomIndexMap = Vector; + + [[nodiscard]] bool buildAtomIndexMap(FrontendContext* fc, + const CompilationStencil& delazification, + AtomIndexMap& atomIndexMap); + + public: + CompilationStencilMerger() = default; + + // Set the initial stencil and prepare for merging. + [[nodiscard]] bool setInitial( + FrontendContext* fc, UniquePtr&& initial); + + // Merge the delazification stencil into the initial stencil. + [[nodiscard]] bool addDelazification( + FrontendContext* fc, const CompilationStencil& delazification); + + ExtensibleCompilationStencil& getResult() const { return *initial_; } + UniquePtr takeResult() { + return std::move(initial_); + } +}; + +const ScopeStencil& ScopeStencilRef::scope() const { + return context_.scopeData[scopeIndex_]; +} + +InputScope InputScope::enclosing() const { + return scope_.match( + [](const Scope* ptr) { + // This may return a nullptr Scope pointer. + return InputScope(ptr->enclosing()); + }, + [](const ScopeStencilRef& ref) { + if (ref.scope().hasEnclosing()) { + return InputScope(ref.context_, ref.scope().enclosing()); + } + return InputScope(nullptr); + }); +} + +FunctionFlags InputScope::functionFlags() const { + return scope_.match( + [](const Scope* ptr) { + JSFunction* fun = ptr->as().canonicalFunction(); + return fun->flags(); + }, + [](const ScopeStencilRef& ref) { + MOZ_ASSERT(ref.scope().isFunction()); + ScriptIndex scriptIndex = ref.scope().functionIndex(); + ScriptStencil& data = ref.context_.scriptData[scriptIndex]; + return data.functionFlags; + }); +} + +ImmutableScriptFlags InputScope::immutableFlags() const { + return scope_.match( + [](const Scope* ptr) { + JSFunction* fun = ptr->as().canonicalFunction(); + return fun->baseScript()->immutableFlags(); + }, + [](const ScopeStencilRef& ref) { + MOZ_ASSERT(ref.scope().isFunction()); + ScriptIndex scriptIndex = ref.scope().functionIndex(); + ScriptStencilExtra& extra = ref.context_.scriptExtra[scriptIndex]; + return extra.immutableFlags; + }); +} + +MemberInitializers InputScope::getMemberInitializers() const { + return scope_.match( + [](const Scope* ptr) { + JSFunction* fun = ptr->as().canonicalFunction(); + return fun->baseScript()->getMemberInitializers(); + }, + [](const ScopeStencilRef& ref) { + MOZ_ASSERT(ref.scope().isFunction()); + ScriptIndex scriptIndex = ref.scope().functionIndex(); + ScriptStencilExtra& extra = ref.context_.scriptExtra[scriptIndex]; + return extra.memberInitializers(); + }); +} + +const ScriptStencil& ScriptStencilRef::scriptData() const { + return context_.scriptData[scriptIndex_]; +} + +const ScriptStencilExtra& ScriptStencilRef::scriptExtra() const { + return context_.scriptExtra[scriptIndex_]; +} + +} // namespace frontend +} // namespace js + +#endif // frontend_CompilationStencil_h diff --git a/js/src/frontend/CompileScript.cpp b/js/src/frontend/CompileScript.cpp new file mode 100644 index 0000000000..0c75b40ccb --- /dev/null +++ b/js/src/frontend/CompileScript.cpp @@ -0,0 +1,175 @@ +/* -*- 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 "js/experimental/CompileScript.h" + +#include "frontend/BytecodeCompilation.h" // frontend::CompileGlobalScriptToStencil +#include "frontend/BytecodeCompiler.h" // frontend::ParseModuleToStencil +#include "frontend/CompilationStencil.h" // frontend::{CompilationStencil,CompilationInput} +#include "frontend/FrontendContext.h" // frontend::FrontendContext +#include "frontend/ScopeBindingCache.h" // frontend::NoScopeBindingCache +#include "js/SourceText.h" // JS::SourceText + +using namespace js; +using namespace js::frontend; + +JS_PUBLIC_API FrontendContext* JS::NewFrontendContext() { + MOZ_ASSERT(JS::detail::libraryInitState == JS::detail::InitState::Running, + "must call JS_Init prior to creating any FrontendContexts"); + + return js::NewFrontendContext(); +} + +JS_PUBLIC_API void JS::DestroyFrontendContext(FrontendContext* fc) { + return js::DestroyFrontendContext(fc); +} + +JS_PUBLIC_API void JS::SetNativeStackQuota(JS::FrontendContext* fc, + JS::NativeStackSize stackSize) { + fc->setStackQuota(stackSize); +} + +JS_PUBLIC_API bool JS::SetSupportedImportAssertions( + FrontendContext* fc, + const JS::ImportAssertionVector& supportedImportAssertions) { + return fc->setSupportedImportAssertions(supportedImportAssertions); +} + +JS::CompilationStorage::~CompilationStorage() { + if (input_ && !isBorrowed_) { + js_delete(input_); + input_ = nullptr; + } +} + +size_t JS::CompilationStorage::sizeOfIncludingThis( + mozilla::MallocSizeOf mallocSizeOf) const { + size_t sizeOfCompilationInput = + input_ ? input_->sizeOfExcludingThis(mallocSizeOf) : 0; + return mallocSizeOf(this) + sizeOfCompilationInput; +} + +bool JS::CompilationStorage::allocateInput( + FrontendContext* fc, const JS::ReadOnlyCompileOptions& options) { + MOZ_ASSERT(!input_); + input_ = fc->getAllocator()->new_(options); + return !!input_; +} + +void JS::CompilationStorage::trace(JSTracer* trc) { + if (input_) { + input_->trace(trc); + } +} + +template +static already_AddRefed CompileGlobalScriptToStencilImpl( + JS::FrontendContext* fc, const JS::ReadOnlyCompileOptions& options, + JS::SourceText& srcBuf, JS::CompilationStorage& compilationStorage) { + ScopeKind scopeKind = + options.nonSyntacticScope ? ScopeKind::NonSyntactic : ScopeKind::Global; + + JS::SourceText data(std::move(srcBuf)); + + compilationStorage.allocateInput(fc, options); + if (!compilationStorage.hasInput()) { + return nullptr; + } + + frontend::NoScopeBindingCache scopeCache; + LifoAlloc tempLifoAlloc(JSContext::TEMP_LIFO_ALLOC_PRIMARY_CHUNK_SIZE); + RefPtr stencil_ = + frontend::CompileGlobalScriptToStencil(nullptr, fc, tempLifoAlloc, + compilationStorage.getInput(), + &scopeCache, data, scopeKind); + return stencil_.forget(); +} + +template +static already_AddRefed CompileModuleScriptToStencilImpl( + JS::FrontendContext* fc, const JS::ReadOnlyCompileOptions& optionsInput, + JS::SourceText& srcBuf, JS::CompilationStorage& compilationStorage) { + JS::CompileOptions options(nullptr, optionsInput); + options.setModule(); + + compilationStorage.allocateInput(fc, options); + if (!compilationStorage.hasInput()) { + return nullptr; + } + + NoScopeBindingCache scopeCache; + js::LifoAlloc tempLifoAlloc(JSContext::TEMP_LIFO_ALLOC_PRIMARY_CHUNK_SIZE); + RefPtr stencil = + ParseModuleToStencil(nullptr, fc, tempLifoAlloc, + compilationStorage.getInput(), &scopeCache, srcBuf); + if (!stencil) { + return nullptr; + } + + // Convert the UniquePtr to a RefPtr and increment the count (to 1). + return stencil.forget(); +} + +already_AddRefed JS::CompileGlobalScriptToStencil( + JS::FrontendContext* fc, const JS::ReadOnlyCompileOptions& options, + JS::SourceText& srcBuf, + JS::CompilationStorage& compileStorage) { + return CompileGlobalScriptToStencilImpl(fc, options, srcBuf, compileStorage); +} + +already_AddRefed JS::CompileGlobalScriptToStencil( + JS::FrontendContext* fc, const JS::ReadOnlyCompileOptions& options, + JS::SourceText& srcBuf, JS::CompilationStorage& compileStorage) { + return CompileGlobalScriptToStencilImpl(fc, options, srcBuf, compileStorage); +} + +already_AddRefed JS::CompileModuleScriptToStencil( + JS::FrontendContext* fc, const JS::ReadOnlyCompileOptions& optionsInput, + JS::SourceText& srcBuf, + JS::CompilationStorage& compileStorage) { + return CompileModuleScriptToStencilImpl(fc, optionsInput, srcBuf, + compileStorage); +} + +already_AddRefed JS::CompileModuleScriptToStencil( + JS::FrontendContext* fc, const JS::ReadOnlyCompileOptions& optionsInput, + JS::SourceText& srcBuf, JS::CompilationStorage& compileStorage) { + return CompileModuleScriptToStencilImpl(fc, optionsInput, srcBuf, + compileStorage); +} + +#ifdef DEBUG +// We don't need to worry about GC if the CompilationInput has no GC pointers +static bool isGCSafe(js::frontend::CompilationInput& input) { + bool isGlobalOrModule = + input.target == CompilationInput::CompilationTarget::Global || + input.target == CompilationInput::CompilationTarget::Module; + bool scopeHasNoGC = + input.enclosingScope.isStencil() || input.enclosingScope.isNull(); + bool scriptHasNoGC = + input.lazyOuterScript().isStencil() || input.lazyOuterScript().isNull(); + bool cacheHasNoGC = input.atomCache.empty(); + + return isGlobalOrModule && scopeHasNoGC && scriptHasNoGC && cacheHasNoGC; +} +#endif // DEBUG + +bool JS::PrepareForInstantiate(JS::FrontendContext* fc, + JS::CompilationStorage& compileStorage, + JS::Stencil& stencil, + JS::InstantiationStorage& storage) { + MOZ_ASSERT(compileStorage.hasInput()); + MOZ_ASSERT(isGCSafe(compileStorage.getInput())); + if (!storage.gcOutput_) { + storage.gcOutput_ = + fc->getAllocator()->new_(); + if (!storage.gcOutput_) { + return false; + } + } + return CompilationStencil::prepareForInstantiate( + fc, compileStorage.getInput().atomCache, stencil, *storage.gcOutput_); +} diff --git a/js/src/frontend/DecoratorEmitter.cpp b/js/src/frontend/DecoratorEmitter.cpp new file mode 100644 index 0000000000..f5771ba9e4 --- /dev/null +++ b/js/src/frontend/DecoratorEmitter.cpp @@ -0,0 +1,840 @@ +/* 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/DecoratorEmitter.h" + +#include "frontend/BytecodeEmitter.h" +#include "frontend/CallOrNewEmitter.h" +#include "frontend/IfEmitter.h" +#include "frontend/NameAnalysisTypes.h" +#include "frontend/ObjectEmitter.h" +#include "frontend/ParseNode.h" +#include "frontend/ParserAtom.h" +#include "frontend/WhileEmitter.h" +#include "vm/ThrowMsgKind.h" + +using namespace js; +using namespace js::frontend; + +DecoratorEmitter::DecoratorEmitter(BytecodeEmitter* bce) : bce_(bce) {} + +bool DecoratorEmitter::emitApplyDecoratorsToElementDefinition( + DecoratorEmitter::Kind kind, ParseNode* key, ListNode* decorators, + bool isStatic) { + MOZ_ASSERT(kind != Kind::Field); + + // The DecoratorEmitter expects the value to be decorated to be at the top + // of the stack prior to this call. It will apply the decorators to this + // value, possibly replacing the value with a value returned by a decorator. + // [stack] VAL + + // Decorators Proposal + // https://arai-a.github.io/ecma262-compare/?pr=2417&id=sec-applydecoratorstoelementdefinition. + // 1. Let decorators be elementRecord.[[Decorators]]. + // 2. If decorators is empty, return unused. + // This is checked by the caller. + MOZ_ASSERT(!decorators->empty()); + + // 3. Let key be elementRecord.[[Key]]. + // 4. Let kind be elementRecord.[[Kind]]. + // 5. For each element decorator of decorators, do + for (ParseNode* decorator : decorators->contents()) { + // 5.a. Let decorationState be the Record { [[Finished]]: false }. + if (!emitDecorationState()) { + return false; + } + + if (!emitCallDecorator(kind, key, isStatic, decorator)) { + return false; + } + + // 5.i. Set decorationState.[[Finished]] to true. + if (!emitUpdateDecorationState()) { + return false; + } + + // We need to check if the decorator returned undefined, a callable value, + // or any other value. + IfEmitter ie(bce_); + if (!ie.emitIf(mozilla::Nothing())) { + return false; + } + + if (!emitCheckIsUndefined()) { + // [stack] VAL RETVAL ISUNDEFINED + return false; + } + + if (!ie.emitThenElse()) { + // [stack] VAL RETVAL + return false; + } + + // Pop the undefined RETVAL from the stack, leaving the original value in + // place. + if (!bce_->emitPopN(1)) { + // [stack] VAL + return false; + } + + if (!ie.emitElseIf(mozilla::Nothing())) { + return false; + } + + // 5.l.i. If IsCallable(newValue) is true, then + if (!emitCheckIsCallable()) { + // [stack] VAL RETVAL ISCALLABLE_RESULT + return false; + } + + if (!ie.emitThenElse()) { + // [stack] VAL RETVAL + return false; + } + + // clang-format off + // 5.k. Else if kind is accessor, then + // 5.k.i. If newValue is an Object, then + // 5.k.i.1. Let newGetter be ? Get(newValue, "get"). + // 5.k.i.2. If IsCallable(newGetter) is true, set elementRecord.[[Get]] to newGetter. + // 5.k.i.3. Else if newGetter is not undefined, throw a TypeError exception. + // 5.k.i.4. Let newSetter be ? Get(newValue, "set"). + // 5.k.i.5. If IsCallable(newSetter) is true, set elementRecord.[[Set]] to newSetter. + // 5.k.i.6. Else if newSetter is not undefined, throw a TypeError exception. + // 5.k.i.7. Let initializer be ? Get(newValue, "init"). + // 5.k.i.8. If IsCallable(initializer) is true, append initializer to elementRecord.[[Initializers]]. + // 5.k.i.9. Else if initializer is not undefined, throw a TypeError exception. + // 5.k.ii. Else if newValue is not undefined, throw a TypeError exception. + // TODO: See https://bugzilla.mozilla.org/show_bug.cgi?id=1793961. + // clang-format on + // 5.l. Else, + + // 5.l.i.1. Perform MakeMethod(newValue, homeObject). + // MakeMethod occurs in the caller, here we just drop the original method + // which was an argument to the decorator, and leave the new method + // returned by the decorator on the stack. + if (!bce_->emit1(JSOp::Swap)) { + // [stack] RETVAL VAL + return false; + } + if (!bce_->emitPopN(1)) { + // [stack] RETVAL + return false; + } + // 5.j.ii. Else if initializer is not undefined, throw a TypeError + // exception. 5.l.ii. Else if newValue is not undefined, throw a + // TypeError exception. + if (!ie.emitElse()) { + return false; + } + + if (!bce_->emitPopN(1)) { + // [stack] RETVAL + return false; + } + + if (!bce_->emit2(JSOp::ThrowMsg, + uint8_t(ThrowMsgKind::DecoratorInvalidReturnType))) { + return false; + } + + if (!ie.emitEnd()) { + return false; + } + } + + return true; +} + +bool DecoratorEmitter::emitApplyDecoratorsToFieldDefinition( + ParseNode* key, ListNode* decorators, bool isStatic) { + // This method creates a new array to contain initializers added by decorators + // to the stack. start: + // [stack] + // end: + // [stack] ARRAY + + // Decorators Proposal + // https://arai-a.github.io/ecma262-compare/?pr=2417&id=sec-applydecoratorstoelementdefinition. + // 1. Let decorators be elementRecord.[[Decorators]]. + // 2. If decorators is empty, return unused. + // This is checked by the caller. + MOZ_ASSERT(!decorators->empty()); + + // If we're apply decorators to a field, we'll push a new array to the stack + // to hold newly created initializers. + if (!bce_->emitUint32Operand(JSOp::NewArray, 1)) { + // [stack] ARRAY + return false; + } + + if (!emitPropertyKey(key)) { + // [stack] ARRAY NAME + return false; + } + + if (!bce_->emitUint32Operand(JSOp::InitElemArray, 0)) { + // [stack] ARRAY + return false; + } + + if (!bce_->emit1(JSOp::One)) { + // [stack] ARRAY INDEX + return false; + } + + // 3. Let key be elementRecord.[[Key]]. + // 4. Let kind be elementRecord.[[Kind]]. + // 5. For each element decorator of decorators, do + for (ParseNode* decorator : decorators->contents()) { + // 5.a. Let decorationState be the Record { [[Finished]]: false }. + if (!emitDecorationState()) { + return false; + } + + if (!emitCallDecorator(Kind::Field, key, isStatic, decorator)) { + // [stack] ARRAY INDEX RETVAL + return false; + } + + // 5.i. Set decorationState.[[Finished]] to true. + if (!emitUpdateDecorationState()) { + // [stack] ARRAY INDEX RETVAL + return false; + } + + // We need to check if the decorator returned undefined, a callable value, + // or any other value. + IfEmitter ie(bce_); + if (!ie.emitIf(mozilla::Nothing())) { + return false; + } + + if (!emitCheckIsUndefined()) { + // [stack] ARRAY INDEX RETVAL ISUNDEFINED + return false; + } + + if (!ie.emitThenElse()) { + // [stack] ARRAY INDEX RETVAL + return false; + } + + // Pop the undefined RETVAL from the stack, leaving the original value in + // place. + if (!bce_->emitPopN(1)) { + // [stack] ARRAY INDEX + return false; + } + + if (!ie.emitElseIf(mozilla::Nothing())) { + return false; + } + + // 5.l.i. If IsCallable(newValue) is true, then + if (!emitCheckIsCallable()) { + // [stack] ARRAY INDEX RETVAL ISCALLABLE_RESULT + return false; + } + + if (!ie.emitThenElse()) { + // [stack] ARRAY INDEX RETVAL + return false; + } + + // 5.j. If kind is field, then + // 5.j.i. If IsCallable(initializer) is true, append initializer + // to elementRecord.[[Initializers]]. + if (!bce_->emit1(JSOp::InitElemInc)) { + // [stack] ARRAY INDEX + return false; + } + + // 5.j.ii. Else if initializer is not undefined, throw a TypeError + // exception. 5.l.ii. Else if newValue is not undefined, throw a + // TypeError exception. + if (!ie.emitElse()) { + return false; + } + + if (!bce_->emitPopN(1)) { + // [stack] ARRAY INDEX + return false; + } + + if (!bce_->emit2(JSOp::ThrowMsg, + uint8_t(ThrowMsgKind::DecoratorInvalidReturnType))) { + return false; + } + + if (!ie.emitEnd()) { + return false; + } + } + + // Pop INDEX + return bce_->emitPopN(1); + // [stack] ARRAY +} + +bool DecoratorEmitter::emitInitializeFieldOrAccessor() { + // [stack] THIS INITIALIZERS + + // Decorators Proposal + // https://arai-a.github.io/ecma262-compare/?pr=2417&id=sec-applydecoratorstoelementdefinition. + // + // 1. Assert: elementRecord.[[Kind]] is field or accessor. + // 2. If elementRecord.[[BackingStorageKey]] is present, let fieldName be + // elementRecord.[[BackingStorageKey]]. + // 3. Else, let fieldName be elementRecord.[[Key]]. + // We've stored the fieldname in the first element of the initializers array. + if (!bce_->emit1(JSOp::Dup)) { + // [stack] THIS INITIALIZERS INITIALIZERS + return false; + } + + if (!bce_->emit1(JSOp::Zero)) { + // [stack] THIS INITIALIZERS INITIALIZERS INDEX + return false; + } + + if (!bce_->emit1(JSOp::GetElem)) { + // [stack] THIS INITIALIZERS FIELDNAME + return false; + } + + // Retrieve initial value of the field + if (!bce_->emit1(JSOp::Dup)) { + // [stack] THIS INITIALIZERS FIELDNAME FIELDNAME + return false; + } + + if (!bce_->emitDupAt(3)) { + // [stack] THIS INITIALIZERS FIELDNAME FIELDNAME THIS + return false; + } + + if (!bce_->emit1(JSOp::Swap)) { + // [stack] THIS INITIALIZERS FIELDNAME THIS FIELDNAME + return false; + } + + // 4. Let initValue be undefined. + // TODO: (See Bug 1817993) At the moment, we're applying the initialization + // logic in two steps. The pre-decorator initialization code runs, stores + // the initial value, and then we retrieve it here and apply the initializers + // added by decorators. We should unify these two steps. + if (!bce_->emit1(JSOp::GetElem)) { + // [stack] THIS INITIALIZERS FIELDNAME VALUE + return false; + } + + if (!bce_->emit2(JSOp::Pick, 2)) { + // [stack] THIS FIELDNAME VALUE INITIALIZERS + return false; + } + + // Retrieve the length of the initializers array. + if (!bce_->emit1(JSOp::Dup)) { + // [stack] THIS FIELDNAME VALUE INITIALIZERS INITIALIZERS + return false; + } + + if (!bce_->emitAtomOp(JSOp::GetProp, + TaggedParserAtomIndex::WellKnown::length())) { + // [stack] THIS FIELDNAME VALUE INITIALIZERS LENGTH + return false; + } + + if (!bce_->emit1(JSOp::One)) { + // [stack] THIS FIELDNAME VALUE INITIALIZERS LENGTH INDEX + return false; + } + + // 5. For each element initializer of elementRecord.[[Initializers]], do + WhileEmitter wh(bce_); + // At this point, we have no context to determine offsets in the + // code for this while statement. Ideally, it would correspond to + // the field we're initializing. + if (!wh.emitCond(0, 0, 0)) { + // [stack] THIS FIELDNAME VALUE INITIALIZERS LENGTH INDEX + return false; + } + + if (!bce_->emit1(JSOp::Dup)) { + // [stack] THIS FIELDNAME VALUE INITIALIZERS LENGTH INDEX INDEX + return false; + } + + if (!bce_->emitDupAt(2)) { + // [stack] THIS FIELDNAME VALUE INITIALIZERS LENGTH INDEX INDEX + // LENGTH + return false; + } + + if (!bce_->emit1(JSOp::Lt)) { + // [stack] THIS FIELDNAME VALUE INITIALIZERS LENGTH INDEX BOOL + return false; + } + + // a. Set initValue to ? Call(initializer, receiver, « initValue »). + if (!wh.emitBody()) { + // [stack] THIS FIELDNAME VALUE INITIALIZERS LENGTH INDEX + return false; + } + + if (!bce_->emitDupAt(2)) { + // [stack] THIS FIELDNAME VALUE INITIALIZERS LENGTH INDEX + // INITIALIZERS + return false; + } + + if (!bce_->emitDupAt(1)) { + // [stack] THIS FIELDNAME VALUE INITIALIZERS LENGTH INDEX + // INITIALIZERS INDEX + return false; + } + + if (!bce_->emit1(JSOp::GetElem)) { + // [stack] THIS FIELDNAME VALUE INITIALIZERS LENGTH INDEX FUNC + return false; + } + + // This is guaranteed to run after super(), so we don't need TDZ checks. + if (!bce_->emitGetName(TaggedParserAtomIndex::WellKnown::dotThis())) { + // [stack] THIS FIELDNAME VALUE INITIALIZERS LENGTH INDEX FUNC + // THIS + return false; + } + + // Pass value in as argument to the initializer + if (!bce_->emit2(JSOp::Pick, 5)) { + // [stack] THIS FIELDNAME INITIALIZERS LENGTH INDEX FUNC THIS + // VALUE + return false; + } + + // Callee is always internal function. + if (!bce_->emitCall(JSOp::Call, 1)) { + // [stack] THIS FIELDNAME INITIALIZERS LENGTH INDEX RVAL + return false; + } + + // Store returned value for next iteration + if (!bce_->emit2(JSOp::Unpick, 3)) { + // [stack] THIS FIELDNAME VALUE INITIALIZERS LENGTH INDEX + return false; + } + + if (!bce_->emit1(JSOp::Inc)) { + // [stack] THIS FIELDNAME VALUE INITIALIZERS LENGTH INDEX + return false; + } + + if (!wh.emitEnd()) { + // [stack] THIS FIELDNAME VALUE INITIALIZERS LENGTH INDEX + return false; + } + + // 6. If fieldName is a Private Name, then + // 6.a. Perform ? PrivateFieldAdd(receiver, fieldName, initValue). + // 7. Else, + // 6.a. Assert: IsPropertyKey(fieldName) is true. + // 6.b. Perform ? CreateDataPropertyOrThrow(receiver, fieldName, + // initValue). + // TODO: (See Bug 1817993) Because the field already exists, we just store the + // updated value here. + if (!bce_->emitPopN(3)) { + // [stack] THIS FIELDNAME VALUE + return false; + } + + if (!bce_->emit1(JSOp::InitElem)) { + // [stack] THIS + return false; + } + + // 8. Return unused. + return bce_->emitPopN(1); + // [stack] +} + +bool DecoratorEmitter::emitPropertyKey(ParseNode* key) { + if (key->is()) { + NameNode* keyAsNameNode = &key->as(); + if (keyAsNameNode->privateNameKind() == PrivateNameKind::None) { + if (!bce_->emitStringOp(JSOp::String, keyAsNameNode->atom())) { + // [stack] NAME + return false; + } + } else { + MOZ_ASSERT(keyAsNameNode->privateNameKind() == PrivateNameKind::Field); + if (!bce_->emitGetPrivateName(keyAsNameNode)) { + // [stack] NAME + return false; + } + } + } else if (key->isKind(ParseNodeKind::NumberExpr)) { + if (!bce_->emitNumberOp(key->as().value())) { + // [stack] NAME + return false; + } + } else { + // Otherwise this is a computed property name. BigInt keys are parsed + // as (synthetic) computed property names, too. + MOZ_ASSERT(key->isKind(ParseNodeKind::ComputedName)); + + if (!bce_->emitComputedPropertyName(&key->as())) { + // [stack] NAME + return false; + } + } + + return true; +} + +bool DecoratorEmitter::emitDecorationState() { + // TODO: See https://bugzilla.mozilla.org/show_bug.cgi?id=1800724. + return true; +} + +bool DecoratorEmitter::emitUpdateDecorationState() { + // TODO: See https://bugzilla.mozilla.org/show_bug.cgi?id=1800724. + return true; +} + +[[nodiscard]] bool DecoratorEmitter::emitCallDecorator(Kind kind, + ParseNode* key, + bool isStatic, + ParseNode* decorator) { + // Prepare to call decorator + CallOrNewEmitter cone(bce_, JSOp::Call, + CallOrNewEmitter::ArgumentsKind::Other, + ValueUsage::WantValue); + + // DecoratorMemberExpression: IdentifierReference e.g. @dec + if (decorator->is()) { + if (!cone.emitNameCallee(decorator->as().name())) { + // [stack] VAL? CALLEE THIS? + return false; + } + } else if (decorator->is()) { + // DecoratorMemberExpression: DecoratorMemberExpression . IdentifierName + // e.g. @decorators.nested.dec + PropOpEmitter& poe = cone.prepareForPropCallee(false); + if (!poe.prepareForObj()) { + return false; + } + + ListNode* ln = &decorator->as(); + bool first = true; + for (ParseNode* node : ln->contentsTo(ln->last())) { + // We should have only placed NameNode instances in this list while + // parsing. + MOZ_ASSERT(node->is()); + + if (first) { + NameNode* obj = &node->as(); + if (!bce_->emitGetName(obj)) { + return false; + } + first = false; + } else { + NameNode* prop = &node->as(); + GCThingIndex propAtomIndex; + + if (!bce_->makeAtomIndex(prop->atom(), ParserAtom::Atomize::Yes, + &propAtomIndex)) { + return false; + } + + if (!bce_->emitAtomOp(JSOp::GetProp, propAtomIndex)) { + return false; + } + } + } + + NameNode* prop = &ln->last()->as(); + if (!poe.emitGet(prop->atom())) { + return false; + } + } else { + // DecoratorCallExpression | DecoratorParenthesizedExpression, + // e.g. @dec('argument') | @((value, context) => value) + if (!cone.prepareForFunctionCallee()) { + return false; + } + + if (!bce_->emitTree(decorator)) { + return false; + } + } + + if (!cone.emitThis()) { + // [stack] VAL? CALLEE THIS + return false; + } + + if (!cone.prepareForNonSpreadArguments()) { + return false; + } + + if (kind == Kind::Field) { + // 5.c. Let value be undefined. + if (!bce_->emit1(JSOp::Undefined)) { + // [stack] VAL? CALLEE THIS undefined + return false; + } + } else if (kind == Kind::Getter || kind == Kind::Method || + kind == Kind::Setter) { + // 5.d. If kind is method, set value to elementRecord.[[Value]]. + // 5.e. Else if kind is getter, set value to elementRecord.[[Get]]. + // 5.f. Else if kind is setter, set value to elementRecord.[[Set]]. + // The DecoratorEmitter expects the method to already be on the stack. + // We dup the value here so we can use it as an argument to the decorator. + if (!bce_->emitDupAt(2)) { + // [stack] VAL CALLEE THIS VAL + return false; + } + } + // 5.g. Else if kind is accessor, then + // 5.g.i. Set value to OrdinaryObjectCreate(%Object.prototype%). + // 5.g.ii. Perform ! CreateDataPropertyOrThrow(accessor, "get", + // elementRecord.[[Get]]). 5.g.iii. Perform + // ! CreateDataPropertyOrThrow(accessor, "set", + // elementRecord.[[Set]]). + // TODO: See https://bugzilla.mozilla.org/show_bug.cgi?id=1793961. + // 5.b. Let context be CreateDecoratorContextObject(kind, key, + // extraInitializers, decorationState, isStatic). + if (!emitCreateDecoratorContextObject(kind, key, isStatic, + decorator->pn_pos)) { + // [stack] VAL? CALLEE THIS VAL + // context + return false; + } + + // 5.h. Let newValue be ? Call(decorator, undefined, « value, context + // »). + return cone.emitEnd(2, decorator->pn_pos.begin); + // [stack] VAL? RETVAL +} + +bool DecoratorEmitter::emitCreateDecoratorAccessObject() { + // TODO: See https://bugzilla.mozilla.org/show_bug.cgi?id=1800725. + ObjectEmitter oe(bce_); + if (!oe.emitObject(0)) { + return false; + } + return oe.emitEnd(); +} + +bool DecoratorEmitter::emitCheckIsUndefined() { + // This emits code to check if the value at the top of the stack is + // undefined. The value is left on the stack. + // [stack] VAL + if (!bce_->emit1(JSOp::Dup)) { + // [stack] VAL VAL + return false; + } + if (!bce_->emit1(JSOp::Undefined)) { + // [stack] VAL VAL undefined + return false; + } + return bce_->emit1(JSOp::Eq); + // [stack] VAL ISUNDEFINED +} + +bool DecoratorEmitter::emitCheckIsCallable() { + // This emits code to check if the value at the top of the stack is + // callable. The value is left on the stack. + // [stack] VAL + if (!bce_->emitAtomOp(JSOp::GetIntrinsic, + TaggedParserAtomIndex::WellKnown::IsCallable())) { + // [stack] VAL ISCALLABLE + return false; + } + if (!bce_->emit1(JSOp::Undefined)) { + // [stack] VAL ISCALLABLE UNDEFINED + return false; + } + if (!bce_->emitDupAt(2)) { + // [stack] VAL ISCALLABLE UNDEFINED VAL + return false; + } + return bce_->emitCall(JSOp::Call, 1); + // [stack] VAL ISCALLABLE_RESULT +} + +bool DecoratorEmitter::emitCreateAddInitializerFunction() { + // TODO: See https://bugzilla.mozilla.org/show_bug.cgi?id=1800724. + ObjectEmitter oe(bce_); + if (!oe.emitObject(0)) { + return false; + } + return oe.emitEnd(); +} + +bool DecoratorEmitter::emitCreateDecoratorContextObject(Kind kind, + ParseNode* key, + bool isStatic, + TokenPos pos) { + // 1. Let contextObj be OrdinaryObjectCreate(%Object.prototype%). + ObjectEmitter oe(bce_); + if (!oe.emitObject(/* propertyCount */ 6)) { + // [stack] context + return false; + } + if (!oe.prepareForPropValue(pos.begin, PropertyEmitter::Kind::Prototype)) { + return false; + } + + if (kind == Kind::Method) { + // 2. If kind is method, let kindStr be "method". + if (!bce_->emitStringOp( + JSOp::String, + frontend::TaggedParserAtomIndex::WellKnown::method())) { + // [stack] context "method" + return false; + } + } else if (kind == Kind::Getter) { + // 3. Else if kind is getter, let kindStr be "getter". + if (!bce_->emitStringOp( + JSOp::String, + frontend::TaggedParserAtomIndex::WellKnown::getter())) { + // [stack] context "getter" + return false; + } + } else if (kind == Kind::Setter) { + // 4. Else if kind is setter, let kindStr be "setter". + if (!bce_->emitStringOp( + JSOp::String, + frontend::TaggedParserAtomIndex::WellKnown::setter())) { + // [stack] context "setter" + return false; + } + } else if (kind == Kind::Field) { + // 6. Else if kind is field, let kindStr be "field". + if (!bce_->emitStringOp( + JSOp::String, + frontend::TaggedParserAtomIndex::WellKnown::field())) { + // [stack] context "field" + return false; + } + } else { + // clang-format off + + + // TODO: See https://bugzilla.mozilla.org/show_bug.cgi?id=1793960. + // 5. Else if kind is accessor, let kindStr be "accessor". + // TODO: See https://bugzilla.mozilla.org/show_bug.cgi?id=1793961. + // 7. Else, + // a. Assert: kind is class. + // b. Let kindStr be "class". + // TODO: See https://bugzilla.mozilla.org/show_bug.cgi?id=1793963. + // clang-format on + return false; + } + + // 8. Perform ! CreateDataPropertyOrThrow(contextObj, "kind", kindStr). + if (!oe.emitInit(frontend::AccessorType::None, + frontend::TaggedParserAtomIndex::WellKnown::kind())) { + // [stack] context + return false; + } + // 9. If kind is not class, then + if (kind != Kind::Class) { + // 9.a. Perform ! CreateDataPropertyOrThrow(contextObj, "access", + // CreateDecoratorAccessObject(kind, name)). + if (!oe.prepareForPropValue(pos.begin, PropertyEmitter::Kind::Prototype)) { + return false; + } + if (!emitCreateDecoratorAccessObject()) { + return false; + } + if (!oe.emitInit(frontend::AccessorType::None, + frontend::TaggedParserAtomIndex::WellKnown::access())) { + // [stack] context + return false; + } + // 9.b. If isStatic is present, perform + // ! CreateDataPropertyOrThrow(contextObj, "static", isStatic). + if (!oe.prepareForPropValue(pos.begin, PropertyEmitter::Kind::Prototype)) { + return false; + } + if (!bce_->emit1(isStatic ? JSOp::True : JSOp::False)) { + // [stack] context isStatic + return false; + } + if (!oe.emitInit(frontend::AccessorType::None, + frontend::TaggedParserAtomIndex::WellKnown::static_())) { + // [stack] context + return false; + } + // 9.c. If name is a Private Name, then + // 9.c.i. Perform ! CreateDataPropertyOrThrow(contextObj, "private", + // true). + // 9.d. Else, + // 9.d.i. Perform ! CreateDataPropertyOrThrow(contextObj, "private", + // false). + if (!oe.prepareForPropValue(pos.begin, PropertyEmitter::Kind::Prototype)) { + return false; + } + if (!bce_->emit1(key->isKind(ParseNodeKind::PrivateName) ? JSOp::True + : JSOp::False)) { + // [stack] context private + return false; + } + if (!oe.emitInit(frontend::AccessorType::None, + frontend::TaggedParserAtomIndex::WellKnown::private_())) { + // [stack] context + return false; + } + // 9.c.ii. Perform ! CreateDataPropertyOrThrow(contextObj, "name", + // name.[[Description]]). + // + // 9.d.ii. Perform + // ! CreateDataPropertyOrThrow(contextObj, "name", name).) + if (!oe.prepareForPropValue(pos.begin, PropertyEmitter::Kind::Prototype)) { + return false; + } + if (key->is()) { + if (!bce_->emitStringOp(JSOp::String, key->as().atom())) { + return false; + } + } else { + if (!emitPropertyKey(key)) { + return false; + } + } + if (!oe.emitInit(frontend::AccessorType::None, + frontend::TaggedParserAtomIndex::WellKnown::name())) { + // [stack] context + return false; + } + } else { + // 10. Else, + // 10.a. Perform ! CreateDataPropertyOrThrow(contextObj, "name", name). + // TODO: See https://bugzilla.mozilla.org/show_bug.cgi?id=1793963 + return false; + } + // 11. Let addInitializer be CreateAddInitializerFunction(initializers, + // decorationState). + if (!oe.prepareForPropValue(pos.begin, PropertyEmitter::Kind::Prototype)) { + return false; + } + if (!emitCreateAddInitializerFunction()) { + // [stack] context addInitializer + return false; + } + // 12. Perform ! CreateDataPropertyOrThrow(contextObj, "addInitializer", + // addInitializer). + if (!oe.emitInit( + frontend::AccessorType::None, + frontend::TaggedParserAtomIndex::WellKnown::addInitializer())) { + // [stack] context + return false; + } + // 13. Return contextObj. + return oe.emitEnd(); +} diff --git a/js/src/frontend/DecoratorEmitter.h b/js/src/frontend/DecoratorEmitter.h new file mode 100644 index 0000000000..42cad2e81c --- /dev/null +++ b/js/src/frontend/DecoratorEmitter.h @@ -0,0 +1,59 @@ +/* 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_DecoratorEmitter_h +#define frontend_DecoratorEmitter_h + +#include "mozilla/Attributes.h" + +#include "frontend/ParseNode.h" + +namespace js::frontend { + +struct BytecodeEmitter; + +class MOZ_STACK_CLASS DecoratorEmitter { + private: + BytecodeEmitter* bce_; + + public: + enum Kind { Method, Getter, Setter, Field, Accessor, Class }; + + explicit DecoratorEmitter(BytecodeEmitter* bce); + + [[nodiscard]] bool emitApplyDecoratorsToElementDefinition( + Kind kind, ParseNode* key, ListNode* decorators, bool isStatic); + + [[nodiscard]] bool emitApplyDecoratorsToFieldDefinition(ParseNode* key, + ListNode* decorators, + bool isStatic); + + [[nodiscard]] bool emitInitializeFieldOrAccessor(); + + private: + [[nodiscard]] bool emitPropertyKey(ParseNode* key); + + [[nodiscard]] bool emitDecorationState(); + + [[nodiscard]] bool emitUpdateDecorationState(); + + [[nodiscard]] bool emitCallDecorator(Kind kind, ParseNode* key, bool isStatic, + ParseNode* decorator); + + [[nodiscard]] bool emitCreateDecoratorAccessObject(); + + [[nodiscard]] bool emitCheckIsUndefined(); + + [[nodiscard]] bool emitCheckIsCallable(); + + [[nodiscard]] bool emitCreateAddInitializerFunction(); + + [[nodiscard]] bool emitCreateDecoratorContextObject(Kind kind, ParseNode* key, + bool isStatic, + TokenPos pos); +}; + +} /* namespace js::frontend */ + +#endif /* frontend_DecoratorEmitter_h */ diff --git a/js/src/frontend/DefaultEmitter.cpp b/js/src/frontend/DefaultEmitter.cpp new file mode 100644 index 0000000000..1377e4b7ad --- /dev/null +++ b/js/src/frontend/DefaultEmitter.cpp @@ -0,0 +1,75 @@ +/* -*- 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/DefaultEmitter.h" + +#include "mozilla/Assertions.h" // MOZ_ASSERT + +#include "frontend/BytecodeEmitter.h" // BytecodeEmitter +#include "vm/Opcodes.h" // JSOp + +using namespace js; +using namespace js::frontend; + +using mozilla::Nothing; + +DefaultEmitter::DefaultEmitter(BytecodeEmitter* bce) : bce_(bce) {} + +bool DefaultEmitter::prepareForDefault() { + MOZ_ASSERT(state_ == State::Start); + + // [stack] VALUE + + ifUndefined_.emplace(bce_); + if (!ifUndefined_->emitIf(Nothing())) { + return false; + } + + if (!bce_->emit1(JSOp::Dup)) { + // [stack] VALUE VALUE + return false; + } + if (!bce_->emit1(JSOp::Undefined)) { + // [stack] VALUE VALUE UNDEFINED + return false; + } + if (!bce_->emit1(JSOp::StrictEq)) { + // [stack] VALUE EQ? + return false; + } + + if (!ifUndefined_->emitThen()) { + // [stack] VALUE + return false; + } + + if (!bce_->emit1(JSOp::Pop)) { + // [stack] + return false; + } + +#ifdef DEBUG + state_ = State::Default; +#endif + return true; +} + +bool DefaultEmitter::emitEnd() { + MOZ_ASSERT(state_ == State::Default); + + // [stack] DEFAULTVALUE + + if (!ifUndefined_->emitEnd()) { + // [stack] VALUE/DEFAULTVALUE + return false; + } + ifUndefined_.reset(); + +#ifdef DEBUG + state_ = State::End; +#endif + return true; +} diff --git a/js/src/frontend/DefaultEmitter.h b/js/src/frontend/DefaultEmitter.h new file mode 100644 index 0000000000..3dc9aba21b --- /dev/null +++ b/js/src/frontend/DefaultEmitter.h @@ -0,0 +1,65 @@ +/* -*- 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/. */ + +#ifndef frontend_DefaultEmitter_h +#define frontend_DefaultEmitter_h + +#include "mozilla/Attributes.h" // MOZ_STACK_CLASS +#include "mozilla/Maybe.h" // Maybe + +#include "frontend/IfEmitter.h" // IfEmitter + +namespace js { +namespace frontend { + +struct BytecodeEmitter; + +// Class for emitting default parameter or default value. +// +// Usage: (check for the return value is omitted for simplicity) +// +// `x = 10` in `function (x = 10) {}` +// // the value of arguments[0] is on the stack +// DefaultEmitter de(this); +// de.prepareForDefault(); +// emit(10); +// de.emitEnd(); +// +class MOZ_STACK_CLASS DefaultEmitter { + BytecodeEmitter* bce_; + + mozilla::Maybe ifUndefined_; + +#ifdef DEBUG + // The state of this emitter. + // + // +-------+ prepareForDefault +---------+ emitEnd +-----+ + // | Start |------------------>| Default |-------->| End | + // +-------+ +---------+ +-----+ + enum class State { + // The initial state. + Start, + + // After calling prepareForDefault. + Default, + + // After calling emitEnd. + End + }; + State state_ = State::Start; +#endif + + public: + explicit DefaultEmitter(BytecodeEmitter* bce); + + [[nodiscard]] bool prepareForDefault(); + [[nodiscard]] bool emitEnd(); +}; + +} /* namespace frontend */ +} /* namespace js */ + +#endif /* frontend_LabelEmitter_h */ diff --git a/js/src/frontend/DestructuringFlavor.h b/js/src/frontend/DestructuringFlavor.h new file mode 100644 index 0000000000..d9d8fb26e1 --- /dev/null +++ b/js/src/frontend/DestructuringFlavor.h @@ -0,0 +1,24 @@ +/* -*- 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/. */ + +#ifndef frontend_DestructuringFlavor_h +#define frontend_DestructuringFlavor_h + +namespace js { +namespace frontend { + +enum class DestructuringFlavor { + // Destructuring into a declaration. + Declaration, + + // Destructuring as part of an AssignmentExpression. + Assignment +}; + +} /* namespace frontend */ +} /* namespace js */ + +#endif /* frontend_DestructuringFlavor_h */ diff --git a/js/src/frontend/DoWhileEmitter.cpp b/js/src/frontend/DoWhileEmitter.cpp new file mode 100644 index 0000000000..acdd8074b6 --- /dev/null +++ b/js/src/frontend/DoWhileEmitter.cpp @@ -0,0 +1,69 @@ +/* -*- 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/DoWhileEmitter.h" + +#include "frontend/BytecodeEmitter.h" +#include "vm/Opcodes.h" +#include "vm/StencilEnums.h" // TryNoteKind + +using namespace js; +using namespace js::frontend; + +DoWhileEmitter::DoWhileEmitter(BytecodeEmitter* bce) : bce_(bce) {} + +bool DoWhileEmitter::emitBody(uint32_t doPos, uint32_t bodyPos) { + MOZ_ASSERT(state_ == State::Start); + + // Ensure that the column of the 'do' is set properly. + if (!bce_->updateSourceCoordNotes(doPos)) { + return false; + } + + // We need a nop here to make it possible to set a breakpoint on `do`. + if (!bce_->emit1(JSOp::Nop)) { + return false; + } + + loopInfo_.emplace(bce_, StatementKind::DoLoop); + + if (!loopInfo_->emitLoopHead(bce_, mozilla::Some(bodyPos))) { + return false; + } + +#ifdef DEBUG + state_ = State::Body; +#endif + return true; +} + +bool DoWhileEmitter::emitCond() { + MOZ_ASSERT(state_ == State::Body); + + if (!loopInfo_->emitContinueTarget(bce_)) { + return false; + } + +#ifdef DEBUG + state_ = State::Cond; +#endif + return true; +} + +bool DoWhileEmitter::emitEnd() { + MOZ_ASSERT(state_ == State::Cond); + + if (!loopInfo_->emitLoopEnd(bce_, JSOp::JumpIfTrue, TryNoteKind::Loop)) { + return false; + } + + loopInfo_.reset(); + +#ifdef DEBUG + state_ = State::End; +#endif + return true; +} diff --git a/js/src/frontend/DoWhileEmitter.h b/js/src/frontend/DoWhileEmitter.h new file mode 100644 index 0000000000..7b970a4595 --- /dev/null +++ b/js/src/frontend/DoWhileEmitter.h @@ -0,0 +1,80 @@ +/* -*- 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/. */ + +#ifndef frontend_DoWhileEmitter_h +#define frontend_DoWhileEmitter_h + +#include "mozilla/Attributes.h" +#include "mozilla/Maybe.h" + +#include + +#include "frontend/BytecodeControlStructures.h" + +namespace js { +namespace frontend { + +struct BytecodeEmitter; + +// Class for emitting bytecode for do-while loop. +// +// Usage: (check for the return value is omitted for simplicity) +// +// `do body while (cond);` +// DoWhileEmitter doWhile(this); +// doWhile.emitBody(offset_of_do, offset_of_body); +// emit(body); +// doWhile.emitCond(); +// emit(cond); +// doWhile.emitEnd(); +// +class MOZ_STACK_CLASS DoWhileEmitter { + BytecodeEmitter* bce_; + + mozilla::Maybe loopInfo_; + +#ifdef DEBUG + // The state of this emitter. + // + // +-------+ emitBody +------+ emitCond +------+ emitEnd +-----+ + // | Start |--------->| Body |--------->| Cond |--------->| End | + // +-------+ +------+ +------+ +-----+ + enum class State { + // The initial state. + Start, + + // After calling emitBody. + Body, + + // After calling emitCond. + Cond, + + // After calling emitEnd. + End + }; + State state_ = State::Start; +#endif + + public: + explicit DoWhileEmitter(BytecodeEmitter* bce); + + // Parameters are the offset in the source code for each character below: + // + // do { ... } while ( x < 20 ); + // ^ ^ + // | | + // | bodyPos + // | + // doPos + [[nodiscard]] bool emitBody(uint32_t doPos, uint32_t bodyPos); + [[nodiscard]] bool emitCond(); + [[nodiscard]] bool emitEnd(); +}; + +} /* namespace frontend */ +} /* namespace js */ + +#endif /* frontend_DoWhileEmitter_h */ diff --git a/js/src/frontend/EitherParser.h b/js/src/frontend/EitherParser.h new file mode 100644 index 0000000000..a5201eedcd --- /dev/null +++ b/js/src/frontend/EitherParser.h @@ -0,0 +1,58 @@ +/* -*- 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/. */ + +/* + * A variant-like class abstracting operations on a Parser with a given + * ParseHandler but unspecified character type. + */ + +#ifndef frontend_EitherParser_h +#define frontend_EitherParser_h + +#include "mozilla/Utf8.h" +#include "mozilla/Variant.h" + +#include +#include + +#include "frontend/Parser.h" + +namespace js::frontend { + +class EitherParser final { + // Leave this as a variant, to promote good form until 8-bit parser + // integration. + mozilla::Variant* const, + Parser* const> + parser; + + public: + template + explicit EitherParser(Parser* parser) : parser(parser) {} + + const ErrorReporter& errorReporter() const { + return parser.match([](auto* parser) -> const frontend::ErrorReporter& { + return parser->tokenStream; + }); + } + + void computeLineAndColumn(uint32_t offset, uint32_t* line, + uint32_t* column) const { + return parser.match([offset, line, column](auto* parser) -> void { + parser->tokenStream.computeLineAndColumn(offset, line, column); + }); + } + + ParserAtomsTable& parserAtoms() { + auto& base = parser.match( + [](auto* parser) -> frontend::ParserSharedBase& { return *parser; }); + return base.parserAtoms(); + } +}; + +} /* namespace js::frontend */ + +#endif /* frontend_EitherParser_h */ diff --git a/js/src/frontend/ElemOpEmitter.cpp b/js/src/frontend/ElemOpEmitter.cpp new file mode 100644 index 0000000000..0d563e466d --- /dev/null +++ b/js/src/frontend/ElemOpEmitter.cpp @@ -0,0 +1,257 @@ +/* -*- 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/ElemOpEmitter.h" + +#include "frontend/BytecodeEmitter.h" +#include "frontend/SharedContext.h" +#include "vm/Opcodes.h" +#include "vm/ThrowMsgKind.h" // ThrowMsgKind + +using namespace js; +using namespace js::frontend; + +ElemOpEmitter::ElemOpEmitter(BytecodeEmitter* bce, Kind kind, ObjKind objKind) + : bce_(bce), kind_(kind), objKind_(objKind) {} + +bool ElemOpEmitter::prepareForObj() { + MOZ_ASSERT(state_ == State::Start); + +#ifdef DEBUG + state_ = State::Obj; +#endif + return true; +} + +bool ElemOpEmitter::prepareForKey() { + MOZ_ASSERT(state_ == State::Obj); + + if (isCall()) { + if (!bce_->emit1(JSOp::Dup)) { + // [stack] # if Super + // [stack] THIS THIS + // [stack] # otherwise + // [stack] OBJ OBJ + return false; + } + } + +#ifdef DEBUG + state_ = State::Key; +#endif + return true; +} + +bool ElemOpEmitter::emitGet() { + MOZ_ASSERT(state_ == State::Key); + + // Inc/dec and compound assignment use the KEY twice, but if it's an object, + // it must be converted ToPropertyKey only once, per spec. + if (isIncDec() || isCompoundAssignment()) { + if (!bce_->emit1(JSOp::ToPropertyKey)) { + // [stack] # if Super + // [stack] THIS KEY + // [stack] # otherwise + // [stack] OBJ KEY + return false; + } + } + + if (isSuper()) { + if (!bce_->emitSuperBase()) { + // [stack] THIS? THIS KEY SUPERBASE + return false; + } + } + if (isIncDec() || isCompoundAssignment()) { + if (isSuper()) { + if (!bce_->emitDupAt(2, 3)) { + // [stack] THIS KEY SUPERBASE THIS KEY SUPERBASE + return false; + } + } else { + if (!bce_->emit1(JSOp::Dup2)) { + // [stack] OBJ KEY OBJ KEY + return false; + } + } + } + + JSOp op; + if (isSuper()) { + op = JSOp::GetElemSuper; + } else { + op = JSOp::GetElem; + } + if (!bce_->emitElemOpBase(op)) { + // [stack] # if Get + // [stack] ELEM + // [stack] # if Call + // [stack] THIS ELEM + // [stack] # if Inc/Dec/Assignment, with Super + // [stack] THIS KEY SUPERBASE ELEM + // [stack] # if Inc/Dec/Assignment, other + // [stack] OBJ KEY ELEM + return false; + } + if (isCall()) { + if (!bce_->emit1(JSOp::Swap)) { + // [stack] ELEM THIS + return false; + } + } + +#ifdef DEBUG + state_ = State::Get; +#endif + return true; +} + +bool ElemOpEmitter::prepareForRhs() { + MOZ_ASSERT(isSimpleAssignment() || isPropInit() || isCompoundAssignment()); + MOZ_ASSERT_IF(isSimpleAssignment() || isPropInit(), state_ == State::Key); + MOZ_ASSERT_IF(isCompoundAssignment(), state_ == State::Get); + + if (isSimpleAssignment() || isPropInit()) { + // For CompoundAssignment, SuperBase is already emitted by emitGet. + if (isSuper()) { + if (!bce_->emitSuperBase()) { + // [stack] THIS KEY SUPERBASE + return false; + } + } + } + +#ifdef DEBUG + state_ = State::Rhs; +#endif + return true; +} + +bool ElemOpEmitter::skipObjAndKeyAndRhs() { + MOZ_ASSERT(state_ == State::Start); + MOZ_ASSERT(isSimpleAssignment() || isPropInit()); + +#ifdef DEBUG + state_ = State::Rhs; +#endif + return true; +} + +bool ElemOpEmitter::emitDelete() { + MOZ_ASSERT(state_ == State::Key); + MOZ_ASSERT(isDelete()); + + if (isSuper()) { + if (!bce_->emit1(JSOp::ToPropertyKey)) { + // [stack] THIS KEY + return false; + } + if (!bce_->emitSuperBase()) { + // [stack] THIS KEY SUPERBASE + return false; + } + + // Unconditionally throw when attempting to delete a super-reference. + if (!bce_->emit2(JSOp::ThrowMsg, uint8_t(ThrowMsgKind::CantDeleteSuper))) { + // [stack] THIS KEY SUPERBASE + return false; + } + + // Another wrinkle: Balance the stack from the emitter's point of view. + // Execution will not reach here, as the last bytecode threw. + if (!bce_->emitPopN(2)) { + // [stack] THIS + return false; + } + } else { + JSOp op = bce_->sc->strict() ? JSOp::StrictDelElem : JSOp::DelElem; + if (!bce_->emitElemOpBase(op)) { + // SUCCEEDED + return false; + } + } + +#ifdef DEBUG + state_ = State::Delete; +#endif + return true; +} + +bool ElemOpEmitter::emitAssignment() { + MOZ_ASSERT(isSimpleAssignment() || isPropInit() || isCompoundAssignment()); + MOZ_ASSERT(state_ == State::Rhs); + + MOZ_ASSERT_IF(isPropInit(), !isSuper()); + + JSOp setOp = isPropInit() ? JSOp::InitElem + : isSuper() ? bce_->sc->strict() ? JSOp::StrictSetElemSuper + : JSOp::SetElemSuper + : bce_->sc->strict() ? JSOp::StrictSetElem + : JSOp::SetElem; + if (!bce_->emitElemOpBase(setOp)) { + // [stack] ELEM + return false; + } + +#ifdef DEBUG + state_ = State::Assignment; +#endif + return true; +} + +bool ElemOpEmitter::emitIncDec(ValueUsage valueUsage) { + MOZ_ASSERT(state_ == State::Key); + MOZ_ASSERT(isIncDec()); + + if (!emitGet()) { + // [stack] ... ELEM + return false; + } + + MOZ_ASSERT(state_ == State::Get); + + JSOp incOp = isInc() ? JSOp::Inc : JSOp::Dec; + if (!bce_->emit1(JSOp::ToNumeric)) { + // [stack] ... N + return false; + } + if (isPostIncDec() && valueUsage == ValueUsage::WantValue) { + // [stack] OBJ KEY SUPERBASE? N + if (!bce_->emit1(JSOp::Dup)) { + // [stack] ... N N + return false; + } + if (!bce_->emit2(JSOp::Unpick, 3 + isSuper())) { + // [stack] N OBJ KEY SUPERBASE? N + return false; + } + } + if (!bce_->emit1(incOp)) { + // [stack] ... N+1 + return false; + } + + JSOp setOp = + isSuper() + ? (bce_->sc->strict() ? JSOp::StrictSetElemSuper : JSOp::SetElemSuper) + : (bce_->sc->strict() ? JSOp::StrictSetElem : JSOp::SetElem); + if (!bce_->emitElemOpBase(setOp)) { + // [stack] N? N+1 + return false; + } + if (isPostIncDec() && valueUsage == ValueUsage::WantValue) { + if (!bce_->emit1(JSOp::Pop)) { + // [stack] N + return false; + } + } + +#ifdef DEBUG + state_ = State::IncDec; +#endif + return true; +} diff --git a/js/src/frontend/ElemOpEmitter.h b/js/src/frontend/ElemOpEmitter.h new file mode 100644 index 0000000000..cf528bf312 --- /dev/null +++ b/js/src/frontend/ElemOpEmitter.h @@ -0,0 +1,267 @@ +/* -*- 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/. */ + +#ifndef frontend_ElemOpEmitter_h +#define frontend_ElemOpEmitter_h + +#include "mozilla/Attributes.h" + +namespace js { +namespace frontend { + +struct BytecodeEmitter; +enum class ValueUsage; + +// Class for emitting bytecode for element operation. +// +// Usage: (check for the return value is omitted for simplicity) +// +// `obj[key];` +// ElemOpEmitter eoe(this, +// ElemOpEmitter::Kind::Get, +// ElemOpEmitter::ObjKind::Other); +// eoe.prepareForObj(); +// emit(obj); +// eoe.prepareForKey(); +// emit(key); +// eoe.emitGet(); +// +// `super[key];` +// ElemOpEmitter eoe(this, +// ElemOpEmitter::Kind::Get, +// ElemOpEmitter::ObjKind::Super); +// eoe.prepareForObj(); +// emit(this_for_super); +// eoe.prepareForKey(); +// emit(key); +// eoe.emitGet(); +// +// `obj[key]();` +// ElemOpEmitter eoe(this, +// ElemOpEmitter::Kind::Call, +// ElemOpEmitter::ObjKind::Other); +// eoe.prepareForObj(); +// emit(obj); +// eoe.prepareForKey(); +// emit(key); +// eoe.emitGet(); +// emit_call_here(); +// +// `new obj[key]();` +// ElemOpEmitter eoe(this, +// ElemOpEmitter::Kind::Call, +// ElemOpEmitter::ObjKind::Other); +// eoe.prepareForObj(); +// emit(obj); +// eoe.prepareForKey(); +// emit(key); +// eoe.emitGet(); +// emit_call_here(); +// +// `delete obj[key];` +// ElemOpEmitter eoe(this, +// ElemOpEmitter::Kind::Delete, +// ElemOpEmitter::ObjKind::Other); +// eoe.prepareForObj(); +// emit(obj); +// eoe.prepareForKey(); +// emit(key); +// eoe.emitDelete(); +// +// `delete super[key];` +// ElemOpEmitter eoe(this, +// ElemOpEmitter::Kind::Delete, +// ElemOpEmitter::ObjKind::Super); +// eoe.prepareForObj(); +// emit(this_for_super); +// eoe.prepareForKey(); +// emit(key); +// eoe.emitDelete(); +// +// `obj[key]++;` +// ElemOpEmitter eoe(this, +// ElemOpEmitter::Kind::PostIncrement, +// ElemOpEmitter::ObjKind::Other); +// eoe.prepareForObj(); +// emit(obj); +// eoe.prepareForKey(); +// emit(key); +// eoe.emitIncDec(); +// +// `obj[key] = value;` +// ElemOpEmitter eoe(this, +// ElemOpEmitter::Kind::SimpleAssignment, +// ElemOpEmitter::ObjKind::Other); +// eoe.prepareForObj(); +// emit(obj); +// eoe.prepareForKey(); +// emit(key); +// eoe.prepareForRhs(); +// emit(value); +// eoe.emitAssignment(); +// +// `obj[key] += value;` +// ElemOpEmitter eoe(this, +// ElemOpEmitter::Kind::CompoundAssignment, +// ElemOpEmitter::ObjKind::Other); +// eoe.prepareForObj(); +// emit(obj); +// eoe.prepareForKey(); +// emit(key); +// eoe.emitGet(); +// eoe.prepareForRhs(); +// emit(value); +// emit_add_op_here(); +// eoe.emitAssignment(); +// +class MOZ_STACK_CLASS ElemOpEmitter { + public: + enum class Kind { + Get, + Call, + Delete, + PostIncrement, + PreIncrement, + PostDecrement, + PreDecrement, + SimpleAssignment, + PropInit, + CompoundAssignment + }; + enum class ObjKind { Super, Other }; + + private: + BytecodeEmitter* bce_; + + Kind kind_; + ObjKind objKind_; + +#ifdef DEBUG + // The state of this emitter. + // + // skipObjAndKeyAndRhs + // +------------------------------------------------+ + // | | + // +-------+ | prepareForObj +-----+ prepareForKey +-----+ | + // | Start |-+-------------->| Obj |-------------->| Key |-+ | + // +-------+ +-----+ +-----+ | | + // | | + // +-------------------------------------------------------+ | + // | | + // | [Get] | + // | [Call] | + // | emitGet +-----+ | + // +---------->| Get | | + // | +-----+ | + // | | + // | [Delete] | + // | emitDelete +--------+ | + // +------------->| Delete | | + // | +--------+ | + // | | + // | [PostIncrement] | + // | [PreIncrement] | + // | [PostDecrement] | + // | [PreDecrement] | + // | emitIncDec +--------+ | + // +------------->| IncDec | | + // | +--------+ | + // | +-------------------+ + // | [SimpleAssignment] | + // | [PropInit] | + // | prepareForRhs v +-----+ + // +--------------------->+-------------->+->| Rhs |-+ + // | ^ +-----+ | + // | | | + // | | +-------------+ + // | [CompoundAssignment] | | + // | emitGet +-----+ | | emitAssignment +------------+ + // +---------->| Get |----+ +--------------->| Assignment | + // +-----+ +------------+ + enum class State { + // The initial state. + Start, + + // After calling prepareForObj. + Obj, + + // After calling emitKey. + Key, + + // After calling emitGet. + Get, + + // After calling emitDelete. + Delete, + + // After calling emitIncDec. + IncDec, + + // After calling prepareForRhs or skipObjAndKeyAndRhs. + Rhs, + + // After calling emitAssignment. + Assignment, + }; + State state_ = State::Start; +#endif + + public: + ElemOpEmitter(BytecodeEmitter* bce, Kind kind, ObjKind objKind); + + private: + [[nodiscard]] bool isCall() const { return kind_ == Kind::Call; } + + [[nodiscard]] bool isSimpleAssignment() const { + return kind_ == Kind::SimpleAssignment; + } + + [[nodiscard]] bool isPropInit() const { return kind_ == Kind::PropInit; } + + [[nodiscard]] bool isDelete() const { return kind_ == Kind::Delete; } + + [[nodiscard]] bool isCompoundAssignment() const { + return kind_ == Kind::CompoundAssignment; + } + + [[nodiscard]] bool isIncDec() const { + return isPostIncDec() || isPreIncDec(); + } + + [[nodiscard]] bool isPostIncDec() const { + return kind_ == Kind::PostIncrement || kind_ == Kind::PostDecrement; + } + + [[nodiscard]] bool isPreIncDec() const { + return kind_ == Kind::PreIncrement || kind_ == Kind::PreDecrement; + } + + [[nodiscard]] bool isInc() const { + return kind_ == Kind::PostIncrement || kind_ == Kind::PreIncrement; + } + + [[nodiscard]] bool isSuper() const { return objKind_ == ObjKind::Super; } + + public: + [[nodiscard]] bool prepareForObj(); + [[nodiscard]] bool prepareForKey(); + + [[nodiscard]] bool emitGet(); + + [[nodiscard]] bool prepareForRhs(); + [[nodiscard]] bool skipObjAndKeyAndRhs(); + + [[nodiscard]] bool emitDelete(); + + [[nodiscard]] bool emitAssignment(); + + [[nodiscard]] bool emitIncDec(ValueUsage valueUsage); +}; + +} /* namespace frontend */ +} // namespace js + +#endif /* frontend_ElemOpEmitter_h */ diff --git a/js/src/frontend/EmitterScope.cpp b/js/src/frontend/EmitterScope.cpp new file mode 100644 index 0000000000..96cb8a08b2 --- /dev/null +++ b/js/src/frontend/EmitterScope.cpp @@ -0,0 +1,1117 @@ +/* -*- 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/EmitterScope.h" + +#include "frontend/AbstractScopePtr.h" +#include "frontend/BytecodeEmitter.h" +#include "frontend/ModuleSharedContext.h" +#include "frontend/TDZCheckCache.h" +#include "js/friend/ErrorMessages.h" // JSMSG_* +#include "vm/EnvironmentObject.h" // ClassBodyLexicalEnvironmentObject +#include "vm/WellKnownAtom.h" // js_*_str + +using namespace js; +using namespace js::frontend; + +using mozilla::DebugOnly; +using mozilla::Maybe; +using mozilla::Nothing; +using mozilla::Some; + +EmitterScope::EmitterScope(BytecodeEmitter* bce) + : Nestable(&bce->innermostEmitterScope_), + nameCache_(bce->fc->nameCollectionPool()), + hasEnvironment_(false), + environmentChainLength_(0), + nextFrameSlot_(0), + scopeIndex_(ScopeNote::NoScopeIndex), + noteIndex_(ScopeNote::NoScopeNoteIndex) {} + +bool EmitterScope::ensureCache(BytecodeEmitter* bce) { + return nameCache_.acquire(bce->fc); +} + +bool EmitterScope::checkSlotLimits(BytecodeEmitter* bce, + const ParserBindingIter& bi) { + if (bi.nextFrameSlot() >= LOCALNO_LIMIT || + bi.nextEnvironmentSlot() >= ENVCOORD_SLOT_LIMIT) { + bce->reportError(nullptr, JSMSG_TOO_MANY_LOCALS); + return false; + } + return true; +} + +bool EmitterScope::checkEnvironmentChainLength(BytecodeEmitter* bce) { + uint32_t hops; + if (EmitterScope* emitterScope = enclosing(&bce)) { + hops = emitterScope->environmentChainLength_; + } else if (!bce->compilationState.input.enclosingScope.isNull()) { + hops = + bce->compilationState.scopeContext.enclosingScopeEnvironmentChainLength; + } else { + // If we're compiling module, enclosingScope is nullptr and it means empty + // global scope. + // See also the assertion in CompilationStencil::instantiateStencils. + // + // Global script also uses enclosingScope == nullptr, but it shouldn't call + // checkEnvironmentChainLength. + MOZ_ASSERT(bce->sc->isModule()); + hops = ModuleScope::EnclosingEnvironmentChainLength; + } + + if (hops >= ENVCOORD_HOPS_LIMIT - 1) { + bce->reportError(nullptr, JSMSG_TOO_DEEP, js_function_str); + return false; + } + + environmentChainLength_ = mozilla::AssertedCast(hops + 1); + return true; +} + +void EmitterScope::updateFrameFixedSlots(BytecodeEmitter* bce, + const ParserBindingIter& bi) { + nextFrameSlot_ = bi.nextFrameSlot(); + if (nextFrameSlot_ > bce->maxFixedSlots) { + bce->maxFixedSlots = nextFrameSlot_; + } +} + +bool EmitterScope::putNameInCache(BytecodeEmitter* bce, + TaggedParserAtomIndex name, + NameLocation loc) { + NameLocationMap& cache = *nameCache_; + NameLocationMap::AddPtr p = cache.lookupForAdd(name); + MOZ_ASSERT(!p); + if (!cache.add(p, name, loc)) { + ReportOutOfMemory(bce->fc); + return false; + } + return true; +} + +Maybe EmitterScope::lookupInCache(BytecodeEmitter* bce, + TaggedParserAtomIndex name) { + if (NameLocationMap::Ptr p = nameCache_->lookup(name)) { + return Some(p->value().wrapped); + } + if (fallbackFreeNameLocation_ && nameCanBeFree(bce, name)) { + return fallbackFreeNameLocation_; + } + return Nothing(); +} + +EmitterScope* EmitterScope::enclosing(BytecodeEmitter** bce) const { + // There is an enclosing scope with access to the same frame. + if (EmitterScope* inFrame = enclosingInFrame()) { + return inFrame; + } + + // We are currently compiling the enclosing script, look in the + // enclosing BCE. + if ((*bce)->parent) { + *bce = (*bce)->parent; + return (*bce)->innermostEmitterScopeNoCheck(); + } + + return nullptr; +} + +mozilla::Maybe EmitterScope::enclosingScopeIndex( + BytecodeEmitter* bce) const { + if (EmitterScope* es = enclosing(&bce)) { + // NOTE: A value of Nothing for the ScopeIndex will occur when the enclosing + // scope is the empty-global-scope. This is only allowed for self-hosting + // code. + MOZ_ASSERT_IF(es->scopeIndex(bce).isNothing(), + bce->emitterMode == BytecodeEmitter::SelfHosting); + return es->scopeIndex(bce); + } + + // The enclosing script is already compiled or the current script is the + // global script. + return mozilla::Nothing(); +} + +/* static */ +bool EmitterScope::nameCanBeFree(BytecodeEmitter* bce, + TaggedParserAtomIndex name) { + // '.generator' cannot be accessed by name. + return name != TaggedParserAtomIndex::WellKnown::dotGenerator(); +} + +NameLocation EmitterScope::searchAndCache(BytecodeEmitter* bce, + TaggedParserAtomIndex name) { + Maybe loc; + uint8_t hops = hasEnvironment() ? 1 : 0; + DebugOnly inCurrentScript = enclosingInFrame(); + + // Start searching in the current compilation. + for (EmitterScope* es = enclosing(&bce); es; es = es->enclosing(&bce)) { + loc = es->lookupInCache(bce, name); + if (loc) { + if (loc->kind() == NameLocation::Kind::EnvironmentCoordinate) { + *loc = loc->addHops(hops); + } + break; + } + + if (es->hasEnvironment()) { + hops++; + } + +#ifdef DEBUG + if (!es->enclosingInFrame()) { + inCurrentScript = false; + } +#endif + } + + // If the name is not found in the current compilation, walk the Scope + // chain encompassing the compilation. + if (!loc) { + MOZ_ASSERT(bce->compilationState.input.target == + CompilationInput::CompilationTarget::Delazification || + bce->compilationState.input.target == + CompilationInput::CompilationTarget::Eval); + inCurrentScript = false; + loc = Some(bce->compilationState.scopeContext.searchInEnclosingScope( + bce->fc, bce->compilationState.input, bce->parserAtoms(), name)); + if (loc->kind() == NameLocation::Kind::EnvironmentCoordinate) { + *loc = loc->addHops(hops); + } + } + + // Each script has its own frame. A free name that is accessed + // from an inner script must not be a frame slot access. If this + // assertion is hit, it is a bug in the free name analysis in the + // parser. + MOZ_ASSERT_IF(!inCurrentScript, loc->kind() != NameLocation::Kind::FrameSlot); + + // It is always correct to not cache the location. Ignore OOMs to make + // lookups infallible. + if (!putNameInCache(bce, name, *loc)) { + bce->fc->recoverFromOutOfMemory(); + } + + return *loc; +} + +bool EmitterScope::internEmptyGlobalScopeAsBody(BytecodeEmitter* bce) { + // Only the self-hosted top-level script uses this. If this changes, you must + // update ScopeStencil::enclosing. + MOZ_ASSERT(bce->emitterMode == BytecodeEmitter::SelfHosting); + + hasEnvironment_ = Scope::hasEnvironment(ScopeKind::Global); + + bce->bodyScopeIndex = + GCThingIndex(bce->perScriptData().gcThingList().length()); + return bce->perScriptData().gcThingList().appendEmptyGlobalScope( + &scopeIndex_); +} + +bool EmitterScope::internScopeStencil(BytecodeEmitter* bce, + ScopeIndex scopeIndex) { + ScopeStencil& scope = bce->compilationState.scopeData[scopeIndex.index]; + hasEnvironment_ = scope.hasEnvironment(); + return bce->perScriptData().gcThingList().append(scopeIndex, &scopeIndex_); +} + +bool EmitterScope::internBodyScopeStencil(BytecodeEmitter* bce, + ScopeIndex scopeIndex) { + MOZ_ASSERT(bce->bodyScopeIndex == ScopeNote::NoScopeIndex, + "There can be only one body scope"); + bce->bodyScopeIndex = + GCThingIndex(bce->perScriptData().gcThingList().length()); + return internScopeStencil(bce, scopeIndex); +} + +bool EmitterScope::appendScopeNote(BytecodeEmitter* bce) { + MOZ_ASSERT(ScopeKindIsInBody(scope(bce).kind()) && enclosingInFrame(), + "Scope notes are not needed for body-level scopes."); + noteIndex_ = bce->bytecodeSection().scopeNoteList().length(); + return bce->bytecodeSection().scopeNoteList().append( + index(), bce->bytecodeSection().offset(), + enclosingInFrame() ? enclosingInFrame()->noteIndex() + : ScopeNote::NoScopeNoteIndex); +} + +bool EmitterScope::clearFrameSlotRange(BytecodeEmitter* bce, JSOp opcode, + uint32_t slotStart, + uint32_t slotEnd) const { + MOZ_ASSERT(opcode == JSOp::Uninitialized || opcode == JSOp::Undefined); + + // Lexical bindings throw ReferenceErrors if they are used before + // initialization. See ES6 8.1.1.1.6. + // + // For completeness, lexical bindings are initialized in ES6 by calling + // InitializeBinding, after which touching the binding will no longer + // throw reference errors. See 13.1.11, 9.2.13, 13.6.3.4, 13.6.4.6, + // 13.6.4.8, 13.14.5, 15.1.8, and 15.2.0.15. + // + // This code is also used to reset `var`s to `undefined` when entering an + // extra body var scope; and to clear slots when leaving a block, in + // generators and async functions, to avoid keeping garbage alive + // indefinitely. + if (slotStart != slotEnd) { + if (!bce->emit1(opcode)) { + return false; + } + for (uint32_t slot = slotStart; slot < slotEnd; slot++) { + if (!bce->emitLocalOp(JSOp::InitLexical, slot)) { + return false; + } + } + if (!bce->emit1(JSOp::Pop)) { + return false; + } + } + + return true; +} + +void EmitterScope::dump(BytecodeEmitter* bce) { + fprintf(stdout, "EmitterScope [%s] %p\n", ScopeKindString(scope(bce).kind()), + this); + + for (NameLocationMap::Range r = nameCache_->all(); !r.empty(); r.popFront()) { + const NameLocation& l = r.front().value(); + + auto atom = r.front().key(); + UniqueChars bytes = bce->parserAtoms().toPrintableString(atom); + if (!bytes) { + ReportOutOfMemory(bce->fc); + return; + } + if (l.kind() != NameLocation::Kind::Dynamic) { + fprintf(stdout, " %s %s ", BindingKindString(l.bindingKind()), + bytes.get()); + } else { + fprintf(stdout, " %s ", bytes.get()); + } + + switch (l.kind()) { + case NameLocation::Kind::Dynamic: + fprintf(stdout, "dynamic\n"); + break; + case NameLocation::Kind::Global: + fprintf(stdout, "global\n"); + break; + case NameLocation::Kind::Intrinsic: + fprintf(stdout, "intrinsic\n"); + break; + case NameLocation::Kind::NamedLambdaCallee: + fprintf(stdout, "named lambda callee\n"); + break; + case NameLocation::Kind::Import: + fprintf(stdout, "import\n"); + break; + case NameLocation::Kind::ArgumentSlot: + fprintf(stdout, "arg slot=%u\n", l.argumentSlot()); + break; + case NameLocation::Kind::FrameSlot: + fprintf(stdout, "frame slot=%u\n", l.frameSlot()); + break; + case NameLocation::Kind::EnvironmentCoordinate: + fprintf(stdout, "environment hops=%u slot=%u\n", + l.environmentCoordinate().hops(), + l.environmentCoordinate().slot()); + break; + case NameLocation::Kind::DebugEnvironmentCoordinate: + fprintf(stdout, "debugEnvironment hops=%u slot=%u\n", + l.environmentCoordinate().hops(), + l.environmentCoordinate().slot()); + break; + case NameLocation::Kind::DynamicAnnexBVar: + fprintf(stdout, "dynamic annex b var\n"); + break; + } + } + + fprintf(stdout, "\n"); +} + +bool EmitterScope::enterLexical(BytecodeEmitter* bce, ScopeKind kind, + LexicalScope::ParserData* bindings) { + MOZ_ASSERT(kind != ScopeKind::NamedLambda && + kind != ScopeKind::StrictNamedLambda); + MOZ_ASSERT(this == bce->innermostEmitterScopeNoCheck()); + + if (!ensureCache(bce)) { + return false; + } + + // Resolve bindings. + TDZCheckCache* tdzCache = bce->innermostTDZCheckCache; + uint32_t firstFrameSlot = frameSlotStart(); + ParserBindingIter bi(*bindings, firstFrameSlot, /* isNamedLambda = */ false); + for (; bi; bi++) { + if (!checkSlotLimits(bce, bi)) { + return false; + } + + NameLocation loc = bi.nameLocation(); + if (!putNameInCache(bce, bi.name(), loc)) { + return false; + } + + if (!tdzCache->noteTDZCheck(bce, bi.name(), CheckTDZ)) { + return false; + } + } + + updateFrameFixedSlots(bce, bi); + + ScopeIndex scopeIndex; + if (!ScopeStencil::createForLexicalScope( + bce->fc, bce->compilationState, kind, bindings, firstFrameSlot, + enclosingScopeIndex(bce), &scopeIndex)) { + return false; + } + if (!internScopeStencil(bce, scopeIndex)) { + return false; + } + + if (ScopeKindIsInBody(kind) && hasEnvironment()) { + // After interning the VM scope we can get the scope index. + if (!bce->emitInternedScopeOp(index(), JSOp::PushLexicalEnv)) { + return false; + } + } + + // Lexical scopes need notes to be mapped from a pc. + if (!appendScopeNote(bce)) { + return false; + } + + // Put frame slots in TDZ. Environment slots are poisoned during + // environment creation. + // + // This must be done after appendScopeNote to be considered in the extent + // of the scope. + if (!deadZoneFrameSlotRange(bce, firstFrameSlot, frameSlotEnd())) { + return false; + } + + return checkEnvironmentChainLength(bce); +} + +bool EmitterScope::enterClassBody(BytecodeEmitter* bce, ScopeKind kind, + ClassBodyScope::ParserData* bindings) { + MOZ_ASSERT(kind == ScopeKind::ClassBody); + MOZ_ASSERT(this == bce->innermostEmitterScopeNoCheck()); + + if (!ensureCache(bce)) { + return false; + } + + // Resolve bindings. + TDZCheckCache* tdzCache = bce->innermostTDZCheckCache; + uint32_t firstFrameSlot = frameSlotStart(); + ParserBindingIter bi(*bindings, firstFrameSlot); + for (; bi; bi++) { + if (!checkSlotLimits(bce, bi)) { + return false; + } + + NameLocation loc = bi.nameLocation(); + if (!putNameInCache(bce, bi.name(), loc)) { + return false; + } + + if (!tdzCache->noteTDZCheck(bce, bi.name(), CheckTDZ)) { + return false; + } + } + + updateFrameFixedSlots(bce, bi); + + ScopeIndex scopeIndex; + if (!ScopeStencil::createForClassBodyScope( + bce->fc, bce->compilationState, kind, bindings, firstFrameSlot, + enclosingScopeIndex(bce), &scopeIndex)) { + return false; + } + if (!internScopeStencil(bce, scopeIndex)) { + return false; + } + + if (ScopeKindIsInBody(kind) && hasEnvironment()) { + // After interning the VM scope we can get the scope index. + // + // ClassBody uses PushClassBodyEnv, however, PopLexicalEnv supports both + // cases and doesn't need extra specialization. + if (!bce->emitInternedScopeOp(index(), JSOp::PushClassBodyEnv)) { + return false; + } + } + + // Lexical scopes need notes to be mapped from a pc. + if (!appendScopeNote(bce)) { + return false; + } + + return checkEnvironmentChainLength(bce); +} + +bool EmitterScope::enterNamedLambda(BytecodeEmitter* bce, FunctionBox* funbox) { + MOZ_ASSERT(this == bce->innermostEmitterScopeNoCheck()); + MOZ_ASSERT(funbox->namedLambdaBindings()); + + if (!ensureCache(bce)) { + return false; + } + + ParserBindingIter bi(*funbox->namedLambdaBindings(), LOCALNO_LIMIT, + /* isNamedLambda = */ true); + MOZ_ASSERT(bi.kind() == BindingKind::NamedLambdaCallee); + + // The lambda name, if not closed over, is accessed via JSOp::Callee and + // not a frame slot. Do not update frame slot information. + NameLocation loc = bi.nameLocation(); + if (!putNameInCache(bce, bi.name(), loc)) { + return false; + } + + bi++; + MOZ_ASSERT(!bi, "There should be exactly one binding in a NamedLambda scope"); + + ScopeKind scopeKind = + funbox->strict() ? ScopeKind::StrictNamedLambda : ScopeKind::NamedLambda; + + ScopeIndex scopeIndex; + if (!ScopeStencil::createForLexicalScope( + bce->fc, bce->compilationState, scopeKind, + funbox->namedLambdaBindings(), LOCALNO_LIMIT, + enclosingScopeIndex(bce), &scopeIndex)) { + return false; + } + if (!internScopeStencil(bce, scopeIndex)) { + return false; + } + + return checkEnvironmentChainLength(bce); +} + +bool EmitterScope::enterFunction(BytecodeEmitter* bce, FunctionBox* funbox) { + MOZ_ASSERT(this == bce->innermostEmitterScopeNoCheck()); + + // If there are parameter expressions, there is an extra var scope. + if (!funbox->functionHasExtraBodyVarScope()) { + bce->setVarEmitterScope(this); + } + + if (!ensureCache(bce)) { + return false; + } + + // Resolve body-level bindings, if there are any. + auto bindings = funbox->functionScopeBindings(); + if (bindings) { + NameLocationMap& cache = *nameCache_; + + ParserBindingIter bi(*bindings, funbox->hasParameterExprs); + for (; bi; bi++) { + if (!checkSlotLimits(bce, bi)) { + return false; + } + + NameLocation loc = bi.nameLocation(); + NameLocationMap::AddPtr p = cache.lookupForAdd(bi.name()); + + // The only duplicate bindings that occur are simple formal + // parameters, in which case the last position counts, so update the + // location. + if (p) { + MOZ_ASSERT(bi.kind() == BindingKind::FormalParameter); + MOZ_ASSERT(!funbox->hasDestructuringArgs); + MOZ_ASSERT(!funbox->hasRest()); + p->value() = loc; + continue; + } + + if (!cache.add(p, bi.name(), loc)) { + ReportOutOfMemory(bce->fc); + return false; + } + } + + updateFrameFixedSlots(bce, bi); + } else { + nextFrameSlot_ = 0; + } + + // If the function's scope may be extended at runtime due to sloppy direct + // eval, any names beyond the function scope must be accessed dynamically as + // we don't know if the name will become a 'var' binding due to direct eval. + if (funbox->funHasExtensibleScope()) { + fallbackFreeNameLocation_ = Some(NameLocation::Dynamic()); + } else if (funbox->isStandalone) { + // If the function is standalone, the enclosing scope is either an empty + // global or non-syntactic scope, and there's no static bindings. + if (bce->compilationState.input.target == + CompilationInput::CompilationTarget:: + StandaloneFunctionInNonSyntacticScope) { + fallbackFreeNameLocation_ = Some(NameLocation::Dynamic()); + } else { + fallbackFreeNameLocation_ = Some(NameLocation::Global(BindingKind::Var)); + } + } + + // In case of parameter expressions, the parameters are lexical + // bindings and have TDZ. + if (funbox->hasParameterExprs && nextFrameSlot_) { + uint32_t paramFrameSlotEnd = 0; + for (ParserBindingIter bi(*bindings, true); bi; bi++) { + if (!BindingKindIsLexical(bi.kind())) { + break; + } + + NameLocation loc = bi.nameLocation(); + if (loc.kind() == NameLocation::Kind::FrameSlot) { + MOZ_ASSERT(paramFrameSlotEnd <= loc.frameSlot()); + paramFrameSlotEnd = loc.frameSlot() + 1; + } + } + + if (!deadZoneFrameSlotRange(bce, 0, paramFrameSlotEnd)) { + return false; + } + } + + ScopeIndex scopeIndex; + if (!ScopeStencil::createForFunctionScope( + bce->fc, bce->compilationState, funbox->functionScopeBindings(), + funbox->hasParameterExprs, + funbox->needsCallObjectRegardlessOfBindings(), funbox->index(), + funbox->isArrow(), enclosingScopeIndex(bce), &scopeIndex)) { + return false; + } + if (!internBodyScopeStencil(bce, scopeIndex)) { + return false; + } + + return checkEnvironmentChainLength(bce); +} + +bool EmitterScope::enterFunctionExtraBodyVar(BytecodeEmitter* bce, + FunctionBox* funbox) { + MOZ_ASSERT(funbox->hasParameterExprs); + MOZ_ASSERT(funbox->extraVarScopeBindings() || + funbox->needsExtraBodyVarEnvironmentRegardlessOfBindings()); + MOZ_ASSERT(this == bce->innermostEmitterScopeNoCheck()); + + // The extra var scope is never popped once it's entered. It replaces the + // function scope as the var emitter scope. + bce->setVarEmitterScope(this); + + if (!ensureCache(bce)) { + return false; + } + + // Resolve body-level bindings, if there are any. + uint32_t firstFrameSlot = frameSlotStart(); + if (auto bindings = funbox->extraVarScopeBindings()) { + ParserBindingIter bi(*bindings, firstFrameSlot); + for (; bi; bi++) { + if (!checkSlotLimits(bce, bi)) { + return false; + } + + NameLocation loc = bi.nameLocation(); + MOZ_ASSERT(bi.kind() == BindingKind::Var); + if (!putNameInCache(bce, bi.name(), loc)) { + return false; + } + } + + uint32_t priorEnd = bce->maxFixedSlots; + updateFrameFixedSlots(bce, bi); + + // If any of the bound slots were previously used, reset them to undefined. + // This doesn't break TDZ for let/const/class bindings because there aren't + // any in extra body var scopes. We assert above that bi.kind() is Var. + uint32_t end = std::min(priorEnd, nextFrameSlot_); + if (firstFrameSlot < end) { + if (!clearFrameSlotRange(bce, JSOp::Undefined, firstFrameSlot, end)) { + return false; + } + } + } else { + nextFrameSlot_ = firstFrameSlot; + } + + // If the extra var scope may be extended at runtime due to sloppy + // direct eval, any names beyond the var scope must be accessed + // dynamically as we don't know if the name will become a 'var' binding + // due to direct eval. + if (funbox->funHasExtensibleScope()) { + fallbackFreeNameLocation_ = Some(NameLocation::Dynamic()); + } + + // Create and intern the VM scope. + ScopeIndex scopeIndex; + if (!ScopeStencil::createForVarScope( + bce->fc, bce->compilationState, ScopeKind::FunctionBodyVar, + funbox->extraVarScopeBindings(), firstFrameSlot, + funbox->needsExtraBodyVarEnvironmentRegardlessOfBindings(), + enclosingScopeIndex(bce), &scopeIndex)) { + return false; + } + if (!internScopeStencil(bce, scopeIndex)) { + return false; + } + + if (hasEnvironment()) { + if (!bce->emitInternedScopeOp(index(), JSOp::PushVarEnv)) { + return false; + } + } + + // The extra var scope needs a note to be mapped from a pc. + if (!appendScopeNote(bce)) { + return false; + } + + return checkEnvironmentChainLength(bce); +} + +bool EmitterScope::enterGlobal(BytecodeEmitter* bce, + GlobalSharedContext* globalsc) { + MOZ_ASSERT(this == bce->innermostEmitterScopeNoCheck()); + + // TODO-Stencil + // This is another snapshot-sensitive location. + // The incoming atoms from the global scope object should be snapshotted. + // For now, converting them to ParserAtoms here individually. + + bce->setVarEmitterScope(this); + + if (!ensureCache(bce)) { + return false; + } + + if (bce->emitterMode == BytecodeEmitter::SelfHosting) { + // In self-hosting, it is incorrect to consult the global scope because + // self-hosted scripts are cloned into their target compartments before + // they are run. Instead of Global, Intrinsic is used for all names. + // + // Intrinsic lookups are redirected to the special intrinsics holder + // in the global object, into which any missing values are cloned + // lazily upon first access. + fallbackFreeNameLocation_ = Some(NameLocation::Intrinsic()); + + return internEmptyGlobalScopeAsBody(bce); + } + + ScopeIndex scopeIndex; + if (!ScopeStencil::createForGlobalScope(bce->fc, bce->compilationState, + globalsc->scopeKind(), + globalsc->bindings, &scopeIndex)) { + return false; + } + if (!internBodyScopeStencil(bce, scopeIndex)) { + return false; + } + + // See: JSScript::outermostScope. + MOZ_ASSERT(bce->bodyScopeIndex == GCThingIndex::outermostScopeIndex(), + "Global scope must be index 0"); + + // Resolve binding names. + // + // NOTE: BytecodeEmitter::emitDeclarationInstantiation will emit the + // redeclaration check and initialize these bindings. + if (globalsc->bindings) { + for (ParserBindingIter bi(*globalsc->bindings); bi; bi++) { + NameLocation loc = bi.nameLocation(); + if (!putNameInCache(bce, bi.name(), loc)) { + return false; + } + } + } + + // Note that to save space, we don't add free names to the cache for + // global scopes. They are assumed to be global vars in the syntactic + // global scope, dynamic accesses under non-syntactic global scope. + if (globalsc->scopeKind() == ScopeKind::Global) { + fallbackFreeNameLocation_ = Some(NameLocation::Global(BindingKind::Var)); + } else { + fallbackFreeNameLocation_ = Some(NameLocation::Dynamic()); + } + + return true; +} + +bool EmitterScope::enterEval(BytecodeEmitter* bce, EvalSharedContext* evalsc) { + MOZ_ASSERT(this == bce->innermostEmitterScopeNoCheck()); + + bce->setVarEmitterScope(this); + + if (!ensureCache(bce)) { + return false; + } + + // Create the `var` scope. Note that there is also a lexical scope, created + // separately in emitScript(). + ScopeKind scopeKind = + evalsc->strict() ? ScopeKind::StrictEval : ScopeKind::Eval; + + ScopeIndex scopeIndex; + if (!ScopeStencil::createForEvalScope( + bce->fc, bce->compilationState, scopeKind, evalsc->bindings, + enclosingScopeIndex(bce), &scopeIndex)) { + return false; + } + if (!internBodyScopeStencil(bce, scopeIndex)) { + return false; + } + + if (evalsc->strict()) { + if (evalsc->bindings) { + ParserBindingIter bi(*evalsc->bindings, true); + for (; bi; bi++) { + if (!checkSlotLimits(bce, bi)) { + return false; + } + + NameLocation loc = bi.nameLocation(); + if (!putNameInCache(bce, bi.name(), loc)) { + return false; + } + } + + updateFrameFixedSlots(bce, bi); + } + } else { + // For simplicity, treat all free name lookups in nonstrict eval scripts as + // dynamic. + fallbackFreeNameLocation_ = Some(NameLocation::Dynamic()); + } + + if (hasEnvironment()) { + if (!bce->emitInternedScopeOp(index(), JSOp::PushVarEnv)) { + return false; + } + } else { + // NOTE: BytecodeEmitter::emitDeclarationInstantiation will emit the + // redeclaration check and initialize these bindings for sloppy + // eval. + + // As an optimization, if the eval does not have its own var + // environment and is directly enclosed in a global scope, then all + // free name lookups are global. + if (scope(bce).enclosing().is()) { + fallbackFreeNameLocation_ = Some(NameLocation::Global(BindingKind::Var)); + } + } + + return checkEnvironmentChainLength(bce); +} + +bool EmitterScope::enterModule(BytecodeEmitter* bce, + ModuleSharedContext* modulesc) { + MOZ_ASSERT(this == bce->innermostEmitterScopeNoCheck()); + + bce->setVarEmitterScope(this); + + if (!ensureCache(bce)) { + return false; + } + + // Resolve body-level bindings, if there are any. + TDZCheckCache* tdzCache = bce->innermostTDZCheckCache; + Maybe firstLexicalFrameSlot; + if (ModuleScope::ParserData* bindings = modulesc->bindings) { + ParserBindingIter bi(*bindings); + for (; bi; bi++) { + if (!checkSlotLimits(bce, bi)) { + return false; + } + + NameLocation loc = bi.nameLocation(); + if (!putNameInCache(bce, bi.name(), loc)) { + return false; + } + + if (BindingKindIsLexical(bi.kind())) { + if (loc.kind() == NameLocation::Kind::FrameSlot && + !firstLexicalFrameSlot) { + firstLexicalFrameSlot = Some(loc.frameSlot()); + } + + if (!tdzCache->noteTDZCheck(bce, bi.name(), CheckTDZ)) { + return false; + } + } + } + + updateFrameFixedSlots(bce, bi); + } else { + nextFrameSlot_ = 0; + } + + // Modules are toplevel, so any free names are global. + fallbackFreeNameLocation_ = Some(NameLocation::Global(BindingKind::Var)); + + // Put lexical frame slots in TDZ. Environment slots are poisoned during + // environment creation. + if (firstLexicalFrameSlot) { + if (!deadZoneFrameSlotRange(bce, *firstLexicalFrameSlot, frameSlotEnd())) { + return false; + } + } + + // Create and intern the VM scope creation data. + ScopeIndex scopeIndex; + if (!ScopeStencil::createForModuleScope( + bce->fc, bce->compilationState, modulesc->bindings, + enclosingScopeIndex(bce), &scopeIndex)) { + return false; + } + if (!internBodyScopeStencil(bce, scopeIndex)) { + return false; + } + + return checkEnvironmentChainLength(bce); +} + +bool EmitterScope::enterWith(BytecodeEmitter* bce) { + MOZ_ASSERT(this == bce->innermostEmitterScopeNoCheck()); + + if (!ensureCache(bce)) { + return false; + } + + // 'with' make all accesses dynamic and unanalyzable. + fallbackFreeNameLocation_ = Some(NameLocation::Dynamic()); + + ScopeIndex scopeIndex; + if (!ScopeStencil::createForWithScope(bce->fc, bce->compilationState, + enclosingScopeIndex(bce), + &scopeIndex)) { + return false; + } + + if (!internScopeStencil(bce, scopeIndex)) { + return false; + } + + if (!bce->emitInternedScopeOp(index(), JSOp::EnterWith)) { + return false; + } + + if (!appendScopeNote(bce)) { + return false; + } + + return checkEnvironmentChainLength(bce); +} + +bool EmitterScope::deadZoneFrameSlots(BytecodeEmitter* bce) const { + return deadZoneFrameSlotRange(bce, frameSlotStart(), frameSlotEnd()); +} + +bool EmitterScope::leave(BytecodeEmitter* bce, bool nonLocal) { + // If we aren't leaving the scope due to a non-local jump (e.g., break), + // we must be the innermost scope. + MOZ_ASSERT_IF(!nonLocal, this == bce->innermostEmitterScopeNoCheck()); + + ScopeKind kind = scope(bce).kind(); + switch (kind) { + case ScopeKind::Lexical: + case ScopeKind::SimpleCatch: + case ScopeKind::Catch: + case ScopeKind::FunctionLexical: + case ScopeKind::ClassBody: + if (bce->sc->isFunctionBox() && + bce->sc->asFunctionBox()->needsClearSlotsOnExit()) { + if (!deadZoneFrameSlots(bce)) { + return false; + } + } + if (!bce->emit1(hasEnvironment() ? JSOp::PopLexicalEnv + : JSOp::DebugLeaveLexicalEnv)) { + return false; + } + break; + + case ScopeKind::With: + if (!bce->emit1(JSOp::LeaveWith)) { + return false; + } + break; + + case ScopeKind::Function: + case ScopeKind::FunctionBodyVar: + case ScopeKind::NamedLambda: + case ScopeKind::StrictNamedLambda: + case ScopeKind::Eval: + case ScopeKind::StrictEval: + case ScopeKind::Global: + case ScopeKind::NonSyntactic: + case ScopeKind::Module: + break; + + case ScopeKind::WasmInstance: + case ScopeKind::WasmFunction: + MOZ_CRASH("No wasm function scopes in JS"); + } + + // Finish up the scope if we are leaving it in LIFO fashion. + if (!nonLocal) { + // Popping scopes due to non-local jumps generate additional scope + // notes. See NonLocalExitControl::prepareForNonLocalJump. + if (ScopeKindIsInBody(kind)) { + if (kind == ScopeKind::FunctionBodyVar) { + // The extra function var scope is never popped once it's pushed, + // so its scope note extends until the end of any possible code. + bce->bytecodeSection().scopeNoteList().recordEndFunctionBodyVar( + noteIndex_); + } else { + bce->bytecodeSection().scopeNoteList().recordEnd( + noteIndex_, bce->bytecodeSection().offset()); + } + } + } + + return true; +} + +AbstractScopePtr EmitterScope::scope(const BytecodeEmitter* bce) const { + return bce->perScriptData().gcThingList().getScope(index()); +} + +mozilla::Maybe EmitterScope::scopeIndex( + const BytecodeEmitter* bce) const { + return bce->perScriptData().gcThingList().getScopeIndex(index()); +} + +NameLocation EmitterScope::lookup(BytecodeEmitter* bce, + TaggedParserAtomIndex name) { + if (Maybe loc = lookupInCache(bce, name)) { + return *loc; + } + return searchAndCache(bce, name); +} + +/* static */ +uint32_t EmitterScope::CountEnclosingCompilationEnvironments( + BytecodeEmitter* bce, EmitterScope* emitterScope) { + uint32_t environments = emitterScope->hasEnvironment() ? 1 : 0; + while ((emitterScope = emitterScope->enclosing(&bce))) { + if (emitterScope->hasEnvironment()) { + environments++; + } + } + return environments; +} + +void EmitterScope::lookupPrivate(BytecodeEmitter* bce, + TaggedParserAtomIndex name, NameLocation& loc, + mozilla::Maybe& brandLoc) { + loc = lookup(bce, name); + + // Private Brand checking relies on the ability to construct a new + // environment coordinate for a name at a fixed offset, which will + // correspond to the private brand for that class. + // + // If our name lookup isn't a fixed location, we must construct a + // new environment coordinate, using information available from our private + // field cache. (See cachePrivateFieldsForEval, and Bug 1638309). + // + // This typically involves a DebugEnvironmentProxy, so we need to use a + // DebugEnvironmentCoordinate. + // + // See also Bug 793345 which argues that we should remove the + // DebugEnvironmentProxy. + if (loc.kind() != NameLocation::Kind::EnvironmentCoordinate && + loc.kind() != NameLocation::Kind::FrameSlot) { + MOZ_ASSERT(loc.kind() == NameLocation::Kind::Dynamic || + loc.kind() == NameLocation::Kind::Global); + // Private fields don't require brand checking and can be correctly + // code-generated with dynamic name lookup bytecode we have today. However, + // for that to happen we first need to figure out if we have a Private + // method or private field, which we cannot disambiguate based on the + // dynamic lookup. + // + // However, this is precisely the case that the private field eval case can + // help us handle. It knows the truth about these private bindings. + mozilla::Maybe cacheEntry = + bce->compilationState.scopeContext.getPrivateFieldLocation(name); + MOZ_ASSERT(cacheEntry); + + if (cacheEntry->bindingKind() == BindingKind::PrivateMethod) { + MOZ_ASSERT(cacheEntry->kind() == + NameLocation::Kind::DebugEnvironmentCoordinate); + // To construct the brand check there are two hop values required: + // + // 1. Compilation Hops: The number of environment hops required to get to + // the compilation enclosing environment. + // 2. "external hops", to get from compilation enclosing debug environment + // to the environment that actually contains our brand. This is + // determined by the cacheEntry. This traversal will bypass a Debug + // environment proxy, which is why need to use + // DebugEnvironmentCoordinate. + + uint32_t compilation_hops = + CountEnclosingCompilationEnvironments(bce, this); + + uint32_t external_hops = cacheEntry->environmentCoordinate().hops(); + + brandLoc = Some(NameLocation::DebugEnvironmentCoordinate( + BindingKind::Synthetic, compilation_hops + external_hops, + ClassBodyLexicalEnvironmentObject::privateBrandSlot())); + } else { + brandLoc = Nothing(); + } + return; + } + + if (loc.bindingKind() == BindingKind::PrivateMethod) { + uint32_t hops = 0; + if (loc.kind() == NameLocation::Kind::EnvironmentCoordinate) { + hops = loc.environmentCoordinate().hops(); + } else { + // If we have a FrameSlot, then our innermost emitter scope must be a + // class body scope, and we can generate an environment coordinate with + // hops=0 to find the associated brand location. + MOZ_ASSERT(bce->innermostScope().is()); + } + + brandLoc = Some(NameLocation::EnvironmentCoordinate( + BindingKind::Synthetic, hops, + ClassBodyLexicalEnvironmentObject::privateBrandSlot())); + } else { + brandLoc = Nothing(); + } +} + +Maybe EmitterScope::locationBoundInScope( + TaggedParserAtomIndex name, EmitterScope* target) { + // The target scope must be an intra-frame enclosing scope of this + // one. Count the number of extra hops to reach it. + uint8_t extraHops = 0; + for (EmitterScope* es = this; es != target; es = es->enclosingInFrame()) { + if (es->hasEnvironment()) { + extraHops++; + } + } + + // Caches are prepopulated with bound names. So if the name is bound in a + // particular scope, it must already be in the cache. Furthermore, don't + // consult the fallback location as we only care about binding names. + Maybe loc; + if (NameLocationMap::Ptr p = target->nameCache_->lookup(name)) { + NameLocation l = p->value().wrapped; + if (l.kind() == NameLocation::Kind::EnvironmentCoordinate) { + loc = Some(l.addHops(extraHops)); + } else { + loc = Some(l); + } + } + return loc; +} diff --git a/js/src/frontend/EmitterScope.h b/js/src/frontend/EmitterScope.h new file mode 100644 index 0000000000..8f985faffe --- /dev/null +++ b/js/src/frontend/EmitterScope.h @@ -0,0 +1,210 @@ +/* -*- 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/. */ + +#ifndef frontend_EmitterScope_h +#define frontend_EmitterScope_h + +#include "mozilla/Maybe.h" + +#include + +#include "ds/Nestable.h" +#include "frontend/AbstractScopePtr.h" +#include "frontend/NameAnalysisTypes.h" +#include "frontend/NameCollections.h" +#include "frontend/Stencil.h" +#include "vm/Opcodes.h" // JSOp +#include "vm/SharedStencil.h" // GCThingIndex + +namespace js { +namespace frontend { + +struct BytecodeEmitter; +class EvalSharedContext; +class FunctionBox; +class GlobalSharedContext; +class ModuleSharedContext; +class TaggedParserAtomIndex; + +// A scope that introduces bindings. +class EmitterScope : public Nestable { + // The cache of bound names that may be looked up in the + // scope. Initially populated as the set of names this scope binds. As + // names are looked up in enclosing scopes, they are cached on the + // current scope. + PooledMapPtr nameCache_; + + // If this scope's cache does not include free names, such as the + // global scope, the NameLocation to return. + mozilla::Maybe fallbackFreeNameLocation_; + + // True if there is a corresponding EnvironmentObject on the environment + // chain, false if all bindings are stored in frame slots on the stack. + bool hasEnvironment_; + + // The number of enclosing environments. Used for error checking. + uint8_t environmentChainLength_; + + // The next usable slot on the frame for not-closed over bindings. + // + // The initial frame slot when assigning slots to bindings is the + // enclosing scope's nextFrameSlot. For the first scope in a frame, + // the initial frame slot is 0. + uint32_t nextFrameSlot_; + + // The index in the BytecodeEmitter's interned scope vector, otherwise + // ScopeNote::NoScopeIndex. + GCThingIndex scopeIndex_; + + // If kind is Lexical, Catch, or With, the index in the BytecodeEmitter's + // block scope note list. Otherwise ScopeNote::NoScopeNote. + uint32_t noteIndex_; + + [[nodiscard]] bool ensureCache(BytecodeEmitter* bce); + + [[nodiscard]] bool checkSlotLimits(BytecodeEmitter* bce, + const ParserBindingIter& bi); + + [[nodiscard]] bool checkEnvironmentChainLength(BytecodeEmitter* bce); + + void updateFrameFixedSlots(BytecodeEmitter* bce, const ParserBindingIter& bi); + + [[nodiscard]] bool putNameInCache(BytecodeEmitter* bce, + TaggedParserAtomIndex name, + NameLocation loc); + + mozilla::Maybe lookupInCache(BytecodeEmitter* bce, + TaggedParserAtomIndex name); + + EmitterScope* enclosing(BytecodeEmitter** bce) const; + + mozilla::Maybe enclosingScopeIndex(BytecodeEmitter* bce) const; + + static bool nameCanBeFree(BytecodeEmitter* bce, TaggedParserAtomIndex name); + + NameLocation searchAndCache(BytecodeEmitter* bce, TaggedParserAtomIndex name); + + [[nodiscard]] bool internEmptyGlobalScopeAsBody(BytecodeEmitter* bce); + + [[nodiscard]] bool internScopeStencil(BytecodeEmitter* bce, ScopeIndex index); + + [[nodiscard]] bool internBodyScopeStencil(BytecodeEmitter* bce, + ScopeIndex index); + [[nodiscard]] bool appendScopeNote(BytecodeEmitter* bce); + + [[nodiscard]] bool clearFrameSlotRange(BytecodeEmitter* bce, JSOp opcode, + uint32_t slotStart, + uint32_t slotEnd) const; + + [[nodiscard]] bool deadZoneFrameSlotRange(BytecodeEmitter* bce, + uint32_t slotStart, + uint32_t slotEnd) const { + return clearFrameSlotRange(bce, JSOp::Uninitialized, slotStart, slotEnd); + } + + public: + explicit EmitterScope(BytecodeEmitter* bce); + + void dump(BytecodeEmitter* bce); + + [[nodiscard]] bool enterLexical(BytecodeEmitter* bce, ScopeKind kind, + LexicalScope::ParserData* bindings); + [[nodiscard]] bool enterClassBody(BytecodeEmitter* bce, ScopeKind kind, + ClassBodyScope::ParserData* bindings); + [[nodiscard]] bool enterNamedLambda(BytecodeEmitter* bce, + FunctionBox* funbox); + [[nodiscard]] bool enterFunction(BytecodeEmitter* bce, FunctionBox* funbox); + [[nodiscard]] bool enterFunctionExtraBodyVar(BytecodeEmitter* bce, + FunctionBox* funbox); + [[nodiscard]] bool enterGlobal(BytecodeEmitter* bce, + GlobalSharedContext* globalsc); + [[nodiscard]] bool enterEval(BytecodeEmitter* bce, EvalSharedContext* evalsc); + [[nodiscard]] bool enterModule(BytecodeEmitter* module, + ModuleSharedContext* modulesc); + [[nodiscard]] bool enterWith(BytecodeEmitter* bce); + [[nodiscard]] bool deadZoneFrameSlots(BytecodeEmitter* bce) const; + + [[nodiscard]] bool leave(BytecodeEmitter* bce, bool nonLocal = false); + + GCThingIndex index() const { + MOZ_ASSERT(scopeIndex_ != ScopeNote::NoScopeIndex, + "Did you forget to intern a Scope?"); + return scopeIndex_; + } + + uint32_t noteIndex() const { return noteIndex_; } + + AbstractScopePtr scope(const BytecodeEmitter* bce) const; + mozilla::Maybe scopeIndex(const BytecodeEmitter* bce) const; + + bool hasEnvironment() const { return hasEnvironment_; } + + // The first frame slot used. + uint32_t frameSlotStart() const { + if (EmitterScope* inFrame = enclosingInFrame()) { + return inFrame->nextFrameSlot_; + } + return 0; + } + + // The last frame slot used + 1. + uint32_t frameSlotEnd() const { return nextFrameSlot_; } + + EmitterScope* enclosingInFrame() const { + return Nestable::enclosing(); + } + + NameLocation lookup(BytecodeEmitter* bce, TaggedParserAtomIndex name); + + // Find both the slot associated with a private name and the location of the + // corresponding `.privateBrand` binding. + // + // Simply doing two separate lookups, one for `name` and another for + // `.privateBrand`, would give the wrong answer in this case: + // + // class Outer { + // #outerMethod() { reutrn "ok"; } + // + // test() { + // class Inner { + // #innerMethod() {} + // test(outer) { + // return outer.#outerMethod(); + // } + // } + // return new Inner().test(this); + // } + // } + // + // new Outer().test(); // should return "ok" + // + // At the point in Inner.test where `#outerMethod` is called, we need to + // check for the private brand of `Outer`, not `Inner`; but both class bodies + // have `.privateBrand` bindings. In a normal `lookup`, the inner binding + // would shadow the outer one. + // + // This method instead sets `brandLoc` to the location of the `.privateBrand` + // binding in the same class body as the private name `name`, ignoring + // shadowing. If `name` refers to a name that is actually stamped onto the + // target object (anything other than a non-static private method), then + // `brandLoc` is set to Nothing. + void lookupPrivate(BytecodeEmitter* bce, TaggedParserAtomIndex name, + NameLocation& loc, mozilla::Maybe& brandLoc); + + mozilla::Maybe locationBoundInScope(TaggedParserAtomIndex name, + EmitterScope* target); + + // For a given emitter scope, return the number of enclosing environments in + // the current compilation (this excludes environments that could enclose the + // compilation, like would happen for an eval copmilation). + static uint32_t CountEnclosingCompilationEnvironments( + BytecodeEmitter* bce, EmitterScope* emitterScope); +}; + +} /* namespace frontend */ +} /* namespace js */ + +#endif /* frontend_EmitterScope_h */ diff --git a/js/src/frontend/ErrorReporter.h b/js/src/frontend/ErrorReporter.h new file mode 100644 index 0000000000..b6f14a0852 --- /dev/null +++ b/js/src/frontend/ErrorReporter.h @@ -0,0 +1,342 @@ +/* -*- 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/. */ + +#ifndef frontend_ErrorReporter_h +#define frontend_ErrorReporter_h + +#include "mozilla/Variant.h" + +#include // for va_list +#include // for size_t +#include // for uint32_t + +#include "js/UniquePtr.h" +#include "vm/ErrorReporting.h" // ErrorMetadata, ReportCompile{Error,Warning} + +namespace JS { +class JS_PUBLIC_API ReadOnlyCompileOptions; +} + +namespace js { +namespace frontend { + +// An interface class to provide strictMode getter method, which is used by +// ErrorReportMixin::strictModeError* methods. +// +// This class is separated to be used as a back-channel from TokenStream to the +// strict mode flag which is available inside Parser, to avoid exposing the +// rest of SharedContext to TokenStream. +class StrictModeGetter { + public: + virtual bool strictMode() const = 0; +}; + +// This class provides error reporting methods, including warning, extra +// warning, and strict mode error. +// +// A class that inherits this class must provide the following methods: +// * options +// * getContext +// * computeErrorMetadata +class ErrorReportMixin : public StrictModeGetter { + public: + // Returns a compile options (extra warning, warning as error) for current + // compilation. + virtual const JS::ReadOnlyCompileOptions& options() const = 0; + + // Returns the current context. + virtual FrontendContext* getContext() const = 0; + + // A variant class for the offset of the error or warning. + struct Current {}; + struct NoOffset {}; + using ErrorOffset = mozilla::Variant; + + // Fills ErrorMetadata fields for an error or warning at given offset. + // * offset is uint32_t if methods ending with "At" is called + // * offset is NoOffset if methods ending with "NoOffset" is called + // * offset is Current otherwise + [[nodiscard]] virtual bool computeErrorMetadata( + ErrorMetadata* err, const ErrorOffset& offset) const = 0; + + // ==== error ==== + // + // Reports an error. + // + // Methods ending with "At" are for an error at given offset. + // The offset is passed to computeErrorMetadata method and is transparent + // for this class. + // + // Methods ending with "NoOffset" are for an error that doesn't correspond + // to any offset. NoOffset is passed to computeErrorMetadata for them. + // + // Other methods except errorWithNotesAtVA are for an error at the current + // offset. Current is passed to computeErrorMetadata for them. + // + // Methods contains "WithNotes" can be used if there are error notes. + // + // errorWithNotesAtVA is the actual implementation for all of above. + // This can be called if the caller already has a va_list. + + void error(unsigned errorNumber, ...) const { + va_list args; + va_start(args, errorNumber); + + errorWithNotesAtVA(nullptr, mozilla::AsVariant(Current()), errorNumber, + &args); + + va_end(args); + } + void errorWithNotes(UniquePtr notes, unsigned errorNumber, + ...) const { + va_list args; + va_start(args, errorNumber); + + errorWithNotesAtVA(std::move(notes), mozilla::AsVariant(Current()), + errorNumber, &args); + + va_end(args); + } + void errorAt(uint32_t offset, unsigned errorNumber, ...) const { + va_list args; + va_start(args, errorNumber); + + errorWithNotesAtVA(nullptr, mozilla::AsVariant(offset), errorNumber, &args); + + va_end(args); + } + void errorWithNotesAt(UniquePtr notes, uint32_t offset, + unsigned errorNumber, ...) const { + va_list args; + va_start(args, errorNumber); + + errorWithNotesAtVA(std::move(notes), mozilla::AsVariant(offset), + errorNumber, &args); + + va_end(args); + } + void errorNoOffset(unsigned errorNumber, ...) const { + va_list args; + va_start(args, errorNumber); + + errorWithNotesAtVA(nullptr, mozilla::AsVariant(NoOffset()), errorNumber, + &args); + + va_end(args); + } + void errorWithNotesNoOffset(UniquePtr notes, + unsigned errorNumber, ...) const { + va_list args; + va_start(args, errorNumber); + + errorWithNotesAtVA(std::move(notes), mozilla::AsVariant(NoOffset()), + errorNumber, &args); + + va_end(args); + } + void errorWithNotesAtVA(UniquePtr notes, + const ErrorOffset& offset, unsigned errorNumber, + va_list* args) const { + ErrorMetadata metadata; + if (!computeErrorMetadata(&metadata, offset)) { + return; + } + + ReportCompileErrorLatin1(getContext(), std::move(metadata), + std::move(notes), errorNumber, args); + } + + // ==== warning ==== + // + // Reports a warning. + // + // Returns true if the warning is reported. + // Returns false if the warning is treated as an error, or an error occurs + // while reporting. + // + // See the comment on the error section for details on what the arguments + // and function names indicate for all these functions. + + [[nodiscard]] bool warning(unsigned errorNumber, ...) const { + va_list args; + va_start(args, errorNumber); + + bool result = warningWithNotesAtVA(nullptr, mozilla::AsVariant(Current()), + errorNumber, &args); + + va_end(args); + + return result; + } + [[nodiscard]] bool warningAt(uint32_t offset, unsigned errorNumber, + ...) const { + va_list args; + va_start(args, errorNumber); + + bool result = warningWithNotesAtVA(nullptr, mozilla::AsVariant(offset), + errorNumber, &args); + + va_end(args); + + return result; + } + [[nodiscard]] bool warningNoOffset(unsigned errorNumber, ...) const { + va_list args; + va_start(args, errorNumber); + + bool result = warningWithNotesAtVA(nullptr, mozilla::AsVariant(NoOffset()), + errorNumber, &args); + + va_end(args); + + return result; + } + [[nodiscard]] bool warningWithNotesAtVA(UniquePtr notes, + const ErrorOffset& offset, + unsigned errorNumber, + va_list* args) const { + ErrorMetadata metadata; + if (!computeErrorMetadata(&metadata, offset)) { + return false; + } + + return compileWarning(std::move(metadata), std::move(notes), errorNumber, + args); + } + + // ==== strictModeError ==== + // + // Reports an error if in strict mode code, or warn if not. + // + // Returns true if not in strict mode and a warning is reported. + // Returns false if the error reported, or an error occurs while reporting. + // + // See the comment on the error section for details on what the arguments + // and function names indicate for all these functions. + + [[nodiscard]] bool strictModeError(unsigned errorNumber, ...) const { + va_list args; + va_start(args, errorNumber); + + bool result = strictModeErrorWithNotesAtVA( + nullptr, mozilla::AsVariant(Current()), errorNumber, &args); + + va_end(args); + + return result; + } + [[nodiscard]] bool strictModeErrorWithNotes(UniquePtr notes, + unsigned errorNumber, ...) const { + va_list args; + va_start(args, errorNumber); + + bool result = strictModeErrorWithNotesAtVA( + std::move(notes), mozilla::AsVariant(Current()), errorNumber, &args); + + va_end(args); + + return result; + } + [[nodiscard]] bool strictModeErrorAt(uint32_t offset, unsigned errorNumber, + ...) const { + va_list args; + va_start(args, errorNumber); + + bool result = strictModeErrorWithNotesAtVA( + nullptr, mozilla::AsVariant(offset), errorNumber, &args); + + va_end(args); + + return result; + } + [[nodiscard]] bool strictModeErrorWithNotesAt(UniquePtr notes, + uint32_t offset, + unsigned errorNumber, + ...) const { + va_list args; + va_start(args, errorNumber); + + bool result = strictModeErrorWithNotesAtVA( + std::move(notes), mozilla::AsVariant(offset), errorNumber, &args); + + va_end(args); + + return result; + } + [[nodiscard]] bool strictModeErrorNoOffset(unsigned errorNumber, ...) const { + va_list args; + va_start(args, errorNumber); + + bool result = strictModeErrorWithNotesAtVA( + nullptr, mozilla::AsVariant(NoOffset()), errorNumber, &args); + + va_end(args); + + return result; + } + [[nodiscard]] bool strictModeErrorWithNotesNoOffset( + UniquePtr notes, unsigned errorNumber, ...) const { + va_list args; + va_start(args, errorNumber); + + bool result = strictModeErrorWithNotesAtVA( + std::move(notes), mozilla::AsVariant(NoOffset()), errorNumber, &args); + + va_end(args); + + return result; + } + [[nodiscard]] bool strictModeErrorWithNotesAtVA(UniquePtr notes, + const ErrorOffset& offset, + unsigned errorNumber, + va_list* args) const { + if (!strictMode()) { + return true; + } + + ErrorMetadata metadata; + if (!computeErrorMetadata(&metadata, offset)) { + return false; + } + + ReportCompileErrorLatin1(getContext(), std::move(metadata), + std::move(notes), errorNumber, args); + return false; + } + + // Reports a warning, or an error if the warning is treated as an error. + [[nodiscard]] bool compileWarning(ErrorMetadata&& metadata, + UniquePtr notes, + unsigned errorNumber, va_list* args) const { + return ReportCompileWarning(getContext(), std::move(metadata), + std::move(notes), errorNumber, args); + } +}; + +// An interface class to provide miscellaneous methods used by error reporting +// etc. They're mostly used by BytecodeCompiler, BytecodeEmitter, and helper +// classes for emitter. +class ErrorReporter : public ErrorReportMixin { + public: + // Sets *onThisLine to true if the given offset is inside the given line + // number `lineNum`, or false otherwise, and returns true. + // + // Return false if an error happens. This method itself doesn't report an + // error, and any failure is supposed to be reported as OOM in the caller. + virtual bool isOnThisLine(size_t offset, uint32_t lineNum, + bool* onThisLine) const = 0; + + // Returns the line number for given offset. + virtual uint32_t lineAt(size_t offset) const = 0; + + // Returns the column number for given offset. + virtual uint32_t columnAt(size_t offset) const = 0; +}; + +} // namespace frontend +} // namespace js + +#endif // frontend_ErrorReporter_h diff --git a/js/src/frontend/ExpressionStatementEmitter.cpp b/js/src/frontend/ExpressionStatementEmitter.cpp new file mode 100644 index 0000000000..bb00c831b1 --- /dev/null +++ b/js/src/frontend/ExpressionStatementEmitter.cpp @@ -0,0 +1,54 @@ +/* -*- 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/ExpressionStatementEmitter.h" + +#include "frontend/BytecodeEmitter.h" +#include "vm/Opcodes.h" + +using namespace js; +using namespace js::frontend; + +using mozilla::Maybe; + +ExpressionStatementEmitter::ExpressionStatementEmitter(BytecodeEmitter* bce, + ValueUsage valueUsage) + : bce_(bce), valueUsage_(valueUsage) {} + +bool ExpressionStatementEmitter::prepareForExpr(uint32_t beginPos) { + MOZ_ASSERT(state_ == State::Start); + + if (!bce_->updateSourceCoordNotes(beginPos)) { + return false; + } + +#ifdef DEBUG + depth_ = bce_->bytecodeSection().stackDepth(); + state_ = State::Expr; +#endif + return true; +} + +bool ExpressionStatementEmitter::emitEnd() { + MOZ_ASSERT(state_ == State::Expr); + MOZ_ASSERT(bce_->bytecodeSection().stackDepth() == depth_ + 1); + + // [stack] VAL + + JSOp op = valueUsage_ == ValueUsage::WantValue ? JSOp::SetRval : JSOp::Pop; + if (!bce_->emit1(op)) { + // [stack] # if WantValue + // [stack] VAL + // [stack] # otherwise + // [stack] + return false; + } + +#ifdef DEBUG + state_ = State::End; +#endif + return true; +} diff --git a/js/src/frontend/ExpressionStatementEmitter.h b/js/src/frontend/ExpressionStatementEmitter.h new file mode 100644 index 0000000000..d143530420 --- /dev/null +++ b/js/src/frontend/ExpressionStatementEmitter.h @@ -0,0 +1,81 @@ +/* -*- 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/. */ + +#ifndef frontend_ExpressionStatementEmitter_h +#define frontend_ExpressionStatementEmitter_h + +#include "mozilla/Attributes.h" + +#include + +#include "frontend/ValueUsage.h" + +namespace js { +namespace frontend { + +struct BytecodeEmitter; + +// Class for emitting bytecode for expression statement. +// +// Usage: (check for the return value is omitted for simplicity) +// +// `expr;` +// // IgnoreValue if this is in normal script. +// // WantValue if this is in eval script. +// ValueUsage valueUsage = ...; +// +// ExpressionStatementEmitter ese(this, valueUsage); +// ese.prepareForExpr(offset_of_expr); +// emit(expr); +// ese.emitEnd(); +// +class MOZ_STACK_CLASS ExpressionStatementEmitter { + BytecodeEmitter* bce_; + +#ifdef DEBUG + // The stack depth before emitting expression. + int32_t depth_; +#endif + + // The usage of the value of the expression. + ValueUsage valueUsage_; + +#ifdef DEBUG + // The state of this emitter. + // + // +-------+ prepareForExpr +------+ emitEnd +-----+ + // | Start |--------------->| Expr |-------->| End | + // +-------+ +------+ +-----+ + enum class State { + // The initial state. + Start, + + // After calling prepareForExpr. + Expr, + + // After calling emitEnd. + End + }; + State state_ = State::Start; +#endif + + public: + ExpressionStatementEmitter(BytecodeEmitter* bce, ValueUsage valueUsage); + + // Parameters are the offset in the source code for each character below: + // + // expr; + // ^ + // | + // beginPos + [[nodiscard]] bool prepareForExpr(uint32_t beginPos); + [[nodiscard]] bool emitEnd(); +}; + +} // namespace frontend +} // namespace js + +#endif /* frontend_ExpressionStatementEmitter_h */ diff --git a/js/src/frontend/FoldConstants.cpp b/js/src/frontend/FoldConstants.cpp new file mode 100644 index 0000000000..829a9fb505 --- /dev/null +++ b/js/src/frontend/FoldConstants.cpp @@ -0,0 +1,1581 @@ +/* -*- 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/FoldConstants.h" + +#include "mozilla/FloatingPoint.h" +#include "mozilla/Maybe.h" // mozilla::Maybe + +#include "jslibmath.h" +#include "jsmath.h" + +#include "frontend/FullParseHandler.h" +#include "frontend/ParseNode.h" +#include "frontend/ParseNodeVisitor.h" +#include "frontend/ParserAtom.h" // ParserAtomsTable, TaggedParserAtomIndex +#include "js/Conversions.h" +#include "js/Stack.h" // JS::NativeStackLimit +#include "util/StringBuffer.h" // StringBuffer + +using namespace js; +using namespace js::frontend; + +using JS::GenericNaN; +using JS::ToInt32; +using JS::ToUint32; +using mozilla::IsNegative; +using mozilla::NegativeInfinity; +using mozilla::PositiveInfinity; + +struct FoldInfo { + FrontendContext* fc; + ParserAtomsTable& parserAtoms; + FullParseHandler* handler; +}; + +// Don't use ReplaceNode directly, because we want the constant folder to keep +// the attributes isInParens and isDirectRHSAnonFunction of the old node being +// replaced. +[[nodiscard]] inline bool TryReplaceNode(ParseNode** pnp, ParseNode* pn) { + // convenience check: can call TryReplaceNode(pnp, alloc_parsenode()) + // directly, without having to worry about alloc returning null. + if (!pn) { + return false; + } + pn->setInParens((*pnp)->isInParens()); + pn->setDirectRHSAnonFunction((*pnp)->isDirectRHSAnonFunction()); + ReplaceNode(pnp, pn); + return true; +} + +static bool ContainsHoistedDeclaration(FoldInfo& info, ParseNode* node, + bool* result); + +static bool ListContainsHoistedDeclaration(FoldInfo& info, ListNode* list, + bool* result) { + for (ParseNode* node : list->contents()) { + if (!ContainsHoistedDeclaration(info, node, result)) { + return false; + } + if (*result) { + return true; + } + } + + *result = false; + return true; +} + +// Determines whether the given ParseNode contains any declarations whose +// visibility will extend outside the node itself -- that is, whether the +// ParseNode contains any var statements. +// +// THIS IS NOT A GENERAL-PURPOSE FUNCTION. It is only written to work in the +// specific context of deciding that |node|, as one arm of a ParseNodeKind::If +// controlled by a constant condition, contains a declaration that forbids +// |node| being completely eliminated as dead. +static bool ContainsHoistedDeclaration(FoldInfo& info, ParseNode* node, + bool* result) { + AutoCheckRecursionLimit recursion(info.fc); + if (!recursion.check(info.fc)) { + return false; + } + +restart: + + // With a better-typed AST, we would have distinct parse node classes for + // expressions and for statements and would characterize expressions with + // ExpressionKind and statements with StatementKind. Perhaps someday. In + // the meantime we must characterize every ParseNodeKind, even the + // expression/sub-expression ones that, if we handle all statement kinds + // correctly, we'll never see. + switch (node->getKind()) { + // Base case. + case ParseNodeKind::VarStmt: + *result = true; + return true; + + // Non-global lexical declarations are block-scoped (ergo not hoistable). + case ParseNodeKind::LetDecl: + case ParseNodeKind::ConstDecl: + MOZ_ASSERT(node->is()); + *result = false; + return true; + + // Similarly to the lexical declarations above, classes cannot add hoisted + // declarations + case ParseNodeKind::ClassDecl: + MOZ_ASSERT(node->is()); + *result = false; + return true; + + // Function declarations *can* be hoisted declarations. But in the + // magical world of the rewritten frontend, the declaration necessitated + // by a nested function statement, not at body level, doesn't require + // that we preserve an unreachable function declaration node against + // dead-code removal. + case ParseNodeKind::Function: + *result = false; + return true; + + case ParseNodeKind::Module: + *result = false; + return true; + + // Statements with no sub-components at all. + case ParseNodeKind::EmptyStmt: + MOZ_ASSERT(node->is()); + *result = false; + return true; + + case ParseNodeKind::DebuggerStmt: + MOZ_ASSERT(node->is()); + *result = false; + return true; + + // Statements containing only an expression have no declarations. + case ParseNodeKind::ExpressionStmt: + case ParseNodeKind::ThrowStmt: + case ParseNodeKind::ReturnStmt: + MOZ_ASSERT(node->is()); + *result = false; + return true; + + // These two aren't statements in the spec, but we sometimes insert them + // in statement lists anyway. + case ParseNodeKind::InitialYield: + case ParseNodeKind::YieldStarExpr: + case ParseNodeKind::YieldExpr: + MOZ_ASSERT(node->is()); + *result = false; + return true; + + // Other statements with no sub-statement components. + case ParseNodeKind::BreakStmt: + case ParseNodeKind::ContinueStmt: + case ParseNodeKind::ImportDecl: + case ParseNodeKind::ImportSpecList: + case ParseNodeKind::ImportSpec: + case ParseNodeKind::ImportNamespaceSpec: + case ParseNodeKind::ExportFromStmt: + case ParseNodeKind::ExportDefaultStmt: + case ParseNodeKind::ExportSpecList: + case ParseNodeKind::ExportSpec: + case ParseNodeKind::ExportNamespaceSpec: + case ParseNodeKind::ExportStmt: + case ParseNodeKind::ExportBatchSpecStmt: + case ParseNodeKind::CallImportExpr: + case ParseNodeKind::CallImportSpec: + case ParseNodeKind::ImportAssertionList: + case ParseNodeKind::ImportAssertion: + case ParseNodeKind::ImportModuleRequest: + *result = false; + return true; + + // Statements possibly containing hoistable declarations only in the left + // half, in ParseNode terms -- the loop body in AST terms. + case ParseNodeKind::DoWhileStmt: + return ContainsHoistedDeclaration(info, node->as().left(), + result); + + // Statements possibly containing hoistable declarations only in the + // right half, in ParseNode terms -- the loop body or nested statement + // (usually a block statement), in AST terms. + case ParseNodeKind::WhileStmt: + case ParseNodeKind::WithStmt: + return ContainsHoistedDeclaration(info, node->as().right(), + result); + + case ParseNodeKind::LabelStmt: + return ContainsHoistedDeclaration( + info, node->as().statement(), result); + + // Statements with more complicated structures. + + // if-statement nodes may have hoisted declarations in their consequent + // and alternative components. + case ParseNodeKind::IfStmt: { + TernaryNode* ifNode = &node->as(); + ParseNode* consequent = ifNode->kid2(); + if (!ContainsHoistedDeclaration(info, consequent, result)) { + return false; + } + if (*result) { + return true; + } + + if ((node = ifNode->kid3())) { + goto restart; + } + + *result = false; + return true; + } + + // try-statements have statements to execute, and one or both of a + // catch-list and a finally-block. + case ParseNodeKind::TryStmt: { + TernaryNode* tryNode = &node->as(); + + MOZ_ASSERT(tryNode->kid2() || tryNode->kid3(), + "must have either catch or finally"); + + ParseNode* tryBlock = tryNode->kid1(); + if (!ContainsHoistedDeclaration(info, tryBlock, result)) { + return false; + } + if (*result) { + return true; + } + + if (ParseNode* catchScope = tryNode->kid2()) { + BinaryNode* catchNode = + &catchScope->as().scopeBody()->as(); + MOZ_ASSERT(catchNode->isKind(ParseNodeKind::Catch)); + + ParseNode* catchStatements = catchNode->right(); + if (!ContainsHoistedDeclaration(info, catchStatements, result)) { + return false; + } + if (*result) { + return true; + } + } + + if (ParseNode* finallyBlock = tryNode->kid3()) { + return ContainsHoistedDeclaration(info, finallyBlock, result); + } + + *result = false; + return true; + } + + // A switch node's left half is an expression; only its right half (a + // list of cases/defaults, or a block node) could contain hoisted + // declarations. + case ParseNodeKind::SwitchStmt: { + SwitchStatement* switchNode = &node->as(); + return ContainsHoistedDeclaration(info, &switchNode->lexicalForCaseList(), + result); + } + + case ParseNodeKind::Case: { + CaseClause* caseClause = &node->as(); + return ContainsHoistedDeclaration(info, caseClause->statementList(), + result); + } + + case ParseNodeKind::ForStmt: { + ForNode* forNode = &node->as(); + TernaryNode* loopHead = forNode->head(); + MOZ_ASSERT(loopHead->isKind(ParseNodeKind::ForHead) || + loopHead->isKind(ParseNodeKind::ForIn) || + loopHead->isKind(ParseNodeKind::ForOf)); + + if (loopHead->isKind(ParseNodeKind::ForHead)) { + // for (init?; cond?; update?), with only init possibly containing + // a hoisted declaration. (Note: a lexical-declaration |init| is + // (at present) hoisted in SpiderMonkey parlance -- but such + // hoisting doesn't extend outside of this statement, so it is not + // hoisting in the sense meant by ContainsHoistedDeclaration.) + ParseNode* init = loopHead->kid1(); + if (init && init->isKind(ParseNodeKind::VarStmt)) { + *result = true; + return true; + } + } else { + MOZ_ASSERT(loopHead->isKind(ParseNodeKind::ForIn) || + loopHead->isKind(ParseNodeKind::ForOf)); + + // for each? (target in ...), where only target may introduce + // hoisted declarations. + // + // -- or -- + // + // for (target of ...), where only target may introduce hoisted + // declarations. + // + // Either way, if |target| contains a declaration, it's |loopHead|'s + // first kid. + ParseNode* decl = loopHead->kid1(); + if (decl && decl->isKind(ParseNodeKind::VarStmt)) { + *result = true; + return true; + } + } + + ParseNode* loopBody = forNode->body(); + return ContainsHoistedDeclaration(info, loopBody, result); + } + + case ParseNodeKind::LexicalScope: { + LexicalScopeNode* scope = &node->as(); + ParseNode* expr = scope->scopeBody(); + + if (expr->isKind(ParseNodeKind::ForStmt) || expr->is()) { + return ContainsHoistedDeclaration(info, expr, result); + } + + MOZ_ASSERT(expr->isKind(ParseNodeKind::StatementList)); + return ListContainsHoistedDeclaration( + info, &scope->scopeBody()->as(), result); + } + + // List nodes with all non-null children. + case ParseNodeKind::StatementList: + return ListContainsHoistedDeclaration(info, &node->as(), + result); + + // Grammar sub-components that should never be reached directly by this + // method, because some parent component should have asserted itself. + case ParseNodeKind::ObjectPropertyName: + case ParseNodeKind::ComputedName: + case ParseNodeKind::Spread: + case ParseNodeKind::MutateProto: + case ParseNodeKind::PropertyDefinition: + case ParseNodeKind::Shorthand: + case ParseNodeKind::ConditionalExpr: + case ParseNodeKind::TypeOfNameExpr: + case ParseNodeKind::TypeOfExpr: + case ParseNodeKind::AwaitExpr: + case ParseNodeKind::VoidExpr: + case ParseNodeKind::NotExpr: + case ParseNodeKind::BitNotExpr: + case ParseNodeKind::DeleteNameExpr: + case ParseNodeKind::DeletePropExpr: + case ParseNodeKind::DeleteElemExpr: + case ParseNodeKind::DeleteOptionalChainExpr: + case ParseNodeKind::DeleteExpr: + case ParseNodeKind::PosExpr: + case ParseNodeKind::NegExpr: + case ParseNodeKind::PreIncrementExpr: + case ParseNodeKind::PostIncrementExpr: + case ParseNodeKind::PreDecrementExpr: + case ParseNodeKind::PostDecrementExpr: + case ParseNodeKind::CoalesceExpr: + case ParseNodeKind::OrExpr: + case ParseNodeKind::AndExpr: + case ParseNodeKind::BitOrExpr: + case ParseNodeKind::BitXorExpr: + case ParseNodeKind::BitAndExpr: + case ParseNodeKind::StrictEqExpr: + case ParseNodeKind::EqExpr: + case ParseNodeKind::StrictNeExpr: + case ParseNodeKind::NeExpr: + case ParseNodeKind::LtExpr: + case ParseNodeKind::LeExpr: + case ParseNodeKind::GtExpr: + case ParseNodeKind::GeExpr: + case ParseNodeKind::InstanceOfExpr: + case ParseNodeKind::InExpr: + case ParseNodeKind::PrivateInExpr: + case ParseNodeKind::LshExpr: + case ParseNodeKind::RshExpr: + case ParseNodeKind::UrshExpr: + case ParseNodeKind::AddExpr: + case ParseNodeKind::SubExpr: + case ParseNodeKind::MulExpr: + case ParseNodeKind::DivExpr: + case ParseNodeKind::ModExpr: + case ParseNodeKind::PowExpr: + case ParseNodeKind::InitExpr: + case ParseNodeKind::AssignExpr: + case ParseNodeKind::AddAssignExpr: + case ParseNodeKind::SubAssignExpr: + case ParseNodeKind::CoalesceAssignExpr: + case ParseNodeKind::OrAssignExpr: + case ParseNodeKind::AndAssignExpr: + case ParseNodeKind::BitOrAssignExpr: + case ParseNodeKind::BitXorAssignExpr: + case ParseNodeKind::BitAndAssignExpr: + case ParseNodeKind::LshAssignExpr: + case ParseNodeKind::RshAssignExpr: + case ParseNodeKind::UrshAssignExpr: + case ParseNodeKind::MulAssignExpr: + case ParseNodeKind::DivAssignExpr: + case ParseNodeKind::ModAssignExpr: + case ParseNodeKind::PowAssignExpr: + case ParseNodeKind::CommaExpr: + case ParseNodeKind::ArrayExpr: + case ParseNodeKind::ObjectExpr: + case ParseNodeKind::PropertyNameExpr: + case ParseNodeKind::DotExpr: + case ParseNodeKind::ElemExpr: + case ParseNodeKind::Arguments: + case ParseNodeKind::CallExpr: + case ParseNodeKind::PrivateMemberExpr: + case ParseNodeKind::OptionalChain: + case ParseNodeKind::OptionalDotExpr: + case ParseNodeKind::OptionalElemExpr: + case ParseNodeKind::OptionalCallExpr: + case ParseNodeKind::OptionalPrivateMemberExpr: + case ParseNodeKind::Name: + case ParseNodeKind::PrivateName: + case ParseNodeKind::TemplateStringExpr: + case ParseNodeKind::TemplateStringListExpr: + case ParseNodeKind::TaggedTemplateExpr: + case ParseNodeKind::CallSiteObj: + case ParseNodeKind::StringExpr: + case ParseNodeKind::RegExpExpr: + case ParseNodeKind::TrueExpr: + case ParseNodeKind::FalseExpr: + case ParseNodeKind::NullExpr: + case ParseNodeKind::RawUndefinedExpr: + case ParseNodeKind::ThisExpr: + case ParseNodeKind::Elision: + case ParseNodeKind::NumberExpr: + case ParseNodeKind::BigIntExpr: + case ParseNodeKind::NewExpr: + case ParseNodeKind::Generator: + case ParseNodeKind::ParamsBody: + case ParseNodeKind::Catch: + case ParseNodeKind::ForIn: + case ParseNodeKind::ForOf: + case ParseNodeKind::ForHead: + case ParseNodeKind::DefaultConstructor: + case ParseNodeKind::ClassBodyScope: + case ParseNodeKind::ClassMethod: + case ParseNodeKind::ClassField: + case ParseNodeKind::StaticClassBlock: + case ParseNodeKind::ClassMemberList: + case ParseNodeKind::ClassNames: + case ParseNodeKind::NewTargetExpr: + case ParseNodeKind::ImportMetaExpr: + case ParseNodeKind::PosHolder: + case ParseNodeKind::SuperCallExpr: + case ParseNodeKind::SuperBase: + case ParseNodeKind::SetThis: +#ifdef ENABLE_DECORATORS + case ParseNodeKind::DecoratorList: +#endif + MOZ_CRASH( + "ContainsHoistedDeclaration should have indicated false on " + "some parent node without recurring to test this node"); + case ParseNodeKind::LastUnused: + case ParseNodeKind::Limit: + MOZ_CRASH("unexpected sentinel ParseNodeKind in node"); + +#ifdef ENABLE_RECORD_TUPLE + case ParseNodeKind::RecordExpr: + case ParseNodeKind::TupleExpr: + MOZ_CRASH("Record and Tuple are not supported yet"); +#endif + } + + MOZ_CRASH("invalid node kind"); +} + +/* + * Fold from one constant type to another. + * XXX handles only strings and numbers for now + */ +static bool FoldType(FoldInfo info, ParseNode** pnp, ParseNodeKind kind) { + const ParseNode* pn = *pnp; + if (!pn->isKind(kind)) { + switch (kind) { + case ParseNodeKind::NumberExpr: + if (pn->isKind(ParseNodeKind::StringExpr)) { + auto atom = pn->as().atom(); + double d = info.parserAtoms.toNumber(atom); + if (!TryReplaceNode( + pnp, info.handler->newNumber(d, NoDecimal, pn->pn_pos))) { + return false; + } + } + break; + + case ParseNodeKind::StringExpr: + if (pn->isKind(ParseNodeKind::NumberExpr)) { + TaggedParserAtomIndex atom = + pn->as().toAtom(info.fc, info.parserAtoms); + if (!atom) { + return false; + } + if (!TryReplaceNode( + pnp, info.handler->newStringLiteral(atom, pn->pn_pos))) { + return false; + } + } + break; + + default: + MOZ_CRASH("Invalid type in constant folding FoldType"); + } + } + return true; +} + +static bool IsEffectless(ParseNode* node) { + return node->isKind(ParseNodeKind::TrueExpr) || + node->isKind(ParseNodeKind::FalseExpr) || + node->isKind(ParseNodeKind::StringExpr) || + node->isKind(ParseNodeKind::TemplateStringExpr) || + node->isKind(ParseNodeKind::NumberExpr) || + node->isKind(ParseNodeKind::BigIntExpr) || + node->isKind(ParseNodeKind::NullExpr) || + node->isKind(ParseNodeKind::RawUndefinedExpr) || + node->isKind(ParseNodeKind::Function); +} + +enum Truthiness { Truthy, Falsy, Unknown }; + +static Truthiness Boolish(ParseNode* pn) { + switch (pn->getKind()) { + case ParseNodeKind::NumberExpr: + return (pn->as().value() != 0 && + !std::isnan(pn->as().value())) + ? Truthy + : Falsy; + + case ParseNodeKind::BigIntExpr: + return (pn->as().isZero()) ? Falsy : Truthy; + + case ParseNodeKind::StringExpr: + case ParseNodeKind::TemplateStringExpr: + return (pn->as().atom() == + TaggedParserAtomIndex::WellKnown::empty()) + ? Falsy + : Truthy; + + case ParseNodeKind::TrueExpr: + case ParseNodeKind::Function: + return Truthy; + + case ParseNodeKind::FalseExpr: + case ParseNodeKind::NullExpr: + case ParseNodeKind::RawUndefinedExpr: + return Falsy; + + case ParseNodeKind::VoidExpr: { + // |void | evaluates to |undefined| which isn't truthy. But the + // sense of this method requires that the expression be literally + // replaceable with true/false: not the case if the nested expression + // is effectful, might throw, &c. Walk past the |void| (and nested + // |void| expressions, for good measure) and check that the nested + // expression doesn't break this requirement before indicating falsity. + do { + pn = pn->as().kid(); + } while (pn->isKind(ParseNodeKind::VoidExpr)); + + return IsEffectless(pn) ? Falsy : Unknown; + } + + default: + return Unknown; + } +} + +static bool SimplifyCondition(FoldInfo info, ParseNode** nodePtr) { + // Conditions fold like any other expression, but then they sometimes can be + // further folded to constants. *nodePtr should already have been + // constant-folded. + + ParseNode* node = *nodePtr; + if (Truthiness t = Boolish(node); t != Unknown) { + // We can turn function nodes into constant nodes here, but mutating + // function nodes is tricky --- in particular, mutating a function node + // that appears on a method list corrupts the method list. However, + // methods are M's in statements of the form 'this.foo = M;', which we + // never fold, so we're okay. + if (!TryReplaceNode(nodePtr, info.handler->newBooleanLiteral( + t == Truthy, node->pn_pos))) { + return false; + } + } + + return true; +} + +static bool FoldTypeOfExpr(FoldInfo info, ParseNode** nodePtr) { + UnaryNode* node = &(*nodePtr)->as(); + MOZ_ASSERT(node->isKind(ParseNodeKind::TypeOfExpr)); + ParseNode* expr = node->kid(); + + // Constant-fold the entire |typeof| if given a constant with known type. + TaggedParserAtomIndex result; + if (expr->isKind(ParseNodeKind::StringExpr) || + expr->isKind(ParseNodeKind::TemplateStringExpr)) { + result = TaggedParserAtomIndex::WellKnown::string(); + } else if (expr->isKind(ParseNodeKind::NumberExpr)) { + result = TaggedParserAtomIndex::WellKnown::number(); + } else if (expr->isKind(ParseNodeKind::BigIntExpr)) { + result = TaggedParserAtomIndex::WellKnown::bigint(); + } else if (expr->isKind(ParseNodeKind::NullExpr)) { + result = TaggedParserAtomIndex::WellKnown::object(); + } else if (expr->isKind(ParseNodeKind::TrueExpr) || + expr->isKind(ParseNodeKind::FalseExpr)) { + result = TaggedParserAtomIndex::WellKnown::boolean(); + } else if (expr->is()) { + result = TaggedParserAtomIndex::WellKnown::function(); + } + + if (result) { + if (!TryReplaceNode(nodePtr, + info.handler->newStringLiteral(result, node->pn_pos))) { + return false; + } + } + + return true; +} + +static bool FoldDeleteExpr(FoldInfo info, ParseNode** nodePtr) { + UnaryNode* node = &(*nodePtr)->as(); + + MOZ_ASSERT(node->isKind(ParseNodeKind::DeleteExpr)); + ParseNode* expr = node->kid(); + + // Expression deletion evaluates the expression, then evaluates to true. + // For effectless expressions, eliminate the expression evaluation. + if (IsEffectless(expr)) { + if (!TryReplaceNode(nodePtr, + info.handler->newBooleanLiteral(true, node->pn_pos))) { + return false; + } + } + + return true; +} + +static bool FoldDeleteElement(FoldInfo info, ParseNode** nodePtr) { + UnaryNode* node = &(*nodePtr)->as(); + MOZ_ASSERT(node->isKind(ParseNodeKind::DeleteElemExpr)); + ParseNode* expr = node->kid(); + + // If we're deleting an element, but constant-folding converted our + // element reference into a dotted property access, we must *also* + // morph the node's kind. + // + // In principle this also applies to |super["foo"] -> super.foo|, + // but we don't constant-fold |super["foo"]| yet. + MOZ_ASSERT(expr->isKind(ParseNodeKind::ElemExpr) || + expr->isKind(ParseNodeKind::DotExpr)); + if (expr->isKind(ParseNodeKind::DotExpr)) { + // newDelete will detect and use DeletePropExpr + if (!TryReplaceNode(nodePtr, + info.handler->newDelete(node->pn_pos.begin, expr))) { + return false; + } + MOZ_ASSERT((*nodePtr)->getKind() == ParseNodeKind::DeletePropExpr); + } + + return true; +} + +static bool FoldNot(FoldInfo info, ParseNode** nodePtr) { + UnaryNode* node = &(*nodePtr)->as(); + MOZ_ASSERT(node->isKind(ParseNodeKind::NotExpr)); + + if (!SimplifyCondition(info, node->unsafeKidReference())) { + return false; + } + + ParseNode* expr = node->kid(); + + if (expr->isKind(ParseNodeKind::TrueExpr) || + expr->isKind(ParseNodeKind::FalseExpr)) { + bool newval = !expr->isKind(ParseNodeKind::TrueExpr); + + if (!TryReplaceNode( + nodePtr, info.handler->newBooleanLiteral(newval, node->pn_pos))) { + return false; + } + } + + return true; +} + +static bool FoldUnaryArithmetic(FoldInfo info, ParseNode** nodePtr) { + UnaryNode* node = &(*nodePtr)->as(); + MOZ_ASSERT(node->isKind(ParseNodeKind::BitNotExpr) || + node->isKind(ParseNodeKind::PosExpr) || + node->isKind(ParseNodeKind::NegExpr), + "need a different method for this node kind"); + + ParseNode* expr = node->kid(); + + if (expr->isKind(ParseNodeKind::NumberExpr) || + expr->isKind(ParseNodeKind::TrueExpr) || + expr->isKind(ParseNodeKind::FalseExpr)) { + double d = expr->isKind(ParseNodeKind::NumberExpr) + ? expr->as().value() + : double(expr->isKind(ParseNodeKind::TrueExpr)); + + if (node->isKind(ParseNodeKind::BitNotExpr)) { + d = ~ToInt32(d); + } else if (node->isKind(ParseNodeKind::NegExpr)) { + d = -d; + } else { + MOZ_ASSERT(node->isKind(ParseNodeKind::PosExpr)); // nothing to do + } + + if (!TryReplaceNode(nodePtr, + info.handler->newNumber(d, NoDecimal, node->pn_pos))) { + return false; + } + } + + return true; +} + +static bool FoldAndOrCoalesce(FoldInfo info, ParseNode** nodePtr) { + ListNode* node = &(*nodePtr)->as(); + + MOZ_ASSERT(node->isKind(ParseNodeKind::AndExpr) || + node->isKind(ParseNodeKind::CoalesceExpr) || + node->isKind(ParseNodeKind::OrExpr)); + + bool isOrNode = node->isKind(ParseNodeKind::OrExpr); + bool isAndNode = node->isKind(ParseNodeKind::AndExpr); + bool isCoalesceNode = node->isKind(ParseNodeKind::CoalesceExpr); + ParseNode** elem = node->unsafeHeadReference(); + do { + Truthiness t = Boolish(*elem); + + // If we don't know the constant-folded node's truthiness, we can't + // reduce this node with its surroundings. Continue folding any + // remaining nodes. + if (t == Unknown) { + elem = &(*elem)->pn_next; + continue; + } + + bool isTruthyCoalesceNode = + isCoalesceNode && !((*elem)->isKind(ParseNodeKind::NullExpr) || + (*elem)->isKind(ParseNodeKind::VoidExpr) || + (*elem)->isKind(ParseNodeKind::RawUndefinedExpr)); + bool canShortCircuit = (isOrNode && t == Truthy) || + (isAndNode && t == Falsy) || isTruthyCoalesceNode; + + // If the constant-folded node's truthiness will terminate the + // condition -- `a || true || expr` or `b && false && expr` or + // `false ?? c ?? expr` -- then trailing nodes will never be + // evaluated. Truncate the list after the known-truthiness node, + // as it's the overall result. + if (canShortCircuit) { + for (ParseNode* next = (*elem)->pn_next; next; next = next->pn_next) { + node->unsafeDecrementCount(); + } + + // Terminate the original and/or list at the known-truthiness + // node. + (*elem)->pn_next = nullptr; + elem = &(*elem)->pn_next; + break; + } + + // We've encountered a vacuous node that'll never short-circuit + // evaluation. + if ((*elem)->pn_next) { + // This node is never the overall result when there are + // subsequent nodes. Remove it. + ParseNode* elt = *elem; + *elem = elt->pn_next; + node->unsafeDecrementCount(); + } else { + // Otherwise this node is the result of the overall expression, + // so leave it alone. And we're done. + elem = &(*elem)->pn_next; + break; + } + } while (*elem); + + node->unsafeReplaceTail(elem); + + // If we removed nodes, we may have to replace a one-element list with + // its element. + if (node->count() == 1) { + ParseNode* first = node->head(); + if (!TryReplaceNode(nodePtr, first)) { + ; + return false; + } + } + + return true; +} + +static bool Fold(FoldInfo info, ParseNode** pnp); + +static bool FoldConditional(FoldInfo info, ParseNode** nodePtr) { + ParseNode** nextNode = nodePtr; + + do { + // |nextNode| on entry points to the C?T:F expression to be folded. + // Reset it to exit the loop in the common case where F isn't another + // ?: expression. + nodePtr = nextNode; + nextNode = nullptr; + + TernaryNode* node = &(*nodePtr)->as(); + MOZ_ASSERT(node->isKind(ParseNodeKind::ConditionalExpr)); + + ParseNode** expr = node->unsafeKid1Reference(); + if (!Fold(info, expr)) { + return false; + } + if (!SimplifyCondition(info, expr)) { + return false; + } + + ParseNode** ifTruthy = node->unsafeKid2Reference(); + if (!Fold(info, ifTruthy)) { + return false; + } + + ParseNode** ifFalsy = node->unsafeKid3Reference(); + + // If our C?T:F node has F as another ?: node, *iteratively* constant- + // fold F *after* folding C and T (and possibly eliminating C and one + // of T/F entirely); otherwise fold F normally. Making |nextNode| non- + // null causes this loop to run again to fold F. + // + // Conceivably we could instead/also iteratively constant-fold T, if T + // were more complex than F. Such an optimization is unimplemented. + if ((*ifFalsy)->isKind(ParseNodeKind::ConditionalExpr)) { + MOZ_ASSERT((*ifFalsy)->is()); + nextNode = ifFalsy; + } else { + if (!Fold(info, ifFalsy)) { + return false; + } + } + + // Try to constant-fold based on the condition expression. + Truthiness t = Boolish(*expr); + if (t == Unknown) { + continue; + } + + // Otherwise reduce 'C ? T : F' to T or F as directed by C. + ParseNode* replacement = t == Truthy ? *ifTruthy : *ifFalsy; + + // Otherwise perform a replacement. This invalidates |nextNode|, so + // reset it (if the replacement requires folding) or clear it (if + // |ifFalsy| is dead code) as needed. + if (nextNode) { + nextNode = (*nextNode == replacement) ? nodePtr : nullptr; + } + ReplaceNode(nodePtr, replacement); + } while (nextNode); + + return true; +} + +static bool FoldIf(FoldInfo info, ParseNode** nodePtr) { + ParseNode** nextNode = nodePtr; + + do { + // |nextNode| on entry points to the initial |if| to be folded. Reset + // it to exit the loop when the |else| arm isn't another |if|. + nodePtr = nextNode; + nextNode = nullptr; + + TernaryNode* node = &(*nodePtr)->as(); + MOZ_ASSERT(node->isKind(ParseNodeKind::IfStmt)); + + ParseNode** expr = node->unsafeKid1Reference(); + if (!Fold(info, expr)) { + return false; + } + if (!SimplifyCondition(info, expr)) { + return false; + } + + ParseNode** consequent = node->unsafeKid2Reference(); + if (!Fold(info, consequent)) { + return false; + } + + ParseNode** alternative = node->unsafeKid3Reference(); + if (*alternative) { + // If in |if (C) T; else F;| we have |F| as another |if|, + // *iteratively* constant-fold |F| *after* folding |C| and |T| (and + // possibly completely replacing the whole thing with |T| or |F|); + // otherwise fold F normally. Making |nextNode| non-null causes + // this loop to run again to fold F. + if ((*alternative)->isKind(ParseNodeKind::IfStmt)) { + MOZ_ASSERT((*alternative)->is()); + nextNode = alternative; + } else { + if (!Fold(info, alternative)) { + return false; + } + } + } + + // Eliminate the consequent or alternative if the condition has + // constant truthiness. + Truthiness t = Boolish(*expr); + if (t == Unknown) { + continue; + } + + // Careful! Either of these can be null: |replacement| in |if (0) T;|, + // and |discarded| in |if (true) T;|. + ParseNode* replacement; + ParseNode* discarded; + if (t == Truthy) { + replacement = *consequent; + discarded = *alternative; + } else { + replacement = *alternative; + discarded = *consequent; + } + + bool performReplacement = true; + if (discarded) { + // A declaration that hoists outside the discarded arm prevents the + // |if| from being folded away. + bool containsHoistedDecls; + if (!ContainsHoistedDeclaration(info, discarded, &containsHoistedDecls)) { + return false; + } + + performReplacement = !containsHoistedDecls; + } + + if (!performReplacement) { + continue; + } + + if (!replacement) { + // If there's no replacement node, we have a constantly-false |if| + // with no |else|. Replace the entire thing with an empty + // statement list. + if (!TryReplaceNode(nodePtr, + info.handler->newStatementList(node->pn_pos))) { + return false; + } + } else { + // Replacement invalidates |nextNode|, so reset it (if the + // replacement requires folding) or clear it (if |alternative| + // is dead code) as needed. + if (nextNode) { + nextNode = (*nextNode == replacement) ? nodePtr : nullptr; + } + ReplaceNode(nodePtr, replacement); + } + } while (nextNode); + + return true; +} + +static double ComputeBinary(ParseNodeKind kind, double left, double right) { + if (kind == ParseNodeKind::AddExpr) { + return left + right; + } + + if (kind == ParseNodeKind::SubExpr) { + return left - right; + } + + if (kind == ParseNodeKind::MulExpr) { + return left * right; + } + + if (kind == ParseNodeKind::ModExpr) { + return NumberMod(left, right); + } + + if (kind == ParseNodeKind::UrshExpr) { + return ToUint32(left) >> (ToUint32(right) & 31); + } + + if (kind == ParseNodeKind::DivExpr) { + return NumberDiv(left, right); + } + + MOZ_ASSERT(kind == ParseNodeKind::LshExpr || kind == ParseNodeKind::RshExpr); + + int32_t i = ToInt32(left); + uint32_t j = ToUint32(right) & 31; + return int32_t((kind == ParseNodeKind::LshExpr) ? uint32_t(i) << j : i >> j); +} + +static bool FoldBinaryArithmetic(FoldInfo info, ParseNode** nodePtr) { + ListNode* node = &(*nodePtr)->as(); + MOZ_ASSERT(node->isKind(ParseNodeKind::SubExpr) || + node->isKind(ParseNodeKind::MulExpr) || + node->isKind(ParseNodeKind::LshExpr) || + node->isKind(ParseNodeKind::RshExpr) || + node->isKind(ParseNodeKind::UrshExpr) || + node->isKind(ParseNodeKind::DivExpr) || + node->isKind(ParseNodeKind::ModExpr)); + MOZ_ASSERT(node->count() >= 2); + + // Fold each operand to a number if possible. + ParseNode** listp = node->unsafeHeadReference(); + for (; *listp; listp = &(*listp)->pn_next) { + if (!FoldType(info, listp, ParseNodeKind::NumberExpr)) { + return false; + } + } + node->unsafeReplaceTail(listp); + + // Now fold all leading numeric terms together into a single number. + // (Trailing terms for the non-shift operations can't be folded together + // due to floating point imprecision. For example, if |x === -2**53|, + // |x - 1 - 1 === -2**53| but |x - 2 === -2**53 - 2|. Shifts could be + // folded, but it doesn't seem worth the effort.) + ParseNode** elem = node->unsafeHeadReference(); + ParseNode** next = &(*elem)->pn_next; + if ((*elem)->isKind(ParseNodeKind::NumberExpr)) { + ParseNodeKind kind = node->getKind(); + while (true) { + if (!*next || !(*next)->isKind(ParseNodeKind::NumberExpr)) { + break; + } + + double d = ComputeBinary(kind, (*elem)->as().value(), + (*next)->as().value()); + + TokenPos pos((*elem)->pn_pos.begin, (*next)->pn_pos.end); + if (!TryReplaceNode(elem, info.handler->newNumber(d, NoDecimal, pos))) { + return false; + } + + (*elem)->pn_next = (*next)->pn_next; + next = &(*elem)->pn_next; + node->unsafeDecrementCount(); + } + + if (node->count() == 1) { + MOZ_ASSERT(node->head() == *elem); + MOZ_ASSERT((*elem)->isKind(ParseNodeKind::NumberExpr)); + + if (!TryReplaceNode(nodePtr, *elem)) { + return false; + } + } + } + + return true; +} + +static bool FoldExponentiation(FoldInfo info, ParseNode** nodePtr) { + ListNode* node = &(*nodePtr)->as(); + MOZ_ASSERT(node->isKind(ParseNodeKind::PowExpr)); + MOZ_ASSERT(node->count() >= 2); + + // Fold each operand, ideally into a number. + ParseNode** listp = node->unsafeHeadReference(); + for (; *listp; listp = &(*listp)->pn_next) { + if (!FoldType(info, listp, ParseNodeKind::NumberExpr)) { + return false; + } + } + + node->unsafeReplaceTail(listp); + + // Unlike all other binary arithmetic operators, ** is right-associative: + // 2**3**5 is 2**(3**5), not (2**3)**5. As list nodes singly-link their + // children, full constant-folding requires either linear space or dodgy + // in-place linked list reversal. So we only fold one exponentiation: it's + // easy and addresses common cases like |2**32|. + if (node->count() > 2) { + return true; + } + + ParseNode* base = node->head(); + ParseNode* exponent = base->pn_next; + if (!base->isKind(ParseNodeKind::NumberExpr) || + !exponent->isKind(ParseNodeKind::NumberExpr)) { + return true; + } + + double d1 = base->as().value(); + double d2 = exponent->as().value(); + + return TryReplaceNode(nodePtr, info.handler->newNumber( + ecmaPow(d1, d2), NoDecimal, node->pn_pos)); +} + +static bool FoldElement(FoldInfo info, ParseNode** nodePtr) { + PropertyByValue* elem = &(*nodePtr)->as(); + + ParseNode* expr = &elem->expression(); + ParseNode* key = &elem->key(); + TaggedParserAtomIndex name; + if (key->isKind(ParseNodeKind::StringExpr)) { + auto keyIndex = key->as().atom(); + uint32_t index; + if (info.parserAtoms.isIndex(keyIndex, &index)) { + // Optimization 1: We have something like expr["100"]. This is + // equivalent to expr[100] which is faster. + if (!TryReplaceNode( + elem->unsafeRightReference(), + info.handler->newNumber(index, NoDecimal, key->pn_pos))) { + return false; + } + key = &elem->key(); + } else { + name = keyIndex; + } + } else if (key->isKind(ParseNodeKind::NumberExpr)) { + auto* numeric = &key->as(); + double number = numeric->value(); + if (number != ToUint32(number)) { + // Optimization 2: We have something like expr[3.14]. The number + // isn't an array index, so it converts to a string ("3.14"), + // enabling optimization 3 below. + name = numeric->toAtom(info.fc, info.parserAtoms); + if (!name) { + return false; + } + } + } + + // If we don't have a name, we can't optimize to getprop. + if (!name) { + return true; + } + + // Optimization 3: We have expr["foo"] where foo is not an index. Convert + // to a property access (like expr.foo) that optimizes better downstream. + + NameNode* propertyNameExpr = info.handler->newPropertyName(name, key->pn_pos); + if (!propertyNameExpr) { + return false; + } + if (!TryReplaceNode( + nodePtr, info.handler->newPropertyAccess(expr, propertyNameExpr))) { + return false; + } + + return true; +} + +static bool FoldAdd(FoldInfo info, ParseNode** nodePtr) { + ListNode* node = &(*nodePtr)->as(); + + MOZ_ASSERT(node->isKind(ParseNodeKind::AddExpr)); + MOZ_ASSERT(node->count() >= 2); + + // Fold leading numeric operands together: + // + // (1 + 2 + x) becomes (3 + x) + // + // Don't go past the leading operands: additions after a string are + // string concatenations, not additions: ("1" + 2 + 3 === "123"). + ParseNode** current = node->unsafeHeadReference(); + ParseNode** next = &(*current)->pn_next; + if ((*current)->isKind(ParseNodeKind::NumberExpr)) { + do { + if (!(*next)->isKind(ParseNodeKind::NumberExpr)) { + break; + } + + double left = (*current)->as().value(); + double right = (*next)->as().value(); + TokenPos pos((*current)->pn_pos.begin, (*next)->pn_pos.end); + + if (!TryReplaceNode( + current, info.handler->newNumber(left + right, NoDecimal, pos))) { + return false; + } + + (*current)->pn_next = (*next)->pn_next; + next = &(*current)->pn_next; + + node->unsafeDecrementCount(); + } while (*next); + } + + // If any operands remain, attempt string concatenation folding. + do { + // If no operands remain, we're done. + if (!*next) { + break; + } + + // (number + string) is string concatenation *only* at the start of + // the list: (x + 1 + "2" !== x + "12") when x is a number. + if ((*current)->isKind(ParseNodeKind::NumberExpr) && + (*next)->isKind(ParseNodeKind::StringExpr)) { + if (!FoldType(info, current, ParseNodeKind::StringExpr)) { + return false; + } + next = &(*current)->pn_next; + } + + // The first string forces all subsequent additions to be + // string concatenations. + do { + if ((*current)->isKind(ParseNodeKind::StringExpr)) { + break; + } + + current = next; + next = &(*current)->pn_next; + } while (*next); + + // If there's nothing left to fold, we're done. + if (!*next) { + break; + } + + do { + // Concat all strings. + MOZ_ASSERT((*current)->isKind(ParseNodeKind::StringExpr)); + + // To avoid unnecessarily copy when there's no strings after the + // first item, lazily construct StringBuffer and append the first item. + mozilla::Maybe accum; + TaggedParserAtomIndex firstAtom; + firstAtom = (*current)->as().atom(); + + do { + // Try folding the next operand to a string. + if (!FoldType(info, next, ParseNodeKind::StringExpr)) { + return false; + } + + // Stop glomming once folding doesn't produce a string. + if (!(*next)->isKind(ParseNodeKind::StringExpr)) { + break; + } + + if (!accum) { + accum.emplace(info.fc); + if (!accum->append(info.parserAtoms, firstAtom)) { + return false; + } + } + // Append this string and remove the node. + if (!accum->append(info.parserAtoms, (*next)->as().atom())) { + return false; + } + + (*current)->pn_next = (*next)->pn_next; + next = &(*current)->pn_next; + + node->unsafeDecrementCount(); + } while (*next); + + // Replace with concatenation if we multiple nodes. + if (accum) { + auto combination = accum->finishParserAtom(info.parserAtoms, info.fc); + if (!combination) { + return false; + } + + // Replace |current|'s string with the entire combination. + MOZ_ASSERT((*current)->isKind(ParseNodeKind::StringExpr)); + (*current)->as().setAtom(combination); + } + + // If we're out of nodes, we're done. + if (!*next) { + break; + } + + current = next; + next = &(*current)->pn_next; + + // If we're out of nodes *after* the non-foldable-to-string + // node, we're done. + if (!*next) { + break; + } + + // Otherwise find the next node foldable to a string, and loop. + do { + current = next; + + if (!FoldType(info, current, ParseNodeKind::StringExpr)) { + return false; + } + next = &(*current)->pn_next; + } while (!(*current)->isKind(ParseNodeKind::StringExpr) && *next); + } while (*next); + } while (false); + + MOZ_ASSERT(!*next, "must have considered all nodes here"); + MOZ_ASSERT(!(*current)->pn_next, "current node must be the last node"); + + node->unsafeReplaceTail(&(*current)->pn_next); + + if (node->count() == 1) { + // We reduced the list to a constant. Replace the ParseNodeKind::Add node + // with that constant. + ReplaceNode(nodePtr, *current); + } + + return true; +} + +class FoldVisitor : public RewritingParseNodeVisitor { + using Base = RewritingParseNodeVisitor; + + ParserAtomsTable& parserAtoms; + FullParseHandler* handler; + + FoldInfo info() const { return FoldInfo{fc_, parserAtoms, handler}; } + + public: + FoldVisitor(FrontendContext* fc, ParserAtomsTable& parserAtoms, + FullParseHandler* handler) + : RewritingParseNodeVisitor(fc), + parserAtoms(parserAtoms), + handler(handler) {} + + bool visitElemExpr(ParseNode*& pn) { + return Base::visitElemExpr(pn) && FoldElement(info(), &pn); + } + + bool visitTypeOfExpr(ParseNode*& pn) { + return Base::visitTypeOfExpr(pn) && FoldTypeOfExpr(info(), &pn); + } + + bool visitDeleteExpr(ParseNode*& pn) { + return Base::visitDeleteExpr(pn) && FoldDeleteExpr(info(), &pn); + } + + bool visitDeleteElemExpr(ParseNode*& pn) { + return Base::visitDeleteElemExpr(pn) && FoldDeleteElement(info(), &pn); + } + + bool visitNotExpr(ParseNode*& pn) { + return Base::visitNotExpr(pn) && FoldNot(info(), &pn); + } + + bool visitBitNotExpr(ParseNode*& pn) { + return Base::visitBitNotExpr(pn) && FoldUnaryArithmetic(info(), &pn); + } + + bool visitPosExpr(ParseNode*& pn) { + return Base::visitPosExpr(pn) && FoldUnaryArithmetic(info(), &pn); + } + + bool visitNegExpr(ParseNode*& pn) { + return Base::visitNegExpr(pn) && FoldUnaryArithmetic(info(), &pn); + } + + bool visitPowExpr(ParseNode*& pn) { + return Base::visitPowExpr(pn) && FoldExponentiation(info(), &pn); + } + + bool visitMulExpr(ParseNode*& pn) { + return Base::visitMulExpr(pn) && FoldBinaryArithmetic(info(), &pn); + } + + bool visitDivExpr(ParseNode*& pn) { + return Base::visitDivExpr(pn) && FoldBinaryArithmetic(info(), &pn); + } + + bool visitModExpr(ParseNode*& pn) { + return Base::visitModExpr(pn) && FoldBinaryArithmetic(info(), &pn); + } + + bool visitAddExpr(ParseNode*& pn) { + return Base::visitAddExpr(pn) && FoldAdd(info(), &pn); + } + + bool visitSubExpr(ParseNode*& pn) { + return Base::visitSubExpr(pn) && FoldBinaryArithmetic(info(), &pn); + } + + bool visitLshExpr(ParseNode*& pn) { + return Base::visitLshExpr(pn) && FoldBinaryArithmetic(info(), &pn); + } + + bool visitRshExpr(ParseNode*& pn) { + return Base::visitRshExpr(pn) && FoldBinaryArithmetic(info(), &pn); + } + + bool visitUrshExpr(ParseNode*& pn) { + return Base::visitUrshExpr(pn) && FoldBinaryArithmetic(info(), &pn); + } + + bool visitAndExpr(ParseNode*& pn) { + // Note that this does result in the unfortunate fact that dead arms of this + // node get constant folded. The same goes for visitOr and visitCoalesce. + return Base::visitAndExpr(pn) && FoldAndOrCoalesce(info(), &pn); + } + + bool visitOrExpr(ParseNode*& pn) { + return Base::visitOrExpr(pn) && FoldAndOrCoalesce(info(), &pn); + } + + bool visitCoalesceExpr(ParseNode*& pn) { + return Base::visitCoalesceExpr(pn) && FoldAndOrCoalesce(info(), &pn); + } + + bool visitConditionalExpr(ParseNode*& pn) { + // Don't call base-class visitConditional because FoldConditional processes + // pn's child nodes specially to save stack space. + return FoldConditional(info(), &pn); + } + + private: + bool internalVisitCall(BinaryNode* node) { + MOZ_ASSERT(node->isKind(ParseNodeKind::CallExpr) || + node->isKind(ParseNodeKind::OptionalCallExpr) || + node->isKind(ParseNodeKind::SuperCallExpr) || + node->isKind(ParseNodeKind::NewExpr) || + node->isKind(ParseNodeKind::TaggedTemplateExpr)); + + // Don't fold a parenthesized callable component in an invocation, as this + // might cause a different |this| value to be used, changing semantics: + // + // var prop = "global"; + // var obj = { prop: "obj", f: function() { return this.prop; } }; + // assertEq((true ? obj.f : null)(), "global"); + // assertEq(obj.f(), "obj"); + // assertEq((true ? obj.f : null)``, "global"); + // assertEq(obj.f``, "obj"); + // + // As an exception to this, we do allow folding the function in + // `(function() { ... })()` (the module pattern), because that lets us + // constant fold code inside that function. + // + // See bug 537673 and bug 1182373. + ParseNode* callee = node->left(); + if (node->isKind(ParseNodeKind::NewExpr) || !callee->isInParens() || + callee->is()) { + if (!visit(*node->unsafeLeftReference())) { + return false; + } + } + + if (!visit(*node->unsafeRightReference())) { + return false; + } + + return true; + } + + public: + bool visitCallExpr(ParseNode*& pn) { + return internalVisitCall(&pn->as()); + } + + bool visitOptionalCallExpr(ParseNode*& pn) { + return internalVisitCall(&pn->as()); + } + + bool visitNewExpr(ParseNode*& pn) { + return internalVisitCall(&pn->as()); + } + + bool visitSuperCallExpr(ParseNode*& pn) { + return internalVisitCall(&pn->as()); + } + + bool visitTaggedTemplateExpr(ParseNode*& pn) { + return internalVisitCall(&pn->as()); + } + + bool visitIfStmt(ParseNode*& pn) { + // Don't call base-class visitIf because FoldIf processes pn's child nodes + // specially to save stack space. + return FoldIf(info(), &pn); + } + + bool visitForStmt(ParseNode*& pn) { + if (!Base::visitForStmt(pn)) { + return false; + } + + ForNode& stmt = pn->as(); + if (stmt.left()->isKind(ParseNodeKind::ForHead)) { + TernaryNode& head = stmt.left()->as(); + ParseNode** test = head.unsafeKid2Reference(); + if (*test) { + if (!SimplifyCondition(info(), test)) { + return false; + } + if ((*test)->isKind(ParseNodeKind::TrueExpr)) { + *test = nullptr; + } + } + } + + return true; + } + + bool visitWhileStmt(ParseNode*& pn) { + BinaryNode& node = pn->as(); + return Base::visitWhileStmt(pn) && + SimplifyCondition(info(), node.unsafeLeftReference()); + } + + bool visitDoWhileStmt(ParseNode*& pn) { + BinaryNode& node = pn->as(); + return Base::visitDoWhileStmt(pn) && + SimplifyCondition(info(), node.unsafeRightReference()); + } + + bool visitFunction(ParseNode*& pn) { + FunctionNode& node = pn->as(); + + // Don't constant-fold inside "use asm" code, as this could create a parse + // tree that doesn't type-check as asm.js. + if (node.funbox()->useAsmOrInsideUseAsm()) { + return true; + } + + return Base::visitFunction(pn); + } + + bool visitArrayExpr(ParseNode*& pn) { + if (!Base::visitArrayExpr(pn)) { + return false; + } + + ListNode* list = &pn->as(); + // Empty arrays are non-constant, since we cannot easily determine their + // type. + if (list->hasNonConstInitializer() && list->count() > 0) { + for (ParseNode* node : list->contents()) { + if (!node->isConstant()) { + return true; + } + } + list->unsetHasNonConstInitializer(); + } + return true; + } + + bool visitObjectExpr(ParseNode*& pn) { + if (!Base::visitObjectExpr(pn)) { + return false; + } + + ListNode* list = &pn->as(); + if (list->hasNonConstInitializer()) { + for (ParseNode* node : list->contents()) { + if (node->getKind() != ParseNodeKind::PropertyDefinition) { + return true; + } + BinaryNode* binary = &node->as(); + if (binary->left()->isKind(ParseNodeKind::ComputedName)) { + return true; + } + if (!binary->right()->isConstant()) { + return true; + } + } + list->unsetHasNonConstInitializer(); + } + return true; + } +}; + +static bool Fold(FrontendContext* fc, ParserAtomsTable& parserAtoms, + FullParseHandler* handler, ParseNode** pnp) { + FoldVisitor visitor(fc, parserAtoms, handler); + return visitor.visit(*pnp); +} +static bool Fold(FoldInfo info, ParseNode** pnp) { + return Fold(info.fc, info.parserAtoms, info.handler, pnp); +} + +bool frontend::FoldConstants(FrontendContext* fc, ParserAtomsTable& parserAtoms, + ParseNode** pnp, FullParseHandler* handler) { + return Fold(fc, parserAtoms, handler, pnp); +} diff --git a/js/src/frontend/FoldConstants.h b/js/src/frontend/FoldConstants.h new file mode 100644 index 0000000000..70252b148b --- /dev/null +++ b/js/src/frontend/FoldConstants.h @@ -0,0 +1,53 @@ +/* -*- 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/. */ + +#ifndef frontend_FoldConstants_h +#define frontend_FoldConstants_h + +#include "frontend/SyntaxParseHandler.h" + +namespace js { + +class FrontendContext; + +namespace frontend { + +class FullParseHandler; +template +class PerHandlerParser; +class ParserAtomsTable; + +// Perform constant folding on the given AST. For example, the program +// `print(2 + 2)` would become `print(4)`. +// +// pnp is the address of a pointer variable that points to the root node of the +// AST. On success, *pnp points to the root node of the new tree, which may be +// the same node (unchanged or modified in place) or a new node. +// +// Usage: +// pn = parser->statement(); +// if (!pn) { +// return false; +// } +// if (!FoldConstants(fc, parserAtoms, &pn, parser)) { +// return false; +// } +[[nodiscard]] extern bool FoldConstants(FrontendContext* fc, + ParserAtomsTable& parserAtoms, + ParseNode** pnp, + FullParseHandler* handler); + +[[nodiscard]] inline bool FoldConstants(FrontendContext* fc, + ParserAtomsTable& parserAtoms, + typename SyntaxParseHandler::Node* pnp, + SyntaxParseHandler* handler) { + return true; +} + +} /* namespace frontend */ +} /* namespace js */ + +#endif /* frontend_FoldConstants_h */ diff --git a/js/src/frontend/ForInEmitter.cpp b/js/src/frontend/ForInEmitter.cpp new file mode 100644 index 0000000000..4d9f9ad540 --- /dev/null +++ b/js/src/frontend/ForInEmitter.cpp @@ -0,0 +1,154 @@ +/* -*- 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/ForInEmitter.h" + +#include "frontend/BytecodeEmitter.h" +#include "frontend/EmitterScope.h" +#include "vm/Opcodes.h" +#include "vm/StencilEnums.h" // TryNoteKind + +using namespace js; +using namespace js::frontend; + +using mozilla::Nothing; + +ForInEmitter::ForInEmitter(BytecodeEmitter* bce, + const EmitterScope* headLexicalEmitterScope) + : bce_(bce), headLexicalEmitterScope_(headLexicalEmitterScope) {} + +bool ForInEmitter::emitIterated() { + MOZ_ASSERT(state_ == State::Start); + tdzCacheForIteratedValue_.emplace(bce_); + +#ifdef DEBUG + state_ = State::Iterated; +#endif + return true; +} + +bool ForInEmitter::emitInitialize() { + MOZ_ASSERT(state_ == State::Iterated); + tdzCacheForIteratedValue_.reset(); + + if (!bce_->emit1(JSOp::Iter)) { + // [stack] ITER + return false; + } + + loopInfo_.emplace(bce_, StatementKind::ForInLoop); + + if (!loopInfo_->emitLoopHead(bce_, Nothing())) { + // [stack] ITER + return false; + } + + if (!bce_->emit1(JSOp::MoreIter)) { + // [stack] ITER NEXTITERVAL? + return false; + } + if (!bce_->emit1(JSOp::IsNoIter)) { + // [stack] ITER NEXTITERVAL? ISNOITER + return false; + } + if (!bce_->emitJump(JSOp::JumpIfTrue, &loopInfo_->breaks)) { + // [stack] ITER NEXTITERVAL? + return false; + } + + // If the loop had an escaping lexical declaration, reset the declaration's + // bindings to uninitialized to implement TDZ semantics. + if (headLexicalEmitterScope_) { + // The environment chain only includes an environment for the + // for-in loop head *if* a scope binding is captured, thereby + // requiring recreation each iteration. If a lexical scope exists + // for the head, it must be the innermost one. If that scope has + // closed-over bindings inducing an environment, recreate the + // current environment. + MOZ_ASSERT(headLexicalEmitterScope_ == bce_->innermostEmitterScope()); + MOZ_ASSERT(headLexicalEmitterScope_->scope(bce_).kind() == + ScopeKind::Lexical); + + if (headLexicalEmitterScope_->hasEnvironment()) { + if (!bce_->emitInternedScopeOp(headLexicalEmitterScope_->index(), + JSOp::RecreateLexicalEnv)) { + // [stack] ITER ITERVAL + return false; + } + } + + // For uncaptured bindings, put them back in TDZ. + if (!headLexicalEmitterScope_->deadZoneFrameSlots(bce_)) { + return false; + } + } + +#ifdef DEBUG + loopDepth_ = bce_->bytecodeSection().stackDepth(); +#endif + MOZ_ASSERT(loopDepth_ >= 2); + +#ifdef DEBUG + state_ = State::Initialize; +#endif + return true; +} + +bool ForInEmitter::emitBody() { + MOZ_ASSERT(state_ == State::Initialize); + + MOZ_ASSERT(bce_->bytecodeSection().stackDepth() == loopDepth_, + "iterator and iterval must be left on the stack"); + +#ifdef DEBUG + state_ = State::Body; +#endif + return true; +} + +bool ForInEmitter::emitEnd(uint32_t forPos) { + MOZ_ASSERT(state_ == State::Body); + + // Make sure this code is attributed to the "for". + if (!bce_->updateSourceCoordNotes(forPos)) { + return false; + } + + if (!loopInfo_->emitContinueTarget(bce_)) { + // [stack] ITER ITERVAL + return false; + } + + if (!bce_->emit1(JSOp::Pop)) { + // [stack] ITER + return false; + } + if (!loopInfo_->emitLoopEnd(bce_, JSOp::Goto, TryNoteKind::ForIn)) { + // [stack] ITER + return false; + } + + // When we leave the loop body and jump to this point, the iteration value is + // still on the stack. Account for that by updating the stack depth manually. + int32_t stackDepth = bce_->bytecodeSection().stackDepth() + 1; + MOZ_ASSERT(stackDepth == loopDepth_); + bce_->bytecodeSection().setStackDepth(stackDepth); + + // [stack] ITER ITERVAL + + // Pop the value and iterator and close the iterator. + if (!bce_->emit1(JSOp::EndIter)) { + // [stack] + return false; + } + + loopInfo_.reset(); + +#ifdef DEBUG + state_ = State::End; +#endif + return true; +} diff --git a/js/src/frontend/ForInEmitter.h b/js/src/frontend/ForInEmitter.h new file mode 100644 index 0000000000..ed5aa312c5 --- /dev/null +++ b/js/src/frontend/ForInEmitter.h @@ -0,0 +1,117 @@ +/* -*- 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/. */ + +#ifndef frontend_ForInEmitter_h +#define frontend_ForInEmitter_h + +#include "mozilla/Attributes.h" +#include "mozilla/Maybe.h" + +#include + +#include "frontend/BytecodeControlStructures.h" +#include "frontend/TDZCheckCache.h" + +namespace js { +namespace frontend { + +struct BytecodeEmitter; +class EmitterScope; + +// Class for emitting bytecode for for-in loop. +// +// Usage: (check for the return value is omitted for simplicity) +// +// `for (init in iterated) body` +// // headLexicalEmitterScope: lexical scope for init +// ForInEmitter forIn(this, headLexicalEmitterScope); +// forIn.emitIterated(); +// emit(iterated); +// forIn.emitInitialize(); +// emit(init); +// forIn.emitBody(); +// emit(body); +// forIn.emitEnd(offset_of_for); +// +class MOZ_STACK_CLASS ForInEmitter { + BytecodeEmitter* bce_; + +#ifdef DEBUG + // The stack depth before emitting initialize code inside loop. + int32_t loopDepth_ = 0; +#endif + + mozilla::Maybe loopInfo_; + + // The lexical scope to be freshened for each iteration. See the comment + // in `emitBody` for more details. Can be nullptr if there's no lexical + // scope. + const EmitterScope* headLexicalEmitterScope_; + + // Cache for the iterated value. + // (The cache for the iteration body is inside `loopInfo_`) + // + // The iterated value needs its own TDZCheckCache, separated from both the + // enclosing block and the iteration body, in order to make the sanity check + // in Ion work properly. + // In term of the execution order, the TDZCheckCache for the iterated value + // dominates the one for the iteration body, that means the checks in the + // iteration body is dead, and we can optimize them away. But the sanity + // check in Ion doesn't know it's dead. + // (see bug 1368360 for more context) + mozilla::Maybe tdzCacheForIteratedValue_; + +#ifdef DEBUG + // The state of this emitter. + // + // +-------+ emitIterated +----------+ emitInitialize +------------+ + // | Start |------------->| Iterated |--------------->| Initialize |-+ + // +-------+ +----------+ +------------+ | + // | + // +----------------------------------+ + // | + // | emitBody +------+ emitEnd +-----+ + // +----------| Body |--------->| End | + // +------+ +-----+ + enum class State { + // The initial state. + Start, + + // After calling emitIterated. + Iterated, + + // After calling emitInitialize. + Initialize, + + // After calling emitBody. + Body, + + // After calling emitEnd. + End + }; + State state_ = State::Start; +#endif + + public: + ForInEmitter(BytecodeEmitter* bce, + const EmitterScope* headLexicalEmitterScope); + + // Parameters are the offset in the source code for each character below: + // + // for ( var x in obj ) { ... } + // ^ + // | + // forPos + [[nodiscard]] bool emitIterated(); + [[nodiscard]] bool emitInitialize(); + [[nodiscard]] bool emitBody(); + [[nodiscard]] bool emitEnd(uint32_t forPos); +}; + +} /* namespace frontend */ +} /* namespace js */ + +#endif /* frontend_ForInEmitter_h */ diff --git a/js/src/frontend/ForOfEmitter.cpp b/js/src/frontend/ForOfEmitter.cpp new file mode 100644 index 0000000000..2bef507a99 --- /dev/null +++ b/js/src/frontend/ForOfEmitter.cpp @@ -0,0 +1,223 @@ +/* -*- 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/ForOfEmitter.h" + +#include "frontend/BytecodeEmitter.h" +#include "frontend/EmitterScope.h" +#include "frontend/ParserAtom.h" // TaggedParserAtomIndex +#include "vm/Opcodes.h" +#include "vm/StencilEnums.h" // TryNoteKind + +using namespace js; +using namespace js::frontend; + +using mozilla::Nothing; + +ForOfEmitter::ForOfEmitter(BytecodeEmitter* bce, + const EmitterScope* headLexicalEmitterScope, + SelfHostedIter selfHostedIter, IteratorKind iterKind) + : bce_(bce), + selfHostedIter_(selfHostedIter), + iterKind_(iterKind), + headLexicalEmitterScope_(headLexicalEmitterScope) {} + +bool ForOfEmitter::emitIterated() { + MOZ_ASSERT(state_ == State::Start); + + // Evaluate the expression being iterated. The forHeadExpr should use a + // distinct TDZCheckCache to evaluate since (abstractly) it runs in its + // own LexicalEnvironment. + tdzCacheForIteratedValue_.emplace(bce_); + +#ifdef DEBUG + state_ = State::Iterated; +#endif + return true; +} + +bool ForOfEmitter::emitInitialize(uint32_t forPos, + bool isIteratorMethodOnStack) { + MOZ_ASSERT(state_ == State::Iterated); + + tdzCacheForIteratedValue_.reset(); + + // [stack] # if isIteratorMethodOnStack + // [stack] ITERABLE ITERFN SYNC_ITERFN? + // [stack] # else isIteratorMethodOnStack + // [stack] ITERABLE + + if (iterKind_ == IteratorKind::Async) { + if (!bce_->emitAsyncIterator(selfHostedIter_, isIteratorMethodOnStack)) { + // [stack] NEXT ITER + return false; + } + } else { + if (!bce_->emitIterator(selfHostedIter_, isIteratorMethodOnStack)) { + // [stack] NEXT ITER + return false; + } + } + + // For-of loops have the iterator next method and the iterator itself on the + // stack. + + int32_t iterDepth = bce_->bytecodeSection().stackDepth(); + loopInfo_.emplace(bce_, iterDepth, selfHostedIter_, iterKind_); + + if (!loopInfo_->emitLoopHead(bce_, Nothing())) { + // [stack] NEXT ITER + return false; + } + + // If the loop had an escaping lexical declaration, replace the current + // environment with an dead zoned one to implement TDZ semantics. + if (headLexicalEmitterScope_) { + // The environment chain only includes an environment for the for-of + // loop head *if* a scope binding is captured, thereby requiring + // recreation each iteration. If a lexical scope exists for the head, + // it must be the innermost one. If that scope has closed-over + // bindings inducing an environment, recreate the current environment. + MOZ_ASSERT(headLexicalEmitterScope_ == bce_->innermostEmitterScope()); + MOZ_ASSERT(headLexicalEmitterScope_->scope(bce_).kind() == + ScopeKind::Lexical); + + if (headLexicalEmitterScope_->hasEnvironment()) { + if (!bce_->emitInternedScopeOp(headLexicalEmitterScope_->index(), + JSOp::RecreateLexicalEnv)) { + // [stack] NEXT ITER + return false; + } + } + + // For uncaptured bindings, put them back in TDZ. + if (!headLexicalEmitterScope_->deadZoneFrameSlots(bce_)) { + return false; + } + } + +#ifdef DEBUG + loopDepth_ = bce_->bytecodeSection().stackDepth(); +#endif + + // Make sure this code is attributed to the "for". + if (!bce_->updateSourceCoordNotes(forPos)) { + return false; + } + + if (!bce_->emit1(JSOp::Dup2)) { + // [stack] NEXT ITER NEXT ITER + return false; + } + + if (!bce_->emitIteratorNext(mozilla::Some(forPos), iterKind_, + selfHostedIter_)) { + // [stack] NEXT ITER RESULT + return false; + } + + if (!bce_->emit1(JSOp::Dup)) { + // [stack] NEXT ITER RESULT RESULT + return false; + } + if (!bce_->emitAtomOp(JSOp::GetProp, + TaggedParserAtomIndex::WellKnown::done())) { + // [stack] NEXT ITER RESULT DONE + return false; + } + + // if (done) break; + MOZ_ASSERT(bce_->innermostNestableControl == loopInfo_.ptr(), + "must be at the top-level of the loop"); + if (!bce_->emitJump(JSOp::JumpIfTrue, &loopInfo_->breaks)) { + // [stack] NEXT ITER RESULT + return false; + } + + // Emit code to assign result.value to the iteration variable. + // + // Note that ES 13.7.5.13, step 5.c says getting result.value does not + // call IteratorClose, so start TryNoteKind::ForOfIterClose after the GetProp. + if (!bce_->emitAtomOp(JSOp::GetProp, + TaggedParserAtomIndex::WellKnown::value())) { + // [stack] NEXT ITER VALUE + return false; + } + + if (!loopInfo_->emitBeginCodeNeedingIteratorClose(bce_)) { + return false; + } + +#ifdef DEBUG + state_ = State::Initialize; +#endif + return true; +} + +bool ForOfEmitter::emitBody() { + MOZ_ASSERT(state_ == State::Initialize); + + MOZ_ASSERT(bce_->bytecodeSection().stackDepth() == loopDepth_ + 1, + "the stack must be balanced around the initializing " + "operation"); + +#ifdef DEBUG + state_ = State::Body; +#endif + return true; +} + +bool ForOfEmitter::emitEnd(uint32_t iteratedPos) { + MOZ_ASSERT(state_ == State::Body); + + MOZ_ASSERT(bce_->bytecodeSection().stackDepth() == loopDepth_ + 1, + "the stack must be balanced around the for-of body"); + + if (!loopInfo_->emitEndCodeNeedingIteratorClose(bce_)) { + // [stack] NEXT ITER VALUE + return false; + } + + if (!loopInfo_->emitContinueTarget(bce_)) { + // [stack] NEXT ITER VALUE + return false; + } + + // We use the iterated value's position to attribute the backedge, + // which corresponds to the iteration protocol. + // This is a bit misleading for 2nd and later iterations and might need + // some fix (bug 1482003). + if (!bce_->updateSourceCoordNotes(iteratedPos)) { + return false; + } + + if (!bce_->emit1(JSOp::Pop)) { + // [stack] NEXT ITER + return false; + } + + if (!loopInfo_->emitLoopEnd(bce_, JSOp::Goto, TryNoteKind::ForOf)) { + // [stack] NEXT ITER + return false; + } + + // All jumps/breaks to this point still have an extra value on the stack. + MOZ_ASSERT(bce_->bytecodeSection().stackDepth() == loopDepth_); + bce_->bytecodeSection().setStackDepth(bce_->bytecodeSection().stackDepth() + + 1); + + if (!bce_->emitPopN(3)) { + // [stack] + return false; + } + + loopInfo_.reset(); + +#ifdef DEBUG + state_ = State::End; +#endif + return true; +} diff --git a/js/src/frontend/ForOfEmitter.h b/js/src/frontend/ForOfEmitter.h new file mode 100644 index 0000000000..22e67f3f94 --- /dev/null +++ b/js/src/frontend/ForOfEmitter.h @@ -0,0 +1,116 @@ +/* -*- 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/. */ + +#ifndef frontend_ForOfEmitter_h +#define frontend_ForOfEmitter_h + +#include "mozilla/Attributes.h" // MOZ_STACK_CLASS +#include "mozilla/Maybe.h" // mozilla::Maybe + +#include // int32_t + +#include "frontend/ForOfLoopControl.h" // ForOfLoopControl +#include "frontend/IteratorKind.h" // IteratorKind +#include "frontend/SelfHostedIter.h" // SelfHostedIter +#include "frontend/TDZCheckCache.h" // TDZCheckCache + +namespace js { +namespace frontend { + +struct BytecodeEmitter; +class EmitterScope; + +// Class for emitting bytecode for for-of loop. +// +// Usage: (check for the return value is omitted for simplicity) +// +// `for (init of iterated) body` +// // headLexicalEmitterScope: lexical scope for init +// ForOfEmitter forOf(this, headLexicalEmitterScope); +// forOf.emitIterated(); +// emit(iterated); +// forOf.emitInitialize(offset_of_for); +// emit(init); +// forOf.emitBody(); +// emit(body); +// forOf.emitEnd(offset_of_iterated); +// +class MOZ_STACK_CLASS ForOfEmitter { + BytecodeEmitter* bce_; + +#ifdef DEBUG + // The stack depth before emitting IteratorNext code inside loop. + int32_t loopDepth_ = 0; +#endif + + SelfHostedIter selfHostedIter_; + IteratorKind iterKind_; + + mozilla::Maybe loopInfo_; + + // The lexical scope to be freshened for each iteration. + // See the comment in `emitBody` for more details. + const EmitterScope* headLexicalEmitterScope_; + + // Cache for the iterated value. + // (The cache for the iteration body is inside `loopInfo_`) + mozilla::Maybe tdzCacheForIteratedValue_; + +#ifdef DEBUG + // The state of this emitter. + // + // +-------+ emitIterated +----------+ emitInitialize +------------+ + // | Start |------------->| Iterated |--------------->| Initialize |-+ + // +-------+ +----------+ +------------+ | + // | + // +----------------------------------+ + // | + // | emitBody +------+ emitEnd +-----+ + // +----------| Body |--------->| End | + // +------+ +-----+ + enum class State { + // The initial state. + Start, + + // After calling emitIterated. + Iterated, + + // After calling emitInitialize. + Initialize, + + // After calling emitBody. + Body, + + // After calling emitEnd. + End + }; + State state_ = State::Start; +#endif + + public: + ForOfEmitter(BytecodeEmitter* bce, + const EmitterScope* headLexicalEmitterScope, + SelfHostedIter selfHostedIter, IteratorKind iterKind); + + // The offset in the source code for each character below: + // + // for ( var x of obj ) { ... } + // ^ ^ + // | | + // | iteratedPos + // | + // forPos + [[nodiscard]] bool emitIterated(); + [[nodiscard]] bool emitInitialize(uint32_t forPos, + bool isIteratorMethodOnStack); + [[nodiscard]] bool emitBody(); + [[nodiscard]] bool emitEnd(uint32_t iteratedPos); +}; + +} /* namespace frontend */ +} /* namespace js */ + +#endif /* frontend_ForOfEmitter_h */ diff --git a/js/src/frontend/ForOfLoopControl.cpp b/js/src/frontend/ForOfLoopControl.cpp new file mode 100644 index 0000000000..084f8d80eb --- /dev/null +++ b/js/src/frontend/ForOfLoopControl.cpp @@ -0,0 +1,196 @@ +/* -*- 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/ForOfLoopControl.h" + +#include "frontend/BytecodeEmitter.h" // BytecodeEmitter +#include "frontend/IfEmitter.h" // InternalIfEmitter +#include "vm/CompletionKind.h" // CompletionKind +#include "vm/Opcodes.h" // JSOp + +using namespace js; +using namespace js::frontend; + +ForOfLoopControl::ForOfLoopControl(BytecodeEmitter* bce, int32_t iterDepth, + SelfHostedIter selfHostedIter, + IteratorKind iterKind) + : LoopControl(bce, StatementKind::ForOfLoop), + iterDepth_(iterDepth), + numYieldsAtBeginCodeNeedingIterClose_(UINT32_MAX), + selfHostedIter_(selfHostedIter), + iterKind_(iterKind) {} + +bool ForOfLoopControl::emitBeginCodeNeedingIteratorClose(BytecodeEmitter* bce) { + tryCatch_.emplace(bce, TryEmitter::Kind::TryCatch, + TryEmitter::ControlKind::NonSyntactic); + + if (!tryCatch_->emitTry()) { + return false; + } + + MOZ_ASSERT(numYieldsAtBeginCodeNeedingIterClose_ == UINT32_MAX); + numYieldsAtBeginCodeNeedingIterClose_ = bce->bytecodeSection().numYields(); + + return true; +} + +bool ForOfLoopControl::emitEndCodeNeedingIteratorClose(BytecodeEmitter* bce) { + if (!tryCatch_->emitCatch()) { + // [stack] ITER ... EXCEPTION + return false; + } + + unsigned slotFromTop = bce->bytecodeSection().stackDepth() - iterDepth_; + if (!bce->emitDupAt(slotFromTop)) { + // [stack] ITER ... EXCEPTION ITER + return false; + } + if (!emitIteratorCloseInInnermostScopeWithTryNote(bce, + CompletionKind::Throw)) { + return false; // ITER ... EXCEPTION + } + + if (!bce->emit1(JSOp::Throw)) { + // [stack] ITER ... + return false; + } + + // If any yields were emitted, then this for-of loop is inside a star + // generator and must handle the case of Generator.return. Like in + // yield*, it is handled with a finally block. If the generator is + // closing, then the exception/resumeindex value (second value on + // the stack) will be a magic JS_GENERATOR_CLOSING value. + // TODO: Refactor this to eliminate the swaps. + uint32_t numYieldsEmitted = bce->bytecodeSection().numYields(); + if (numYieldsEmitted > numYieldsAtBeginCodeNeedingIterClose_) { + if (!tryCatch_->emitFinally()) { + return false; + } + // [stack] ITER ... FVALUE FTYPE + InternalIfEmitter ifGeneratorClosing(bce); + if (!bce->emit1(JSOp::Swap)) { + // [stack] ITER ... FTYPE FVALUE + return false; + } + if (!bce->emit1(JSOp::IsGenClosing)) { + // [stack] ITER ... FTYPE FVALUE CLOSING + return false; + } + if (!ifGeneratorClosing.emitThen()) { + // [stack] ITER ... FTYPE FVALUE + return false; + } + if (!bce->emitDupAt(slotFromTop + 1)) { + // [stack] ITER ... FTYPE FVALUE ITER + return false; + } + if (!emitIteratorCloseInInnermostScopeWithTryNote(bce, + CompletionKind::Normal)) { + // [stack] ITER ... FTYPE FVALUE + return false; + } + if (!ifGeneratorClosing.emitEnd()) { + // [stack] ITER ... FTYPE FVALUE + return false; + } + if (!bce->emit1(JSOp::Swap)) { + // [stack] ITER ... FVALUE FTYPE + return false; + } + } + + if (!tryCatch_->emitEnd()) { + return false; + } + + tryCatch_.reset(); + numYieldsAtBeginCodeNeedingIterClose_ = UINT32_MAX; + + return true; +} + +bool ForOfLoopControl::emitIteratorCloseInInnermostScopeWithTryNote( + BytecodeEmitter* bce, + CompletionKind completionKind /* = CompletionKind::Normal */) { + BytecodeOffset start = bce->bytecodeSection().offset(); + if (!emitIteratorCloseInScope(bce, *bce->innermostEmitterScope(), + completionKind)) { + return false; + } + BytecodeOffset end = bce->bytecodeSection().offset(); + return bce->addTryNote(TryNoteKind::ForOfIterClose, 0, start, end); +} + +bool ForOfLoopControl::emitIteratorCloseInScope( + BytecodeEmitter* bce, EmitterScope& currentScope, + CompletionKind completionKind /* = CompletionKind::Normal */) { + return bce->emitIteratorCloseInScope(currentScope, iterKind_, completionKind, + selfHostedIter_); +} + +// Since we're in the middle of emitting code that will leave +// |bce->innermostEmitterScope()|, passing the innermost emitter scope to +// emitIteratorCloseInScope and looking up .generator there would be very, +// very wrong. We'd find .generator in the function environment, and we'd +// compute a NameLocation with the correct slot, but we'd compute a +// hop-count to the function environment that was too big. At runtime we'd +// either crash, or we'd find a user-controllable value in that slot, and +// Very Bad Things would ensue as we reinterpreted that value as an +// iterator. +bool ForOfLoopControl::emitPrepareForNonLocalJumpFromScope( + BytecodeEmitter* bce, EmitterScope& currentScope, bool isTarget, + BytecodeOffset* tryNoteStart) { + // Pop unnecessary value from the stack. Effectively this means + // leaving try-catch block. However, the performing IteratorClose can + // reach the depth for try-catch, and effectively re-enter the + // try-catch block. + if (!bce->emit1(JSOp::Pop)) { + // [stack] NEXT ITER + return false; + } + + // Pop the iterator's next method. + if (!bce->emit1(JSOp::Swap)) { + // [stack] ITER NEXT + return false; + } + if (!bce->emit1(JSOp::Pop)) { + // [stack] ITER + return false; + } + + if (!bce->emit1(JSOp::Dup)) { + // [stack] ITER ITER + return false; + } + + *tryNoteStart = bce->bytecodeSection().offset(); + if (!emitIteratorCloseInScope(bce, currentScope, CompletionKind::Normal)) { + // [stack] ITER + return false; + } + + if (isTarget) { + // At the level of the target block, there's bytecode after the + // loop that will pop the next method, the iterator, and the + // value, so push two undefineds to balance the stack. + if (!bce->emit1(JSOp::Undefined)) { + // [stack] ITER UNDEF + return false; + } + if (!bce->emit1(JSOp::Undefined)) { + // [stack] ITER UNDEF UNDEF + return false; + } + } else { + if (!bce->emit1(JSOp::Pop)) { + // [stack] + return false; + } + } + + return true; +} diff --git a/js/src/frontend/ForOfLoopControl.h b/js/src/frontend/ForOfLoopControl.h new file mode 100644 index 0000000000..5ffda9a8ba --- /dev/null +++ b/js/src/frontend/ForOfLoopControl.h @@ -0,0 +1,97 @@ +/* -*- 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/. */ + +#ifndef frontend_ForOfLoopControl_h +#define frontend_ForOfLoopControl_h + +#include "mozilla/Maybe.h" // mozilla::Maybe + +#include // int32_t, uint32_t + +#include "frontend/BytecodeControlStructures.h" // NestableControl, LoopControl +#include "frontend/IteratorKind.h" // IteratorKind +#include "frontend/SelfHostedIter.h" // SelfHostedIter +#include "frontend/TryEmitter.h" // TryEmitter +#include "vm/CompletionKind.h" // CompletionKind + +namespace js { +namespace frontend { + +struct BytecodeEmitter; +class BytecodeOffset; +class EmitterScope; + +class ForOfLoopControl : public LoopControl { + // The stack depth of the iterator. + int32_t iterDepth_; + + // For-of loops, when throwing from non-iterator code (i.e. from the body + // or from evaluating the LHS of the loop condition), need to call + // IteratorClose. This is done by enclosing the body of the loop with + // try-catch and calling IteratorClose in the `catch` block. + // + // If IteratorClose itself throws, we must not re-call + // IteratorClose. Since non-local jumps like break and return call + // IteratorClose, whenever a non-local jump is emitted, we must + // prevent the catch block from catching any exception thrown from + // IteratorClose. We do this by wrapping the non-local jump in a + // ForOfIterClose try-note. + // + // for (x of y) { + // // Operations for iterator (IteratorNext etc) are outside of + // // try-block. + // try { + // ... + // if (...) { + // // Before non-local jump, close iterator. + // CloseIter(iter, CompletionKind::Return); // Covered by + // return; // trynote + // } + // ... + // } catch (e) { + // // When propagating an exception, we swallow any exceptions + // // thrown while closing the iterator. + // CloseIter(iter, CompletionKind::Throw); + // throw e; + // } + // } + mozilla::Maybe tryCatch_; + + // Used to track if any yields were emitted between calls to to + // emitBeginCodeNeedingIteratorClose and emitEndCodeNeedingIteratorClose. + uint32_t numYieldsAtBeginCodeNeedingIterClose_; + + SelfHostedIter selfHostedIter_; + + IteratorKind iterKind_; + + public: + ForOfLoopControl(BytecodeEmitter* bce, int32_t iterDepth, + SelfHostedIter selfHostedIter, IteratorKind iterKind); + + [[nodiscard]] bool emitBeginCodeNeedingIteratorClose(BytecodeEmitter* bce); + [[nodiscard]] bool emitEndCodeNeedingIteratorClose(BytecodeEmitter* bce); + + [[nodiscard]] bool emitIteratorCloseInInnermostScopeWithTryNote( + BytecodeEmitter* bce, + CompletionKind completionKind = CompletionKind::Normal); + [[nodiscard]] bool emitIteratorCloseInScope( + BytecodeEmitter* bce, EmitterScope& currentScope, + CompletionKind completionKind = CompletionKind::Normal); + + [[nodiscard]] bool emitPrepareForNonLocalJumpFromScope( + BytecodeEmitter* bce, EmitterScope& currentScope, bool isTarget, + BytecodeOffset* tryNoteStart); +}; +template <> +inline bool NestableControl::is() const { + return kind_ == StatementKind::ForOfLoop; +} + +} /* namespace frontend */ +} /* namespace js */ + +#endif /* frontend_ForOfLoopControl_h */ diff --git a/js/src/frontend/Frontend2.cpp b/js/src/frontend/Frontend2.cpp new file mode 100644 index 0000000000..0b4f2d638c --- /dev/null +++ b/js/src/frontend/Frontend2.cpp @@ -0,0 +1,701 @@ +/* -*- 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/Frontend2.h" + +#include "mozilla/Maybe.h" // mozilla::Maybe +#include "mozilla/OperatorNewExtensions.h" // mozilla::KnownNotNull +#include "mozilla/Range.h" // mozilla::Range +#include "mozilla/Span.h" // mozilla::Span +#include "mozilla/Variant.h" // mozilla::AsVariant + +#include // size_t +#include // uint8_t, uint32_t + +#include "jsapi.h" + +#include "frontend/AbstractScopePtr.h" // ScopeIndex +#include "frontend/BytecodeSection.h" // EmitScriptThingsVector +#include "frontend/CompilationStencil.h" // CompilationState, CompilationStencil +#include "frontend/FrontendContext.h" // AutoReportFrontendContext +#include "frontend/Parser.h" // NewEmptyLexicalScopeData, NewEmptyGlobalScopeData, NewEmptyVarScopeData, NewEmptyFunctionScopeData +#include "frontend/ParserAtom.h" // ParserAtomsTable, TaggedParserAtomIndex +#include "frontend/ScriptIndex.h" // ScriptIndex +#include "frontend/smoosh_generated.h" // CVec, Smoosh*, smoosh_* +#include "frontend/SourceNotes.h" // SrcNote +#include "frontend/Stencil.h" // ScopeStencil, RegExpIndex +#include "frontend/TokenStream.h" // TokenStreamAnyChars +#include "irregexp/RegExpAPI.h" // irregexp::CheckPatternSyntax +#include "js/CharacterEncoding.h" // JS::UTF8Chars, UTF8CharsToNewTwoByteCharsZ +#include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_* +#include "js/GCAPI.h" // JS::AutoCheckCannotGC +#include "js/HeapAPI.h" // JS::GCCellPtr +#include "js/RegExpFlags.h" // JS::RegExpFlag, JS::RegExpFlags +#include "js/RootingAPI.h" // JS::MutableHandle +#include "js/UniquePtr.h" // js::UniquePtr +#include "js/Utility.h" // JS::UniqueTwoByteChars, StringBufferArena +#include "vm/JSScript.h" // JSScript +#include "vm/Scope.h" // GetScopeDataTrailingNames +#include "vm/ScopeKind.h" // ScopeKind +#include "vm/SharedStencil.h" // ImmutableScriptData, ScopeNote, TryNote, GCThingIndex + +using mozilla::Utf8Unit; + +using namespace js::gc; +using namespace js::frontend; +using namespace js; + +namespace js { + +namespace frontend { + +// Given the result of SmooshMonkey's parser, Convert the list of atoms into +// the list of ParserAtoms. +bool ConvertAtoms(JSContext* cx, FrontendContext* fc, + const SmooshResult& result, + CompilationState& compilationState, + Vector& allAtoms) { + size_t numAtoms = result.all_atoms_len; + + if (!allAtoms.reserve(numAtoms)) { + return false; + } + + for (size_t i = 0; i < numAtoms; i++) { + auto s = reinterpret_cast( + smoosh_get_atom_at(result, i)); + auto len = smoosh_get_atom_len_at(result, i); + auto atom = compilationState.parserAtoms.internUtf8(fc, s, len); + if (!atom) { + return false; + } + // We don't collect atomization information in smoosh yet. + // Assume it needs to be atomized. + compilationState.parserAtoms.markUsedByStencil(atom, + ParserAtom::Atomize::Yes); + allAtoms.infallibleAppend(atom); + } + + return true; +} + +void CopyBindingNames(JSContext* cx, CVec& from, + Vector& allAtoms, + ParserBindingName* to) { + // We're setting trailing array's content before setting its length. + JS::AutoCheckCannotGC nogc(cx); + + size_t numBindings = from.len; + for (size_t i = 0; i < numBindings; i++) { + SmooshBindingName& name = from.data[i]; + new (mozilla::KnownNotNull, &to[i]) ParserBindingName( + allAtoms[name.name], name.is_closed_over, name.is_top_level_function); + } +} + +void CopyBindingNames(JSContext* cx, CVec>& from, + Vector& allAtoms, + ParserBindingName* to) { + // We're setting trailing array's content before setting its length. + JS::AutoCheckCannotGC nogc(cx); + + size_t numBindings = from.len; + for (size_t i = 0; i < numBindings; i++) { + COption& maybeName = from.data[i]; + if (maybeName.IsSome()) { + SmooshBindingName& name = maybeName.AsSome(); + new (mozilla::KnownNotNull, &to[i]) ParserBindingName( + allAtoms[name.name], name.is_closed_over, name.is_top_level_function); + } else { + new (mozilla::KnownNotNull, &to[i]) + ParserBindingName(TaggedParserAtomIndex::null(), false, false); + } + } +} + +// Given the result of SmooshMonkey's parser, convert a list of scope data +// into a list of ScopeStencil. +bool ConvertScopeStencil(JSContext* cx, FrontendContext* fc, + const SmooshResult& result, + Vector& allAtoms, + CompilationState& compilationState) { + LifoAlloc& alloc = compilationState.alloc; + + if (result.scopes.len > TaggedScriptThingIndex::IndexLimit) { + ReportAllocationOverflow(fc); + return false; + } + + for (size_t i = 0; i < result.scopes.len; i++) { + SmooshScopeData& scopeData = result.scopes.data[i]; + ScopeIndex index; + + switch (scopeData.tag) { + case SmooshScopeData::Tag::Global: { + auto& global = scopeData.AsGlobal(); + + size_t numBindings = global.bindings.len; + GlobalScope::ParserData* data = + NewEmptyGlobalScopeData(fc, alloc, numBindings); + if (!data) { + return false; + } + + CopyBindingNames(cx, global.bindings, allAtoms, + GetScopeDataTrailingNamesPointer(data)); + + data->slotInfo.letStart = global.let_start; + data->slotInfo.constStart = global.const_start; + data->length = numBindings; + + if (!ScopeStencil::createForGlobalScope( + fc, compilationState, ScopeKind::Global, data, &index)) { + return false; + } + break; + } + case SmooshScopeData::Tag::Var: { + auto& var = scopeData.AsVar(); + + size_t numBindings = var.bindings.len; + + VarScope::ParserData* data = + NewEmptyVarScopeData(fc, alloc, numBindings); + if (!data) { + return false; + } + + CopyBindingNames(cx, var.bindings, allAtoms, + GetScopeDataTrailingNamesPointer(data)); + + // NOTE: data->slotInfo.nextFrameSlot is set in + // ScopeStencil::createForVarScope. + + data->length = numBindings; + + uint32_t firstFrameSlot = var.first_frame_slot; + ScopeIndex enclosingIndex(var.enclosing); + if (!ScopeStencil::createForVarScope( + fc, compilationState, ScopeKind::FunctionBodyVar, data, + firstFrameSlot, var.function_has_extensible_scope, + mozilla::Some(enclosingIndex), &index)) { + return false; + } + break; + } + case SmooshScopeData::Tag::Lexical: { + auto& lexical = scopeData.AsLexical(); + + size_t numBindings = lexical.bindings.len; + LexicalScope::ParserData* data = + NewEmptyLexicalScopeData(fc, alloc, numBindings); + if (!data) { + return false; + } + + CopyBindingNames(cx, lexical.bindings, allAtoms, + GetScopeDataTrailingNamesPointer(data)); + + // NOTE: data->slotInfo.nextFrameSlot is set in + // ScopeStencil::createForLexicalScope. + + data->slotInfo.constStart = lexical.const_start; + data->length = numBindings; + + uint32_t firstFrameSlot = lexical.first_frame_slot; + ScopeIndex enclosingIndex(lexical.enclosing); + if (!ScopeStencil::createForLexicalScope( + fc, compilationState, ScopeKind::Lexical, data, firstFrameSlot, + mozilla::Some(enclosingIndex), &index)) { + return false; + } + break; + } + case SmooshScopeData::Tag::Function: { + auto& function = scopeData.AsFunction(); + + size_t numBindings = function.bindings.len; + FunctionScope::ParserData* data = + NewEmptyFunctionScopeData(fc, alloc, numBindings); + if (!data) { + return false; + } + + CopyBindingNames(cx, function.bindings, allAtoms, + GetScopeDataTrailingNamesPointer(data)); + + // NOTE: data->slotInfo.nextFrameSlot is set in + // ScopeStencil::createForFunctionScope. + + if (function.has_parameter_exprs) { + data->slotInfo.setHasParameterExprs(); + } + data->slotInfo.nonPositionalFormalStart = + function.non_positional_formal_start; + data->slotInfo.varStart = function.var_start; + data->length = numBindings; + + bool hasParameterExprs = function.has_parameter_exprs; + bool needsEnvironment = function.non_positional_formal_start; + ScriptIndex functionIndex = ScriptIndex(function.function_index); + bool isArrow = function.is_arrow; + + ScopeIndex enclosingIndex(function.enclosing); + if (!ScopeStencil::createForFunctionScope( + fc, compilationState, data, hasParameterExprs, needsEnvironment, + functionIndex, isArrow, mozilla::Some(enclosingIndex), + &index)) { + return false; + } + break; + } + } + + // `ConvertGCThings` depends on this condition. + MOZ_ASSERT(index == i); + } + + return true; +} + +// Given the result of SmooshMonkey's parser, convert a list of RegExp data +// into a list of RegExpStencil. +bool ConvertRegExpData(JSContext* cx, FrontendContext* fc, + const SmooshResult& result, + CompilationState& compilationState) { + auto len = result.regexps.len; + if (len == 0) { + return true; + } + + if (len > TaggedScriptThingIndex::IndexLimit) { + ReportAllocationOverflow(fc); + return false; + } + + if (!compilationState.regExpData.reserve(len)) { + js::ReportOutOfMemory(fc); + return false; + } + + for (size_t i = 0; i < len; i++) { + SmooshRegExpItem& item = result.regexps.data[i]; + auto s = smoosh_get_slice_at(result, item.pattern); + auto len = smoosh_get_slice_len_at(result, item.pattern); + + JS::RegExpFlags::Flag flags = JS::RegExpFlag::NoFlags; + if (item.global) { + flags |= JS::RegExpFlag::Global; + } + if (item.ignore_case) { + flags |= JS::RegExpFlag::IgnoreCase; + } + if (item.multi_line) { + flags |= JS::RegExpFlag::Multiline; + } + if (item.dot_all) { + flags |= JS::RegExpFlag::DotAll; + } + if (item.sticky) { + flags |= JS::RegExpFlag::Sticky; + } + if (item.unicode) { + flags |= JS::RegExpFlag::Unicode; + } + + // FIXME: This check should be done at parse time. + size_t length; + JS::UniqueTwoByteChars pattern( + UTF8CharsToNewTwoByteCharsZ(cx, JS::UTF8Chars(s, len), &length, + StringBufferArena) + .get()); + if (!pattern) { + return false; + } + + mozilla::Range range(pattern.get(), length); + + TokenStreamAnyChars ts(fc, compilationState.input.options, + /* smg = */ nullptr); + + // See Parser::newRegExp. + + if (!irregexp::CheckPatternSyntax(cx->tempLifoAlloc(), fc->stackLimit(), ts, + range, flags)) { + return false; + } + + const mozilla::Utf8Unit* sUtf8 = + reinterpret_cast(s); + auto atom = compilationState.parserAtoms.internUtf8(fc, sUtf8, len); + if (!atom) { + return false; + } + + // RegExp patterm must be atomized. + compilationState.parserAtoms.markUsedByStencil(atom, + ParserAtom::Atomize::Yes); + compilationState.regExpData.infallibleEmplaceBack(atom, + JS::RegExpFlags(flags)); + } + + return true; +} + +// Convert SmooshImmutableScriptData into ImmutableScriptData. +UniquePtr ConvertImmutableScriptData( + JSContext* cx, const SmooshImmutableScriptData& smooshScriptData, + bool isFunction) { + Vector scopeNotes; + if (!scopeNotes.resize(smooshScriptData.scope_notes.len)) { + return nullptr; + } + for (size_t i = 0; i < smooshScriptData.scope_notes.len; i++) { + SmooshScopeNote& scopeNote = smooshScriptData.scope_notes.data[i]; + scopeNotes[i].index = GCThingIndex(scopeNote.index); + scopeNotes[i].start = scopeNote.start; + scopeNotes[i].length = scopeNote.length; + scopeNotes[i].parent = scopeNote.parent; + } + + AutoReportFrontendContext fc(cx); + return ImmutableScriptData::new_( + &fc, smooshScriptData.main_offset, smooshScriptData.nfixed, + smooshScriptData.nslots, GCThingIndex(smooshScriptData.body_scope_index), + smooshScriptData.num_ic_entries, isFunction, smooshScriptData.fun_length, + 0, + mozilla::Span(smooshScriptData.bytecode.data, + smooshScriptData.bytecode.len), + mozilla::Span(), mozilla::Span(), + scopeNotes, mozilla::Span()); +} + +// Given the result of SmooshMonkey's parser, convert a list of GC things +// used by a script into ScriptThingsVector. +bool ConvertGCThings(JSContext* cx, FrontendContext* fc, + const SmooshResult& result, + const SmooshScriptStencil& smooshScript, + CompilationState& compilationState, + Vector& allAtoms, + ScriptIndex scriptIndex) { + size_t ngcthings = smooshScript.gcthings.len; + + // If there are no things, avoid the allocation altogether. + if (ngcthings == 0) { + return true; + } + + TaggedScriptThingIndex* cursor = nullptr; + if (!compilationState.allocateGCThingsUninitialized(fc, scriptIndex, + ngcthings, &cursor)) { + return false; + } + + for (size_t i = 0; i < ngcthings; i++) { + SmooshGCThing& item = smooshScript.gcthings.data[i]; + + // Pointer to the uninitialized element. + void* raw = &cursor[i]; + + switch (item.tag) { + case SmooshGCThing::Tag::Null: { + new (raw) TaggedScriptThingIndex(); + break; + } + case SmooshGCThing::Tag::Atom: { + new (raw) TaggedScriptThingIndex(allAtoms[item.AsAtom()]); + break; + } + case SmooshGCThing::Tag::Function: { + new (raw) TaggedScriptThingIndex(ScriptIndex(item.AsFunction())); + break; + } + case SmooshGCThing::Tag::Scope: { + new (raw) TaggedScriptThingIndex(ScopeIndex(item.AsScope())); + break; + } + case SmooshGCThing::Tag::RegExp: { + new (raw) TaggedScriptThingIndex(RegExpIndex(item.AsRegExp())); + break; + } + } + } + + return true; +} + +// Given the result of SmooshMonkey's parser, convert a specific script +// or function to a StencilScript, given a fixed set of source atoms. +// +// The StencilScript would then be in charge of handling the lifetime and +// (until GC things gets removed from stencil) tracing API of the GC. +bool ConvertScriptStencil(JSContext* cx, FrontendContext* fc, + const SmooshResult& result, + const SmooshScriptStencil& smooshScript, + Vector& allAtoms, + CompilationState& compilationState, + ScriptIndex scriptIndex) { + using ImmutableFlags = js::ImmutableScriptFlagsEnum; + + const JS::ReadOnlyCompileOptions& options = compilationState.input.options; + + ScriptStencil& script = compilationState.scriptData[scriptIndex]; + ScriptStencilExtra& scriptExtra = compilationState.scriptExtra[scriptIndex]; + + scriptExtra.immutableFlags = + ImmutableScriptFlags(smooshScript.immutable_flags); + + // FIXME: The following flags should be set in jsparagus. + scriptExtra.immutableFlags.setFlag(ImmutableFlags::SelfHosted, + options.selfHostingMode); + scriptExtra.immutableFlags.setFlag(ImmutableFlags::ForceStrict, + options.forceStrictMode()); + scriptExtra.immutableFlags.setFlag(ImmutableFlags::HasNonSyntacticScope, + options.nonSyntacticScope); + + if (&smooshScript == &result.scripts.data[0]) { + scriptExtra.immutableFlags.setFlag(ImmutableFlags::TreatAsRunOnce, + options.isRunOnce); + scriptExtra.immutableFlags.setFlag(ImmutableFlags::NoScriptRval, + options.noScriptRval); + } + + bool isFunction = + scriptExtra.immutableFlags.hasFlag(ImmutableFlags::IsFunction); + + if (smooshScript.immutable_script_data.IsSome()) { + auto index = smooshScript.immutable_script_data.AsSome(); + auto immutableScriptData = ConvertImmutableScriptData( + cx, result.script_data_list.data[index], isFunction); + if (!immutableScriptData) { + return false; + } + + auto sharedData = SharedImmutableScriptData::createWith( + fc, std::move(immutableScriptData)); + if (!sharedData) { + return false; + } + + if (!compilationState.sharedData.addAndShare(fc, scriptIndex, sharedData)) { + return false; + } + + script.setHasSharedData(); + } + + scriptExtra.extent.sourceStart = smooshScript.extent.source_start; + scriptExtra.extent.sourceEnd = smooshScript.extent.source_end; + scriptExtra.extent.toStringStart = smooshScript.extent.to_string_start; + scriptExtra.extent.toStringEnd = smooshScript.extent.to_string_end; + scriptExtra.extent.lineno = smooshScript.extent.lineno; + scriptExtra.extent.column = smooshScript.extent.column; + + if (isFunction) { + if (smooshScript.fun_name.IsSome()) { + script.functionAtom = allAtoms[smooshScript.fun_name.AsSome()]; + } + script.functionFlags = FunctionFlags(smooshScript.fun_flags); + scriptExtra.nargs = smooshScript.fun_nargs; + if (smooshScript.lazy_function_enclosing_scope_index.IsSome()) { + script.setLazyFunctionEnclosingScopeIndex(ScopeIndex( + smooshScript.lazy_function_enclosing_scope_index.AsSome())); + } + if (smooshScript.was_function_emitted) { + script.setWasEmittedByEnclosingScript(); + } + } + + if (!ConvertGCThings(cx, fc, result, smooshScript, compilationState, allAtoms, + scriptIndex)) { + return false; + } + + return true; +} + +// Free given SmooshResult on leaving scope. +class AutoFreeSmooshResult { + SmooshResult* result_; + + public: + AutoFreeSmooshResult() = delete; + + explicit AutoFreeSmooshResult(SmooshResult* result) : result_(result) {} + ~AutoFreeSmooshResult() { + if (result_) { + smoosh_free(*result_); + } + } +}; + +// Free given SmooshParseResult on leaving scope. +class AutoFreeSmooshParseResult { + SmooshParseResult* result_; + + public: + AutoFreeSmooshParseResult() = delete; + + explicit AutoFreeSmooshParseResult(SmooshParseResult* result) + : result_(result) {} + ~AutoFreeSmooshParseResult() { + if (result_) { + smoosh_free_parse_result(*result_); + } + } +}; + +void InitSmoosh() { smoosh_init(); } + +void ReportSmooshCompileError(JSContext* cx, FrontendContext* fc, + ErrorMetadata&& metadata, int errorNumber, ...) { + va_list args; + va_start(args, errorNumber); + ReportCompileErrorUTF8(fc, std::move(metadata), /* notes = */ nullptr, + errorNumber, &args); + va_end(args); +} + +/* static */ +bool Smoosh::tryCompileGlobalScriptToExtensibleStencil( + JSContext* cx, FrontendContext* fc, CompilationInput& input, + JS::SourceText& srcBuf, + UniquePtr& stencilOut) { + // FIXME: check info members and return with *unimplemented = true + // if any field doesn't match to smoosh_run. + + auto bytes = reinterpret_cast(srcBuf.get()); + size_t length = srcBuf.length(); + + SmooshCompileOptions compileOptions; + compileOptions.no_script_rval = input.options.noScriptRval; + + SmooshResult result = smoosh_run(bytes, length, &compileOptions); + AutoFreeSmooshResult afsr(&result); + + if (result.error.data) { + ErrorMetadata metadata; + metadata.filename = ""; + metadata.lineNumber = 1; + metadata.columnNumber = 0; + metadata.isMuted = false; + ReportSmooshCompileError(cx, fc, std::move(metadata), + JSMSG_SMOOSH_COMPILE_ERROR, + reinterpret_cast(result.error.data)); + return false; + } + + if (result.unimplemented) { + MOZ_ASSERT(!stencilOut); + return true; + } + + if (!input.initForGlobal(fc)) { + return false; + } + + LifoAllocScope parserAllocScope(&cx->tempLifoAlloc()); + + Vector allAtoms(fc); + CompilationState compilationState(fc, parserAllocScope, input); + if (!ConvertAtoms(cx, fc, result, compilationState, allAtoms)) { + return false; + } + + if (!ConvertScopeStencil(cx, fc, result, allAtoms, compilationState)) { + return false; + } + + if (!ConvertRegExpData(cx, fc, result, compilationState)) { + return false; + } + + auto len = result.scripts.len; + if (len == 0) { + // FIXME: What does it mean to have no scripts? + MOZ_ASSERT(!stencilOut); + return true; + } + + if (len > TaggedScriptThingIndex::IndexLimit) { + ReportAllocationOverflow(fc); + return false; + } + + if (!compilationState.scriptData.resize(len)) { + js::ReportOutOfMemory(fc); + return false; + } + + if (!compilationState.scriptExtra.resize(len)) { + js::ReportOutOfMemory(fc); + return false; + } + + // NOTE: Currently we don't support delazification or standalone function. + // Once we support, fix the following loop to include 0-th item + // and check if it's function. + MOZ_ASSERT_IF(result.scripts.len > 0, result.scripts.data[0].fun_flags == 0); + for (size_t i = 1; i < result.scripts.len; i++) { + auto& script = result.scripts.data[i]; + if (script.immutable_script_data.IsSome()) { + compilationState.nonLazyFunctionCount++; + } + } + + if (!compilationState.prepareSharedDataStorage(fc)) { + return false; + } + + for (size_t i = 0; i < len; i++) { + if (!ConvertScriptStencil(cx, fc, result, result.scripts.data[i], allAtoms, + compilationState, ScriptIndex(i))) { + return false; + } + } + + auto stencil = + fc->getAllocator()->make_unique( + std::move(compilationState)); + if (!stencil) { + return false; + } + + stencilOut = std::move(stencil); + return true; +} + +bool SmooshParseScript(JSContext* cx, const uint8_t* bytes, size_t length) { + SmooshParseResult result = smoosh_test_parse_script(bytes, length); + AutoFreeSmooshParseResult afspr(&result); + if (result.error.data) { + JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, + result.unimplemented ? JSMSG_SMOOSH_UNIMPLEMENTED + : JSMSG_SMOOSH_COMPILE_ERROR, + reinterpret_cast(result.error.data)); + return false; + } + + return true; +} + +bool SmooshParseModule(JSContext* cx, const uint8_t* bytes, size_t length) { + SmooshParseResult result = smoosh_test_parse_module(bytes, length); + AutoFreeSmooshParseResult afspr(&result); + if (result.error.data) { + JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, + result.unimplemented ? JSMSG_SMOOSH_UNIMPLEMENTED + : JSMSG_SMOOSH_COMPILE_ERROR, + reinterpret_cast(result.error.data)); + return false; + } + + return true; +} + +} // namespace frontend + +} // namespace js diff --git a/js/src/frontend/Frontend2.h b/js/src/frontend/Frontend2.h new file mode 100644 index 0000000000..6d14de999c --- /dev/null +++ b/js/src/frontend/Frontend2.h @@ -0,0 +1,61 @@ +/* -*- 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/. */ + +#ifndef frontend_Frontend2_h +#define frontend_Frontend2_h + +#include "mozilla/Utf8.h" // mozilla::Utf8Unit + +#include // size_t +#include // uint8_t + +#include "js/CompileOptions.h" // JS::ReadOnlyCompileOptions +#include "js/RootingAPI.h" // JS::Handle +#include "js/SourceText.h" // JS::SourceText +#include "js/UniquePtr.h" // js::UniquePtr +#include "vm/JSScript.h" // JSScript + +struct JSContext; + +struct SmooshResult; + +namespace js { + +class ScriptSourceObject; +class FrontendContext; + +namespace frontend { + +struct CompilationInput; +struct ExtensibleCompilationStencil; +struct CompilationGCOutput; +struct CompilationState; + +// This is declarated as a class mostly to solve dependency around `friend` +// declarations in the simple way. +class Smoosh { + public: + [[nodiscard]] static bool tryCompileGlobalScriptToExtensibleStencil( + JSContext* cx, FrontendContext* fc, CompilationInput& input, + JS::SourceText& srcBuf, + UniquePtr& stencilOut); +}; + +// Initialize SmooshMonkey globals, such as the logging system. +void InitSmoosh(); + +// Use the SmooshMonkey frontend to parse and free the generated AST. Returns +// true if no error were detected while parsing. +[[nodiscard]] bool SmooshParseScript(JSContext* cx, const uint8_t* bytes, + size_t length); +[[nodiscard]] bool SmooshParseModule(JSContext* cx, const uint8_t* bytes, + size_t length); + +} // namespace frontend + +} // namespace js + +#endif /* frontend_Frontend2_h */ diff --git a/js/src/frontend/FrontendContext.cpp b/js/src/frontend/FrontendContext.cpp new file mode 100644 index 0000000000..c9f1bf75b4 --- /dev/null +++ b/js/src/frontend/FrontendContext.cpp @@ -0,0 +1,237 @@ +/* -*- 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/FrontendContext.h" + +#include "gc/GC.h" +#include "js/AllocPolicy.h" // js::ReportOutOfMemory +#include "js/friend/StackLimits.h" // js::ReportOverRecursed +#include "js/Modules.h" +#include "util/DifferentialTesting.h" +#include "util/NativeStack.h" // GetNativeStackBase +#include "vm/JSContext.h" + +using namespace js; + +void FrontendAllocator::reportAllocationOverflow() { + fc_->onAllocationOverflow(); +} + +void* FrontendAllocator::onOutOfMemory(AllocFunction allocFunc, + arena_id_t arena, size_t nbytes, + void* reallocPtr) { + return fc_->onOutOfMemory(allocFunc, arena, nbytes, reallocPtr); +} + +FrontendContext::~FrontendContext() { + if (ownNameCollectionPool_) { + MOZ_ASSERT(nameCollectionPool_); + js_delete(nameCollectionPool_); + } +} + +bool FrontendContext::setSupportedImportAssertions( + const JS::ImportAssertionVector& supportedImportAssertions) { + MOZ_ASSERT(supportedImportAssertions_.empty()); + if (!supportedImportAssertions_.appendAll(supportedImportAssertions)) { + ReportOutOfMemory(); + return false; + } + return true; +} + +void FrontendContext::setStackQuota(JS::NativeStackSize stackSize) { +#ifdef __wasi__ + stackLimit_ = JS::WASINativeStackLimit; +#else // __wasi__ + if (stackSize == 0) { + stackLimit_ = JS::NativeStackLimitMax; + } else { + stackLimit_ = JS::GetNativeStackLimit(GetNativeStackBase(), stackSize - 1); + } +#endif // !__wasi__ +} + +bool FrontendContext::allocateOwnedPool() { + MOZ_ASSERT(!nameCollectionPool_); + + nameCollectionPool_ = js_new(); + if (!nameCollectionPool_) { + return false; + } + ownNameCollectionPool_ = true; + return true; +} + +bool FrontendContext::hadErrors() const { + if (maybeCx_) { + if (maybeCx_->isExceptionPending()) { + return true; + } + } + + return errors_.hadErrors(); +} + +void* FrontendContext::onOutOfMemory(AllocFunction allocFunc, arena_id_t arena, + size_t nbytes, void* reallocPtr) { + addPendingOutOfMemory(); + return nullptr; +} + +void FrontendContext::onAllocationOverflow() { + errors_.allocationOverflow = true; +} + +void FrontendContext::onOutOfMemory() { addPendingOutOfMemory(); } + +void FrontendContext::onOverRecursed() { errors_.overRecursed = true; } + +void FrontendContext::recoverFromOutOfMemory() { + // TODO: Remove this branch once error report directly against JSContext is + // removed from the frontend code. + if (maybeCx_) { + maybeCx_->recoverFromOutOfMemory(); + } + + errors_.outOfMemory = false; +} + +const JSErrorFormatString* FrontendContext::gcSafeCallback( + JSErrorCallback callback, void* userRef, const unsigned errorNumber) { + mozilla::Maybe suppressGC; + if (maybeCx_) { + suppressGC.emplace(maybeCx_); + } + + return callback(userRef, errorNumber); +} + +void FrontendContext::reportError(CompileError&& err) { + if (errors_.error) { + errors_.error.reset(); + } + + // When compiling off thread, save the error so that the thread finishing the + // parse can report it later. + errors_.error.emplace(std::move(err)); +} + +bool FrontendContext::reportWarning(CompileError&& err) { + if (!errors_.warnings.append(std::move(err))) { + ReportOutOfMemory(); + return false; + } + + return true; +} + +void FrontendContext::ReportOutOfMemory() { + /* + * OOMs are non-deterministic, especially across different execution modes + * (e.g. interpreter vs JIT). When doing differential testing, print to + * stderr so that the fuzzers can detect this. + */ + if (SupportDifferentialTesting()) { + fprintf(stderr, "ReportOutOfMemory called\n"); + } + + addPendingOutOfMemory(); +} + +void FrontendContext::addPendingOutOfMemory() { errors_.outOfMemory = true; } + +void FrontendContext::setCurrentJSContext(JSContext* cx) { + MOZ_ASSERT(!nameCollectionPool_); + + maybeCx_ = cx; + nameCollectionPool_ = &cx->frontendCollectionPool(); + scriptDataTableHolder_ = &cx->runtime()->scriptDataTableHolder(); + stackLimit_ = cx->stackLimitForCurrentPrincipal(); +} + +bool FrontendContext::convertToRuntimeError( + JSContext* cx, Warning warning /* = Warning::Report */) { + // Report out of memory errors eagerly, or errors could be malformed. + if (hadOutOfMemory()) { + js::ReportOutOfMemory(cx); + return false; + } + + if (maybeError()) { + if (!maybeError()->throwError(cx)) { + return false; + } + } + if (warning == Warning::Report) { + for (CompileError& error : warnings()) { + if (!error.throwError(cx)) { + return false; + } + } + } + if (hadOverRecursed()) { + js::ReportOverRecursed(cx); + } + if (hadAllocationOverflow()) { + js::ReportAllocationOverflow(cx); + } + return true; +} + +void FrontendContext::linkWithJSContext(JSContext* cx) { + if (cx) { + cx->setFrontendErrors(&errors_); + } +} + +#ifdef __wasi__ +void FrontendContext::incWasiRecursionDepth() { + if (maybeCx_) { + IncWasiRecursionDepth(maybeCx_); + } +} + +void FrontendContext::decWasiRecursionDepth() { + if (maybeCx_) { + DecWasiRecursionDepth(maybeCx_); + } +} + +bool FrontendContext::checkWasiRecursionLimit() { + if (maybeCx_) { + return CheckWasiRecursionLimit(maybeCx_); + } + return true; +} + +JS_PUBLIC_API void js::IncWasiRecursionDepth(FrontendContext* fc) { + fc->incWasiRecursionDepth(); +} + +JS_PUBLIC_API void js::DecWasiRecursionDepth(FrontendContext* fc) { + fc->decWasiRecursionDepth(); +} + +JS_PUBLIC_API bool js::CheckWasiRecursionLimit(FrontendContext* fc) { + return fc->checkWasiRecursionLimit(); +} +#endif // __wasi__ + +FrontendContext* js::NewFrontendContext() { + UniquePtr fc = MakeUnique(); + if (!fc) { + return nullptr; + } + + if (!fc->allocateOwnedPool()) { + return nullptr; + } + + return fc.release(); +} + +void js::DestroyFrontendContext(FrontendContext* fc) { js_delete_poison(fc); } diff --git a/js/src/frontend/FrontendContext.h b/js/src/frontend/FrontendContext.h new file mode 100644 index 0000000000..72c9e15154 --- /dev/null +++ b/js/src/frontend/FrontendContext.h @@ -0,0 +1,260 @@ +/* -*- 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/. */ + +#ifndef frontend_FrontendContext_h +#define frontend_FrontendContext_h + +#include "mozilla/Assertions.h" // MOZ_ASSERT +#include "mozilla/Attributes.h" // MOZ_STACK_CLASS +#include "mozilla/Maybe.h" // mozilla::Maybe + +#include // size_t + +#include "js/AllocPolicy.h" // SystemAllocPolicy, AllocFunction +#include "js/ErrorReport.h" // JSErrorCallback, JSErrorFormatString +#include "js/Modules.h" // JS::ImportAssertionVector +#include "js/Stack.h" // JS::NativeStackSize, JS::NativeStackLimit, JS::NativeStackLimitMax +#include "js/Vector.h" // Vector +#include "vm/ErrorReporting.h" // CompileError +#include "vm/MallocProvider.h" // MallocProvider +#include "vm/SharedScriptDataTableHolder.h" // js::SharedScriptDataTableHolder, js::globalSharedScriptDataTableHolder + +struct JSContext; + +namespace js { + +class FrontendContext; + +namespace frontend { +class NameCollectionPool; +} // namespace frontend + +struct FrontendErrors { + FrontendErrors() = default; + // Any errors or warnings produced during compilation. These are reported + // when finishing the script. + mozilla::Maybe error; + Vector warnings; + bool overRecursed = false; + bool outOfMemory = false; + bool allocationOverflow = false; + + bool hadErrors() const { + return outOfMemory || overRecursed || allocationOverflow || error; + } +}; + +class FrontendAllocator : public MallocProvider { + private: + FrontendContext* const fc_; + + public: + explicit FrontendAllocator(FrontendContext* fc) : fc_(fc) {} + + void* onOutOfMemory(js::AllocFunction allocFunc, arena_id_t arena, + size_t nbytes, void* reallocPtr = nullptr); + void reportAllocationOverflow(); +}; + +class FrontendContext { + private: + FrontendAllocator alloc_; + js::FrontendErrors errors_; + + // NameCollectionPool can be either: + // * owned by this FrontendContext, or + // * borrowed from JSContext + frontend::NameCollectionPool* nameCollectionPool_; + bool ownNameCollectionPool_; + + js::SharedScriptDataTableHolder* scriptDataTableHolder_; + + JS::ImportAssertionVector supportedImportAssertions_; + + JS::NativeStackLimit stackLimit_ = JS::NativeStackLimitMax; + + protected: + // (optional) Current JSContext to support main-thread-specific + // handling for error reporting, GC, and memory allocation. + // + // Set by setCurrentJSContext. + JSContext* maybeCx_ = nullptr; + + public: + FrontendContext() + : alloc_(this), + nameCollectionPool_(nullptr), + ownNameCollectionPool_(false), + scriptDataTableHolder_(&js::globalSharedScriptDataTableHolder), + supportedImportAssertions_() {} + ~FrontendContext(); + + void setStackQuota(JS::NativeStackSize stackSize); + JS::NativeStackLimit stackLimit() const { return stackLimit_; } + + bool allocateOwnedPool(); + + frontend::NameCollectionPool& nameCollectionPool() { + MOZ_ASSERT( + nameCollectionPool_, + "Either allocateOwnedPool or setCurrentJSContext must be called"); + return *nameCollectionPool_; + } + + js::SharedScriptDataTableHolder* scriptDataTableHolder() { + MOZ_ASSERT(scriptDataTableHolder_); + return scriptDataTableHolder_; + } + + FrontendAllocator* getAllocator() { return &alloc_; } + + // Use the given JSContext's for: + // * js::frontend::NameCollectionPool for reusing allocation + // * js::SharedScriptDataTableHolder for de-duplicating bytecode + // within given runtime + // * Copy the native stack limit from the JSContext + // + // And also this JSContext can be retrieved by maybeCurrentJSContext below. + void setCurrentJSContext(JSContext* cx); + + // Returns JSContext if any. + // + // This can be used only for: + // * Main-thread-specific operation, such as operating on JSAtom + // * Optional operation, such as providing better error message + JSContext* maybeCurrentJSContext() { return maybeCx_; } + + const JS::ImportAssertionVector& getSupportedImportAssertions() const { + return supportedImportAssertions_; + } + bool setSupportedImportAssertions( + const JS::ImportAssertionVector& supportedImportAssertions); + + enum class Warning { Suppress, Report }; + + // Returns false if the error cannot be converted (such as due to OOM). An + // error might still be reported to the given JSContext. Returns true + // otherwise. + bool convertToRuntimeError(JSContext* cx, Warning warning = Warning::Report); + + void linkWithJSContext(JSContext* cx); + + mozilla::Maybe& maybeError() { return errors_.error; } + Vector& warnings() { + return errors_.warnings; + } + + // Report CompileErrors + void reportError(js::CompileError&& err); + bool reportWarning(js::CompileError&& err); + + // Report FrontendAllocator errors + void* onOutOfMemory(js::AllocFunction allocFunc, arena_id_t arena, + size_t nbytes, void* reallocPtr = nullptr); + void onAllocationOverflow(); + + void onOutOfMemory(); + void onOverRecursed(); + + void recoverFromOutOfMemory(); + + const JSErrorFormatString* gcSafeCallback(JSErrorCallback callback, + void* userRef, + const unsigned errorNumber); + + // Status of errors reported to this FrontendContext + bool hadOutOfMemory() const { return errors_.outOfMemory; } + bool hadOverRecursed() const { return errors_.overRecursed; } + bool hadAllocationOverflow() const { return errors_.allocationOverflow; } + bool hadErrors() const; + +#ifdef __wasi__ + void incWasiRecursionDepth(); + void decWasiRecursionDepth(); + bool checkWasiRecursionLimit(); +#endif // __wasi__ + + private: + void ReportOutOfMemory(); + void addPendingOutOfMemory(); +}; + +// Automatically report any pending exception when leaving the scope. +class MOZ_STACK_CLASS AutoReportFrontendContext : public FrontendContext { + // The target JSContext to report the errors to. + JSContext* cx_; + + Warning warning_; + + public: + explicit AutoReportFrontendContext(JSContext* cx, + Warning warning = Warning::Report) + : FrontendContext(), cx_(cx), warning_(warning) { + setCurrentJSContext(cx_); + MOZ_ASSERT(cx_ == maybeCx_); + } + + ~AutoReportFrontendContext() { + if (cx_) { + convertToRuntimeErrorAndClear(); + } + } + + void clearAutoReport() { cx_ = nullptr; } + + bool convertToRuntimeErrorAndClear() { + bool result = convertToRuntimeError(cx_, warning_); + cx_ = nullptr; + return result; + } +}; + +/* + * Explicitly report any pending exception before leaving the scope. + * + * Before an instance of this class leaves the scope, you must call either + * failure() (if there are exceptions to report) or ok() (if there are no + * exceptions to report). + */ +class ManualReportFrontendContext : public FrontendContext { + JSContext* cx_; +#ifdef DEBUG + bool handled_ = false; +#endif + + public: + explicit ManualReportFrontendContext(JSContext* cx) + : FrontendContext(), cx_(cx) { + setCurrentJSContext(cx_); + } + + ~ManualReportFrontendContext() { MOZ_ASSERT(handled_); } + + void ok() { +#ifdef DEBUG + handled_ = true; +#endif + } + + void failure() { +#ifdef DEBUG + handled_ = true; +#endif + convertToRuntimeError(cx_); + } +}; + +// Create function for FrontendContext, which is manually allocated and +// exclusively owned. +extern FrontendContext* NewFrontendContext(); + +// Destroy function for FrontendContext, which was allocated with +// NewFrontendContext. +extern void DestroyFrontendContext(FrontendContext* fc); + +} // namespace js + +#endif /* frontend_FrontendContext_h */ diff --git a/js/src/frontend/FullParseHandler.h b/js/src/frontend/FullParseHandler.h new file mode 100644 index 0000000000..d5dc32026b --- /dev/null +++ b/js/src/frontend/FullParseHandler.h @@ -0,0 +1,1195 @@ +/* -*- 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/. */ + +#ifndef frontend_FullParseHandler_h +#define frontend_FullParseHandler_h + +#include "mozilla/Maybe.h" // mozilla::Maybe + +#include // std::nullptr_t +#include + +#include "jstypes.h" + +#include "frontend/CompilationStencil.h" // CompilationState +#include "frontend/FunctionSyntaxKind.h" // FunctionSyntaxKind +#include "frontend/NameAnalysisTypes.h" // PrivateNameKind +#include "frontend/ParseNode.h" +#include "frontend/ParserAtom.h" // TaggedParserAtomIndex +#include "frontend/SharedContext.h" +#include "frontend/Stencil.h" + +namespace js { +namespace frontend { + +class TokenStreamAnyChars; + +// 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) { + return static_cast(allocator.allocNode(size)); + } + + // If this is a full parse to construct the bytecode for a function that + // was previously lazily parsed, we still don't want to full parse the + // inner functions. These members are used for this functionality: + // + // - reuseGCThings if ture it means that the following fields are valid. + // - gcThingsData holds an incomplete stencil-like copy of inner functions as + // well as atoms. + // - scriptData and scriptExtra_ hold information necessary to locate inner + // functions to skip over each. + // - lazyInnerFunctionIndex is used as we skip over inner functions + // (see skipLazyInnerFunction), + // - lazyClosedOverBindingIndex is used to synchronize binding computation + // with the scope traversal. + // (see propagateFreeNamesAndMarkClosedOverBindings), + const CompilationSyntaxParseCache& previousParseCache_; + + size_t lazyInnerFunctionIndex; + size_t lazyClosedOverBindingIndex; + + bool reuseGCThings; + + public: + /* new_ methods for creating parse nodes. These report OOM on context. */ + JS_DECLARE_NEW_METHODS(new_, allocParseNode, inline) + + // FIXME: Use ListNode instead of ListNodeType as an alias (bug 1489008). + using Node = ParseNode*; + +#define DECLARE_TYPE(typeName, longTypeName, asMethodName) \ + using longTypeName = typeName*; + FOR_EACH_PARSENODE_SUBCLASS(DECLARE_TYPE) +#undef DECLARE_TYPE + + using NullNode = std::nullptr_t; + + bool isPropertyOrPrivateMemberAccess(Node node) { + return node->isKind(ParseNodeKind::DotExpr) || + node->isKind(ParseNodeKind::ElemExpr) || + node->isKind(ParseNodeKind::PrivateMemberExpr); + } + + bool isOptionalPropertyOrPrivateMemberAccess(Node node) { + return node->isKind(ParseNodeKind::OptionalDotExpr) || + node->isKind(ParseNodeKind::OptionalElemExpr) || + node->isKind(ParseNodeKind::PrivateMemberExpr); + } + + bool isFunctionCall(Node node) { + // Note: super() is a special form, *not* a function call. + return node->isKind(ParseNodeKind::CallExpr); + } + + static bool isUnparenthesizedDestructuringPattern(Node node) { + return !node->isInParens() && (node->isKind(ParseNodeKind::ObjectExpr) || + node->isKind(ParseNodeKind::ArrayExpr)); + } + + static bool isParenthesizedDestructuringPattern(Node 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(ParseNodeKind::ObjectExpr) || + node->isKind(ParseNodeKind::ArrayExpr)); + } + + FullParseHandler(FrontendContext* fc, CompilationState& compilationState) + : allocator(fc, compilationState.parserAllocScope.alloc()), + previousParseCache_(compilationState.previousParseCache), + lazyInnerFunctionIndex(0), + lazyClosedOverBindingIndex(0), + reuseGCThings(compilationState.input.isDelazifying()) {} + + static NullNode null() { return NullNode(); } + +#define DECLARE_AS(typeName, longTypeName, asMethodName) \ + static longTypeName asMethodName(Node node) { return &node->as(); } + FOR_EACH_PARSENODE_SUBCLASS(DECLARE_AS) +#undef DECLARE_AS + + NameNodeType newName(TaggedParserAtomIndex name, const TokenPos& pos) { + return new_(ParseNodeKind::Name, name, pos); + } + + UnaryNodeType newComputedName(Node expr, uint32_t begin, uint32_t end) { + TokenPos pos(begin, end); + return new_(ParseNodeKind::ComputedName, pos, expr); + } + + UnaryNodeType newSyntheticComputedName(Node expr, uint32_t begin, + uint32_t end) { + TokenPos pos(begin, end); + UnaryNode* node = new_(ParseNodeKind::ComputedName, pos, expr); + if (!node) { + return nullptr; + } + node->setSyntheticComputedName(); + return node; + } + + NameNodeType newObjectLiteralPropertyName(TaggedParserAtomIndex atom, + const TokenPos& pos) { + return new_(ParseNodeKind::ObjectPropertyName, atom, pos); + } + + NameNodeType newPrivateName(TaggedParserAtomIndex atom, const TokenPos& pos) { + return new_(ParseNodeKind::PrivateName, atom, pos); + } + + NumericLiteralType newNumber(double value, DecimalPoint decimalPoint, + const TokenPos& pos) { + return new_(value, decimalPoint, pos); + } + + BigIntLiteralType newBigInt(BigIntIndex index, bool isZero, + const TokenPos& pos) { + return new_(index, isZero, pos); + } + + BooleanLiteralType newBooleanLiteral(bool cond, const TokenPos& pos) { + return new_(cond, pos); + } + + NameNodeType newStringLiteral(TaggedParserAtomIndex atom, + const TokenPos& pos) { + return new_(ParseNodeKind::StringExpr, atom, pos); + } + + NameNodeType newTemplateStringLiteral(TaggedParserAtomIndex atom, + const TokenPos& pos) { + return new_(ParseNodeKind::TemplateStringExpr, atom, pos); + } + + CallSiteNodeType newCallSiteObject(uint32_t begin) { + CallSiteNode* callSiteObj = new_(begin); + if (!callSiteObj) { + return null(); + } + + ListNode* rawNodes = newArrayLiteral(callSiteObj->pn_pos.begin); + if (!rawNodes) { + return null(); + } + + addArrayElement(callSiteObj, rawNodes); + + return callSiteObj; + } + + void addToCallSiteObject(CallSiteNodeType callSiteObj, Node rawNode, + Node cookedNode) { + MOZ_ASSERT(callSiteObj->isKind(ParseNodeKind::CallSiteObj)); + MOZ_ASSERT(rawNode->isKind(ParseNodeKind::TemplateStringExpr)); + MOZ_ASSERT(cookedNode->isKind(ParseNodeKind::TemplateStringExpr) || + cookedNode->isKind(ParseNodeKind::RawUndefinedExpr)); + + addArrayElement(callSiteObj, cookedNode); + addArrayElement(callSiteObj->rawNodes(), rawNode); + + /* + * We don't know when the last noSubstTemplate will come in, and we + * don't want to deal with this outside this method + */ + setEndPosition(callSiteObj, callSiteObj->rawNodes()); + } + + ThisLiteralType newThisLiteral(const TokenPos& pos, Node thisName) { + return new_(pos, thisName); + } + + NullLiteralType newNullLiteral(const TokenPos& pos) { + return new_(pos); + } + + RawUndefinedLiteralType newRawUndefinedLiteral(const TokenPos& pos) { + return new_(pos); + } + + RegExpLiteralType newRegExp(RegExpIndex index, const TokenPos& pos) { + return new_(index, pos); + } + + ConditionalExpressionType newConditional(Node cond, Node thenExpr, + Node elseExpr) { + return new_(cond, thenExpr, elseExpr); + } + + UnaryNodeType newDelete(uint32_t begin, Node expr) { + if (expr->isKind(ParseNodeKind::Name)) { + return newUnary(ParseNodeKind::DeleteNameExpr, begin, expr); + } + + if (expr->isKind(ParseNodeKind::DotExpr)) { + return newUnary(ParseNodeKind::DeletePropExpr, begin, expr); + } + + if (expr->isKind(ParseNodeKind::ElemExpr)) { + return newUnary(ParseNodeKind::DeleteElemExpr, begin, expr); + } + + if (expr->isKind(ParseNodeKind::OptionalChain)) { + Node kid = expr->as().kid(); + // Handle property deletion explicitly. OptionalCall is handled + // via DeleteExpr. + if (kid->isKind(ParseNodeKind::DotExpr) || + kid->isKind(ParseNodeKind::OptionalDotExpr) || + kid->isKind(ParseNodeKind::ElemExpr) || + kid->isKind(ParseNodeKind::OptionalElemExpr)) { + return newUnary(ParseNodeKind::DeleteOptionalChainExpr, begin, kid); + } + } + + return newUnary(ParseNodeKind::DeleteExpr, begin, expr); + } + + UnaryNodeType newTypeof(uint32_t begin, Node kid) { + ParseNodeKind pnk = kid->isKind(ParseNodeKind::Name) + ? ParseNodeKind::TypeOfNameExpr + : ParseNodeKind::TypeOfExpr; + return newUnary(pnk, begin, kid); + } + + UnaryNodeType newUnary(ParseNodeKind kind, uint32_t begin, Node kid) { + TokenPos pos(begin, kid->pn_pos.end); + return new_(kind, pos, kid); + } + + UnaryNodeType newUpdate(ParseNodeKind kind, uint32_t begin, Node kid) { + TokenPos pos(begin, kid->pn_pos.end); + return new_(kind, pos, kid); + } + + UnaryNodeType newSpread(uint32_t begin, Node kid) { + TokenPos pos(begin, kid->pn_pos.end); + return new_(ParseNodeKind::Spread, pos, kid); + } + + private: + BinaryNodeType newBinary(ParseNodeKind kind, Node left, Node right) { + TokenPos pos(left->pn_pos.begin, right->pn_pos.end); + return new_(kind, pos, left, right); + } + + public: + Node appendOrCreateList(ParseNodeKind kind, Node left, Node right, + ParseContext* pc) { + return ParseNode::appendOrCreateList(kind, left, right, this, pc); + } + + // Expressions + + ListNodeType newArrayLiteral(uint32_t begin) { + return new_(ParseNodeKind::ArrayExpr, TokenPos(begin, begin + 1)); + } + + [[nodiscard]] bool addElision(ListNodeType literal, const TokenPos& pos) { + MOZ_ASSERT(literal->isKind(ParseNodeKind::ArrayExpr)); + + NullaryNode* elision = new_(ParseNodeKind::Elision, pos); + if (!elision) { + return false; + } + addList(/* list = */ literal, /* kid = */ elision); + literal->setHasNonConstInitializer(); + return true; + } + + [[nodiscard]] bool addSpreadElement(ListNodeType literal, uint32_t begin, + Node inner) { + MOZ_ASSERT( + literal->isKind(ParseNodeKind::ArrayExpr) || + IF_RECORD_TUPLE(literal->isKind(ParseNodeKind::TupleExpr), false)); + + UnaryNodeType spread = newSpread(begin, inner); + if (!spread) { + return false; + } + addList(/* list = */ literal, /* kid = */ spread); + literal->setHasNonConstInitializer(); + return true; + } + + void addArrayElement(ListNodeType literal, Node element) { + MOZ_ASSERT( + literal->isKind(ParseNodeKind::ArrayExpr) || + literal->isKind(ParseNodeKind::CallSiteObj) || + IF_RECORD_TUPLE(literal->isKind(ParseNodeKind::TupleExpr), false)); + if (!element->isConstant()) { + literal->setHasNonConstInitializer(); + } + addList(/* list = */ literal, /* kid = */ element); + } + + CallNodeType newCall(Node callee, Node args, JSOp callOp) { + return new_(ParseNodeKind::CallExpr, callOp, callee, args); + } + + OptionalCallNodeType newOptionalCall(Node callee, Node args, JSOp callOp) { + return new_(ParseNodeKind::OptionalCallExpr, callOp, callee, + args); + } + + ListNodeType newArguments(const TokenPos& pos) { + return new_(ParseNodeKind::Arguments, pos); + } + + CallNodeType newSuperCall(Node callee, Node args, bool isSpread) { + return new_(ParseNodeKind::SuperCallExpr, + isSpread ? JSOp::SpreadSuperCall : JSOp::SuperCall, + callee, args); + } + + CallNodeType newTaggedTemplate(Node tag, Node args, JSOp callOp) { + return new_(ParseNodeKind::TaggedTemplateExpr, callOp, tag, args); + } + + ListNodeType newObjectLiteral(uint32_t begin) { + return new_(ParseNodeKind::ObjectExpr, + TokenPos(begin, begin + 1)); + } + +#ifdef ENABLE_RECORD_TUPLE + ListNodeType newRecordLiteral(uint32_t begin) { + return new_(ParseNodeKind::RecordExpr, + TokenPos(begin, begin + 1)); + } + + ListNodeType newTupleLiteral(uint32_t begin) { + return new_(ParseNodeKind::TupleExpr, TokenPos(begin, begin + 1)); + } +#endif + + ClassNodeType newClass(Node name, Node heritage, + LexicalScopeNodeType memberBlock, +#ifdef ENABLE_DECORATORS + ListNodeType decorators, +#endif + const TokenPos& pos) { + return new_(name, heritage, memberBlock, +#ifdef ENABLE_DECORATORS + decorators, +#endif + pos); + } + ListNodeType newClassMemberList(uint32_t begin) { + return new_(ParseNodeKind::ClassMemberList, + TokenPos(begin, begin + 1)); + } + ClassNamesType newClassNames(Node outer, Node inner, const TokenPos& pos) { + return new_(outer, inner, pos); + } + NewTargetNodeType newNewTarget(NullaryNodeType newHolder, + NullaryNodeType targetHolder, + NameNodeType newTargetName) { + return new_(newHolder, targetHolder, newTargetName); + } + NullaryNodeType newPosHolder(const TokenPos& pos) { + return new_(ParseNodeKind::PosHolder, pos); + } + UnaryNodeType newSuperBase(Node thisName, const TokenPos& pos) { + return new_(ParseNodeKind::SuperBase, pos, thisName); + } + [[nodiscard]] bool addPrototypeMutation(ListNodeType literal, uint32_t begin, + Node expr) { + MOZ_ASSERT(literal->isKind(ParseNodeKind::ObjectExpr)); + + // Object literals with mutated [[Prototype]] are non-constant so that + // singleton objects will have Object.prototype as their [[Prototype]]. + literal->setHasNonConstInitializer(); + + UnaryNode* mutation = newUnary(ParseNodeKind::MutateProto, begin, expr); + if (!mutation) { + return false; + } + addList(/* list = */ literal, /* kid = */ mutation); + return true; + } + + BinaryNodeType newPropertyDefinition(Node key, Node val) { + MOZ_ASSERT(isUsableAsObjectPropertyName(key)); + checkAndSetIsDirectRHSAnonFunction(val); + return new_(key, val, AccessorType::None); + } + + void addPropertyDefinition(ListNodeType literal, BinaryNodeType propdef) { + MOZ_ASSERT( + literal->isKind(ParseNodeKind::ObjectExpr) || + IF_RECORD_TUPLE(literal->isKind(ParseNodeKind::RecordExpr), false)); + MOZ_ASSERT(propdef->isKind(ParseNodeKind::PropertyDefinition)); + + if (!propdef->right()->isConstant()) { + literal->setHasNonConstInitializer(); + } + + addList(/* list = */ literal, /* kid = */ propdef); + } + + [[nodiscard]] bool addPropertyDefinition(ListNodeType literal, Node key, + Node val) { + BinaryNode* propdef = newPropertyDefinition(key, val); + if (!propdef) { + return false; + } + addPropertyDefinition(literal, propdef); + return true; + } + + [[nodiscard]] bool addShorthand(ListNodeType literal, NameNodeType name, + NameNodeType expr) { + MOZ_ASSERT( + literal->isKind(ParseNodeKind::ObjectExpr) || + IF_RECORD_TUPLE(literal->isKind(ParseNodeKind::RecordExpr), false)); + MOZ_ASSERT(name->isKind(ParseNodeKind::ObjectPropertyName)); + MOZ_ASSERT(expr->isKind(ParseNodeKind::Name)); + MOZ_ASSERT(name->atom() == expr->atom()); + + literal->setHasNonConstInitializer(); + BinaryNode* propdef = newBinary(ParseNodeKind::Shorthand, name, expr); + if (!propdef) { + return false; + } + addList(/* list = */ literal, /* kid = */ propdef); + return true; + } + + [[nodiscard]] bool addSpreadProperty(ListNodeType literal, uint32_t begin, + Node inner) { + MOZ_ASSERT( + literal->isKind(ParseNodeKind::ObjectExpr) || + IF_RECORD_TUPLE(literal->isKind(ParseNodeKind::RecordExpr), false)); + + literal->setHasNonConstInitializer(); + ParseNode* spread = newSpread(begin, inner); + if (!spread) { + return false; + } + addList(/* list = */ literal, /* kid = */ spread); + return true; + } + + [[nodiscard]] bool addObjectMethodDefinition(ListNodeType literal, Node key, + FunctionNodeType funNode, + AccessorType atype) { + literal->setHasNonConstInitializer(); + + checkAndSetIsDirectRHSAnonFunction(funNode); + + ParseNode* propdef = + newObjectMethodOrPropertyDefinition(key, funNode, atype); + if (!propdef) { + return false; + } + + addList(/* list = */ literal, /* kid = */ propdef); + return true; + } + + [[nodiscard]] ClassMethod* newDefaultClassConstructor( + Node key, FunctionNodeType funNode) { + MOZ_ASSERT(isUsableAsObjectPropertyName(key)); + + checkAndSetIsDirectRHSAnonFunction(funNode); + + return new_( + ParseNodeKind::DefaultConstructor, key, funNode, AccessorType::None, + /* isStatic = */ false, /* initializeIfPrivate = */ nullptr +#ifdef ENABLE_DECORATORS + , + /* decorators = */ nullptr +#endif + ); + } + + [[nodiscard]] ClassMethod* newClassMethodDefinition( + Node key, FunctionNodeType funNode, AccessorType atype, bool isStatic, + mozilla::Maybe initializerIfPrivate +#ifdef ENABLE_DECORATORS + , + ListNodeType decorators +#endif + ) { + MOZ_ASSERT(isUsableAsObjectPropertyName(key)); + + checkAndSetIsDirectRHSAnonFunction(funNode); + + if (initializerIfPrivate.isSome()) { + return new_(ParseNodeKind::ClassMethod, key, funNode, atype, + isStatic, initializerIfPrivate.value() +#ifdef ENABLE_DECORATORS + , + decorators +#endif + ); + } + return new_(ParseNodeKind::ClassMethod, key, funNode, atype, + isStatic, /* initializeIfPrivate = */ nullptr +#ifdef ENABLE_DECORATORS + , + decorators +#endif + ); + } + + [[nodiscard]] ClassField* newClassFieldDefinition( + Node name, FunctionNodeType initializer, bool isStatic +#ifdef ENABLE_DECORATORS + , + ListNodeType decorators, bool hasAccessor +#endif + ) { + MOZ_ASSERT(isUsableAsObjectPropertyName(name)); + + return new_(name, initializer, isStatic +#if ENABLE_DECORATORS + , + decorators, hasAccessor +#endif + ); + } + + [[nodiscard]] StaticClassBlock* newStaticClassBlock(FunctionNodeType block) { + return new_(block); + } + + [[nodiscard]] bool addClassMemberDefinition(ListNodeType memberList, + Node member) { + MOZ_ASSERT(memberList->isKind(ParseNodeKind::ClassMemberList)); + // Constructors can be surrounded by LexicalScopes. + MOZ_ASSERT(member->isKind(ParseNodeKind::DefaultConstructor) || + member->isKind(ParseNodeKind::ClassMethod) || + member->isKind(ParseNodeKind::ClassField) || + member->isKind(ParseNodeKind::StaticClassBlock) || + (member->isKind(ParseNodeKind::LexicalScope) && + member->as().scopeBody()->is())); + + addList(/* list = */ memberList, /* kid = */ member); + return true; + } + + UnaryNodeType newInitialYieldExpression(uint32_t begin, Node gen) { + TokenPos pos(begin, begin + 1); + return new_(ParseNodeKind::InitialYield, pos, gen); + } + + UnaryNodeType newYieldExpression(uint32_t begin, Node value) { + TokenPos pos(begin, value ? value->pn_pos.end : begin + 1); + return new_(ParseNodeKind::YieldExpr, pos, value); + } + + UnaryNodeType newYieldStarExpression(uint32_t begin, Node value) { + TokenPos pos(begin, value->pn_pos.end); + return new_(ParseNodeKind::YieldStarExpr, pos, value); + } + + UnaryNodeType newAwaitExpression(uint32_t begin, Node value) { + TokenPos pos(begin, value ? value->pn_pos.end : begin + 1); + return new_(ParseNodeKind::AwaitExpr, pos, value); + } + + UnaryNodeType newOptionalChain(uint32_t begin, Node value) { + TokenPos pos(begin, value->pn_pos.end); + return new_(ParseNodeKind::OptionalChain, pos, value); + } + + // Statements + + ListNodeType newStatementList(const TokenPos& pos) { + return new_(ParseNodeKind::StatementList, pos); + } + + [[nodiscard]] bool isFunctionStmt(Node stmt) { + while (stmt->isKind(ParseNodeKind::LabelStmt)) { + stmt = stmt->as().statement(); + } + return stmt->is(); + } + + void addStatementToList(ListNodeType list, Node stmt) { + MOZ_ASSERT(list->isKind(ParseNodeKind::StatementList)); + + addList(/* list = */ list, /* kid = */ stmt); + + if (isFunctionStmt(stmt)) { + // Notify the emitter that the block contains body-level function + // definitions that should be processed before the rest of nodes. + list->setHasTopLevelFunctionDeclarations(); + } + } + + void setListEndPosition(ListNodeType list, const TokenPos& pos) { + MOZ_ASSERT(list->isKind(ParseNodeKind::StatementList)); + list->pn_pos.end = pos.end; + } + + void addCaseStatementToList(ListNodeType list, CaseClauseType caseClause) { + MOZ_ASSERT(list->isKind(ParseNodeKind::StatementList)); + + addList(/* list = */ list, /* kid = */ caseClause); + + if (caseClause->statementList()->hasTopLevelFunctionDeclarations()) { + list->setHasTopLevelFunctionDeclarations(); + } + } + + [[nodiscard]] bool prependInitialYield(ListNodeType stmtList, Node genName) { + MOZ_ASSERT(stmtList->isKind(ParseNodeKind::StatementList)); + + TokenPos yieldPos(stmtList->pn_pos.begin, stmtList->pn_pos.begin + 1); + NullaryNode* makeGen = + new_(ParseNodeKind::Generator, yieldPos); + if (!makeGen) { + return false; + } + + ParseNode* genInit = + newAssignment(ParseNodeKind::AssignExpr, /* lhs = */ genName, + /* rhs = */ makeGen); + if (!genInit) { + return false; + } + + UnaryNode* initialYield = + newInitialYieldExpression(yieldPos.begin, genInit); + if (!initialYield) { + return false; + } + + stmtList->prepend(initialYield); + return true; + } + + BinaryNodeType newSetThis(Node thisName, Node value) { + return newBinary(ParseNodeKind::SetThis, thisName, value); + } + + NullaryNodeType newEmptyStatement(const TokenPos& pos) { + return new_(ParseNodeKind::EmptyStmt, pos); + } + + BinaryNodeType newImportAssertion(Node keyNode, Node valueNode) { + return newBinary(ParseNodeKind::ImportAssertion, keyNode, valueNode); + } + + BinaryNodeType newModuleRequest(Node moduleSpec, Node importAssertionList, + const TokenPos& pos) { + return new_(ParseNodeKind::ImportModuleRequest, pos, moduleSpec, + importAssertionList); + } + + BinaryNodeType newImportDeclaration(Node importSpecSet, Node moduleRequest, + const TokenPos& pos) { + return new_(ParseNodeKind::ImportDecl, pos, importSpecSet, + moduleRequest); + } + + BinaryNodeType newImportSpec(Node importNameNode, Node bindingName) { + return newBinary(ParseNodeKind::ImportSpec, importNameNode, bindingName); + } + + UnaryNodeType newImportNamespaceSpec(uint32_t begin, Node bindingName) { + return newUnary(ParseNodeKind::ImportNamespaceSpec, begin, bindingName); + } + + UnaryNodeType newExportDeclaration(Node kid, const TokenPos& pos) { + return new_(ParseNodeKind::ExportStmt, pos, kid); + } + + BinaryNodeType newExportFromDeclaration(uint32_t begin, Node exportSpecSet, + Node moduleRequest) { + BinaryNode* decl = new_(ParseNodeKind::ExportFromStmt, + exportSpecSet, moduleRequest); + if (!decl) { + return nullptr; + } + decl->pn_pos.begin = begin; + return decl; + } + + BinaryNodeType newExportDefaultDeclaration(Node kid, Node maybeBinding, + const TokenPos& pos) { + if (maybeBinding) { + MOZ_ASSERT(maybeBinding->isKind(ParseNodeKind::Name)); + MOZ_ASSERT(!maybeBinding->isInParens()); + + checkAndSetIsDirectRHSAnonFunction(kid); + } + + return new_(ParseNodeKind::ExportDefaultStmt, pos, kid, + maybeBinding); + } + + BinaryNodeType newExportSpec(Node bindingName, Node exportName) { + return newBinary(ParseNodeKind::ExportSpec, bindingName, exportName); + } + + UnaryNodeType newExportNamespaceSpec(uint32_t begin, Node exportName) { + return newUnary(ParseNodeKind::ExportNamespaceSpec, begin, exportName); + } + + NullaryNodeType newExportBatchSpec(const TokenPos& pos) { + return new_(ParseNodeKind::ExportBatchSpecStmt, pos); + } + + BinaryNodeType newImportMeta(NullaryNodeType importHolder, + NullaryNodeType metaHolder) { + return new_(ParseNodeKind::ImportMetaExpr, importHolder, + metaHolder); + } + + BinaryNodeType newCallImport(NullaryNodeType importHolder, Node singleArg) { + return new_(ParseNodeKind::CallImportExpr, importHolder, + singleArg); + } + + BinaryNodeType newCallImportSpec(Node specifierArg, Node optionalArg) { + return new_(ParseNodeKind::CallImportSpec, specifierArg, + optionalArg); + } + + UnaryNodeType newExprStatement(Node expr, uint32_t end) { + MOZ_ASSERT(expr->pn_pos.end <= end); + return new_(ParseNodeKind::ExpressionStmt, + TokenPos(expr->pn_pos.begin, end), expr); + } + + TernaryNodeType newIfStatement(uint32_t begin, Node cond, Node thenBranch, + Node elseBranch) { + TernaryNode* node = + new_(ParseNodeKind::IfStmt, cond, thenBranch, elseBranch); + if (!node) { + return nullptr; + } + node->pn_pos.begin = begin; + return node; + } + + BinaryNodeType newDoWhileStatement(Node body, Node cond, + const TokenPos& pos) { + return new_(ParseNodeKind::DoWhileStmt, pos, body, cond); + } + + BinaryNodeType newWhileStatement(uint32_t begin, Node cond, Node body) { + TokenPos pos(begin, body->pn_pos.end); + return new_(ParseNodeKind::WhileStmt, pos, cond, body); + } + + ForNodeType newForStatement(uint32_t begin, TernaryNodeType forHead, + Node body, unsigned iflags) { + return new_(TokenPos(begin, body->pn_pos.end), forHead, body, + iflags); + } + + TernaryNodeType newForHead(Node init, Node test, Node update, + const TokenPos& pos) { + return new_(ParseNodeKind::ForHead, init, test, update, pos); + } + + TernaryNodeType newForInOrOfHead(ParseNodeKind kind, Node target, + Node iteratedExpr, const TokenPos& pos) { + MOZ_ASSERT(kind == ParseNodeKind::ForIn || kind == ParseNodeKind::ForOf); + return new_(kind, target, nullptr, iteratedExpr, pos); + } + + SwitchStatementType newSwitchStatement( + uint32_t begin, Node discriminant, + LexicalScopeNodeType lexicalForCaseList, bool hasDefault) { + return new_(begin, discriminant, lexicalForCaseList, + hasDefault); + } + + CaseClauseType newCaseOrDefault(uint32_t begin, Node expr, Node body) { + return new_(expr, body, begin); + } + + ContinueStatementType newContinueStatement(TaggedParserAtomIndex label, + const TokenPos& pos) { + return new_(label, pos); + } + + BreakStatementType newBreakStatement(TaggedParserAtomIndex label, + const TokenPos& pos) { + return new_(label, pos); + } + + UnaryNodeType newReturnStatement(Node expr, const TokenPos& pos) { + MOZ_ASSERT_IF(expr, pos.encloses(expr->pn_pos)); + return new_(ParseNodeKind::ReturnStmt, pos, expr); + } + + UnaryNodeType newExpressionBody(Node expr) { + return new_(ParseNodeKind::ReturnStmt, expr->pn_pos, expr); + } + + BinaryNodeType newWithStatement(uint32_t begin, Node expr, Node body) { + return new_(ParseNodeKind::WithStmt, + TokenPos(begin, body->pn_pos.end), expr, body); + } + + LabeledStatementType newLabeledStatement(TaggedParserAtomIndex label, + Node stmt, uint32_t begin) { + return new_(label, stmt, begin); + } + + UnaryNodeType newThrowStatement(Node expr, const TokenPos& pos) { + MOZ_ASSERT(pos.encloses(expr->pn_pos)); + return new_(ParseNodeKind::ThrowStmt, pos, expr); + } + + TernaryNodeType newTryStatement(uint32_t begin, Node body, + LexicalScopeNodeType catchScope, + Node finallyBlock) { + return new_(begin, body, catchScope, finallyBlock); + } + + DebuggerStatementType newDebuggerStatement(const TokenPos& pos) { + return new_(pos); + } + + NameNodeType newPropertyName(TaggedParserAtomIndex name, + const TokenPos& pos) { + return new_(ParseNodeKind::PropertyNameExpr, name, pos); + } + + PropertyAccessType newPropertyAccess(Node expr, NameNodeType key) { + return new_(expr, key, expr->pn_pos.begin, key->pn_pos.end); + } + + PropertyByValueType newPropertyByValue(Node lhs, Node index, uint32_t end) { + return new_(lhs, index, lhs->pn_pos.begin, end); + } + + OptionalPropertyAccessType newOptionalPropertyAccess(Node expr, + NameNodeType key) { + return new_(expr, key, expr->pn_pos.begin, + key->pn_pos.end); + } + + OptionalPropertyByValueType newOptionalPropertyByValue(Node lhs, Node index, + uint32_t end) { + return new_(lhs, index, lhs->pn_pos.begin, end); + } + + PrivateMemberAccessType newPrivateMemberAccess(Node lhs, + NameNodeType privateName, + uint32_t end) { + return new_(lhs, privateName, lhs->pn_pos.begin, end); + } + + OptionalPrivateMemberAccessType newOptionalPrivateMemberAccess( + Node lhs, NameNodeType privateName, uint32_t end) { + return new_(lhs, privateName, + lhs->pn_pos.begin, end); + } + + bool setupCatchScope(LexicalScopeNodeType lexicalScope, Node catchName, + Node catchBody) { + BinaryNode* catchClause; + if (catchName) { + catchClause = + new_(ParseNodeKind::Catch, catchName, catchBody); + } else { + catchClause = new_(ParseNodeKind::Catch, catchBody->pn_pos, + catchName, catchBody); + } + if (!catchClause) { + return false; + } + lexicalScope->setScopeBody(catchClause); + return true; + } + + [[nodiscard]] inline bool setLastFunctionFormalParameterDefault( + FunctionNodeType funNode, Node defaultValue); + + void checkAndSetIsDirectRHSAnonFunction(Node pn) { + if (IsAnonymousFunctionDefinition(pn)) { + pn->setDirectRHSAnonFunction(true); + } + } + + ParamsBodyNodeType newParamsBody(const TokenPos& pos) { + return new_(pos); + } + + FunctionNodeType newFunction(FunctionSyntaxKind syntaxKind, + const TokenPos& pos) { + return new_(syntaxKind, pos); + } + + BinaryNodeType newObjectMethodOrPropertyDefinition(Node key, Node value, + AccessorType atype) { + MOZ_ASSERT(isUsableAsObjectPropertyName(key)); + + return new_(key, value, atype); + } + + void setFunctionFormalParametersAndBody(FunctionNodeType funNode, + ParamsBodyNodeType paramsBody) { + funNode->setBody(paramsBody); + } + void setFunctionBox(FunctionNodeType funNode, FunctionBox* funbox) { + funNode->setFunbox(funbox); + funbox->functionNode = funNode; + } + void addFunctionFormalParameter(FunctionNodeType funNode, Node argpn) { + addList(/* list = */ funNode->body(), /* kid = */ argpn); + } + void setFunctionBody(FunctionNodeType funNode, LexicalScopeNodeType body) { + addList(/* list = */ funNode->body(), /* kid = */ body); + } + + ModuleNodeType newModule(const TokenPos& pos) { + return new_(pos); + } + + LexicalScopeNodeType newLexicalScope(LexicalScope::ParserData* bindings, + Node body, + ScopeKind kind = ScopeKind::Lexical) { + return new_(bindings, body, kind); + } + + ClassBodyScopeNodeType newClassBodyScope(ClassBodyScope::ParserData* bindings, + ListNodeType body) { + return new_(bindings, body); + } + + CallNodeType newNewExpression(uint32_t begin, Node ctor, Node args, + bool isSpread) { + return new_(ParseNodeKind::NewExpr, + isSpread ? JSOp::SpreadNew : JSOp::New, + TokenPos(begin, args->pn_pos.end), ctor, args); + } + + AssignmentNodeType newAssignment(ParseNodeKind kind, Node lhs, Node rhs) { + if ((kind == ParseNodeKind::AssignExpr || + kind == ParseNodeKind::CoalesceAssignExpr || + kind == ParseNodeKind::OrAssignExpr || + kind == ParseNodeKind::AndAssignExpr) && + lhs->isKind(ParseNodeKind::Name) && !lhs->isInParens()) { + checkAndSetIsDirectRHSAnonFunction(rhs); + } + + return new_(kind, lhs, rhs); + } + + BinaryNodeType newInitExpr(Node lhs, Node rhs) { + TokenPos pos(lhs->pn_pos.begin, rhs->pn_pos.end); + return new_(ParseNodeKind::InitExpr, pos, lhs, rhs); + } + + bool isUnparenthesizedAssignment(Node node) { + if ((node->isKind(ParseNodeKind::AssignExpr)) && !node->isInParens()) { + return true; + } + + return false; + } + + bool isUnparenthesizedUnaryExpression(Node node) { + if (!node->isInParens()) { + ParseNodeKind kind = node->getKind(); + return kind == ParseNodeKind::VoidExpr || + kind == ParseNodeKind::NotExpr || + kind == ParseNodeKind::BitNotExpr || + kind == ParseNodeKind::PosExpr || kind == ParseNodeKind::NegExpr || + kind == ParseNodeKind::AwaitExpr || IsTypeofKind(kind) || + IsDeleteKind(kind); + } + return false; + } + + bool isReturnStatement(Node node) { + return node->isKind(ParseNodeKind::ReturnStmt); + } + + bool isStatementPermittedAfterReturnStatement(Node node) { + ParseNodeKind kind = node->getKind(); + return kind == ParseNodeKind::Function || kind == ParseNodeKind::VarStmt || + kind == ParseNodeKind::BreakStmt || + kind == ParseNodeKind::ThrowStmt || kind == ParseNodeKind::EmptyStmt; + } + + bool isSuperBase(Node node) { return node->isKind(ParseNodeKind::SuperBase); } + + bool isUsableAsObjectPropertyName(Node node) { + return node->isKind(ParseNodeKind::NumberExpr) || + node->isKind(ParseNodeKind::BigIntExpr) || + node->isKind(ParseNodeKind::ObjectPropertyName) || + node->isKind(ParseNodeKind::StringExpr) || + node->isKind(ParseNodeKind::ComputedName) || + node->isKind(ParseNodeKind::PrivateName); + } + + AssignmentNodeType finishInitializerAssignment(NameNodeType nameNode, + Node init) { + MOZ_ASSERT(nameNode->isKind(ParseNodeKind::Name)); + MOZ_ASSERT(!nameNode->isInParens()); + + checkAndSetIsDirectRHSAnonFunction(init); + + return newAssignment(ParseNodeKind::AssignExpr, nameNode, init); + } + + void setBeginPosition(Node pn, Node oth) { + setBeginPosition(pn, oth->pn_pos.begin); + } + void setBeginPosition(Node pn, uint32_t begin) { + pn->pn_pos.begin = begin; + MOZ_ASSERT(pn->pn_pos.begin <= pn->pn_pos.end); + } + + void setEndPosition(Node pn, Node oth) { + setEndPosition(pn, oth->pn_pos.end); + } + void setEndPosition(Node pn, uint32_t end) { + pn->pn_pos.end = end; + MOZ_ASSERT(pn->pn_pos.begin <= pn->pn_pos.end); + } + + uint32_t getFunctionNameOffset(Node func, TokenStreamAnyChars& ts) { + return func->pn_pos.begin; + } + + ListNodeType newList(ParseNodeKind kind, const TokenPos& pos) { + auto* list = new_(kind, pos); + MOZ_ASSERT_IF(list, !list->is()); + MOZ_ASSERT_IF(list, !list->is()); + return list; + } + + ListNodeType newList(ParseNodeKind kind, Node kid) { + auto* list = new_(kind, kid); + MOZ_ASSERT_IF(list, !list->is()); + MOZ_ASSERT_IF(list, !list->is()); + return list; + } + + DeclarationListNodeType newDeclarationList(ParseNodeKind kind, + const TokenPos& pos) { + return new_(kind, pos); + } + + ListNodeType newCommaExpressionList(Node kid) { + return new_(ParseNodeKind::CommaExpr, kid); + } + + void addList(ListNodeType list, Node kid) { list->append(kid); } + + void setListHasNonConstInitializer(ListNodeType literal) { + literal->setHasNonConstInitializer(); + } + template + [[nodiscard]] NodeType parenthesize(NodeType node) { + node->setInParens(true); + return node; + } + template + [[nodiscard]] NodeType setLikelyIIFE(NodeType node) { + return parenthesize(node); + } + + bool isName(Node node) { return node->isKind(ParseNodeKind::Name); } + + bool isArgumentsName(Node node) { + return node->isKind(ParseNodeKind::Name) && + node->as().atom() == + TaggedParserAtomIndex::WellKnown::arguments(); + } + + bool isEvalName(Node node) { + return node->isKind(ParseNodeKind::Name) && + node->as().atom() == + TaggedParserAtomIndex::WellKnown::eval(); + } + + bool isAsyncKeyword(Node node) { + return node->isKind(ParseNodeKind::Name) && + node->pn_pos.begin + strlen("async") == node->pn_pos.end && + node->as().atom() == + TaggedParserAtomIndex::WellKnown::async(); + } + + bool isPrivateName(Node node) { + return node->isKind(ParseNodeKind::PrivateName); + } + + bool isPrivateMemberAccess(Node node) { + if (node->isKind(ParseNodeKind::OptionalChain)) { + return isPrivateMemberAccess(node->as().kid()); + } + return node->is(); + } + + TaggedParserAtomIndex maybeDottedProperty(Node pn) { + return pn->is() ? pn->as().name() + : TaggedParserAtomIndex::null(); + } + TaggedParserAtomIndex isStringExprStatement(Node pn, TokenPos* pos) { + if (pn->is()) { + UnaryNode* unary = &pn->as(); + if (auto atom = unary->isStringExprStatement()) { + *pos = unary->kid()->pn_pos; + return atom; + } + } + return TaggedParserAtomIndex::null(); + } + + bool reuseLazyInnerFunctions() { return reuseGCThings; } + bool reuseClosedOverBindings() { return reuseGCThings; } + bool reuseRegexpSyntaxParse() { return reuseGCThings; } + void nextLazyInnerFunction() { lazyInnerFunctionIndex++; } + TaggedParserAtomIndex nextLazyClosedOverBinding() { + // Trailing nullptrs were elided in PerHandlerParser::finishFunction(). + auto closedOverBindings = previousParseCache_.closedOverBindings(); + if (lazyClosedOverBindingIndex >= closedOverBindings.Length()) { + return TaggedParserAtomIndex::null(); + } + + return closedOverBindings[lazyClosedOverBindingIndex++]; + } + const ScriptStencil& cachedScriptData() const { + // lazyInnerFunctionIndex is incremented with nextLazyInnferFunction before + // reading the content, thus we need -1 to access the element that we just + // skipped. + return previousParseCache_.scriptData(lazyInnerFunctionIndex - 1); + } + const ScriptStencilExtra& cachedScriptExtra() const { + // lazyInnerFunctionIndex is incremented with nextLazyInnferFunction before + // reading the content, thus we need -1 to access the element that we just + // skipped. + return previousParseCache_.scriptExtra(lazyInnerFunctionIndex - 1); + } + + void setPrivateNameKind(Node node, PrivateNameKind kind) { + MOZ_ASSERT(node->is()); + node->as().setPrivateNameKind(kind); + } +}; + +inline bool FullParseHandler::setLastFunctionFormalParameterDefault( + FunctionNodeType funNode, Node defaultValue) { + ParamsBodyNode* body = funNode->body(); + ParseNode* arg = body->last(); + ParseNode* pn = newAssignment(ParseNodeKind::AssignExpr, arg, defaultValue); + if (!pn) { + return false; + } + + body->replaceLast(pn); + return true; +} + +} // namespace frontend +} // namespace js + +#endif /* frontend_FullParseHandler_h */ diff --git a/js/src/frontend/FunctionEmitter.cpp b/js/src/frontend/FunctionEmitter.cpp new file mode 100644 index 0000000000..fbff6cf1c5 --- /dev/null +++ b/js/src/frontend/FunctionEmitter.cpp @@ -0,0 +1,1017 @@ +/* -*- 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/FunctionEmitter.h" + +#include "mozilla/Assertions.h" // MOZ_ASSERT + +#include "frontend/AsyncEmitter.h" // AsyncEmitter +#include "frontend/BytecodeEmitter.h" // BytecodeEmitter +#include "frontend/FunctionSyntaxKind.h" // FunctionSyntaxKind +#include "frontend/ModuleSharedContext.h" // ModuleSharedContext +#include "frontend/NameAnalysisTypes.h" // NameLocation +#include "frontend/NameOpEmitter.h" // NameOpEmitter +#include "frontend/SharedContext.h" // SharedContext +#include "vm/ModuleBuilder.h" // ModuleBuilder +#include "vm/Opcodes.h" // JSOp +#include "vm/Scope.h" // BindingKind + +using namespace js; +using namespace js::frontend; + +using mozilla::Maybe; +using mozilla::Some; + +FunctionEmitter::FunctionEmitter(BytecodeEmitter* bce, FunctionBox* funbox, + FunctionSyntaxKind syntaxKind, + IsHoisted isHoisted) + : bce_(bce), + funbox_(funbox), + name_(funbox_->explicitName()), + syntaxKind_(syntaxKind), + isHoisted_(isHoisted) {} + +bool FunctionEmitter::prepareForNonLazy() { + MOZ_ASSERT(state_ == State::Start); + + MOZ_ASSERT(funbox_->isInterpreted()); + MOZ_ASSERT(funbox_->emitBytecode); + MOZ_ASSERT(!funbox_->wasEmittedByEnclosingScript()); + + // [stack] + + funbox_->setWasEmittedByEnclosingScript(true); + +#ifdef DEBUG + state_ = State::NonLazy; +#endif + return true; +} + +bool FunctionEmitter::emitNonLazyEnd() { + MOZ_ASSERT(state_ == State::NonLazy); + + // [stack] + + if (!emitFunction()) { + // [stack] FUN? + return false; + } + +#ifdef DEBUG + state_ = State::End; +#endif + return true; +} + +bool FunctionEmitter::emitLazy() { + MOZ_ASSERT(state_ == State::Start); + + MOZ_ASSERT(funbox_->isInterpreted()); + MOZ_ASSERT(!funbox_->emitBytecode); + MOZ_ASSERT(!funbox_->wasEmittedByEnclosingScript()); + + // [stack] + + funbox_->setWasEmittedByEnclosingScript(true); + + // Prepare to update the inner lazy script now that it's parent is fully + // compiled. These updates will be applied in UpdateEmittedInnerFunctions(). + funbox_->setEnclosingScopeForInnerLazyFunction(bce_->innermostScopeIndex()); + + if (!emitFunction()) { + // [stack] FUN? + return false; + } + +#ifdef DEBUG + state_ = State::End; +#endif + return true; +} + +bool FunctionEmitter::emitAgain() { + MOZ_ASSERT(state_ == State::Start); + MOZ_ASSERT(funbox_->wasEmittedByEnclosingScript()); + + // [stack] + + // Annex B block-scoped functions are hoisted like any other assignment + // that assigns the function to the outer 'var' binding. + if (!funbox_->isAnnexB) { +#ifdef DEBUG + state_ = State::End; +#endif + return true; + } + + // Get the location of the 'var' binding in the body scope. The + // name must be found, else there is a bug in the Annex B handling + // in Parser. + // + // In sloppy eval contexts, this location is dynamic. + Maybe lhsLoc = + bce_->locationOfNameBoundInScope(name_, bce_->varEmitterScope); + + // If there are parameter expressions, the var name could be a + // parameter. + if (!lhsLoc && bce_->sc->isFunctionBox() && + bce_->sc->asFunctionBox()->functionHasExtraBodyVarScope()) { + lhsLoc = bce_->locationOfNameBoundInScope( + name_, bce_->varEmitterScope->enclosingInFrame()); + } + + if (!lhsLoc) { + lhsLoc = Some(NameLocation::DynamicAnnexBVar()); + } else { + MOZ_ASSERT(lhsLoc->bindingKind() == BindingKind::Var || + lhsLoc->bindingKind() == BindingKind::FormalParameter || + (lhsLoc->bindingKind() == BindingKind::Let && + bce_->sc->asFunctionBox()->hasParameterExprs)); + } + + NameOpEmitter noe(bce_, name_, *lhsLoc, + NameOpEmitter::Kind::SimpleAssignment); + if (!noe.prepareForRhs()) { + // [stack] + return false; + } + + if (!bce_->emitGetName(name_)) { + // [stack] FUN + return false; + } + + if (!noe.emitAssignment()) { + // [stack] FUN + return false; + } + + if (!bce_->emit1(JSOp::Pop)) { + // [stack] + return false; + } + +#ifdef DEBUG + state_ = State::End; +#endif + return true; +} + +bool FunctionEmitter::emitAsmJSModule() { + MOZ_ASSERT(state_ == State::Start); + + MOZ_ASSERT(!funbox_->wasEmittedByEnclosingScript()); + MOZ_ASSERT(funbox_->isAsmJSModule()); + + // [stack] + + funbox_->setWasEmittedByEnclosingScript(true); + + if (!emitFunction()) { + // [stack] + return false; + } + +#ifdef DEBUG + state_ = State::End; +#endif + return true; +} + +bool FunctionEmitter::emitFunction() { + // Make the function object a literal in the outer script's pool. + GCThingIndex index; + if (!bce_->perScriptData().gcThingList().append(funbox_, &index)) { + return false; + } + + // [stack] + + if (isHoisted_ == IsHoisted::No) { + return emitNonHoisted(index); + // [stack] FUN? + } + + bool topLevelFunction; + if (bce_->sc->isFunctionBox() || + (bce_->sc->isEvalContext() && bce_->sc->strict())) { + // No nested functions inside other functions are top-level. + topLevelFunction = false; + } else { + // In sloppy eval scripts, top-level functions are accessed dynamically. + // In global and module scripts, top-level functions are those bound in + // the var scope. + NameLocation loc = bce_->lookupName(name_); + topLevelFunction = loc.kind() == NameLocation::Kind::Dynamic || + loc.bindingKind() == BindingKind::Var; + } + + if (topLevelFunction) { + return emitTopLevelFunction(index); + // [stack] + } + + return emitHoisted(index); + // [stack] +} + +bool FunctionEmitter::emitNonHoisted(GCThingIndex index) { + // Non-hoisted functions simply emit their respective op. + + // [stack] + + if (syntaxKind_ == FunctionSyntaxKind::DerivedClassConstructor) { + // [stack] PROTO + if (!bce_->emitGCIndexOp(JSOp::FunWithProto, index)) { + // [stack] FUN + return false; + } + return true; + } + + // This is a FunctionExpression, ArrowFunctionExpression, or class + // constructor. Emit the single instruction (without location info). + if (!bce_->emitGCIndexOp(JSOp::Lambda, index)) { + // [stack] FUN + return false; + } + + return true; +} + +bool FunctionEmitter::emitHoisted(GCThingIndex index) { + MOZ_ASSERT(syntaxKind_ == FunctionSyntaxKind::Statement); + + // [stack] + + // For functions nested within functions and blocks, make a lambda and + // initialize the binding name of the function in the current scope. + + NameOpEmitter noe(bce_, name_, NameOpEmitter::Kind::Initialize); + if (!noe.prepareForRhs()) { + // [stack] + return false; + } + + if (!bce_->emitGCIndexOp(JSOp::Lambda, index)) { + // [stack] FUN + return false; + } + + if (!noe.emitAssignment()) { + // [stack] FUN + return false; + } + + if (!bce_->emit1(JSOp::Pop)) { + // [stack] + return false; + } + + return true; +} + +bool FunctionEmitter::emitTopLevelFunction(GCThingIndex index) { + // [stack] + + if (bce_->sc->isModuleContext()) { + // For modules, we record the function and instantiate the binding + // during ModuleInstantiate(), before the script is run. + return bce_->sc->asModuleContext()->builder.noteFunctionDeclaration( + bce_->fc, index); + } + + MOZ_ASSERT(bce_->sc->isGlobalContext() || bce_->sc->isEvalContext()); + MOZ_ASSERT(syntaxKind_ == FunctionSyntaxKind::Statement); + MOZ_ASSERT(bce_->inPrologue()); + + // NOTE: The `index` is not directly stored as an opcode, but we collect the + // range of indices in `BytecodeEmitter::emitDeclarationInstantiation` instead + // of discrete indices. + (void)index; + + return true; +} + +bool FunctionScriptEmitter::prepareForParameters() { + MOZ_ASSERT(state_ == State::Start); + MOZ_ASSERT(bce_->inPrologue()); + + // [stack] + + if (paramStart_) { + bce_->setScriptStartOffsetIfUnset(*paramStart_); + } + + // The ordering of these EmitterScopes is important. The named lambda + // scope needs to enclose the function scope needs to enclose the extra + // var scope. + + if (funbox_->namedLambdaBindings()) { + namedLambdaEmitterScope_.emplace(bce_); + if (!namedLambdaEmitterScope_->enterNamedLambda(bce_, funbox_)) { + return false; + } + } + + if (funbox_->needsPromiseResult()) { + asyncEmitter_.emplace(bce_); + } + + if (bodyEnd_) { + bce_->setFunctionBodyEndPos(*bodyEnd_); + } + + if (paramStart_) { + if (!bce_->updateLineNumberNotes(*paramStart_)) { + return false; + } + } + + tdzCache_.emplace(bce_); + functionEmitterScope_.emplace(bce_); + + if (!functionEmitterScope_->enterFunction(bce_, funbox_)) { + return false; + } + + if (!emitInitializeClosedOverArgumentBindings()) { + // [stack] + return false; + } + + if (funbox_->hasParameterExprs) { + // There's parameter exprs, emit them in the main section. + // + // One caveat is that Debugger considers ops in the prologue to be + // unreachable (i.e. cannot set a breakpoint on it). If there are no + // parameter exprs, any unobservable environment ops (like pushing the + // call object, setting '.this', etc) need to go in the prologue, else it + // messes up breakpoint tests. + bce_->switchToMain(); + } + + if (!bce_->emitInitializeFunctionSpecialNames()) { + // [stack] + return false; + } + + if (!funbox_->hasParameterExprs) { + bce_->switchToMain(); + } + + if (funbox_->needsPromiseResult()) { + if (funbox_->hasParameterExprs || funbox_->hasDestructuringArgs) { + if (!asyncEmitter_->prepareForParamsWithExpressionOrDestructuring()) { + return false; + } + } else { + if (!asyncEmitter_->prepareForParamsWithoutExpressionOrDestructuring()) { + return false; + } + } + } + + if (funbox_->isClassConstructor()) { + if (!funbox_->isDerivedClassConstructor()) { + if (!bce_->emitInitializeInstanceMembers(false)) { + // [stack] + return false; + } + } + } + +#ifdef DEBUG + state_ = State::Parameters; +#endif + return true; +} + +bool FunctionScriptEmitter::emitInitializeClosedOverArgumentBindings() { + // Initialize CallObject slots for closed-over arguments. If the function has + // parameter expressions, these are lexical bindings and we initialize the + // slots to the magic TDZ value. If the function doesn't have parameter + // expressions, we copy the frame's arguments. + + MOZ_ASSERT(bce_->inPrologue()); + + auto* bindings = funbox_->functionScopeBindings(); + if (!bindings) { + return true; + } + + const bool hasParameterExprs = funbox_->hasParameterExprs; + + bool pushedUninitialized = false; + for (ParserPositionalFormalParameterIter fi(*bindings, hasParameterExprs); fi; + fi++) { + if (!fi.closedOver()) { + continue; + } + + if (hasParameterExprs) { + NameLocation nameLoc = bce_->lookupName(fi.name()); + if (!pushedUninitialized) { + if (!bce_->emit1(JSOp::Uninitialized)) { + // [stack] UNINITIALIZED + return false; + } + pushedUninitialized = true; + } + if (!bce_->emitEnvCoordOp(JSOp::InitAliasedLexical, + nameLoc.environmentCoordinate())) { + // [stack] UNINITIALIZED + return false; + } + } else { + NameOpEmitter noe(bce_, fi.name(), NameOpEmitter::Kind::Initialize); + if (!noe.prepareForRhs()) { + // [stack] + return false; + } + + if (!bce_->emitArgOp(JSOp::GetFrameArg, fi.argumentSlot())) { + // [stack] VAL + return false; + } + + if (!noe.emitAssignment()) { + // [stack] VAL + return false; + } + + if (!bce_->emit1(JSOp::Pop)) { + // [stack] + return false; + } + } + } + + if (pushedUninitialized) { + MOZ_ASSERT(hasParameterExprs); + if (!bce_->emit1(JSOp::Pop)) { + // [stack] + return false; + } + } + + return true; +} + +bool FunctionScriptEmitter::prepareForBody() { + MOZ_ASSERT(state_ == State::Parameters); + + // [stack] + + if (funbox_->needsPromiseResult()) { + if (!asyncEmitter_->emitParamsEpilogue()) { + return false; + } + } + + if (!emitExtraBodyVarScope()) { + // [stack] + return false; + } + + if (funbox_->needsPromiseResult()) { + if (!asyncEmitter_->prepareForBody()) { + return false; + } + } + +#ifdef DEBUG + state_ = State::Body; +#endif + return true; +} + +bool FunctionScriptEmitter::emitExtraBodyVarScope() { + // [stack] + + if (!funbox_->functionHasExtraBodyVarScope()) { + return true; + } + + extraBodyVarEmitterScope_.emplace(bce_); + if (!extraBodyVarEmitterScope_->enterFunctionExtraBodyVar(bce_, funbox_)) { + return false; + } + + if (!funbox_->extraVarScopeBindings() || !funbox_->functionScopeBindings()) { + return true; + } + + // After emitting expressions for all parameters, copy over any formal + // parameters which have been redeclared as vars. For example, in the + // following, the var y in the body scope is 42: + // + // function f(x, y = 42) { var y; } + // + for (ParserBindingIter bi(*funbox_->functionScopeBindings(), true); bi; + bi++) { + auto name = bi.name(); + + // There may not be a var binding of the same name. + if (!bce_->locationOfNameBoundInScope(name, + extraBodyVarEmitterScope_.ptr())) { + continue; + } + + // The '.this', '.newTarget', and '.generator' function special binding + // should never appear in the extra var scope. 'arguments', however, may. + MOZ_ASSERT(name != TaggedParserAtomIndex::WellKnown::dotThis() && + name != TaggedParserAtomIndex::WellKnown::dotNewTarget() && + name != TaggedParserAtomIndex::WellKnown::dotGenerator()); + + NameOpEmitter noe(bce_, name, NameOpEmitter::Kind::Initialize); + if (!noe.prepareForRhs()) { + // [stack] + return false; + } + + NameLocation paramLoc = + *bce_->locationOfNameBoundInScope(name, functionEmitterScope_.ptr()); + if (!bce_->emitGetNameAtLocation(name, paramLoc)) { + // [stack] VAL + return false; + } + + if (!noe.emitAssignment()) { + // [stack] VAL + return false; + } + + if (!bce_->emit1(JSOp::Pop)) { + // [stack] + return false; + } + } + + return true; +} + +bool FunctionScriptEmitter::emitEndBody() { + MOZ_ASSERT(state_ == State::Body); + // [stack] + + if (bodyEnd_) { + if (!bce_->updateSourceCoordNotes(*bodyEnd_)) { + return false; + } + } + + if (funbox_->needsFinalYield()) { + // If we fall off the end of a generator or async function, we + // do a final yield with an |undefined| payload. We put all + // the code to do this in one place, both to reduce bytecode + // size and to prevent any OOM or debugger exception that occurs + // at this point from being caught inside the function. + if (!bce_->emit1(JSOp::Undefined)) { + // [stack] UNDEF + return false; + } + if (!bce_->emit1(JSOp::SetRval)) { + // [stack] + return false; + } + + // Return statements in the body of the function will jump here + // with the return payload in rval. + if (!bce_->emitJumpTargetAndPatch(bce_->finalYields)) { + // [stack] + return false; + } + + if (funbox_->needsIteratorResult()) { + MOZ_ASSERT(!funbox_->needsPromiseResult()); + // Emit final yield bytecode for generators, for example: + // function gen * () { ... } + if (!bce_->emitPrepareIteratorResult()) { + // [stack] RESULT + return false; + } + + if (!bce_->emit1(JSOp::GetRval)) { + // [stack] RESULT RVAL + return false; + } + + if (!bce_->emitFinishIteratorResult(true)) { + // [stack] RESULT + return false; + } + + if (!bce_->emit1(JSOp::SetRval)) { + // [stack] + return false; + } + + } else if (funbox_->needsPromiseResult()) { + // Emit final yield bytecode for async functions, for example: + // async function deferred() { ... } + if (!bce_->emit1(JSOp::GetRval)) { + // [stack] RVAL + return false; + } + + if (!bce_->emitGetDotGeneratorInInnermostScope()) { + // [stack] RVAL GEN + return false; + } + + if (!bce_->emit2(JSOp::AsyncResolve, + uint8_t(AsyncFunctionResolveKind::Fulfill))) { + // [stack] PROMISE + return false; + } + + if (!bce_->emit1(JSOp::SetRval)) { + // [stack] + return false; + } + } + + // Emit the final yield. + if (!bce_->emitGetDotGeneratorInInnermostScope()) { + // [stack] GEN + return false; + } + + if (!bce_->emitYieldOp(JSOp::FinalYieldRval)) { + return false; + } + + if (funbox_->needsPromiseResult()) { + // Emit the reject catch block. + if (!asyncEmitter_->emitEndFunction()) { + return false; + } + } + + } else { + // Non-generator functions just return |undefined|. The + // JSOp::RetRval emitted below will do that, except if the + // script has a finally block: there can be a non-undefined + // value in the return value slot. Make sure the return value + // is |undefined|. + if (bce_->hasTryFinally) { + if (!bce_->emit1(JSOp::Undefined)) { + // [stack] UNDEF + return false; + } + if (!bce_->emit1(JSOp::SetRval)) { + // [stack] + return false; + } + } + } + + // Execute |CheckReturn| right before exiting the class constructor. + if (funbox_->isDerivedClassConstructor()) { + if (!bce_->emitJumpTargetAndPatch(bce_->endOfDerivedClassConstructorBody)) { + return false; + } + + if (!bce_->emitCheckDerivedClassConstructorReturn()) { + // [stack] + return false; + } + } + + if (extraBodyVarEmitterScope_) { + if (!extraBodyVarEmitterScope_->leave(bce_)) { + return false; + } + + extraBodyVarEmitterScope_.reset(); + } + + if (!functionEmitterScope_->leave(bce_)) { + return false; + } + functionEmitterScope_.reset(); + tdzCache_.reset(); + + // We only want to mark the end of a function as a breakable position if + // there is token there that the user can easily associate with the function + // as a whole. Since arrow function single-expression bodies have no closing + // curly bracket, we do not place a breakpoint at their end position. + if (!funbox_->hasExprBody()) { + if (!bce_->markSimpleBreakpoint()) { + return false; + } + } + + // Emit JSOp::RetRval except for sync arrow function with expression body + // which always ends with JSOp::Return. Other parts of the codebase depend + // on these opcodes being the last opcode. + // See JSScript::lastPC and BaselineCompiler::emitBody. + if (!funbox_->hasExprBody() || funbox_->isAsync()) { + if (!bce_->emitReturnRval()) { + // [stack] + return false; + } + } + + if (namedLambdaEmitterScope_) { + if (!namedLambdaEmitterScope_->leave(bce_)) { + return false; + } + namedLambdaEmitterScope_.reset(); + } + +#ifdef DEBUG + state_ = State::EndBody; +#endif + return true; +} + +bool FunctionScriptEmitter::intoStencil() { + MOZ_ASSERT(state_ == State::EndBody); + + if (!bce_->intoScriptStencil(funbox_->index())) { + return false; + } + +#ifdef DEBUG + state_ = State::End; +#endif + + return true; +} + +FunctionParamsEmitter::FunctionParamsEmitter(BytecodeEmitter* bce, + FunctionBox* funbox) + : bce_(bce), + funbox_(funbox), + functionEmitterScope_(bce_->innermostEmitterScope()) {} + +bool FunctionParamsEmitter::emitSimple(TaggedParserAtomIndex paramName) { + MOZ_ASSERT(state_ == State::Start); + + // [stack] + + if (funbox_->hasParameterExprs) { + if (!bce_->emitArgOp(JSOp::GetArg, argSlot_)) { + // [stack] ARG + return false; + } + + if (!emitAssignment(paramName)) { + // [stack] + return false; + } + } + + argSlot_++; + return true; +} + +bool FunctionParamsEmitter::prepareForDefault() { + MOZ_ASSERT(state_ == State::Start); + + // [stack] + + if (!prepareForInitializer()) { + // [stack] + return false; + } + +#ifdef DEBUG + state_ = State::Default; +#endif + return true; +} + +bool FunctionParamsEmitter::emitDefaultEnd(TaggedParserAtomIndex paramName) { + MOZ_ASSERT(state_ == State::Default); + + // [stack] DEFAULT + + if (!emitInitializerEnd()) { + // [stack] ARG/DEFAULT + return false; + } + if (!emitAssignment(paramName)) { + // [stack] + return false; + } + + argSlot_++; + +#ifdef DEBUG + state_ = State::Start; +#endif + return true; +} + +bool FunctionParamsEmitter::prepareForDestructuring() { + MOZ_ASSERT(state_ == State::Start); + + // [stack] + + if (!bce_->emitArgOp(JSOp::GetArg, argSlot_)) { + // [stack] ARG + return false; + } + +#ifdef DEBUG + state_ = State::Destructuring; +#endif + return true; +} + +bool FunctionParamsEmitter::emitDestructuringEnd() { + MOZ_ASSERT(state_ == State::Destructuring); + + // [stack] ARG + + if (!bce_->emit1(JSOp::Pop)) { + // [stack] + return false; + } + + argSlot_++; + +#ifdef DEBUG + state_ = State::Start; +#endif + return true; +} + +bool FunctionParamsEmitter::prepareForDestructuringDefaultInitializer() { + MOZ_ASSERT(state_ == State::Start); + + // [stack] + + if (!prepareForInitializer()) { + // [stack] + return false; + } + +#ifdef DEBUG + state_ = State::DestructuringDefaultInitializer; +#endif + return true; +} + +bool FunctionParamsEmitter::prepareForDestructuringDefault() { + MOZ_ASSERT(state_ == State::DestructuringDefaultInitializer); + + // [stack] DEFAULT + + if (!emitInitializerEnd()) { + // [stack] ARG/DEFAULT + return false; + } + +#ifdef DEBUG + state_ = State::DestructuringDefault; +#endif + return true; +} + +bool FunctionParamsEmitter::emitDestructuringDefaultEnd() { + MOZ_ASSERT(state_ == State::DestructuringDefault); + + // [stack] ARG/DEFAULT + + if (!bce_->emit1(JSOp::Pop)) { + // [stack] + return false; + } + + argSlot_++; + +#ifdef DEBUG + state_ = State::Start; +#endif + return true; +} + +bool FunctionParamsEmitter::emitRest(TaggedParserAtomIndex paramName) { + MOZ_ASSERT(state_ == State::Start); + + // [stack] + + if (!emitRestArray()) { + // [stack] REST + return false; + } + if (!emitAssignment(paramName)) { + // [stack] + return false; + } + +#ifdef DEBUG + state_ = State::End; +#endif + return true; +} + +bool FunctionParamsEmitter::prepareForDestructuringRest() { + MOZ_ASSERT(state_ == State::Start); + + // [stack] + + if (!emitRestArray()) { + // [stack] REST + return false; + } + +#ifdef DEBUG + state_ = State::DestructuringRest; +#endif + return true; +} + +bool FunctionParamsEmitter::emitDestructuringRestEnd() { + MOZ_ASSERT(state_ == State::DestructuringRest); + + // [stack] REST + + if (!bce_->emit1(JSOp::Pop)) { + // [stack] + return false; + } + +#ifdef DEBUG + state_ = State::End; +#endif + return true; +} + +bool FunctionParamsEmitter::prepareForInitializer() { + // [stack] + + // If we have an initializer, emit the initializer and assign it + // to the argument slot. TDZ is taken care of afterwards. + MOZ_ASSERT(funbox_->hasParameterExprs); + if (!bce_->emitArgOp(JSOp::GetArg, argSlot_)) { + // [stack] ARG + return false; + } + default_.emplace(bce_); + if (!default_->prepareForDefault()) { + // [stack] + return false; + } + return true; +} + +bool FunctionParamsEmitter::emitInitializerEnd() { + // [stack] DEFAULT + + if (!default_->emitEnd()) { + // [stack] ARG/DEFAULT + return false; + } + default_.reset(); + return true; +} + +bool FunctionParamsEmitter::emitRestArray() { + // [stack] + + if (!bce_->emit1(JSOp::Rest)) { + // [stack] REST + return false; + } + return true; +} + +bool FunctionParamsEmitter::emitAssignment(TaggedParserAtomIndex paramName) { + // [stack] ARG + + NameLocation paramLoc = + *bce_->locationOfNameBoundInScope(paramName, functionEmitterScope_); + + // RHS is already pushed in the caller side. + // Make sure prepareForRhs doesn't touch stack. + MOZ_ASSERT(paramLoc.kind() == NameLocation::Kind::ArgumentSlot || + paramLoc.kind() == NameLocation::Kind::FrameSlot || + paramLoc.kind() == NameLocation::Kind::EnvironmentCoordinate); + + NameOpEmitter noe(bce_, paramName, paramLoc, NameOpEmitter::Kind::Initialize); + if (!noe.prepareForRhs()) { + // [stack] ARG + return false; + } + + if (!noe.emitAssignment()) { + // [stack] ARG + return false; + } + + if (!bce_->emit1(JSOp::Pop)) { + // [stack] + return false; + } + + return true; +} diff --git a/js/src/frontend/FunctionEmitter.h b/js/src/frontend/FunctionEmitter.h new file mode 100644 index 0000000000..b36f223664 --- /dev/null +++ b/js/src/frontend/FunctionEmitter.h @@ -0,0 +1,436 @@ +/* -*- 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/. */ + +#ifndef frontend_FunctionEmitter_h +#define frontend_FunctionEmitter_h + +#include "mozilla/Attributes.h" // MOZ_STACK_CLASS + +#include // uint16_t, uint32_t + +#include "frontend/AsyncEmitter.h" // AsyncEmitter +#include "frontend/DefaultEmitter.h" // DefaultEmitter +#include "frontend/EmitterScope.h" // EmitterScope +#include "frontend/FunctionSyntaxKind.h" // FunctionSyntaxKind +#include "frontend/ParserAtom.h" // TaggedParserAtomIndex +#include "frontend/TDZCheckCache.h" // TDZCheckCache + +namespace js { + +class GCThingIndex; + +namespace frontend { + +struct BytecodeEmitter; +class FunctionBox; + +// Class for emitting function declaration, expression, or method etc. +// +// This class handles the enclosing script's part (function object creation, +// declaration, etc). The content of the function script is handled by +// FunctionScriptEmitter and FunctionParamsEmitter. +// +// Usage: (check for the return value is omitted for simplicity) +// +// `function f() {}`, non lazy script +// FunctionEmitter fe(this, funbox_for_f, FunctionSyntaxKind::Statement, +// FunctionEmitter::IsHoisted::No); +// fe.prepareForNonLazy(); +// +// // Emit script with FunctionScriptEmitter here. +// ... +// +// fe.emitNonLazyEnd(); +// +// `function f() {}`, lazy script +// FunctionEmitter fe(this, funbox_for_f, FunctionSyntaxKind::Statement, +// FunctionEmitter::IsHoisted::No); +// fe.emitLazy(); +// +// `function f() {}`, emitting hoisted function again +// // See emitAgain comment for more details +// FunctionEmitter fe(this, funbox_for_f, FunctionSyntaxKind::Statement, +// FunctionEmitter::IsHoisted::Yes); +// fe.emitAgain(); +// +// `function f() { "use asm"; }` +// FunctionEmitter fe(this, funbox_for_f, FunctionSyntaxKind::Statement, +// FunctionEmitter::IsHoisted::No); +// fe.emitAsmJSModule(); +// +class MOZ_STACK_CLASS FunctionEmitter { + public: + enum class IsHoisted { No, Yes }; + + private: + BytecodeEmitter* bce_; + + FunctionBox* funbox_; + + // Function's explicit name. + TaggedParserAtomIndex name_; + + FunctionSyntaxKind syntaxKind_; + IsHoisted isHoisted_; + +#ifdef DEBUG + // The state of this emitter. + // + // +-------+ + // | Start |-+ + // +-------+ | + // | + // +-------+ + // | + // | [non-lazy function] + // | prepareForNonLazy +---------+ emitNonLazyEnd +-----+ + // +--------------------->| NonLazy |---------------->+->| End | + // | +---------+ ^ +-----+ + // | | + // | [lazy function] | + // | emitLazy | + // +------------------------------------------------->+ + // | ^ + // | [emitting hoisted function again] | + // | emitAgain | + // +------------------------------------------------->+ + // | ^ + // | [asm.js module] | + // | emitAsmJSModule | + // +--------------------------------------------------+ + // + enum class State { + // The initial state. + Start, + + // After calling prepareForNonLazy. + NonLazy, + + // After calling emitNonLazyEnd, emitLazy, emitAgain, or emitAsmJSModule. + End + }; + State state_ = State::Start; +#endif + + public: + FunctionEmitter(BytecodeEmitter* bce, FunctionBox* funbox, + FunctionSyntaxKind syntaxKind, IsHoisted isHoisted); + + [[nodiscard]] bool prepareForNonLazy(); + [[nodiscard]] bool emitNonLazyEnd(); + + [[nodiscard]] bool emitLazy(); + + [[nodiscard]] bool emitAgain(); + + [[nodiscard]] bool emitAsmJSModule(); + + private: + // Emit the function declaration, expression, method etc. + // This leaves function object on the stack for expression etc, + // and doesn't for declaration. + [[nodiscard]] bool emitFunction(); + + // Helper methods used by emitFunction for each case. + // `index` is the object index of the function. + [[nodiscard]] bool emitNonHoisted(GCThingIndex index); + [[nodiscard]] bool emitHoisted(GCThingIndex index); + [[nodiscard]] bool emitTopLevelFunction(GCThingIndex index); +}; + +// Class for emitting function script. +// Parameters are handled by FunctionParamsEmitter. +// +// Usage: (check for the return value is omitted for simplicity) +// +// `function f(a) { expr }` +// FunctionScriptEmitter fse(this, funbox_for_f, +// Some(offset_of_opening_paren), +// Some(offset_of_closing_brace)); +// fse.prepareForParameters(); +// +// // Emit parameters with FunctionParamsEmitter here. +// ... +// +// fse.prepareForBody(); +// emit(expr); +// fse.emitEnd(); +// +// // Do NameFunctions operation here if needed. +// +// fse.intoStencil(); +// +class MOZ_STACK_CLASS FunctionScriptEmitter { + private: + BytecodeEmitter* bce_; + + FunctionBox* funbox_; + + // Scope for the function name for a named lambda. + // None for anonymous function. + mozilla::Maybe namedLambdaEmitterScope_; + + // Scope for function body. + mozilla::Maybe functionEmitterScope_; + + // Scope for the extra body var. + // None if `funbox_->hasExtraBodyVarScope() == false`. + mozilla::Maybe extraBodyVarEmitterScope_; + + mozilla::Maybe tdzCache_; + + // try-catch block for async function parameter and body. + mozilla::Maybe asyncEmitter_; + + // See the comment for constructor. + mozilla::Maybe paramStart_; + mozilla::Maybe bodyEnd_; + +#ifdef DEBUG + // The state of this emitter. + // + // +-------+ prepareForParameters +------------+ + // | Start |---------------------->| Parameters |-+ + // +-------+ +------------+ | + // | + // +--------------------------------------------+ + // | + // | prepareForBody +------+ emitEndBody +---------+ + // +---------------->| Body |------------->| EndBody |-+ + // +------+ +---------+ | + // | + // +-------------------------------------------------+ + // | + // | intoStencil +-----+ + // +------------>| End | + // +-----+ + enum class State { + // The initial state. + Start, + + // After calling prepareForParameters. + Parameters, + + // After calling prepareForBody. + Body, + + // After calling emitEndBody. + EndBody, + + // After calling intoStencil. + End + }; + State state_ = State::Start; +#endif + + public: + // Parameters are the offset in the source code for each character below: + // + // function f(a, b, ...c) { ... } + // ^ ^ + // | | + // paramStart bodyEnd + // + // Can be Nothing() if not available. + FunctionScriptEmitter(BytecodeEmitter* bce, FunctionBox* funbox, + const mozilla::Maybe& paramStart, + const mozilla::Maybe& bodyEnd) + : bce_(bce), + funbox_(funbox), + paramStart_(paramStart), + bodyEnd_(bodyEnd) {} + + [[nodiscard]] bool prepareForParameters(); + [[nodiscard]] bool prepareForBody(); + [[nodiscard]] bool emitEndBody(); + + // Generate the ScriptStencil using the bytecode emitter data. + [[nodiscard]] bool intoStencil(); + + private: + [[nodiscard]] bool emitExtraBodyVarScope(); + [[nodiscard]] bool emitInitializeClosedOverArgumentBindings(); +}; + +// Class for emitting function parameters. +// +// Usage: (check for the return value is omitted for simplicity) +// +// `function f(a, b=10, ...c) {}` +// FunctionParamsEmitter fpe(this, funbox_for_f); +// +// fpe.emitSimple(atom_of_a); +// +// fpe.prepareForDefault(); +// emit(10); +// fpe.emitDefaultEnd(atom_of_b); +// +// fpe.emitRest(atom_of_c); +// +// `function f([a], [b]=[1], ...[c]) {}` +// FunctionParamsEmitter fpe(this, funbox_for_f); +// +// fpe.prepareForDestructuring(); +// emit(destructuring_for_[a]); +// fpe.emitDestructuringEnd(); +// +// fpe.prepareForDestructuringDefaultInitializer(); +// emit([1]); +// fpe.prepareForDestructuringDefault(); +// emit(destructuring_for_[b]); +// fpe.emitDestructuringDefaultEnd(); +// +// fpe.prepareForDestructuringRest(); +// emit(destructuring_for_[c]); +// fpe.emitDestructuringRestEnd(); +// +class MOZ_STACK_CLASS FunctionParamsEmitter { + private: + BytecodeEmitter* bce_; + + FunctionBox* funbox_; + + // The pointer to `FunctionScriptEmitter::functionEmitterScope_`, + // passed via `BytecodeEmitter::innermostEmitterScope()`. + EmitterScope* functionEmitterScope_; + + // The slot for the current parameter. + // NOTE: after emitting rest parameter, this isn't incremented. + uint16_t argSlot_ = 0; + + // DefaultEmitter for default parameter. + mozilla::Maybe default_; + +#ifdef DEBUG + // The state of this emitter. + // + // +----------------------------------------------------------+ + // | | + // | +-------+ | + // +->| Start |-+ | + // +-------+ | | + // | | + // +------------+ | + // | | + // | [single binding, without default] | + // | emitSimple | + // +--------------------------------------------------------->+ + // | ^ + // | [single binding, with default] | + // | prepareForDefault +---------+ emitDefaultEnd | + // +--------------------->| Default |------------------------>+ + // | +---------+ ^ + // | | + // | [destructuring, without default] | + // | prepareForDestructuring +---------------+ | + // +--------------------------->| Destructuring |-+ | + // | +---------------+ | | + // | | | + // | +-----------------------------------------+ | + // | | | + // | | emitDestructuringEnd | + // | +---------------------------------------------------->+ + // | ^ + // | [destructuring, with default] | + // | prepareForDestructuringDefaultInitializer | + // +---------------------------------------------+ | + // | | | + // | +----------------------------------------+ | + // | | | + // | | +---------------------------------+ | + // | +->| DestructuringDefaultInitializer |-+ | + // | +---------------------------------+ | | + // | | | + // | +------------------------------------+ | + // | | | + // | | prepareForDestructuringDefault | + // | +-------------------------------+ | + // | | | + // | +-----------------------------+ | + // | | | + // | | +----------------------+ | + // | +->| DestructuringDefault |-+ | + // | +----------------------+ | | + // | | | + // | +-------------------------+ | + // | | | + // | | emitDestructuringDefaultEnd | + // | +---------------------------------------------->+ + // | + // | [single binding rest] + // | emitRest +-----+ + // +--------------------------------------------------------->+->| End | + // | ^ +-----+ + // | [destructuring rest] | + // | prepareForDestructuringRest +-------------------+ | + // +-------------------------------->| DestructuringRest |-+ | + // +-------------------+ | | + // | | + // +----------------------------------------------------+ | + // | | + // | emitDestructuringRestEnd | + // +-------------------------------------------------------+ + // + enum class State { + // The initial state, or after emitting non-rest parameter. + Start, + + // After calling prepareForDefault. + Default, + + // After calling prepareForDestructuring. + Destructuring, + + // After calling prepareForDestructuringDefaultInitializer. + DestructuringDefaultInitializer, + + // After calling prepareForDestructuringDefault. + DestructuringDefault, + + // After calling prepareForDestructuringRest. + DestructuringRest, + + // After calling emitRest or emitDestructuringRestEnd. + End, + }; + State state_ = State::Start; +#endif + + public: + FunctionParamsEmitter(BytecodeEmitter* bce, FunctionBox* funbox); + + // paramName is used only when there's at least one expression in the + // paramerters (funbox_->hasParameterExprs == true). + [[nodiscard]] bool emitSimple(TaggedParserAtomIndex paramName); + + [[nodiscard]] bool prepareForDefault(); + [[nodiscard]] bool emitDefaultEnd(TaggedParserAtomIndex paramName); + + [[nodiscard]] bool prepareForDestructuring(); + [[nodiscard]] bool emitDestructuringEnd(); + + [[nodiscard]] bool prepareForDestructuringDefaultInitializer(); + [[nodiscard]] bool prepareForDestructuringDefault(); + [[nodiscard]] bool emitDestructuringDefaultEnd(); + + [[nodiscard]] bool emitRest(TaggedParserAtomIndex paramName); + + [[nodiscard]] bool prepareForDestructuringRest(); + [[nodiscard]] bool emitDestructuringRestEnd(); + + private: + [[nodiscard]] bool prepareForInitializer(); + [[nodiscard]] bool emitInitializerEnd(); + + [[nodiscard]] bool emitRestArray(); + + [[nodiscard]] bool emitAssignment(TaggedParserAtomIndex paramName); +}; + +} /* namespace frontend */ +} /* namespace js */ + +#endif /* frontend_FunctionEmitter_h */ diff --git a/js/src/frontend/FunctionSyntaxKind.h b/js/src/frontend/FunctionSyntaxKind.h new file mode 100644 index 0000000000..2f3d59ab08 --- /dev/null +++ b/js/src/frontend/FunctionSyntaxKind.h @@ -0,0 +1,41 @@ +/* -*- 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/. */ + +#ifndef frontend_FunctionSyntaxKind_h +#define frontend_FunctionSyntaxKind_h + +#include // uint8_t + +namespace js { +namespace frontend { + +enum class FunctionSyntaxKind : uint8_t { + // A non-arrow function expression. + Expression, + + // A named function appearing as a Statement. + Statement, + + Arrow, + + // Method of a class or object. Field initializers also desugar to methods. + Method, + FieldInitializer, + + // Mostly static class blocks act similar to field initializers, however, + // there is some difference in static semantics. + StaticClassBlock, + + ClassConstructor, + DerivedClassConstructor, + Getter, + Setter, +}; + +} /* namespace frontend */ +} /* namespace js */ + +#endif /* frontend_FunctionSyntaxKind_h */ diff --git a/js/src/frontend/GenerateReservedWords.py b/js/src/frontend/GenerateReservedWords.py new file mode 100644 index 0000000000..078f8bf833 --- /dev/null +++ b/js/src/frontend/GenerateReservedWords.py @@ -0,0 +1,232 @@ +# 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/. + +import re +import sys + + +def read_reserved_word_list(filename, enable_decorators): + macro_pat = re.compile(r"MACRO\(([^,]+), *[^,]+, *[^\)]+\)\s*\\?") + + reserved_word_list = [] + index = 0 + with open(filename, "r") as f: + for line in f: + m = macro_pat.search(line) + if m: + reserved_word = m.group(1) + if reserved_word == "accessor" and not enable_decorators: + continue + reserved_word_list.append((index, reserved_word)) + index += 1 + + assert len(reserved_word_list) != 0 + + return reserved_word_list + + +def line(opt, s): + opt["output"].write("{}{}\n".format(" " * opt["indent_level"], s)) + + +def indent(opt): + opt["indent_level"] += 1 + + +def dedent(opt): + opt["indent_level"] -= 1 + + +def span_and_count_at(reserved_word_list, column): + assert len(reserved_word_list) != 0 + + chars_dict = {} + for index, word in reserved_word_list: + chars_dict[ord(word[column])] = True + + chars = sorted(chars_dict.keys()) + return chars[-1] - chars[0] + 1, len(chars) + + +def optimal_switch_column(opt, reserved_word_list, columns, unprocessed_columns): + assert len(reserved_word_list) != 0 + assert unprocessed_columns != 0 + + min_count = 0 + min_span = 0 + min_count_index = 0 + min_span_index = 0 + + for index in range(0, unprocessed_columns): + span, count = span_and_count_at(reserved_word_list, columns[index]) + assert span != 0 + + if span == 1: + assert count == 1 + return 1, True + + assert count != 1 + if index == 0 or min_span > span: + min_span = span + min_span_index = index + + if index == 0 or min_count > count: + min_count = count + min_count_index = index + + if min_count <= opt["use_if_threshold"]: + return min_count_index, True + + return min_span_index, False + + +def split_list_per_column(reserved_word_list, column): + assert len(reserved_word_list) != 0 + + column_dict = {} + for item in reserved_word_list: + index, word = item + per_column = column_dict.setdefault(word[column], []) + per_column.append(item) + + return sorted(column_dict.items()) + + +def generate_letter_switch(opt, unprocessed_columns, reserved_word_list, columns=None): + assert len(reserved_word_list) != 0 + + if not columns: + columns = range(0, unprocessed_columns) + + if len(reserved_word_list) == 1: + index, word = reserved_word_list[0] + + if unprocessed_columns == 0: + line(opt, "JSRW_GOT_MATCH({}) /* {} */".format(index, word)) + return + + if unprocessed_columns > opt["char_tail_test_threshold"]: + line(opt, "JSRW_TEST_GUESS({}) /* {} */".format(index, word)) + return + + conds = [] + for column in columns[0:unprocessed_columns]: + quoted = repr(word[column]) + conds.append("JSRW_AT({})=={}".format(column, quoted)) + + line(opt, "if ({}) {{".format(" && ".join(conds))) + + indent(opt) + line(opt, "JSRW_GOT_MATCH({}) /* {} */".format(index, word)) + dedent(opt) + + line(opt, "}") + line(opt, "JSRW_NO_MATCH()") + return + + assert unprocessed_columns != 0 + + optimal_column_index, use_if = optimal_switch_column( + opt, reserved_word_list, columns, unprocessed_columns + ) + optimal_column = columns[optimal_column_index] + + # Make a copy to avoid breaking passed list. + columns = list(columns) + columns[optimal_column_index] = columns[unprocessed_columns - 1] + + list_per_column = split_list_per_column(reserved_word_list, optimal_column) + + if not use_if: + line(opt, "switch (JSRW_AT({})) {{".format(optimal_column)) + + for char, reserved_word_list_per_column in list_per_column: + quoted = repr(char) + if use_if: + line(opt, "if (JSRW_AT({}) == {}) {{".format(optimal_column, quoted)) + else: + line(opt, " case {}:".format(quoted)) + + indent(opt) + generate_letter_switch( + opt, unprocessed_columns - 1, reserved_word_list_per_column, columns + ) + dedent(opt) + + if use_if: + line(opt, "}") + + if not use_if: + line(opt, "}") + + line(opt, "JSRW_NO_MATCH()") + + +def split_list_per_length(reserved_word_list): + assert len(reserved_word_list) != 0 + + length_dict = {} + for item in reserved_word_list: + index, word = item + per_length = length_dict.setdefault(len(word), []) + per_length.append(item) + + return sorted(length_dict.items()) + + +def generate_switch(opt, reserved_word_list): + assert len(reserved_word_list) != 0 + + line(opt, "/*") + line( + opt, + " * Generating switch for the list of {} entries:".format( + len(reserved_word_list) + ), + ) + for index, word in reserved_word_list: + line(opt, " * {}".format(word)) + line(opt, " */") + + list_per_length = split_list_per_length(reserved_word_list) + + use_if = False + if len(list_per_length) < opt["use_if_threshold"]: + use_if = True + + if not use_if: + line(opt, "switch (JSRW_LENGTH()) {") + + for length, reserved_word_list_per_length in list_per_length: + if use_if: + line(opt, "if (JSRW_LENGTH() == {}) {{".format(length)) + else: + line(opt, " case {}:".format(length)) + + indent(opt) + generate_letter_switch(opt, length, reserved_word_list_per_length) + dedent(opt) + + if use_if: + line(opt, "}") + + if not use_if: + line(opt, "}") + line(opt, "JSRW_NO_MATCH()") + + +def main(output, reserved_words_h, enable_decorators=False): + reserved_word_list = read_reserved_word_list(reserved_words_h, enable_decorators) + + opt = { + "indent_level": 1, + "use_if_threshold": 3, + "char_tail_test_threshold": 4, + "output": output, + } + generate_switch(opt, reserved_word_list) + + +if __name__ == "__main__": + main(sys.stdout, *sys.argv[1:]) diff --git a/js/src/frontend/IfEmitter.cpp b/js/src/frontend/IfEmitter.cpp new file mode 100644 index 0000000000..ced510e0b7 --- /dev/null +++ b/js/src/frontend/IfEmitter.cpp @@ -0,0 +1,286 @@ +/* -*- 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/IfEmitter.h" + +#include "frontend/BytecodeEmitter.h" +#include "vm/Opcodes.h" + +using namespace js; +using namespace js::frontend; + +using mozilla::Maybe; + +BranchEmitterBase::BranchEmitterBase(BytecodeEmitter* bce, + LexicalKind lexicalKind) + : bce_(bce), lexicalKind_(lexicalKind) {} + +IfEmitter::IfEmitter(BytecodeEmitter* bce, LexicalKind lexicalKind) + : BranchEmitterBase(bce, lexicalKind) {} + +IfEmitter::IfEmitter(BytecodeEmitter* bce) + : IfEmitter(bce, LexicalKind::MayContainLexicalAccessInBranch) {} + +bool BranchEmitterBase::emitThenInternal(ConditionKind conditionKind) { + // The end of TDZCheckCache for cond for else-if. + if (lexicalKind_ == LexicalKind::MayContainLexicalAccessInBranch) { + tdzCache_.reset(); + } + + // Emit a jump around the then part. + JSOp op = conditionKind == ConditionKind::Positive ? JSOp::JumpIfFalse + : JSOp::JumpIfTrue; + if (!bce_->emitJump(op, &jumpAroundThen_)) { + return false; + } + + // To restore stack depth in else part (if present), save depth of the then + // part. + thenDepth_ = bce_->bytecodeSection().stackDepth(); + + // Enclose then-branch with TDZCheckCache. + if (lexicalKind_ == LexicalKind::MayContainLexicalAccessInBranch) { + tdzCache_.emplace(bce_); + } + + return true; +} + +void BranchEmitterBase::calculateOrCheckPushed() { +#ifdef DEBUG + if (!calculatedPushed_) { + pushed_ = bce_->bytecodeSection().stackDepth() - thenDepth_; + calculatedPushed_ = true; + } else { + MOZ_ASSERT(pushed_ == bce_->bytecodeSection().stackDepth() - thenDepth_); + } +#endif +} + +bool BranchEmitterBase::emitElseInternal() { + calculateOrCheckPushed(); + + // The end of TDZCheckCache for then-clause. + if (lexicalKind_ == LexicalKind::MayContainLexicalAccessInBranch) { + MOZ_ASSERT(tdzCache_.isSome()); + tdzCache_.reset(); + } + + // Emit a jump from the end of our then part around the else part. The + // patchJumpsToTarget call at the bottom of this function will fix up + // the offset with jumpsAroundElse value. + if (!bce_->emitJump(JSOp::Goto, &jumpsAroundElse_)) { + return false; + } + + // Ensure the branch-if-false comes here, then emit the else. + if (!bce_->emitJumpTargetAndPatch(jumpAroundThen_)) { + return false; + } + + // Clear jumpAroundThen_ offset, to tell emitEnd there was an else part. + jumpAroundThen_ = JumpList(); + + // Restore stack depth of the then part. + bce_->bytecodeSection().setStackDepth(thenDepth_); + + // Enclose else-branch with TDZCheckCache. + if (lexicalKind_ == LexicalKind::MayContainLexicalAccessInBranch) { + tdzCache_.emplace(bce_); + } + + return true; +} + +bool BranchEmitterBase::emitEndInternal() { + // The end of TDZCheckCache for then or else-clause. + if (lexicalKind_ == LexicalKind::MayContainLexicalAccessInBranch) { + MOZ_ASSERT(tdzCache_.isSome()); + tdzCache_.reset(); + } + + calculateOrCheckPushed(); + + if (jumpAroundThen_.offset.valid()) { + // No else part for the last branch, fixup the branch-if-false to + // come here. + if (!bce_->emitJumpTargetAndPatch(jumpAroundThen_)) { + return false; + } + } + + // Patch all the jumps around else parts. + if (!bce_->emitJumpTargetAndPatch(jumpsAroundElse_)) { + return false; + } + + return true; +} + +bool IfEmitter::emitIf(const Maybe& ifPos) { + MOZ_ASSERT(state_ == State::Start); + + if (ifPos) { + // Make sure this code is attributed to the "if" so that it gets a + // useful column number, instead of the default 0 value. + if (!bce_->updateSourceCoordNotes(*ifPos)) { + return false; + } + } + +#ifdef DEBUG + state_ = State::If; +#endif + return true; +} + +bool IfEmitter::emitThen( + ConditionKind conditionKind /* = ConditionKind::Positive */) { + MOZ_ASSERT(state_ == State::If || state_ == State::ElseIf); + + if (lexicalKind_ == LexicalKind::MayContainLexicalAccessInBranch) { + MOZ_ASSERT_IF(state_ == State::ElseIf, tdzCache_.isSome()); + MOZ_ASSERT_IF(state_ != State::ElseIf, tdzCache_.isNothing()); + } + + if (!emitThenInternal(conditionKind)) { + return false; + } + +#ifdef DEBUG + state_ = State::Then; +#endif + return true; +} + +bool IfEmitter::emitThenElse( + ConditionKind conditionKind /* = ConditionKind::Positive */) { + MOZ_ASSERT(state_ == State::If || state_ == State::ElseIf); + + if (lexicalKind_ == LexicalKind::MayContainLexicalAccessInBranch) { + MOZ_ASSERT_IF(state_ == State::ElseIf, tdzCache_.isSome()); + MOZ_ASSERT_IF(state_ != State::ElseIf, tdzCache_.isNothing()); + } + + if (!emitThenInternal(conditionKind)) { + return false; + } + +#ifdef DEBUG + state_ = State::ThenElse; +#endif + return true; +} + +bool IfEmitter::emitElseIf(const Maybe& ifPos) { + MOZ_ASSERT(state_ == State::ThenElse); + + if (!emitElseInternal()) { + return false; + } + + if (ifPos) { + // Make sure this code is attributed to the "if" so that it gets a + // useful column number, instead of the default 0 value. + if (!bce_->updateSourceCoordNotes(*ifPos)) { + return false; + } + } + +#ifdef DEBUG + state_ = State::ElseIf; +#endif + return true; +} + +bool IfEmitter::emitElse() { + MOZ_ASSERT(state_ == State::ThenElse); + + if (!emitElseInternal()) { + return false; + } + +#ifdef DEBUG + state_ = State::Else; +#endif + return true; +} + +bool IfEmitter::emitEnd() { + MOZ_ASSERT(state_ == State::Then || state_ == State::Else); + // If there was an else part for the last branch, jumpAroundThen_ is + // already fixed up when emitting the else part. + MOZ_ASSERT_IF(state_ == State::Then, jumpAroundThen_.offset.valid()); + MOZ_ASSERT_IF(state_ == State::Else, !jumpAroundThen_.offset.valid()); + + if (!emitEndInternal()) { + return false; + } + +#ifdef DEBUG + state_ = State::End; +#endif + return true; +} + +InternalIfEmitter::InternalIfEmitter(BytecodeEmitter* bce) + : IfEmitter(bce, LexicalKind::NoLexicalAccessInBranch) { +#ifdef DEBUG + // Skip emitIf (see the comment above InternalIfEmitter declaration). + state_ = State::If; +#endif +} + +CondEmitter::CondEmitter(BytecodeEmitter* bce) + : BranchEmitterBase(bce, LexicalKind::MayContainLexicalAccessInBranch) {} + +bool CondEmitter::emitCond() { + MOZ_ASSERT(state_ == State::Start); +#ifdef DEBUG + state_ = State::Cond; +#endif + return true; +} + +bool CondEmitter::emitThenElse( + ConditionKind conditionKind /* = ConditionKind::Positive */) { + MOZ_ASSERT(state_ == State::Cond); + if (!emitThenInternal(conditionKind)) { + return false; + } + +#ifdef DEBUG + state_ = State::ThenElse; +#endif + return true; +} + +bool CondEmitter::emitElse() { + MOZ_ASSERT(state_ == State::ThenElse); + + if (!emitElseInternal()) { + return false; + } + +#ifdef DEBUG + state_ = State::Else; +#endif + return true; +} + +bool CondEmitter::emitEnd() { + MOZ_ASSERT(state_ == State::Else); + MOZ_ASSERT(!jumpAroundThen_.offset.valid()); + + if (!emitEndInternal()) { + return false; + } + +#ifdef DEBUG + state_ = State::End; +#endif + return true; +} diff --git a/js/src/frontend/IfEmitter.h b/js/src/frontend/IfEmitter.h new file mode 100644 index 0000000000..eaf4e2d8f4 --- /dev/null +++ b/js/src/frontend/IfEmitter.h @@ -0,0 +1,311 @@ +/* -*- 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/. */ + +#ifndef frontend_IfEmitter_h +#define frontend_IfEmitter_h + +#include "mozilla/Attributes.h" +#include "mozilla/Maybe.h" + +#include + +#include "frontend/JumpList.h" +#include "frontend/TDZCheckCache.h" + +namespace js { +namespace frontend { + +struct BytecodeEmitter; + +class MOZ_STACK_CLASS BranchEmitterBase { + protected: + BytecodeEmitter* bce_; + + // Jump around the then clause, to the beginning of the else clause. + JumpList jumpAroundThen_; + + // Jump around the else clause, to the end of the entire branch. + JumpList jumpsAroundElse_; + + // The stack depth before emitting the then block. + // Used for restoring stack depth before emitting the else block. + // Also used for assertion to make sure then and else blocks pushed the + // same number of values. + int32_t thenDepth_ = 0; + + enum class ConditionKind { Positive, Negative }; + + // Whether the then-clause, the else-clause, or else-if condition may + // contain declaration or access to lexical variables, which means they + // should have their own TDZCheckCache. Basically TDZCheckCache should be + // created for each basic block, which then-clause, else-clause, and + // else-if condition are, but for internally used branches which are + // known not to touch lexical variables we can skip creating TDZCheckCache + // for them. + // + // See the comment for TDZCheckCache class for more details. + enum class LexicalKind { + // For syntactic branches (if, if-else, and conditional expression), + // which basically may contain declaration or accesses to lexical + // variables inside then-clause, else-clause, and else-if condition. + MayContainLexicalAccessInBranch, + + // For internally used branches which don't touch lexical variables + // inside then-clause, else-clause, nor else-if condition. + NoLexicalAccessInBranch + }; + LexicalKind lexicalKind_; + + mozilla::Maybe tdzCache_; + +#ifdef DEBUG + // The number of values pushed in the then and else blocks. + int32_t pushed_ = 0; + bool calculatedPushed_ = false; +#endif + + protected: + BranchEmitterBase(BytecodeEmitter* bce, LexicalKind lexicalKind); + + [[nodiscard]] bool emitThenInternal(ConditionKind conditionKind); + void calculateOrCheckPushed(); + [[nodiscard]] bool emitElseInternal(); + [[nodiscard]] bool emitEndInternal(); + + public: +#ifdef DEBUG + // Returns the number of values pushed onto the value stack inside + // `then_block` and `else_block`. + // Can be used in assertion after emitting if-then-else. + int32_t pushed() const { return pushed_; } + + // Returns the number of values popped onto the value stack inside + // `then_block` and `else_block`. + // Can be used in assertion after emitting if-then-else. + int32_t popped() const { return -pushed_; } +#endif +}; + +// Class for emitting bytecode for blocks like if-then-else. +// +// This class can be used to emit single if-then-else block, or cascading +// else-if blocks. +// +// Usage: (check for the return value is omitted for simplicity) +// +// `if (cond) then_block` +// IfEmitter ifThen(this); +// ifThen.emitIf(Some(offset_of_if)); +// emit(cond); +// ifThen.emitThen(); +// emit(then_block); +// ifThen.emitEnd(); +// +// `if (!cond) then_block` +// IfEmitter ifThen(this); +// ifThen.emitIf(Some(offset_of_if)); +// emit(cond); +// ifThen.emitThen(IfEmitter::ConditionKind::Negative); +// emit(then_block); +// ifThen.emitEnd(); +// +// `if (cond) then_block else else_block` +// IfEmitter ifThenElse(this); +// ifThen.emitIf(Some(offset_of_if)); +// emit(cond); +// ifThenElse.emitThenElse(); +// emit(then_block); +// ifThenElse.emitElse(); +// emit(else_block); +// ifThenElse.emitEnd(); +// +// `if (c1) b1 else if (c2) b2 else if (c3) b3 else b4` +// IfEmitter ifThenElse(this); +// ifThen.emitIf(Some(offset_of_if)); +// emit(c1); +// ifThenElse.emitThenElse(); +// emit(b1); +// ifThenElse.emitElseIf(Some(offset_of_if)); +// emit(c2); +// ifThenElse.emitThenElse(); +// emit(b2); +// ifThenElse.emitElseIf(Some(offset_of_if)); +// emit(c3); +// ifThenElse.emitThenElse(); +// emit(b3); +// ifThenElse.emitElse(); +// emit(b4); +// ifThenElse.emitEnd(); +// +class MOZ_STACK_CLASS IfEmitter : public BranchEmitterBase { + public: + using ConditionKind = BranchEmitterBase::ConditionKind; + + protected: +#ifdef DEBUG + // The state of this emitter. + // + // +-------+ emitIf +----+ + // | Start |------->| If |-+ + // +-------+ +----+ | + // | + // +--------------------+ + // | + // v emitThen +------+ emitEnd +-----+ + // +->+--------->| Then |---------------------------->+-------->| End | + // ^ | +------+ ^ +-----+ + // | | | + // | | | + // | | | + // | | emitThenElse +----------+ emitElse +------+ | + // | +------------->| ThenElse |-+--------->| Else |-+ + // | +----------+ | +------+ + // | | + // | | emitElseIf +--------+ + // | +----------->| ElseIf |-+ + // | +--------+ | + // | | + // +------------------------------------------------------+ + enum class State { + // The initial state. + Start, + + // After calling emitIf. + If, + + // After calling emitThen. + Then, + + // After calling emitThenElse. + ThenElse, + + // After calling emitElse. + Else, + + // After calling emitElseIf. + ElseIf, + + // After calling emitEnd. + End + }; + State state_ = State::Start; +#endif + + protected: + // For InternalIfEmitter. + IfEmitter(BytecodeEmitter* bce, LexicalKind lexicalKind); + + public: + explicit IfEmitter(BytecodeEmitter* bce); + + // `ifPos` is the offset in the source code for the character below: + // + // if ( cond ) { ... } else if ( cond2 ) { ... } + // ^ ^ + // | | + // | ifPos for emitElseIf + // | + // ifPos for emitIf + // + // Can be Nothing() if not available. + [[nodiscard]] bool emitIf(const mozilla::Maybe& ifPos); + + [[nodiscard]] bool emitThen( + ConditionKind conditionKind = ConditionKind::Positive); + [[nodiscard]] bool emitThenElse( + ConditionKind conditionKind = ConditionKind::Positive); + + [[nodiscard]] bool emitElseIf(const mozilla::Maybe& ifPos); + [[nodiscard]] bool emitElse(); + + [[nodiscard]] bool emitEnd(); +}; + +// Class for emitting bytecode for blocks like if-then-else which doesn't touch +// lexical variables. +// +// See the comments above NoLexicalAccessInBranch for more details when to use +// this instead of IfEmitter. +// Compared to IfEmitter, this class doesn't have emitIf method, given that +// it doesn't have syntactic `if`, and also the `cond` value can be already +// on the stack. +// +// Usage: (check for the return value is omitted for simplicity) +// +// `if (cond) then_block else else_block` (effectively) +// emit(cond); +// InternalIfEmitter ifThenElse(this); +// ifThenElse.emitThenElse(); +// emit(then_block); +// ifThenElse.emitElse(); +// emit(else_block); +// ifThenElse.emitEnd(); +// +class MOZ_STACK_CLASS InternalIfEmitter : public IfEmitter { + public: + explicit InternalIfEmitter(BytecodeEmitter* bce); +}; + +// Class for emitting bytecode for conditional expression. +// +// Usage: (check for the return value is omitted for simplicity) +// +// `cond ? then_expr : else_expr` +// CondEmitter condElse(this); +// condElse.emitCond(); +// emit(cond); +// condElse.emitThenElse(); +// emit(then_expr); +// condElse.emitElse(); +// emit(else_expr); +// condElse.emitEnd(); +// +class MOZ_STACK_CLASS CondEmitter : public BranchEmitterBase { +#ifdef DEBUG + // The state of this emitter. + // + // +-------+ emitCond +------+ emitThenElse +----------+ + // | Start |--------->| Cond |------------->| ThenElse |-+ + // +-------+ +------+ +----------+ | + // | + // +-----------------+ + // | + // | emitElse +------+ emitEnd +-----+ + // +--------->| Else |-------->| End | + // +------+ +-----+ + enum class State { + // The initial state. + Start, + + // After calling emitCond. + Cond, + + // After calling emitThenElse. + ThenElse, + + // After calling emitElse. + Else, + + // After calling emitEnd. + End + }; + State state_ = State::Start; +#endif + + public: + explicit CondEmitter(BytecodeEmitter* bce); + + [[nodiscard]] bool emitCond(); + [[nodiscard]] bool emitThenElse( + ConditionKind conditionKind = ConditionKind::Positive); + [[nodiscard]] bool emitElse(); + [[nodiscard]] bool emitEnd(); +}; + +} /* namespace frontend */ +} /* namespace js */ + +#endif /* frontend_IfEmitter_h */ diff --git a/js/src/frontend/IteratorKind.h b/js/src/frontend/IteratorKind.h new file mode 100644 index 0000000000..7e8f0316b7 --- /dev/null +++ b/js/src/frontend/IteratorKind.h @@ -0,0 +1,16 @@ +/* -*- 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/. */ + +#ifndef frontend_IteratorKind_h +#define frontend_IteratorKind_h + +namespace js::frontend { + +enum class IteratorKind { Sync, Async }; + +} /* namespace js::frontend */ + +#endif /* frontend_IteratorKind_h */ diff --git a/js/src/frontend/JumpList.cpp b/js/src/frontend/JumpList.cpp new file mode 100644 index 0000000000..ec2628ad07 --- /dev/null +++ b/js/src/frontend/JumpList.cpp @@ -0,0 +1,46 @@ +/* -*- 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/JumpList.h" + +#include "mozilla/Assertions.h" // MOZ_ASSERT + +#include "vm/BytecodeUtil.h" // GET_JUMP_OFFSET, SET_JUMP_OFFSET, IsJumpOpcode + +using namespace js; +using namespace js::frontend; + +void JumpList::push(jsbytecode* code, BytecodeOffset jumpOffset) { + if (!offset.valid()) { + SET_JUMP_OFFSET(&code[jumpOffset.value()], END_OF_LIST_DELTA); + } else { + SET_JUMP_OFFSET(&code[jumpOffset.value()], (offset - jumpOffset).value()); + } + offset = jumpOffset; +} + +void JumpList::patchAll(jsbytecode* code, JumpTarget target) { + if (!offset.valid()) { + // This list is not used. Nothing to do. + return; + } + + BytecodeOffsetDiff delta; + BytecodeOffset jumpOffset = offset; + while (true) { + jsbytecode* pc = &code[jumpOffset.value()]; + MOZ_ASSERT(IsJumpOpcode(JSOp(*pc))); + delta = BytecodeOffsetDiff(GET_JUMP_OFFSET(pc)); + MOZ_ASSERT(delta.value() == END_OF_LIST_DELTA || delta.value() < 0); + BytecodeOffsetDiff span = target.offset - jumpOffset; + SET_JUMP_OFFSET(pc, span.value()); + + if (delta.value() == END_OF_LIST_DELTA) { + break; + } + jumpOffset += delta; + } +} diff --git a/js/src/frontend/JumpList.h b/js/src/frontend/JumpList.h new file mode 100644 index 0000000000..97846ce3de --- /dev/null +++ b/js/src/frontend/JumpList.h @@ -0,0 +1,84 @@ +/* -*- 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/. */ + +#ifndef frontend_JumpList_h +#define frontend_JumpList_h + +#include // ptrdiff_t + +#include "frontend/BytecodeOffset.h" // BytecodeOffset +#include "js/TypeDecls.h" // jsbytecode + +namespace js { +namespace frontend { + +// Linked list of jump instructions that need to be patched. The linked list is +// stored in the bytes of the incomplete bytecode that will be patched, so no +// extra memory is needed, and patching the instructions destroys the list. +// +// Example: +// +// JumpList brList; +// if (!emitJump(JSOp::JumpIfFalse, &brList)) { +// return false; +// } +// ... +// JumpTarget label; +// if (!emitJumpTarget(&label)) { +// return false; +// } +// ... +// if (!emitJump(JSOp::Goto, &brList)) { +// return false; +// } +// ... +// patchJumpsToTarget(brList, label); +// +// +-> (the delta is END_OF_LIST_DELTA (=0) for the last +// | item) +// | +// | +// JumpIfFalse .. <+ + +-+ JumpIfFalse .. +// .. | | .. +// label: | +-> label: +// JumpTarget | | JumpTarget +// .. | | .. +// Goto .. <+ +----+ +-+ Goto .. <+ +// | | +// | | +// + + +// brList brList +// +// | ^ +// +------- patchJumpsToTarget -------+ +// + +// Offset of a jump target instruction, used for patching jump instructions. +struct JumpTarget { + BytecodeOffset offset = BytecodeOffset::invalidOffset(); +}; + +struct JumpList { + // Delta value for pre-patchJumpsToTarget that marks the end of the link. + static const ptrdiff_t END_OF_LIST_DELTA = 0; + + // -1 is used to mark the end of jump lists. + JumpList() : offset(BytecodeOffset::invalidOffset()) {} + + BytecodeOffset offset; + + // Add a jump instruction to the list. + void push(jsbytecode* code, BytecodeOffset jumpOffset); + + // Patch all jump instructions in this list to jump to `target`. This + // clobbers the list. + void patchAll(jsbytecode* code, JumpTarget target); +}; + +} /* namespace frontend */ +} /* namespace js */ + +#endif /* frontend_JumpList_h */ diff --git a/js/src/frontend/LabelEmitter.cpp b/js/src/frontend/LabelEmitter.cpp new file mode 100644 index 0000000000..8b9b6233e6 --- /dev/null +++ b/js/src/frontend/LabelEmitter.cpp @@ -0,0 +1,40 @@ +/* -*- 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/LabelEmitter.h" + +#include "mozilla/Assertions.h" // MOZ_ASSERT + +#include "frontend/BytecodeEmitter.h" // BytecodeEmitter + +using namespace js; +using namespace js::frontend; + +void LabelEmitter::emitLabel(TaggedParserAtomIndex name) { + MOZ_ASSERT(state_ == State::Start); + + controlInfo_.emplace(bce_, name, bce_->bytecodeSection().offset()); + +#ifdef DEBUG + state_ = State::Label; +#endif +} + +bool LabelEmitter::emitEnd() { + MOZ_ASSERT(state_ == State::Label); + + // Patch the break/continue to this label. + if (!controlInfo_->patchBreaks(bce_)) { + return false; + } + + controlInfo_.reset(); + +#ifdef DEBUG + state_ = State::End; +#endif + return true; +} diff --git a/js/src/frontend/LabelEmitter.h b/js/src/frontend/LabelEmitter.h new file mode 100644 index 0000000000..f3d9ec67c0 --- /dev/null +++ b/js/src/frontend/LabelEmitter.h @@ -0,0 +1,65 @@ +/* -*- 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/. */ + +#ifndef frontend_LabelEmitter_h +#define frontend_LabelEmitter_h + +#include "mozilla/Attributes.h" // MOZ_STACK_CLASS +#include "mozilla/Maybe.h" // Maybe + +#include "frontend/BytecodeControlStructures.h" // LabelControl + +namespace js { +namespace frontend { + +struct BytecodeEmitter; +class TaggedParserAtomIndex; + +// Class for emitting labeled statement. +// +// Usage: (check for the return value is omitted for simplicity) +// +// `label: expr;` +// LabelEmitter le(this); +// le.emitLabel(name_of_label); +// emit(expr); +// le.emitEnd(); +// +class MOZ_STACK_CLASS LabelEmitter { + BytecodeEmitter* bce_; + + mozilla::Maybe controlInfo_; + +#ifdef DEBUG + // The state of this emitter. + // + // +-------+ emitLabel +-------+ emitEnd +-----+ + // | Start |---------->| Label |-------->| End | + // +-------+ +-------+ +-----+ + enum class State { + // The initial state. + Start, + + // After calling emitLabel. + Label, + + // After calling emitEnd. + End + }; + State state_ = State::Start; +#endif + + public: + explicit LabelEmitter(BytecodeEmitter* bce) : bce_(bce) {} + + void emitLabel(TaggedParserAtomIndex name); + [[nodiscard]] bool emitEnd(); +}; + +} /* namespace frontend */ +} /* namespace js */ + +#endif /* frontend_LabelEmitter_h */ diff --git a/js/src/frontend/LexicalScopeEmitter.cpp b/js/src/frontend/LexicalScopeEmitter.cpp new file mode 100644 index 0000000000..aeda8df9de --- /dev/null +++ b/js/src/frontend/LexicalScopeEmitter.cpp @@ -0,0 +1,57 @@ +/* -*- 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/LexicalScopeEmitter.h" + +using namespace js; +using namespace js::frontend; + +LexicalScopeEmitter::LexicalScopeEmitter(BytecodeEmitter* bce) : bce_(bce) {} + +bool LexicalScopeEmitter::emitScope(ScopeKind kind, + LexicalScope::ParserData* bindings) { + MOZ_ASSERT(state_ == State::Start); + MOZ_ASSERT(bindings); + + tdzCache_.emplace(bce_); + emitterScope_.emplace(bce_); + if (!emitterScope_->enterLexical(bce_, kind, bindings)) { + return false; + } + +#ifdef DEBUG + state_ = State::Scope; +#endif + return true; +} + +bool LexicalScopeEmitter::emitEmptyScope() { + MOZ_ASSERT(state_ == State::Start); + + tdzCache_.emplace(bce_); + +#ifdef DEBUG + state_ = State::Scope; +#endif + return true; +} + +bool LexicalScopeEmitter::emitEnd() { + MOZ_ASSERT(state_ == State::Scope); + + if (emitterScope_) { + if (!emitterScope_->leave(bce_)) { + return false; + } + emitterScope_.reset(); + } + tdzCache_.reset(); + +#ifdef DEBUG + state_ = State::End; +#endif + return true; +} diff --git a/js/src/frontend/LexicalScopeEmitter.h b/js/src/frontend/LexicalScopeEmitter.h new file mode 100644 index 0000000000..379ecc0a89 --- /dev/null +++ b/js/src/frontend/LexicalScopeEmitter.h @@ -0,0 +1,94 @@ +/* -*- 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/. */ + +#ifndef frontend_LexicalScopeEmitter_h +#define frontend_LexicalScopeEmitter_h + +#include "mozilla/Attributes.h" // MOZ_STACK_CLASS +#include "mozilla/Maybe.h" // Maybe + +#include "frontend/EmitterScope.h" // EmitterScope +#include "frontend/TDZCheckCache.h" // TDZCheckCache +#include "vm/Scope.h" // ScopeKind, LexicalScope + +namespace js { +namespace frontend { + +struct BytecodeEmitter; + +// Class for emitting bytecode for lexical scope. +// +// In addition to emitting code for entering and leaving a scope, this RAII +// guard affects the code emitted for `break` and other non-structured +// control flow. See NonLocalExitControl::prepareForNonLocalJump(). +// +// Usage: (check for the return value is omitted for simplicity) +// +// `{ ... }` -- lexical scope with no bindings +// LexicalScopeEmitter lse(this); +// lse.emitEmptyScope(); +// emit(scopeBody); +// lse.emitEnd(); +// +// `{ let a; body }` +// LexicalScopeEmitter lse(this); +// lse.emitScope(ScopeKind::Lexical, scopeBinding); +// emit(let_and_body); +// lse.emitEnd(); +// +// `catch (e) { body }` +// LexicalScopeEmitter lse(this); +// lse.emitScope(ScopeKind::SimpleCatch, scopeBinding); +// emit(body); +// lse.emitEnd(); +// +// `catch ([a, b]) { body }` +// LexicalScopeEmitter lse(this); +// lse.emitScope(ScopeKind::Catch, scopeBinding); +// emit(body); +// lse.emitEnd(); +class MOZ_STACK_CLASS LexicalScopeEmitter { + BytecodeEmitter* bce_; + + mozilla::Maybe tdzCache_; + mozilla::Maybe emitterScope_; + +#ifdef DEBUG + // The state of this emitter. + // + // +-------+ emitScope +-------+ emitEnd +-----+ + // | Start |----------->| Scope |--------->| End | + // +-------+ +-------+ +-----+ + enum class State { + // The initial state. + Start, + + // After calling emitScope/emitEmptyScope. + Scope, + + // After calling emitEnd. + End, + }; + State state_ = State::Start; +#endif + + public: + explicit LexicalScopeEmitter(BytecodeEmitter* bce); + + // Returns the scope object for non-empty scope. + const EmitterScope& emitterScope() const { return *emitterScope_; } + + [[nodiscard]] bool emitScope(ScopeKind kind, + LexicalScope::ParserData* bindings); + [[nodiscard]] bool emitEmptyScope(); + + [[nodiscard]] bool emitEnd(); +}; + +} /* namespace frontend */ +} /* namespace js */ + +#endif /* frontend_LexicalScopeEmitter_h */ diff --git a/js/src/frontend/ModuleSharedContext.h b/js/src/frontend/ModuleSharedContext.h new file mode 100644 index 0000000000..fad729bd30 --- /dev/null +++ b/js/src/frontend/ModuleSharedContext.h @@ -0,0 +1,47 @@ +/* -*- 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/. */ + +#ifndef frontend_ModuleSharedContext_h +#define frontend_ModuleSharedContext_h + +#include "mozilla/Assertions.h" // MOZ_ASSERT +#include "mozilla/Attributes.h" // MOZ_STACK_CLASS + +#include "jstypes.h" + +#include "frontend/SharedContext.h" // js::frontend::SharedContext +#include "vm/Scope.h" // js::ModuleScope + +namespace JS { +class JS_PUBLIC_API ReadOnlyCompileOptions; +} + +namespace js { + +class ModuleBuilder; +struct SourceExtent; + +namespace frontend { + +class MOZ_STACK_CLASS ModuleSharedContext : public SuspendableContext { + public: + ModuleScope::ParserData* bindings; + ModuleBuilder& builder; + + ModuleSharedContext(FrontendContext* fc, + const JS::ReadOnlyCompileOptions& options, + ModuleBuilder& builder, SourceExtent extent); +}; + +inline ModuleSharedContext* SharedContext::asModuleContext() { + MOZ_ASSERT(isModuleContext()); + return static_cast(this); +} + +} // namespace frontend +} // namespace js + +#endif /* frontend_ModuleSharedContext_h */ diff --git a/js/src/frontend/NameAnalysisTypes.h b/js/src/frontend/NameAnalysisTypes.h new file mode 100644 index 0000000000..d2f44c170c --- /dev/null +++ b/js/src/frontend/NameAnalysisTypes.h @@ -0,0 +1,390 @@ +/* -*- 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/. */ + +#ifndef frontend_NameAnalysisTypes_h +#define frontend_NameAnalysisTypes_h + +#include "mozilla/Assertions.h" // MOZ_ASSERT, MOZ_CRASH +#include "mozilla/Casting.h" // mozilla::AssertedCast + +#include // uint8_t, uint16_t, uint32_t + +#include "vm/BindingKind.h" // BindingKind, BindingLocation +#include "vm/BytecodeFormatFlags.h" // JOF_ENVCOORD +#include "vm/BytecodeUtil.h" // ENVCOORD_HOPS_BITS, ENVCOORD_SLOT_BITS, GET_ENVCOORD_HOPS, GET_ENVCOORD_SLOT, ENVCOORD_HOPS_LEN, JOF_OPTYPE, JSOp, LOCALNO_LIMIT + +namespace js { + +// An "environment coordinate" describes how to get from head of the +// environment chain to a given lexically-enclosing variable. An environment +// coordinate has two dimensions: +// - hops: the number of environment objects on the scope chain to skip +// - slot: the slot on the environment object holding the variable's value +class EnvironmentCoordinate { + uint32_t hops_; + uint32_t slot_; + + // Technically, hops_/slot_ are ENVCOORD_(HOPS|SLOT)_BITS wide. Since + // EnvironmentCoordinate is a temporary value, don't bother with a bitfield as + // this only adds overhead. + static_assert(ENVCOORD_HOPS_BITS <= 32, "We have enough bits below"); + static_assert(ENVCOORD_SLOT_BITS <= 32, "We have enough bits below"); + + public: + explicit inline EnvironmentCoordinate(jsbytecode* pc) + : hops_(GET_ENVCOORD_HOPS(pc)), + slot_(GET_ENVCOORD_SLOT(pc + ENVCOORD_HOPS_LEN)) { + MOZ_ASSERT(JOF_OPTYPE(JSOp(*pc)) == JOF_ENVCOORD || + JOF_OPTYPE(JSOp(*pc)) == JOF_DEBUGCOORD); + } + + EnvironmentCoordinate() = default; + + void setHops(uint32_t hops) { + MOZ_ASSERT(hops < ENVCOORD_HOPS_LIMIT); + hops_ = hops; + } + + void setSlot(uint32_t slot) { + MOZ_ASSERT(slot < ENVCOORD_SLOT_LIMIT); + slot_ = slot; + } + + uint32_t hops() const { + MOZ_ASSERT(hops_ < ENVCOORD_HOPS_LIMIT); + return hops_; + } + + uint32_t slot() const { + MOZ_ASSERT(slot_ < ENVCOORD_SLOT_LIMIT); + return slot_; + } + + bool operator==(const EnvironmentCoordinate& rhs) const { + return hops() == rhs.hops() && slot() == rhs.slot(); + } +}; + +namespace frontend { + +enum class ParseGoal : uint8_t { Script, Module }; + +// A detailed kind used for tracking declarations in the Parser. Used for +// specific early error semantics and better error messages. +enum class DeclarationKind : uint8_t { + PositionalFormalParameter, + FormalParameter, + CoverArrowParameter, + Var, + Let, + Const, + Class, // Handled as same as `let` after parsing. + Import, + BodyLevelFunction, + ModuleBodyLevelFunction, + LexicalFunction, + SloppyLexicalFunction, + VarForAnnexBLexicalFunction, + SimpleCatchParameter, + CatchParameter, + PrivateName, + Synthetic, + PrivateMethod, // slot to store nonstatic private method +}; + +// Class field kind. +enum class FieldPlacement : uint8_t { Unspecified, Instance, Static }; + +static inline BindingKind DeclarationKindToBindingKind(DeclarationKind kind) { + switch (kind) { + case DeclarationKind::PositionalFormalParameter: + case DeclarationKind::FormalParameter: + case DeclarationKind::CoverArrowParameter: + return BindingKind::FormalParameter; + + case DeclarationKind::Var: + case DeclarationKind::BodyLevelFunction: + case DeclarationKind::ModuleBodyLevelFunction: + case DeclarationKind::VarForAnnexBLexicalFunction: + return BindingKind::Var; + + case DeclarationKind::Let: + case DeclarationKind::Class: + case DeclarationKind::LexicalFunction: + case DeclarationKind::SloppyLexicalFunction: + case DeclarationKind::SimpleCatchParameter: + case DeclarationKind::CatchParameter: + return BindingKind::Let; + + case DeclarationKind::Const: + return BindingKind::Const; + + case DeclarationKind::Import: + return BindingKind::Import; + + case DeclarationKind::Synthetic: + case DeclarationKind::PrivateName: + return BindingKind::Synthetic; + + case DeclarationKind::PrivateMethod: + return BindingKind::PrivateMethod; + } + + MOZ_CRASH("Bad DeclarationKind"); +} + +static inline bool DeclarationKindIsLexical(DeclarationKind kind) { + return BindingKindIsLexical(DeclarationKindToBindingKind(kind)); +} + +// Used in Parser and BytecodeEmitter to track the kind of a private name. +enum class PrivateNameKind : uint8_t { + None, + Field, + Method, + Getter, + Setter, + GetterSetter, +}; + +enum class ClosedOver : bool { No = false, Yes = true }; + +// Used in Parser to track declared names. +class DeclaredNameInfo { + uint32_t pos_; + DeclarationKind kind_; + + // If the declared name is a binding, whether the binding is closed + // over. Its value is meaningless if the declared name is not a binding + // (i.e., a 'var' declared name in a non-var scope). + bool closedOver_; + + PrivateNameKind privateNameKind_; + + // Only updated for private names (see noteDeclaredPrivateName), + // tracks if declaration was instance or static to allow issuing + // early errors in the case where we mismatch instance and static + // private getter/setters. + FieldPlacement placement_; + + public: + explicit DeclaredNameInfo(DeclarationKind kind, uint32_t pos, + ClosedOver closedOver = ClosedOver::No) + : pos_(pos), + kind_(kind), + closedOver_(bool(closedOver)), + privateNameKind_(PrivateNameKind::None), + placement_(FieldPlacement::Unspecified) {} + + // Needed for InlineMap. + DeclaredNameInfo() = default; + + DeclarationKind kind() const { return kind_; } + + static const uint32_t npos = uint32_t(-1); + + uint32_t pos() const { return pos_; } + + void alterKind(DeclarationKind kind) { kind_ = kind; } + + void setClosedOver() { closedOver_ = true; } + + bool closedOver() const { return closedOver_; } + + void setPrivateNameKind(PrivateNameKind privateNameKind) { + privateNameKind_ = privateNameKind; + } + + void setFieldPlacement(FieldPlacement placement) { + MOZ_ASSERT(placement != FieldPlacement::Unspecified); + placement_ = placement; + } + + PrivateNameKind privateNameKind() const { return privateNameKind_; } + + FieldPlacement placement() const { return placement_; } +}; + +// Used in BytecodeEmitter to map names to locations. +class NameLocation { + public: + enum class Kind : uint8_t { + // Cannot statically determine where the name lives. Needs to walk the + // environment chain to search for the name. + Dynamic, + + // The name lives on the global or is a global lexical binding. Search + // for the name on the global scope. + Global, + + // Special mode used only when emitting self-hosted scripts. See + // BytecodeEmitter::lookupName. + Intrinsic, + + // In a named lambda, the name is the callee itself. + NamedLambdaCallee, + + // The name is a positional formal parameter name and can be retrieved + // directly from the stack using slot_. + ArgumentSlot, + + // The name is not closed over and lives on the frame in slot_. + FrameSlot, + + // The name is closed over and lives on an environment hops_ away in slot_. + EnvironmentCoordinate, + + // The name is closed over and lives on an environment hops_ away in slot_, + // where one or more of the environments may be a DebugEnvironmentProxy + DebugEnvironmentCoordinate, + + // An imported name in a module. + Import, + + // Cannot statically determine where the synthesized var for Annex + // B.3.3 lives. + DynamicAnnexBVar + }; + + private: + // Where the name lives. + Kind kind_; + + // If the name is not Dynamic or DynamicAnnexBVar, the kind of the + // binding. + BindingKind bindingKind_; + + // If the name is closed over and accessed via EnvironmentCoordinate, the + // number of dynamic environments to skip. + // + // Otherwise UINT8_MAX. + uint8_t hops_; + + // If the name lives on the frame, the slot frame. + // + // If the name is closed over and accessed via EnvironmentCoordinate, the + // slot on the environment. + // + // Otherwise 0. + uint32_t slot_ : ENVCOORD_SLOT_BITS; + + static_assert(LOCALNO_BITS == ENVCOORD_SLOT_BITS, + "Frame and environment slots must be same sized."); + + NameLocation(Kind kind, BindingKind bindingKind, uint8_t hops = UINT8_MAX, + uint32_t slot = 0) + : kind_(kind), bindingKind_(bindingKind), hops_(hops), slot_(slot) {} + + public: + // Default constructor for InlineMap. + NameLocation() = default; + + static NameLocation Dynamic() { + return NameLocation(Kind::Dynamic, BindingKind::Import); + } + + static NameLocation Global(BindingKind bindKind) { + MOZ_ASSERT(bindKind != BindingKind::FormalParameter); + return NameLocation(Kind::Global, bindKind); + } + + static NameLocation Intrinsic() { + return NameLocation(Kind::Intrinsic, BindingKind::Var); + } + + static NameLocation NamedLambdaCallee() { + return NameLocation(Kind::NamedLambdaCallee, + BindingKind::NamedLambdaCallee); + } + + static NameLocation ArgumentSlot(uint16_t slot) { + return NameLocation(Kind::ArgumentSlot, BindingKind::FormalParameter, 0, + slot); + } + + static NameLocation FrameSlot(BindingKind bindKind, uint32_t slot) { + MOZ_ASSERT(slot < LOCALNO_LIMIT); + return NameLocation(Kind::FrameSlot, bindKind, 0, slot); + } + + static NameLocation EnvironmentCoordinate(BindingKind bindKind, uint8_t hops, + uint32_t slot) { + MOZ_ASSERT(slot < ENVCOORD_SLOT_LIMIT); + return NameLocation(Kind::EnvironmentCoordinate, bindKind, hops, slot); + } + static NameLocation DebugEnvironmentCoordinate(BindingKind bindKind, + uint8_t hops, uint32_t slot) { + MOZ_ASSERT(slot < ENVCOORD_SLOT_LIMIT); + return NameLocation(Kind::DebugEnvironmentCoordinate, bindKind, hops, slot); + } + + static NameLocation Import() { + return NameLocation(Kind::Import, BindingKind::Import); + } + + static NameLocation DynamicAnnexBVar() { + return NameLocation(Kind::DynamicAnnexBVar, BindingKind::Var); + } + + bool operator==(const NameLocation& other) const { + return kind_ == other.kind_ && bindingKind_ == other.bindingKind_ && + hops_ == other.hops_ && slot_ == other.slot_; + } + + bool operator!=(const NameLocation& other) const { return !(*this == other); } + + Kind kind() const { return kind_; } + + uint16_t argumentSlot() const { + MOZ_ASSERT(kind_ == Kind::ArgumentSlot); + return mozilla::AssertedCast(slot_); + } + + uint32_t frameSlot() const { + MOZ_ASSERT(kind_ == Kind::FrameSlot); + return slot_; + } + + NameLocation addHops(uint8_t more) { + MOZ_ASSERT(hops_ < ENVCOORD_HOPS_LIMIT - more); + MOZ_ASSERT(kind_ == Kind::EnvironmentCoordinate); + return NameLocation(kind_, bindingKind_, hops_ + more, slot_); + } + + class EnvironmentCoordinate environmentCoordinate() const { + MOZ_ASSERT(kind_ == Kind::EnvironmentCoordinate || + kind_ == Kind::DebugEnvironmentCoordinate); + class EnvironmentCoordinate coord; + coord.setHops(hops_); + coord.setSlot(slot_); + return coord; + } + + BindingKind bindingKind() const { + MOZ_ASSERT(kind_ != Kind::Dynamic); + return bindingKind_; + } + + bool isLexical() const { return BindingKindIsLexical(bindingKind()); } + + bool isConst() const { return bindingKind() == BindingKind::Const; } + + bool isSynthetic() const { return bindingKind() == BindingKind::Synthetic; } + + bool isPrivateMethod() const { + return bindingKind() == BindingKind::PrivateMethod; + } + + bool hasKnownSlot() const { + return kind_ == Kind::ArgumentSlot || kind_ == Kind::FrameSlot || + kind_ == Kind::EnvironmentCoordinate; + } +}; + +} // namespace frontend +} // namespace js + +#endif // frontend_NameAnalysisTypes_h diff --git a/js/src/frontend/NameCollections.h b/js/src/frontend/NameCollections.h new file mode 100644 index 0000000000..57806a6984 --- /dev/null +++ b/js/src/frontend/NameCollections.h @@ -0,0 +1,455 @@ +/* -*- 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/. */ + +#ifndef frontend_NameCollections_h +#define frontend_NameCollections_h + +#include "mozilla/Assertions.h" // MOZ_ASSERT +#include "mozilla/Attributes.h" // MOZ_IMPLICIT + +#include // size_t +#include // uint32_t, uint64_t +#include // std::{true_type, false_type, is_trivial_v, is_trivially_copyable_v, is_trivially_destructible_v} +#include // std::forward + +#include "ds/InlineTable.h" // InlineMap, DefaultKeyPolicy +#include "frontend/NameAnalysisTypes.h" // AtomVector, FunctionBoxVector +#include "frontend/ParserAtom.h" // TaggedParserAtomIndex, TrivialTaggedParserAtomIndex +#include "frontend/TaggedParserAtomIndexHasher.h" // TrivialTaggedParserAtomIndexHasher +#include "js/AllocPolicy.h" // SystemAllocPolicy, ReportOutOfMemory +#include "js/Utility.h" // js_new, js_delete +#include "js/Vector.h" // Vector + +namespace js { + +namespace detail { + +// For InlineMap. +// See DefaultKeyPolicy definition in InlineTable.h for more details. +template <> +class DefaultKeyPolicy { + public: + DefaultKeyPolicy() = delete; + DefaultKeyPolicy(const frontend::TrivialTaggedParserAtomIndex&) = delete; + + static bool isTombstone(const frontend::TrivialTaggedParserAtomIndex& atom) { + return atom.isNull(); + } + static void setToTombstone(frontend::TrivialTaggedParserAtomIndex& atom) { + atom = frontend::TrivialTaggedParserAtomIndex::null(); + } +}; + +} // namespace detail + +namespace frontend { + +class FunctionBox; + +// A pool of recyclable containers for use in the frontend. The Parser and +// BytecodeEmitter create many maps for name analysis that are short-lived +// (i.e., for the duration of parsing or emitting a lexical scope). Making +// them recyclable cuts down significantly on allocator churn. +template +class CollectionPool { + using RecyclableCollections = Vector; + + RecyclableCollections all_; + RecyclableCollections recyclable_; + + static RepresentativeCollection* asRepresentative(void* p) { + return reinterpret_cast(p); + } + + RepresentativeCollection* allocate() { + size_t newAllLength = all_.length() + 1; + if (!all_.reserve(newAllLength) || !recyclable_.reserve(newAllLength)) { + return nullptr; + } + + RepresentativeCollection* collection = js_new(); + if (collection) { + all_.infallibleAppend(collection); + } + return collection; + } + + public: + ~CollectionPool() { purgeAll(); } + + void purgeAll() { + void** end = all_.end(); + for (void** it = all_.begin(); it != end; ++it) { + js_delete(asRepresentative(*it)); + } + + all_.clearAndFree(); + recyclable_.clearAndFree(); + } + + // Fallibly aquire one of the supported collection types from the pool. + template + Collection* acquire(FrontendContext* fc) { + ConcreteCollectionPool::template assertInvariants(); + + RepresentativeCollection* collection; + if (recyclable_.empty()) { + collection = allocate(); + if (!collection) { + ReportOutOfMemory(fc); + } + } else { + collection = asRepresentative(recyclable_.popCopy()); + collection->clear(); + } + return reinterpret_cast(collection); + } + + // Release a collection back to the pool. + template + void release(Collection** collection) { + ConcreteCollectionPool::template assertInvariants(); + MOZ_ASSERT(*collection); + +#ifdef DEBUG + bool ok = false; + // Make sure the collection is in |all_| but not already in |recyclable_|. + for (void** it = all_.begin(); it != all_.end(); ++it) { + if (*it == *collection) { + ok = true; + break; + } + } + MOZ_ASSERT(ok); + for (void** it = recyclable_.begin(); it != recyclable_.end(); ++it) { + MOZ_ASSERT(*it != *collection); + } +#endif + + MOZ_ASSERT(recyclable_.length() < all_.length()); + // Reserved in allocateFresh. + recyclable_.infallibleAppend(*collection); + *collection = nullptr; + } +}; + +template +struct RecyclableAtomMapValueWrapper { + using WrappedType = Wrapped; + + union { + Wrapped wrapped; + uint64_t dummy; + }; + + static void assertInvariant() { + static_assert(sizeof(Wrapped) <= sizeof(uint64_t), + "Can only recycle atom maps with values smaller than uint64"); + } + + RecyclableAtomMapValueWrapper() : dummy(0) { assertInvariant(); } + + MOZ_IMPLICIT RecyclableAtomMapValueWrapper(Wrapped w) : wrapped(w) { + assertInvariant(); + } + + MOZ_IMPLICIT operator Wrapped&() { return wrapped; } + + MOZ_IMPLICIT operator Wrapped&() const { return wrapped; } + + Wrapped* operator->() { return &wrapped; } + + const Wrapped* operator->() const { return &wrapped; } +}; + +template +using RecyclableNameMapBase = + InlineMap, 24, + TrivialTaggedParserAtomIndexHasher, SystemAllocPolicy>; + +// Define wrapper methods to accept TaggedParserAtomIndex. +template +class RecyclableNameMap : public RecyclableNameMapBase { + using Base = RecyclableNameMapBase; + + public: + template + [[nodiscard]] MOZ_ALWAYS_INLINE bool add(typename Base::AddPtr& p, + const TaggedParserAtomIndex& key, + Args&&... args) { + return Base::add(p, TrivialTaggedParserAtomIndex::from(key), + std::forward(args)...); + } + + MOZ_ALWAYS_INLINE + typename Base::Ptr lookup(const TaggedParserAtomIndex& l) { + return Base::lookup(TrivialTaggedParserAtomIndex::from(l)); + } + + MOZ_ALWAYS_INLINE + typename Base::AddPtr lookupForAdd(const TaggedParserAtomIndex& l) { + return Base::lookupForAdd(TrivialTaggedParserAtomIndex::from(l)); + } +}; + +using DeclaredNameMap = RecyclableNameMap; +using NameLocationMap = RecyclableNameMap; +// Cannot use GCThingIndex here because it's not trivial type. +using AtomIndexMap = RecyclableNameMap; + +template +class InlineTablePool + : public CollectionPool> { + template + struct IsRecyclableAtomMapValueWrapper : std::false_type {}; + + template + struct IsRecyclableAtomMapValueWrapper> + : std::true_type {}; + + public: + template + static void assertInvariants() { + static_assert( + Table::SizeOfInlineEntries == RepresentativeTable::SizeOfInlineEntries, + "Only tables with the same size for inline entries are usable in the " + "pool."); + + using EntryType = typename Table::Table::Entry; + using KeyType = typename EntryType::KeyType; + using ValueType = typename EntryType::ValueType; + + static_assert(IsRecyclableAtomMapValueWrapper::value, + "Please adjust the static assertions below if you need to " + "support other types than RecyclableAtomMapValueWrapper"); + + using WrappedType = typename ValueType::WrappedType; + + // We can't directly check |std::is_trivial|, because neither + // mozilla::HashMapEntry nor IsRecyclableAtomMapValueWrapper are trivially + // default constructible. Instead we check that the key and the unwrapped + // value are trivial and additionally ensure that the entry itself is + // trivially copyable and destructible. + + static_assert(std::is_trivial_v, + "Only tables with trivial keys are usable in the pool."); + static_assert(std::is_trivial_v, + "Only tables with trivial values are usable in the pool."); + + static_assert( + std::is_trivially_copyable_v, + "Only tables with trivially copyable entries are usable in the pool."); + static_assert(std::is_trivially_destructible_v, + "Only tables with trivially destructible entries are usable " + "in the pool."); + } +}; + +template +class VectorPool : public CollectionPool> { + public: + template + static void assertInvariants() { + static_assert( + Vector::sMaxInlineStorage == RepresentativeVector::sMaxInlineStorage, + "Only vectors with the same size for inline entries are usable in the " + "pool."); + + using ElementType = typename Vector::ElementType; + + static_assert(std::is_trivial_v, + "Only vectors of trivial values are usable in the pool."); + static_assert(std::is_trivially_destructible_v, + "Only vectors of trivially destructible values are usable in " + "the pool."); + + static_assert( + sizeof(ElementType) == + sizeof(typename RepresentativeVector::ElementType), + "Only vectors with same-sized elements are usable in the pool."); + } +}; + +using AtomVector = Vector; + +using FunctionBoxVector = Vector; + +class NameCollectionPool { + InlineTablePool mapPool_; + VectorPool atomVectorPool_; + VectorPool functionBoxVectorPool_; + uint32_t activeCompilations_; + + public: + NameCollectionPool() : activeCompilations_(0) {} + + bool hasActiveCompilation() const { return activeCompilations_ != 0; } + + void addActiveCompilation() { activeCompilations_++; } + + void removeActiveCompilation() { + MOZ_ASSERT(hasActiveCompilation()); + activeCompilations_--; + } + + template + Map* acquireMap(FrontendContext* fc) { + MOZ_ASSERT(hasActiveCompilation()); + return mapPool_.acquire(fc); + } + + template + void releaseMap(Map** map) { + MOZ_ASSERT(hasActiveCompilation()); + MOZ_ASSERT(map); + if (*map) { + mapPool_.release(map); + } + } + + template + inline Vector* acquireVector(FrontendContext* fc); + + template + inline void releaseVector(Vector** vec); + + void purge() { + if (!hasActiveCompilation()) { + mapPool_.purgeAll(); + atomVectorPool_.purgeAll(); + functionBoxVectorPool_.purgeAll(); + } + } +}; + +template <> +inline AtomVector* NameCollectionPool::acquireVector( + FrontendContext* fc) { + MOZ_ASSERT(hasActiveCompilation()); + return atomVectorPool_.acquire(fc); +} + +template <> +inline void NameCollectionPool::releaseVector(AtomVector** vec) { + MOZ_ASSERT(hasActiveCompilation()); + MOZ_ASSERT(vec); + if (*vec) { + atomVectorPool_.release(vec); + } +} + +template <> +inline FunctionBoxVector* NameCollectionPool::acquireVector( + FrontendContext* fc) { + MOZ_ASSERT(hasActiveCompilation()); + return functionBoxVectorPool_.acquire(fc); +} + +template <> +inline void NameCollectionPool::releaseVector( + FunctionBoxVector** vec) { + MOZ_ASSERT(hasActiveCompilation()); + MOZ_ASSERT(vec); + if (*vec) { + functionBoxVectorPool_.release(vec); + } +} + +template typename Impl> +class PooledCollectionPtr { + NameCollectionPool& pool_; + T* collection_ = nullptr; + + protected: + ~PooledCollectionPtr() { Impl::releaseCollection(pool_, &collection_); } + + T& collection() { + MOZ_ASSERT(collection_); + return *collection_; + } + + const T& collection() const { + MOZ_ASSERT(collection_); + return *collection_; + } + + public: + explicit PooledCollectionPtr(NameCollectionPool& pool) : pool_(pool) {} + + bool acquire(FrontendContext* fc) { + MOZ_ASSERT(!collection_); + collection_ = Impl::acquireCollection(fc, pool_); + return !!collection_; + } + + explicit operator bool() const { return !!collection_; } + + T* operator->() { return &collection(); } + + const T* operator->() const { return &collection(); } + + T& operator*() { return collection(); } + + const T& operator*() const { return collection(); } +}; + +template +class PooledMapPtr : public PooledCollectionPtr { + friend class PooledCollectionPtr; + + static Map* acquireCollection(FrontendContext* fc, NameCollectionPool& pool) { + return pool.acquireMap(fc); + } + + static void releaseCollection(NameCollectionPool& pool, Map** ptr) { + pool.releaseMap(ptr); + } + + using Base = PooledCollectionPtr; + + public: + using Base::Base; + + ~PooledMapPtr() = default; +}; + +template +class PooledVectorPtr : public PooledCollectionPtr { + friend class PooledCollectionPtr; + + static Vector* acquireCollection(FrontendContext* fc, + NameCollectionPool& pool) { + return pool.acquireVector(fc); + } + + static void releaseCollection(NameCollectionPool& pool, Vector** ptr) { + pool.releaseVector(ptr); + } + + using Base = PooledCollectionPtr; + using Base::collection; + + public: + using Base::Base; + + ~PooledVectorPtr() = default; + + typename Vector::ElementType& operator[](size_t index) { + return collection()[index]; + } + + const typename Vector::ElementType& operator[](size_t index) const { + return collection()[index]; + } +}; + +} // namespace frontend +} // namespace js + +#endif // frontend_NameCollections_h diff --git a/js/src/frontend/NameFunctions.cpp b/js/src/frontend/NameFunctions.cpp new file mode 100644 index 0000000000..0ad8e55758 --- /dev/null +++ b/js/src/frontend/NameFunctions.cpp @@ -0,0 +1,531 @@ +/* -*- 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/NameFunctions.h" + +#include "mozilla/ScopeExit.h" +#include "mozilla/Sprintf.h" + +#include "frontend/ParseNode.h" +#include "frontend/ParseNodeVisitor.h" +#include "frontend/ParserAtom.h" // ParserAtomsTable +#include "frontend/SharedContext.h" +#include "util/Poison.h" +#include "util/StringBuffer.h" + +using namespace js; +using namespace js::frontend; + +namespace { + +class NameResolver : public ParseNodeVisitor { + using Base = ParseNodeVisitor; + + static const size_t MaxParents = 100; + + FrontendContext* fc_; + ParserAtomsTable& parserAtoms_; + TaggedParserAtomIndex prefix_; + + // Number of nodes in the parents array. + size_t nparents_; + + // Stack of ParseNodes from the root to the current node. + // Only elements 0..nparents_ are initialized. + MOZ_INIT_OUTSIDE_CTOR + ParseNode* parents_[MaxParents]; + + // When naming a function, the buffer where the name is built. + // When we are not under resolveFun, buf_ is empty. + StringBuffer buf_; + + /* Test whether a ParseNode represents a function invocation */ + bool isCall(ParseNode* pn) { + return pn && pn->isKind(ParseNodeKind::CallExpr); + } + + /* + * Append a reference to a property named |name| to |buf_|. If |name| is + * a proper identifier name, then we append '.name'; otherwise, we + * append '["name"]'. + * + * Note that we need the IsIdentifier check for atoms from both + * ParseNodeKind::Name nodes and ParseNodeKind::String nodes: + * given code like a["b c"], the front end will produce a ParseNodeKind::Dot + * with a ParseNodeKind::Name child whose name contains spaces. + */ + bool appendPropertyReference(TaggedParserAtomIndex name) { + if (parserAtoms_.isIdentifier(name)) { + return buf_.append('.') && buf_.append(parserAtoms_, name); + } + + /* Quote the string as needed. */ + UniqueChars source = parserAtoms_.toQuotedString(name); + if (!source) { + ReportOutOfMemory(fc_); + return false; + } + return buf_.append('[') && + buf_.append(source.get(), strlen(source.get())) && buf_.append(']'); + } + + /* Append a number to buf_. */ + bool appendNumber(double n) { + char number[30]; + int digits = SprintfLiteral(number, "%g", n); + return buf_.append(number, digits); + } + + // Append "[]" to buf_, referencing a property named by a numeric literal. + bool appendNumericPropertyReference(double n) { + return buf_.append("[") && appendNumber(n) && buf_.append(']'); + } + + /* + * Walk over the given ParseNode, attempting to convert it to a stringified + * name that represents where the function is being assigned to. + * + * |*foundName| is set to true if a name is found for the expression. + */ + bool nameExpression(ParseNode* n, bool* foundName) { + switch (n->getKind()) { + case ParseNodeKind::DotExpr: { + PropertyAccess* prop = &n->as(); + if (!nameExpression(&prop->expression(), foundName)) { + return false; + } + if (!*foundName) { + return true; + } + return appendPropertyReference(prop->right()->as().atom()); + } + + case ParseNodeKind::Name: + case ParseNodeKind::PrivateName: { + *foundName = true; + return buf_.append(parserAtoms_, n->as().atom()); + } + + case ParseNodeKind::ThisExpr: + *foundName = true; + return buf_.append("this"); + + case ParseNodeKind::ElemExpr: { + PropertyByValue* elem = &n->as(); + if (!nameExpression(&elem->expression(), foundName)) { + return false; + } + if (!*foundName) { + return true; + } + if (!buf_.append('[') || !nameExpression(elem->right(), foundName)) { + return false; + } + if (!*foundName) { + return true; + } + return buf_.append(']'); + } + + case ParseNodeKind::NumberExpr: + *foundName = true; + return appendNumber(n->as().value()); + + default: + // We're confused as to what to call this function. + *foundName = false; + return true; + } + } + + /* + * When naming an anonymous function, the process works loosely by walking + * up the AST and then translating that to a string. The stringification + * happens from some far-up assignment and then going back down the parse + * tree to the function definition point. + * + * This function will walk up the parse tree, gathering relevant nodes used + * for naming, and return the assignment node if there is one. The provided + * array and size will be filled in, and the returned node could be nullptr + * if no assignment is found. The first element of the array will be the + * innermost node relevant to naming, and the last element will be the + * outermost node. + */ + ParseNode* gatherNameable(ParseNode** nameable, size_t* size) { + MOZ_ASSERT(nparents_ > 0); + MOZ_ASSERT(parents_[nparents_ - 1]->is()); + + *size = 0; + + for (int pos = nparents_ - 2; pos >= 0; pos--) { + ParseNode* cur = parents_[pos]; + if (cur->is()) { + return cur; + } + + switch (cur->getKind()) { + case ParseNodeKind::PrivateName: + case ParseNodeKind::Name: + return cur; // found the initialized declaration + case ParseNodeKind::ThisExpr: + return cur; // setting a property of 'this' + case ParseNodeKind::Function: + return nullptr; // won't find an assignment or declaration + + case ParseNodeKind::ReturnStmt: + // Normally the relevant parent of a node is its direct parent, but + // sometimes with code like: + // + // var foo = (function() { return function() {}; })(); + // + // the outer function is just a helper to create a scope for the + // returned function. Hence the name of the returned function should + // actually be 'foo'. This loop sees if the current node is a + // ParseNodeKind::Return, and if there is a direct function + // call we skip to that. + for (int tmp = pos - 1; tmp > 0; tmp--) { + if (isDirectCall(tmp, cur)) { + pos = tmp; + break; + } + if (isCall(cur)) { + // Don't skip too high in the tree. + break; + } + cur = parents_[tmp]; + } + break; + + case ParseNodeKind::PropertyDefinition: + case ParseNodeKind::Shorthand: + // Record the ParseNodeKind::PropertyDefinition/Shorthand but skip the + // ParseNodeKind::Object so we're not flagged as a contributor. + pos--; + [[fallthrough]]; + + default: + // Save any other nodes we encounter on the way up. + MOZ_ASSERT(*size < MaxParents); + nameable[(*size)++] = cur; + break; + } + } + + return nullptr; + } + + /* + * Resolve the name of a function. If the function already has a name + * listed, then it is skipped. Otherwise an intelligent name is guessed to + * assign to the function's displayAtom field. + */ + [[nodiscard]] bool resolveFun(FunctionNode* funNode, + TaggedParserAtomIndex* retId) { + MOZ_ASSERT(funNode != nullptr); + + FunctionBox* funbox = funNode->funbox(); + + MOZ_ASSERT(buf_.empty()); + auto resetBuf = mozilla::MakeScopeExit([&] { buf_.clear(); }); + + *retId = TaggedParserAtomIndex::null(); + + // If the function already has a name, use that. + if (funbox->displayAtom()) { + if (!prefix_) { + *retId = funbox->displayAtom(); + return true; + } + if (!buf_.append(parserAtoms_, prefix_) || !buf_.append('/') || + !buf_.append(parserAtoms_, funbox->displayAtom())) { + return false; + } + *retId = buf_.finishParserAtom(parserAtoms_, fc_); + return !!*retId; + } + + // If a prefix is specified, then it is a form of namespace. + if (prefix_) { + if (!buf_.append(parserAtoms_, prefix_) || !buf_.append('/')) { + return false; + } + } + + // Gather all nodes relevant to naming. + ParseNode* toName[MaxParents]; + size_t size; + ParseNode* assignment = gatherNameable(toName, &size); + + // If the function is assigned to something, then that is very relevant. + if (assignment) { + // e.g, foo = function() {} + if (assignment->is()) { + assignment = assignment->as().left(); + } + bool foundName = false; + if (!nameExpression(assignment, &foundName)) { + return false; + } + if (!foundName) { + return true; + } + } + + // Other than the actual assignment, other relevant nodes to naming are + // those in object initializers and then particular nodes marking a + // contribution. + for (int pos = size - 1; pos >= 0; pos--) { + ParseNode* node = toName[pos]; + + if (node->isKind(ParseNodeKind::PropertyDefinition) || + node->isKind(ParseNodeKind::Shorthand)) { + ParseNode* left = node->as().left(); + if (left->isKind(ParseNodeKind::ObjectPropertyName) || + left->isKind(ParseNodeKind::StringExpr)) { + // Here we handle two cases: + // 1) ObjectPropertyName category, e.g `foo: function() {}` + // 2) StringExpr category, e.g `"foo": function() {}` + if (!appendPropertyReference(left->as().atom())) { + return false; + } + } else if (left->isKind(ParseNodeKind::NumberExpr)) { + // This case handles Number expression Anonymous Functions + // for example: `{ 10: function() {} }`. + if (!appendNumericPropertyReference( + left->as().value())) { + return false; + } + } else if (left->isKind(ParseNodeKind::ComputedName) && + (left->as().kid()->isKind( + ParseNodeKind::StringExpr) || + left->as().kid()->isKind( + ParseNodeKind::NumberExpr)) && + node->as().accessorType() == + AccessorType::None) { + // In this branch we handle computed property with string + // or numeric literal: + // e.g, `{ ["foo"]: function(){} }`, and `{ [10]: function() {} }`. + // + // Note we only handle the names that are known at compile time, + // so if we have `var x = "foo"; ({ [x]: function(){} })`, we don't + // handle that here, it's handled at runtime by JSOp::SetFunName. + // The accessor type of the property must be AccessorType::None, + // given getters and setters need prefix and we cannot handle it here. + ParseNode* kid = left->as().kid(); + if (kid->isKind(ParseNodeKind::StringExpr)) { + if (!appendPropertyReference(kid->as().atom())) { + return false; + } + } else { + MOZ_ASSERT(kid->isKind(ParseNodeKind::NumberExpr)); + if (!appendNumericPropertyReference( + kid->as().value())) { + return false; + } + } + } else { + MOZ_ASSERT(left->isKind(ParseNodeKind::ComputedName) || + left->isKind(ParseNodeKind::BigIntExpr)); + } + } else { + // Don't have consecutive '<' characters, and also don't start + // with a '<' character. + if (!buf_.empty() && buf_.getChar(buf_.length() - 1) != '<' && + !buf_.append('<')) { + return false; + } + } + } + + // functions which are "genuinely anonymous" but are contained in some + // other namespace are rather considered as "contributing" to the outer + // function, so give them a contribution symbol here. + if (!buf_.empty() && buf_.getChar(buf_.length() - 1) == '/' && + !buf_.append('<')) { + return false; + } + + if (buf_.empty()) { + return true; + } + + *retId = buf_.finishParserAtom(parserAtoms_, fc_); + if (!*retId) { + return false; + } + + // Skip assigning the guessed name if the function has a (dynamically) + // computed inferred name. + if (!funNode->isDirectRHSAnonFunction()) { + funbox->setGuessedAtom(*retId); + } + return true; + } + + /* + * Tests whether parents_[pos] is a function call whose callee is cur. + * This is the case for functions which do things like simply create a scope + * for new variables and then return an anonymous function using this scope. + */ + bool isDirectCall(int pos, ParseNode* cur) { + return pos >= 0 && isCall(parents_[pos]) && + parents_[pos]->as().left() == cur; + } + + public: + [[nodiscard]] bool visitFunction(FunctionNode* pn) { + TaggedParserAtomIndex savedPrefix = prefix_; + TaggedParserAtomIndex newPrefix; + if (!resolveFun(pn, &newPrefix)) { + return false; + } + + // If a function looks like (function(){})() where the parent node + // of the definition of the function is a call, then it shouldn't + // contribute anything to the namespace, so don't bother updating + // the prefix to whatever was returned. + if (!isDirectCall(nparents_ - 2, pn)) { + prefix_ = newPrefix; + } + + bool ok = Base::visitFunction(pn); + + prefix_ = savedPrefix; + return ok; + } + + // Skip this type of node. It never contains functions. + [[nodiscard]] bool visitCallSiteObj(CallSiteNode* callSite) { + // This node only contains internal strings or undefined and an array -- no + // user-controlled expressions. + return true; + } + + // Skip walking the list of string parts, which never contains functions. + [[nodiscard]] bool visitTaggedTemplateExpr(BinaryNode* taggedTemplate) { + ParseNode* tag = taggedTemplate->left(); + + // The leading expression, e.g. |tag| in |tag`foo`|, + // that might contain functions. + if (!visit(tag)) { + return false; + } + + // The callsite object node is first. This node only contains + // internal strings or undefined and an array -- no user-controlled + // expressions. + CallSiteNode* element = + &taggedTemplate->right()->as().head()->as(); +#ifdef DEBUG + { + ListNode* rawNodes = &element->head()->as(); + MOZ_ASSERT(rawNodes->isKind(ParseNodeKind::ArrayExpr)); + for (ParseNode* raw : rawNodes->contents()) { + MOZ_ASSERT(raw->isKind(ParseNodeKind::TemplateStringExpr)); + } + for (ParseNode* cooked : element->contentsFrom(rawNodes->pn_next)) { + MOZ_ASSERT(cooked->isKind(ParseNodeKind::TemplateStringExpr) || + cooked->isKind(ParseNodeKind::RawUndefinedExpr)); + } + } +#endif + + // Next come any interpolated expressions in the tagged template. + ParseNode* interpolated = element->pn_next; + for (; interpolated; interpolated = interpolated->pn_next) { + if (!visit(interpolated)) { + return false; + } + } + + return true; + } + + private: + // Speed hack: this type of node can't contain functions, so skip walking it. + [[nodiscard]] bool internalVisitSpecList(ListNode* pn) { + // Import/export spec lists contain import/export specs containing only + // pairs of names or strings. Alternatively, an export spec list may + // contain a single export batch specifier. +#ifdef DEBUG + bool isImport = pn->isKind(ParseNodeKind::ImportSpecList); + ParseNode* item = pn->head(); + if (!isImport && item && item->isKind(ParseNodeKind::ExportBatchSpecStmt)) { + MOZ_ASSERT(item->is()); + } else { + for (ParseNode* item : pn->contents()) { + if (item->is()) { + auto* spec = &item->as(); + MOZ_ASSERT(spec->isKind(isImport + ? ParseNodeKind::ImportNamespaceSpec + : ParseNodeKind::ExportNamespaceSpec)); + MOZ_ASSERT(spec->kid()->isKind(ParseNodeKind::Name) || + spec->kid()->isKind(ParseNodeKind::StringExpr)); + } else { + auto* spec = &item->as(); + MOZ_ASSERT(spec->isKind(isImport ? ParseNodeKind::ImportSpec + : ParseNodeKind::ExportSpec)); + MOZ_ASSERT(spec->left()->isKind(ParseNodeKind::Name) || + spec->left()->isKind(ParseNodeKind::StringExpr)); + MOZ_ASSERT(spec->right()->isKind(ParseNodeKind::Name) || + spec->right()->isKind(ParseNodeKind::StringExpr)); + } + } + } +#endif + return true; + } + + public: + [[nodiscard]] bool visitImportSpecList(ListNode* pn) { + return internalVisitSpecList(pn); + } + + [[nodiscard]] bool visitExportSpecList(ListNode* pn) { + return internalVisitSpecList(pn); + } + + NameResolver(FrontendContext* fc, ParserAtomsTable& parserAtoms) + : ParseNodeVisitor(fc), + fc_(fc), + parserAtoms_(parserAtoms), + nparents_(0), + buf_(fc) {} + + /* + * Resolve names for all anonymous functions in the given ParseNode tree. + */ + [[nodiscard]] bool visit(ParseNode* pn) { + // Push pn to the parse node stack. + if (nparents_ >= MaxParents) { + // Silently skip very deeply nested functions. + return true; + } + auto initialParents = nparents_; + parents_[initialParents] = pn; + nparents_++; + + bool ok = Base::visit(pn); + + // Pop pn from the parse node stack. + nparents_--; + MOZ_ASSERT(initialParents == nparents_, "nparents imbalance detected"); + MOZ_ASSERT(parents_[initialParents] == pn, + "pushed child shouldn't change underneath us"); + AlwaysPoison(&parents_[initialParents], JS_OOB_PARSE_NODE_PATTERN, + sizeof(parents_[initialParents]), MemCheckKind::MakeUndefined); + + return ok; + } +}; + +} /* anonymous namespace */ + +bool frontend::NameFunctions(FrontendContext* fc, ParserAtomsTable& parserAtoms, + ParseNode* pn) { + NameResolver nr(fc, parserAtoms); + return nr.visit(pn); +} diff --git a/js/src/frontend/NameFunctions.h b/js/src/frontend/NameFunctions.h new file mode 100644 index 0000000000..aec65bdc5e --- /dev/null +++ b/js/src/frontend/NameFunctions.h @@ -0,0 +1,27 @@ +/* -*- 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/. */ + +#ifndef frontend_NameFunctions_h +#define frontend_NameFunctions_h + +#include "js/TypeDecls.h" + +namespace js { + +class FrontendContext; + +namespace frontend { + +class ParseNode; +class ParserAtomsTable; + +[[nodiscard]] bool NameFunctions(FrontendContext* fc, + ParserAtomsTable& parserAtoms, ParseNode* pn); + +} /* namespace frontend */ +} /* namespace js */ + +#endif /* frontend_NameFunctions_h */ diff --git a/js/src/frontend/NameOpEmitter.cpp b/js/src/frontend/NameOpEmitter.cpp new file mode 100644 index 0000000000..f36c7a2028 --- /dev/null +++ b/js/src/frontend/NameOpEmitter.cpp @@ -0,0 +1,481 @@ +/* -*- 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/NameOpEmitter.h" + +#include "frontend/AbstractScopePtr.h" +#include "frontend/BytecodeEmitter.h" +#include "frontend/ParserAtom.h" // ParserAtom +#include "frontend/SharedContext.h" +#include "frontend/TDZCheckCache.h" +#include "frontend/ValueUsage.h" +#include "js/Value.h" +#include "vm/Opcodes.h" + +using namespace js; +using namespace js::frontend; + +NameOpEmitter::NameOpEmitter(BytecodeEmitter* bce, TaggedParserAtomIndex name, + Kind kind) + : bce_(bce), kind_(kind), name_(name), loc_(bce_->lookupName(name_)) {} + +NameOpEmitter::NameOpEmitter(BytecodeEmitter* bce, TaggedParserAtomIndex name, + const NameLocation& loc, Kind kind) + : bce_(bce), kind_(kind), name_(name), loc_(loc) {} + +bool NameOpEmitter::emitGet() { + MOZ_ASSERT(state_ == State::Start); + + switch (loc_.kind()) { + case NameLocation::Kind::Dynamic: + if (!bce_->emitAtomOp(JSOp::GetName, name_)) { + // [stack] VAL + return false; + } + break; + case NameLocation::Kind::Global: { + MOZ_ASSERT(bce_->outermostScope().hasNonSyntacticScopeOnChain() == + bce_->sc->hasNonSyntacticScope()); + if (bce_->sc->hasNonSyntacticScope()) { + if (!bce_->emitAtomOp(JSOp::GetName, name_)) { + // [stack] VAL + return false; + } + } else { + // Some names on the global are not configurable and have fixed values + // which we can emit instead. + if (name_ == TaggedParserAtomIndex::WellKnown::undefined()) { + if (!bce_->emit1(JSOp::Undefined)) { + return false; + } + } else if (name_ == TaggedParserAtomIndex::WellKnown::NaN()) { + if (!bce_->emitDouble(JS::GenericNaN())) { + return false; + } + } else if (name_ == TaggedParserAtomIndex::WellKnown::Infinity()) { + if (!bce_->emitDouble(JS::Infinity())) { + return false; + } + } else { + if (!bce_->emitAtomOp(JSOp::GetGName, name_)) { + // [stack] VAL + return false; + } + } + } + break; + } + case NameLocation::Kind::Intrinsic: + if (name_ == TaggedParserAtomIndex::WellKnown::undefined()) { + if (!bce_->emit1(JSOp::Undefined)) { + // [stack] Undefined + return false; + } + } else { + if (!bce_->emitAtomOp(JSOp::GetIntrinsic, name_)) { + // [stack] VAL + return false; + } + } + break; + case NameLocation::Kind::NamedLambdaCallee: + if (!bce_->emit1(JSOp::Callee)) { + // [stack] VAL + return false; + } + break; + case NameLocation::Kind::Import: + if (!bce_->emitAtomOp(JSOp::GetImport, name_)) { + // [stack] VAL + return false; + } + break; + case NameLocation::Kind::ArgumentSlot: + if (!bce_->emitArgOp(JSOp::GetArg, loc_.argumentSlot())) { + // [stack] VAL + return false; + } + break; + case NameLocation::Kind::FrameSlot: + if (!bce_->emitLocalOp(JSOp::GetLocal, loc_.frameSlot())) { + // [stack] VAL + return false; + } + if (loc_.isLexical()) { + if (!bce_->emitTDZCheckIfNeeded(name_, loc_, ValueIsOnStack::Yes)) { + // [stack] VAL + return false; + } + } + break; + case NameLocation::Kind::EnvironmentCoordinate: + case NameLocation::Kind::DebugEnvironmentCoordinate: + if (!bce_->emitEnvCoordOp( + loc_.kind() == NameLocation::Kind::EnvironmentCoordinate + ? JSOp::GetAliasedVar + : JSOp::GetAliasedDebugVar, + loc_.environmentCoordinate())) { + // [stack] VAL + return false; + } + if (loc_.isLexical()) { + if (!bce_->emitTDZCheckIfNeeded(name_, loc_, ValueIsOnStack::Yes)) { + // [stack] VAL + return false; + } + } + break; + case NameLocation::Kind::DynamicAnnexBVar: + MOZ_CRASH( + "Synthesized vars for Annex B.3.3 should only be used in " + "initialization"); + } + + if (isCall()) { + MOZ_ASSERT(bce_->outermostScope().hasNonSyntacticScopeOnChain() == + bce_->sc->hasNonSyntacticScope()); + switch (loc_.kind()) { + case NameLocation::Kind::Dynamic: + case NameLocation::Kind::Global: + MOZ_ASSERT(bce_->emitterMode != BytecodeEmitter::SelfHosting); + if (bce_->needsImplicitThis() || bce_->sc->hasNonSyntacticScope()) { + MOZ_ASSERT_IF(bce_->needsImplicitThis(), + loc_.kind() == NameLocation::Kind::Dynamic); + if (!bce_->emitAtomOp(JSOp::ImplicitThis, name_)) { + // [stack] CALLEE THIS + return false; + } + } else { + if (!bce_->emit1(JSOp::Undefined)) { + // [stack] CALLEE UNDEF + return false; + } + } + break; + case NameLocation::Kind::Intrinsic: + case NameLocation::Kind::NamedLambdaCallee: + case NameLocation::Kind::Import: + case NameLocation::Kind::ArgumentSlot: + case NameLocation::Kind::FrameSlot: + case NameLocation::Kind::EnvironmentCoordinate: + if (bce_->emitterMode == BytecodeEmitter::SelfHosting) { + if (!bce_->emitDebugCheckSelfHosted()) { + // [stack] CALLEE + return false; + } + } + if (!bce_->emit1(JSOp::Undefined)) { + // [stack] CALLEE UNDEF + return false; + } + break; + case NameLocation::Kind::DebugEnvironmentCoordinate: + MOZ_CRASH( + "DebugEnvironmentCoordinate should only be used to get the private " + "brand, and so should never call."); + break; + case NameLocation::Kind::DynamicAnnexBVar: + MOZ_CRASH( + "Synthesized vars for Annex B.3.3 should only be used in " + "initialization"); + } + } + +#ifdef DEBUG + state_ = State::Get; +#endif + return true; +} + +bool NameOpEmitter::prepareForRhs() { + MOZ_ASSERT(state_ == State::Start); + + switch (loc_.kind()) { + case NameLocation::Kind::Dynamic: + case NameLocation::Kind::Import: + case NameLocation::Kind::DynamicAnnexBVar: + if (!bce_->makeAtomIndex(name_, ParserAtom::Atomize::Yes, &atomIndex_)) { + return false; + } + if (loc_.kind() == NameLocation::Kind::DynamicAnnexBVar) { + // Annex B vars always go on the nearest variable environment, + // even if lexical environments in between contain same-named + // bindings. + if (!bce_->emit1(JSOp::BindVar)) { + // [stack] ENV + return false; + } + } else { + if (!bce_->emitAtomOp(JSOp::BindName, atomIndex_)) { + // [stack] ENV + return false; + } + } + emittedBindOp_ = true; + break; + case NameLocation::Kind::Global: + if (!bce_->makeAtomIndex(name_, ParserAtom::Atomize::Yes, &atomIndex_)) { + return false; + } + MOZ_ASSERT(bce_->outermostScope().hasNonSyntacticScopeOnChain() == + bce_->sc->hasNonSyntacticScope()); + + if (loc_.isLexical() && isInitialize()) { + // InitGLexical always gets the global lexical scope. It doesn't + // need a BindName/BindGName. + MOZ_ASSERT(bce_->innermostScope().is()); + } else if (bce_->sc->hasNonSyntacticScope()) { + if (!bce_->emitAtomOp(JSOp::BindName, atomIndex_)) { + // [stack] ENV + return false; + } + emittedBindOp_ = true; + } else { + if (!bce_->emitAtomOp(JSOp::BindGName, atomIndex_)) { + // [stack] ENV + return false; + } + emittedBindOp_ = true; + } + break; + case NameLocation::Kind::Intrinsic: + break; + case NameLocation::Kind::NamedLambdaCallee: + break; + case NameLocation::Kind::ArgumentSlot: + break; + case NameLocation::Kind::FrameSlot: + break; + case NameLocation::Kind::DebugEnvironmentCoordinate: + break; + case NameLocation::Kind::EnvironmentCoordinate: + break; + } + + // For compound assignments, first get the LHS value, then emit + // the RHS and the op. + if (isCompoundAssignment() || isIncDec()) { + if (loc_.kind() == NameLocation::Kind::Dynamic) { + // For dynamic accesses we need to emit GetBoundName instead of + // GetName for correctness: looking up @@unscopables on the + // environment chain (due to 'with' environments) must only happen + // once. + // + // GetBoundName uses the environment already pushed on the stack + // from the earlier BindName. + if (!bce_->emit1(JSOp::Dup)) { + // [stack] ENV ENV + return false; + } + if (!bce_->emitAtomOp(JSOp::GetBoundName, name_)) { + // [stack] ENV V + return false; + } + } else { + if (!emitGet()) { + // [stack] ENV? V + return false; + } + } + } + +#ifdef DEBUG + state_ = State::Rhs; +#endif + return true; +} + +#if defined(__clang__) && defined(XP_WIN) && \ + (defined(_M_X64) || defined(__x86_64__)) +// Work around a CPU bug. See bug 1524257. +__attribute__((__aligned__(32))) +#endif +bool NameOpEmitter::emitAssignment() { + MOZ_ASSERT(state_ == State::Rhs); + + switch (loc_.kind()) { + case NameLocation::Kind::Dynamic: + case NameLocation::Kind::Import: + case NameLocation::Kind::DynamicAnnexBVar: + if (!bce_->emitAtomOp(bce_->strictifySetNameOp(JSOp::SetName), + atomIndex_)) { + return false; + } + break; + case NameLocation::Kind::Global: { + JSOp op; + if (emittedBindOp_) { + MOZ_ASSERT(bce_->outermostScope().hasNonSyntacticScopeOnChain() == + bce_->sc->hasNonSyntacticScope()); + if (bce_->sc->hasNonSyntacticScope()) { + op = bce_->strictifySetNameOp(JSOp::SetName); + } else { + op = bce_->strictifySetNameOp(JSOp::SetGName); + } + } else { + op = JSOp::InitGLexical; + } + if (!bce_->emitAtomOp(op, atomIndex_)) { + return false; + } + break; + } + case NameLocation::Kind::Intrinsic: + if (!bce_->emitAtomOp(JSOp::SetIntrinsic, name_)) { + return false; + } + break; + case NameLocation::Kind::NamedLambdaCallee: + // Assigning to the named lambda is a no-op in sloppy mode but + // throws in strict mode. + if (bce_->sc->strict()) { + if (!bce_->emitAtomOp(JSOp::ThrowSetConst, name_)) { + return false; + } + } + break; + case NameLocation::Kind::ArgumentSlot: + if (!bce_->emitArgOp(JSOp::SetArg, loc_.argumentSlot())) { + return false; + } + break; + case NameLocation::Kind::FrameSlot: { + JSOp op = JSOp::SetLocal; + // Lexicals, Synthetics and Private Methods have very similar handling + // around a variety of areas, including initialization. + if (loc_.isLexical() || loc_.isPrivateMethod() || loc_.isSynthetic()) { + if (isInitialize()) { + op = JSOp::InitLexical; + } else { + if (loc_.isConst()) { + op = JSOp::ThrowSetConst; + } + if (!bce_->emitTDZCheckIfNeeded(name_, loc_, ValueIsOnStack::No)) { + return false; + } + } + } + if (op == JSOp::ThrowSetConst) { + if (!bce_->emitAtomOp(op, name_)) { + return false; + } + } else { + if (!bce_->emitLocalOp(op, loc_.frameSlot())) { + return false; + } + } + if (op == JSOp::InitLexical) { + if (!bce_->innermostTDZCheckCache->noteTDZCheck(bce_, name_, + DontCheckTDZ)) { + return false; + } + } + break; + } + case NameLocation::Kind::EnvironmentCoordinate: { + JSOp op = JSOp::SetAliasedVar; + // Lexicals, Synthetics and Private Methods have very similar handling + // around a variety of areas, including initialization. + if (loc_.isLexical() || loc_.isPrivateMethod() || loc_.isSynthetic()) { + if (isInitialize()) { + op = JSOp::InitAliasedLexical; + } else { + if (loc_.isConst()) { + op = JSOp::ThrowSetConst; + } + if (!bce_->emitTDZCheckIfNeeded(name_, loc_, ValueIsOnStack::No)) { + return false; + } + } + } + if (loc_.bindingKind() == BindingKind::NamedLambdaCallee) { + // Assigning to the named lambda is a no-op in sloppy mode and throws + // in strict mode. + op = JSOp::ThrowSetConst; + if (bce_->sc->strict()) { + if (!bce_->emitAtomOp(op, name_)) { + return false; + } + } + } else { + if (op == JSOp::ThrowSetConst) { + if (!bce_->emitAtomOp(op, name_)) { + return false; + } + } else { + if (!bce_->emitEnvCoordOp(op, loc_.environmentCoordinate())) { + return false; + } + } + } + if (op == JSOp::InitAliasedLexical) { + if (!bce_->innermostTDZCheckCache->noteTDZCheck(bce_, name_, + DontCheckTDZ)) { + return false; + } + } + break; + } + case NameLocation::Kind::DebugEnvironmentCoordinate: + MOZ_CRASH("Shouldn't be assigning to a private brand"); + break; + } + +#ifdef DEBUG + state_ = State::Assignment; +#endif + return true; +} + +bool NameOpEmitter::emitIncDec(ValueUsage valueUsage) { + MOZ_ASSERT(state_ == State::Start); + + JSOp incOp = isInc() ? JSOp::Inc : JSOp::Dec; + if (!prepareForRhs()) { + // [stack] ENV? V + return false; + } + if (!bce_->emit1(JSOp::ToNumeric)) { + // [stack] ENV? N + return false; + } + if (isPostIncDec() && valueUsage == ValueUsage::WantValue) { + if (!bce_->emit1(JSOp::Dup)) { + // [stack] ENV? N N + return false; + } + } + if (!bce_->emit1(incOp)) { + // [stack] ENV? N? N+1 + return false; + } + if (isPostIncDec() && emittedBindOp() && + valueUsage == ValueUsage::WantValue) { + if (!bce_->emit2(JSOp::Pick, 2)) { + // [stack] N N+1 ENV + return false; + } + if (!bce_->emit1(JSOp::Swap)) { + // [stack] N ENV N+1 + return false; + } + } + if (!emitAssignment()) { + // [stack] N? N+1 + return false; + } + if (isPostIncDec() && valueUsage == ValueUsage::WantValue) { + if (!bce_->emit1(JSOp::Pop)) { + // [stack] N + return false; + } + } + +#ifdef DEBUG + state_ = State::IncDec; +#endif + return true; +} diff --git a/js/src/frontend/NameOpEmitter.h b/js/src/frontend/NameOpEmitter.h new file mode 100644 index 0000000000..62d632a321 --- /dev/null +++ b/js/src/frontend/NameOpEmitter.h @@ -0,0 +1,182 @@ +/* -*- 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/. */ + +#ifndef frontend_NameOpEmitter_h +#define frontend_NameOpEmitter_h + +#include "mozilla/Attributes.h" + +#include "frontend/NameAnalysisTypes.h" +#include "frontend/ParserAtom.h" // TaggedParserAtomIndex +#include "vm/SharedStencil.h" // GCThingIndex + +namespace js { +namespace frontend { + +struct BytecodeEmitter; +enum class ValueUsage; + +// Class for emitting bytecode for name operation. +// +// Usage: (check for the return value is omitted for simplicity) +// +// `name;` +// NameOpEmitter noe(this, atom_of_name +// NameOpEmitter::Kind::Get); +// noe.emitGet(); +// +// `name();` +// this is handled in CallOrNewEmitter +// +// `name++;` +// NameOpEmitter noe(this, atom_of_name +// NameOpEmitter::Kind::PostIncrement); +// noe.emitIncDec(); +// +// `name = 10;` +// NameOpEmitter noe(this, atom_of_name +// NameOpEmitter::Kind::SimpleAssignment); +// noe.prepareForRhs(); +// emit(10); +// noe.emitAssignment(); +// +// `name += 10;` +// NameOpEmitter noe(this, atom_of_name +// NameOpEmitter::Kind::CompoundAssignment); +// noe.prepareForRhs(); +// emit(10); +// emit_add_op_here(); +// noe.emitAssignment(); +// +// `name = 10;` part of `let name = 10;` +// NameOpEmitter noe(this, atom_of_name +// NameOpEmitter::Kind::Initialize); +// noe.prepareForRhs(); +// emit(10); +// noe.emitAssignment(); +// +class MOZ_STACK_CLASS NameOpEmitter { + public: + enum class Kind { + Get, + Call, + PostIncrement, + PreIncrement, + PostDecrement, + PreDecrement, + SimpleAssignment, + CompoundAssignment, + Initialize + }; + + private: + BytecodeEmitter* bce_; + + Kind kind_; + + bool emittedBindOp_ = false; + + TaggedParserAtomIndex name_; + + GCThingIndex atomIndex_; + + NameLocation loc_; + +#ifdef DEBUG + // The state of this emitter. + // + // [Get] + // [Call] + // +-------+ emitGet +-----+ + // | Start |-+-+--------->| Get | + // +-------+ | +-----+ + // | + // | [PostIncrement] + // | [PreIncrement] + // | [PostDecrement] + // | [PreDecrement] + // | emitIncDec +--------+ + // +------------->| IncDec | + // | +--------+ + // | + // | [SimpleAssignment] + // | prepareForRhs +-----+ + // +--------------------->+-------------->| Rhs |-+ + // | ^ +-----+ | + // | | | + // | | +------------------+ + // | [CompoundAssignment] | | + // | emitGet +-----+ | | emitAssignment +------------+ + // +---------->| Get |----+ + -------------->| Assignment | + // +-----+ +------------+ + enum class State { + // The initial state. + Start, + + // After calling emitGet. + Get, + + // After calling emitIncDec. + IncDec, + + // After calling prepareForRhs. + Rhs, + + // After calling emitAssignment. + Assignment, + }; + State state_ = State::Start; +#endif + + public: + NameOpEmitter(BytecodeEmitter* bce, TaggedParserAtomIndex name, Kind kind); + NameOpEmitter(BytecodeEmitter* bce, TaggedParserAtomIndex name, + const NameLocation& loc, Kind kind); + + private: + [[nodiscard]] bool isCall() const { return kind_ == Kind::Call; } + + [[nodiscard]] bool isSimpleAssignment() const { + return kind_ == Kind::SimpleAssignment; + } + + [[nodiscard]] bool isCompoundAssignment() const { + return kind_ == Kind::CompoundAssignment; + } + + [[nodiscard]] bool isIncDec() const { + return isPostIncDec() || isPreIncDec(); + } + + [[nodiscard]] bool isPostIncDec() const { + return kind_ == Kind::PostIncrement || kind_ == Kind::PostDecrement; + } + + [[nodiscard]] bool isPreIncDec() const { + return kind_ == Kind::PreIncrement || kind_ == Kind::PreDecrement; + } + + [[nodiscard]] bool isInc() const { + return kind_ == Kind::PostIncrement || kind_ == Kind::PreIncrement; + } + + [[nodiscard]] bool isInitialize() const { return kind_ == Kind::Initialize; } + + public: + [[nodiscard]] bool emittedBindOp() const { return emittedBindOp_; } + + [[nodiscard]] const NameLocation& loc() const { return loc_; } + + [[nodiscard]] bool emitGet(); + [[nodiscard]] bool prepareForRhs(); + [[nodiscard]] bool emitAssignment(); + [[nodiscard]] bool emitIncDec(ValueUsage valueUsage); +}; + +} /* namespace frontend */ +} /* namespace js */ + +#endif /* frontend_NameOpEmitter_h */ diff --git a/js/src/frontend/ObjLiteral.cpp b/js/src/frontend/ObjLiteral.cpp new file mode 100644 index 0000000000..0c2aa98417 --- /dev/null +++ b/js/src/frontend/ObjLiteral.cpp @@ -0,0 +1,537 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: set ts=8 sw=2 et tw=0 ft=c: + * + * 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/ObjLiteral.h" + +#include "mozilla/DebugOnly.h" // mozilla::DebugOnly +#include "mozilla/HashTable.h" // mozilla::HashSet + +#include "NamespaceImports.h" // ValueVector + +#include "builtin/Array.h" // NewDenseCopiedArray +#include "frontend/CompilationStencil.h" // frontend::{CompilationStencil, CompilationAtomCache} +#include "frontend/ParserAtom.h" // frontend::ParserAtomTable +#include "frontend/TaggedParserAtomIndexHasher.h" // TaggedParserAtomIndexHasher +#include "gc/AllocKind.h" // gc::AllocKind +#include "js/Id.h" // INT_TO_JSID +#include "js/Printer.h" // js::Fprinter +#include "js/RootingAPI.h" // Rooted +#include "js/TypeDecls.h" // RootedId, RootedValue +#include "vm/JSObject.h" // TenuredObject +#include "vm/JSONPrinter.h" // js::JSONPrinter +#include "vm/NativeObject.h" // NativeDefineDataProperty +#include "vm/PlainObject.h" // PlainObject + +#include "gc/ObjectKind-inl.h" // gc::GetGCObjectKind +#include "vm/JSAtom-inl.h" // AtomToId +#include "vm/JSObject-inl.h" // NewBuiltinClassInstance +#include "vm/NativeObject-inl.h" // AddDataPropertyNonDelegate + +namespace js { + +bool ObjLiteralWriter::checkForDuplicatedNames(FrontendContext* fc) { + if (!mightContainDuplicatePropertyNames_) { + return true; + } + + // If possible duplicate property names are detected by bloom-filter, + // check again with hash-set. + + mozilla::HashSet + propNameSet; + + if (!propNameSet.reserve(propertyCount_)) { + js::ReportOutOfMemory(fc); + return false; + } + + ObjLiteralReader reader(getCode()); + + while (true) { + ObjLiteralInsn insn; + if (!reader.readInsn(&insn)) { + break; + } + + if (insn.getKey().isArrayIndex()) { + continue; + } + + auto propName = insn.getKey().getAtomIndex(); + + auto p = propNameSet.lookupForAdd(propName); + if (p) { + flags_.setFlag(ObjLiteralFlag::HasIndexOrDuplicatePropName); + break; + } + + // Already reserved above and doesn't fail. + MOZ_ALWAYS_TRUE(propNameSet.add(p, propName)); + } + + return true; +} + +static void InterpretObjLiteralValue( + JSContext* cx, const frontend::CompilationAtomCache& atomCache, + const ObjLiteralInsn& insn, MutableHandleValue valOut) { + switch (insn.getOp()) { + case ObjLiteralOpcode::ConstValue: + valOut.set(insn.getConstValue()); + return; + case ObjLiteralOpcode::ConstString: { + frontend::TaggedParserAtomIndex index = insn.getAtomIndex(); + JSString* str = atomCache.getExistingStringAt(cx, index); + MOZ_ASSERT(str); + valOut.setString(str); + return; + } + case ObjLiteralOpcode::Null: + valOut.setNull(); + return; + case ObjLiteralOpcode::Undefined: + valOut.setUndefined(); + return; + case ObjLiteralOpcode::True: + valOut.setBoolean(true); + return; + case ObjLiteralOpcode::False: + valOut.setBoolean(false); + return; + default: + MOZ_CRASH("Unexpected object-literal instruction opcode"); + } +} + +enum class PropertySetKind { + UniqueNames, + Normal, +}; + +template +bool InterpretObjLiteralObj(JSContext* cx, Handle obj, + const frontend::CompilationAtomCache& atomCache, + const mozilla::Span literalInsns) { + ObjLiteralReader reader(literalInsns); + + RootedId propId(cx); + RootedValue propVal(cx); + while (true) { + // Make sure `insn` doesn't live across GC. + ObjLiteralInsn insn; + if (!reader.readInsn(&insn)) { + break; + } + MOZ_ASSERT(insn.isValid()); + MOZ_ASSERT_IF(kind == PropertySetKind::UniqueNames, + !insn.getKey().isArrayIndex()); + + if (kind == PropertySetKind::Normal && insn.getKey().isArrayIndex()) { + propId = PropertyKey::Int(insn.getKey().getArrayIndex()); + } else { + JSAtom* jsatom = + atomCache.getExistingAtomAt(cx, insn.getKey().getAtomIndex()); + MOZ_ASSERT(jsatom); + propId = AtomToId(jsatom); + } + + InterpretObjLiteralValue(cx, atomCache, insn, &propVal); + + if constexpr (kind == PropertySetKind::UniqueNames) { + if (!AddDataPropertyToPlainObject(cx, obj, propId, propVal)) { + return false; + } + } else { + if (!NativeDefineDataProperty(cx, obj, propId, propVal, + JSPROP_ENUMERATE)) { + return false; + } + } + } + return true; +} + +static gc::AllocKind AllocKindForObjectLiteral(uint32_t propCount) { + // Use NewObjectGCKind for empty object literals to reserve some fixed slots + // for new properties. This improves performance for common patterns such as + // |Object.assign({}, ...)|. + return (propCount == 0) ? NewObjectGCKind() : gc::GetGCObjectKind(propCount); +} + +static JSObject* InterpretObjLiteralObj( + JSContext* cx, const frontend::CompilationAtomCache& atomCache, + const mozilla::Span literalInsns, ObjLiteralFlags flags, + uint32_t propertyCount) { + gc::AllocKind allocKind = AllocKindForObjectLiteral(propertyCount); + + Rooted obj( + cx, NewPlainObjectWithAllocKind(cx, allocKind, TenuredObject)); + if (!obj) { + return nullptr; + } + + if (!flags.hasFlag(ObjLiteralFlag::HasIndexOrDuplicatePropName)) { + if (!InterpretObjLiteralObj( + cx, obj, atomCache, literalInsns)) { + return nullptr; + } + } else { + if (!InterpretObjLiteralObj(cx, obj, atomCache, + literalInsns)) { + return nullptr; + } + } + return obj; +} + +static JSObject* InterpretObjLiteralArray( + JSContext* cx, const frontend::CompilationAtomCache& atomCache, + const mozilla::Span literalInsns, uint32_t propertyCount) { + ObjLiteralReader reader(literalInsns); + ObjLiteralInsn insn; + + Rooted elements(cx, ValueVector(cx)); + if (!elements.reserve(propertyCount)) { + return nullptr; + } + + RootedValue propVal(cx); + while (reader.readInsn(&insn)) { + MOZ_ASSERT(insn.isValid()); + + InterpretObjLiteralValue(cx, atomCache, insn, &propVal); + elements.infallibleAppend(propVal); + } + + return NewDenseCopiedArray(cx, elements.length(), elements.begin(), + NewObjectKind::TenuredObject); +} + +// ES2023 draft rev ee74c9cb74dbfa23e62b486f5226102c345c678e +// +// GetTemplateObject ( templateLiteral ) +// https://tc39.es/ecma262/#sec-gettemplateobject +// +// Steps 8-16. +static JSObject* InterpretObjLiteralCallSiteObj( + JSContext* cx, const frontend::CompilationAtomCache& atomCache, + const mozilla::Span literalInsns, uint32_t propertyCount) { + ObjLiteralReader reader(literalInsns); + ObjLiteralInsn insn; + + // We have to read elements for two arrays. The 'cooked' values are followed + // by the 'raw' values. Both arrays have the same length. + MOZ_ASSERT((propertyCount % 2) == 0); + uint32_t count = propertyCount / 2; + + Rooted elements(cx, ValueVector(cx)); + if (!elements.reserve(count)) { + return nullptr; + } + + RootedValue propVal(cx); + auto readElements = [&](uint32_t count) { + MOZ_ASSERT(elements.empty()); + + for (size_t i = 0; i < count; i++) { + MOZ_ALWAYS_TRUE(reader.readInsn(&insn)); + MOZ_ASSERT(insn.isValid()); + + InterpretObjLiteralValue(cx, atomCache, insn, &propVal); + MOZ_ASSERT(propVal.isString() || propVal.isUndefined()); + elements.infallibleAppend(propVal); + } + }; + + // Create cooked array. + readElements(count); + Rooted cso( + cx, NewDenseCopiedArray(cx, elements.length(), elements.begin(), + NewObjectKind::TenuredObject)); + if (!cso) { + return nullptr; + } + elements.clear(); + + // Create raw array. + readElements(count); + Rooted raw( + cx, NewDenseCopiedArray(cx, elements.length(), elements.begin(), + NewObjectKind::TenuredObject)); + if (!raw) { + return nullptr; + } + + // Define .raw property and freeze both arrays. + RootedValue rawValue(cx, ObjectValue(*raw)); + if (!DefineDataProperty(cx, cso, cx->names().raw, rawValue, 0)) { + return nullptr; + } + if (!FreezeObject(cx, raw)) { + return nullptr; + } + if (!FreezeObject(cx, cso)) { + return nullptr; + } + + return cso; +} + +template +Shape* InterpretObjLiteralShape(JSContext* cx, + const frontend::CompilationAtomCache& atomCache, + const mozilla::Span literalInsns, + uint32_t numFixedSlots) { + ObjLiteralReader reader(literalInsns); + + Rooted map(cx); + uint32_t mapLength = 0; + ObjectFlags objectFlags; + + uint32_t slot = 0; + RootedId propId(cx); + while (true) { + // Make sure `insn` doesn't live across GC. + ObjLiteralInsn insn; + if (!reader.readInsn(&insn)) { + break; + } + MOZ_ASSERT(insn.isValid()); + MOZ_ASSERT(!insn.getKey().isArrayIndex()); + MOZ_ASSERT(insn.getOp() == ObjLiteralOpcode::Undefined); + + JSAtom* jsatom = + atomCache.getExistingAtomAt(cx, insn.getKey().getAtomIndex()); + MOZ_ASSERT(jsatom); + propId = AtomToId(jsatom); + + // Assert or check property names are unique. + if constexpr (kind == PropertySetKind::UniqueNames) { + mozilla::DebugOnly index; + MOZ_ASSERT_IF(map, !map->lookupPure(mapLength, propId, &index)); + } else { + uint32_t index; + if (map && map->lookupPure(mapLength, propId, &index)) { + continue; + } + } + + constexpr PropertyFlags propFlags = PropertyFlags::defaultDataPropFlags; + + if (!SharedPropMap::addPropertyWithKnownSlot(cx, &PlainObject::class_, &map, + &mapLength, propId, propFlags, + slot, &objectFlags)) { + return nullptr; + } + + slot++; + } + + JSObject* proto = &cx->global()->getObjectPrototype(); + return SharedShape::getInitialOrPropMapShape( + cx, &PlainObject::class_, cx->realm(), TaggedProto(proto), numFixedSlots, + map, mapLength, objectFlags); +} + +static Shape* InterpretObjLiteralShape( + JSContext* cx, const frontend::CompilationAtomCache& atomCache, + const mozilla::Span literalInsns, ObjLiteralFlags flags, + uint32_t propertyCount) { + gc::AllocKind allocKind = AllocKindForObjectLiteral(propertyCount); + uint32_t numFixedSlots = GetGCKindSlots(allocKind); + + if (!flags.hasFlag(ObjLiteralFlag::HasIndexOrDuplicatePropName)) { + return InterpretObjLiteralShape( + cx, atomCache, literalInsns, numFixedSlots); + } + return InterpretObjLiteralShape( + cx, atomCache, literalInsns, numFixedSlots); +} + +JS::GCCellPtr ObjLiteralStencil::create( + JSContext* cx, const frontend::CompilationAtomCache& atomCache) const { + switch (kind()) { + case ObjLiteralKind::Array: { + JSObject* obj = + InterpretObjLiteralArray(cx, atomCache, code_, propertyCount_); + if (!obj) { + return JS::GCCellPtr(); + } + return JS::GCCellPtr(obj); + } + case ObjLiteralKind::CallSiteObj: { + JSObject* obj = + InterpretObjLiteralCallSiteObj(cx, atomCache, code_, propertyCount_); + if (!obj) { + return JS::GCCellPtr(); + } + return JS::GCCellPtr(obj); + } + case ObjLiteralKind::Object: { + JSObject* obj = + InterpretObjLiteralObj(cx, atomCache, code_, flags(), propertyCount_); + if (!obj) { + return JS::GCCellPtr(); + } + return JS::GCCellPtr(obj); + } + case ObjLiteralKind::Shape: { + Shape* shape = InterpretObjLiteralShape(cx, atomCache, code_, flags(), + propertyCount_); + if (!shape) { + return JS::GCCellPtr(); + } + return JS::GCCellPtr(shape); + } + case ObjLiteralKind::Invalid: + break; + } + MOZ_CRASH("Invalid kind"); +} + +#ifdef DEBUG +bool ObjLiteralStencil::isContainedIn(const LifoAlloc& alloc) const { + return alloc.contains(code_.data()); +} +#endif + +#if defined(DEBUG) || defined(JS_JITSPEW) + +static void DumpObjLiteralFlagsItems(js::JSONPrinter& json, + ObjLiteralFlags flags) { + if (flags.hasFlag(ObjLiteralFlag::HasIndexOrDuplicatePropName)) { + json.value("HasIndexOrDuplicatePropName"); + flags.clearFlag(ObjLiteralFlag::HasIndexOrDuplicatePropName); + } + + if (!flags.isEmpty()) { + json.value("Unknown(%x)", flags.toRaw()); + } +} + +static const char* ObjLiteralKindToString(ObjLiteralKind kind) { + switch (kind) { + case ObjLiteralKind::Object: + return "Object"; + case ObjLiteralKind::Array: + return "Array"; + case ObjLiteralKind::CallSiteObj: + return "CallSiteObj"; + case ObjLiteralKind::Shape: + return "Shape"; + case ObjLiteralKind::Invalid: + break; + } + MOZ_CRASH("Invalid kind"); +} + +static void DumpObjLiteral(js::JSONPrinter& json, + const frontend::CompilationStencil* stencil, + mozilla::Span code, + ObjLiteralKind kind, const ObjLiteralFlags& flags, + uint32_t propertyCount) { + json.property("kind", ObjLiteralKindToString(kind)); + + json.beginListProperty("flags"); + DumpObjLiteralFlagsItems(json, flags); + json.endList(); + + json.beginListProperty("code"); + ObjLiteralReader reader(code); + ObjLiteralInsn insn; + while (reader.readInsn(&insn)) { + json.beginObject(); + + if (insn.getKey().isNone()) { + json.nullProperty("key"); + } else if (insn.getKey().isAtomIndex()) { + frontend::TaggedParserAtomIndex index = insn.getKey().getAtomIndex(); + json.beginObjectProperty("key"); + DumpTaggedParserAtomIndex(json, index, stencil); + json.endObject(); + } else if (insn.getKey().isArrayIndex()) { + uint32_t index = insn.getKey().getArrayIndex(); + json.formatProperty("key", "ArrayIndex(%u)", index); + } + + switch (insn.getOp()) { + case ObjLiteralOpcode::ConstValue: { + const Value& v = insn.getConstValue(); + json.formatProperty("op", "ConstValue(%f)", v.toNumber()); + break; + } + case ObjLiteralOpcode::ConstString: { + frontend::TaggedParserAtomIndex index = insn.getAtomIndex(); + json.beginObjectProperty("op"); + DumpTaggedParserAtomIndex(json, index, stencil); + json.endObject(); + break; + } + case ObjLiteralOpcode::Null: + json.property("op", "Null"); + break; + case ObjLiteralOpcode::Undefined: + json.property("op", "Undefined"); + break; + case ObjLiteralOpcode::True: + json.property("op", "True"); + break; + case ObjLiteralOpcode::False: + json.property("op", "False"); + break; + default: + json.formatProperty("op", "Invalid(%x)", uint8_t(insn.getOp())); + break; + } + + json.endObject(); + } + json.endList(); + + json.property("propertyCount", propertyCount); +} + +void ObjLiteralWriter::dump() const { + js::Fprinter out(stderr); + js::JSONPrinter json(out); + dump(json, nullptr); +} + +void ObjLiteralWriter::dump(js::JSONPrinter& json, + const frontend::CompilationStencil* stencil) const { + json.beginObject(); + dumpFields(json, stencil); + json.endObject(); +} + +void ObjLiteralWriter::dumpFields( + js::JSONPrinter& json, const frontend::CompilationStencil* stencil) const { + DumpObjLiteral(json, stencil, getCode(), kind_, flags_, propertyCount_); +} + +void ObjLiteralStencil::dump() const { + js::Fprinter out(stderr); + js::JSONPrinter json(out); + dump(json, nullptr); +} + +void ObjLiteralStencil::dump( + js::JSONPrinter& json, const frontend::CompilationStencil* stencil) const { + json.beginObject(); + dumpFields(json, stencil); + json.endObject(); +} + +void ObjLiteralStencil::dumpFields( + js::JSONPrinter& json, const frontend::CompilationStencil* stencil) const { + DumpObjLiteral(json, stencil, code_, kind(), flags(), propertyCount_); +} + +#endif // defined(DEBUG) || defined(JS_JITSPEW) + +} // namespace js diff --git a/js/src/frontend/ObjLiteral.h b/js/src/frontend/ObjLiteral.h new file mode 100644 index 0000000000..e39a920e65 --- /dev/null +++ b/js/src/frontend/ObjLiteral.h @@ -0,0 +1,772 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: set ts=8 sw=2 et tw=0 ft=c: + * + * 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_ObjLiteral_h +#define frontend_ObjLiteral_h + +#include "mozilla/BloomFilter.h" // mozilla::BitBloomFilter +#include "mozilla/Span.h" + +#include "frontend/ParserAtom.h" // ParserAtomsTable, TaggedParserAtomIndex, ParserAtom +#include "js/AllocPolicy.h" +#include "js/Value.h" +#include "js/Vector.h" +#include "util/EnumFlags.h" +#include "vm/BytecodeUtil.h" +#include "vm/Opcodes.h" + +/* + * [SMDOC] ObjLiteral (Object Literal) Handling + * ============================================ + * + * The `ObjLiteral*` family of classes defines an infastructure to handle + * object literals as they are encountered at parse time and translate them + * into objects or shapes that are attached to the bytecode. + * + * The object-literal "instructions", whose opcodes are defined in + * `ObjLiteralOpcode` below, each specify one key (atom property name, or + * numeric index) and one value. An `ObjLiteralWriter` buffers a linear + * sequence of such instructions, along with a side-table of atom references. + * The writer stores a compact binary format that is then interpreted by the + * `ObjLiteralReader` to construct an object or shape according to the + * instructions. + * + * This may seem like an odd dance: create an intermediate data structure that + * specifies key/value pairs, then later build the object/shape. Why not just do + * so directly, as we parse? In fact, we used to do this. However, for several + * good reasons, we want to avoid allocating or touching GC things at all + * *during* the parse. We thus use a sequence of ObjLiteral instructions as an + * intermediate data structure to carry object literal contents from parse to + * the time at which we *can* allocate GC things. + * + * (The original intent was to allow for ObjLiteral instructions to actually be + * invoked by a new JS opcode, JSOp::ObjLiteral, thus replacing the more + * general opcode sequences sometimes generated to fill in objects and removing + * the need to attach actual objects to JSOp::Object or JSOp::NewObject. + * However, this was far too invasive and led to performance regressions, so + * currently ObjLiteral only carries literals as far as the end of the parse + * pipeline, when all GC things are allocated.) + * + * ObjLiteral data structures are used to represent object literals whenever + * they are "compatible". See + * BytecodeEmitter::isPropertyListObjLiteralCompatible for the precise + * conditions; in brief, we can represent object literals with "primitive" + * (numeric, boolean, string, null/undefined) values, and "normal" + * (non-computed) object names. We can also represent arrays with the same + * value restrictions. We cannot represent nested objects. We use ObjLiteral in + * two different ways: + * + * - To build a template shape, when we can support the property keys but not + * the property values. + * - To build the actual result object, when we support the property keys and + * the values and this is a JSOp::Object case (see below). + * + * Design and Performance Considerations + * ------------------------------------- + * + * As a brief overview, there are a number of opcodes that allocate objects: + * + * - JSOp::NewInit allocates a new empty `{}` object. + * + * - JSOp::NewObject, with a shape as an argument (held by the script data + * side-tables), allocates a new object with the given `shape` (property keys) + * and `undefined` property values. + * + * - JSOp::Object, with an object as argument, instructs the runtime to + * literally return the object argument as the result. This is thus only an + * "allocation" in the sense that the object was originally allocated when + * the script data / bytecode was created. It is only used when we know for + * sure that the script, and this program point within the script, will run + * *once*. (See the `treatAsRunOnce` flag on JSScript.) + * + * An operation occurs in a "singleton context", according to the parser, if it + * will only ever execute once. In particular, this happens when (i) the script + * is a "run-once" script, which is usually the case for e.g. top-level scripts + * of web-pages (they run on page load, but no function or handle wraps or + * refers to the script so it can't be invoked again), and (ii) the operation + * itself is not within a loop or function in that run-once script. + * + * When we encounter an object literal, we decide which opcode to use, and we + * construct the ObjLiteral and the bytecode using its result appropriately: + * + * - If in a singleton context, and if we support the values, we use + * JSOp::Object and we build the ObjLiteral instructions with values. + * - Otherwise, if we support the keys but not the values, or if we are not + * in a singleton context, we use JSOp::NewObject. In this case, the initial + * opcode only creates an object with empty values, so BytecodeEmitter then + * generates bytecode to set the values appropriately. + * - Otherwise, we generate JSOp::NewInit and bytecode to add properties one at + * a time. This will always work, but is the slowest and least + * memory-efficient option. + */ + +namespace js { + +class FrontendContext; +class JSONPrinter; +class LifoAlloc; + +namespace frontend { +struct CompilationAtomCache; +struct CompilationStencil; +class StencilXDR; +} // namespace frontend + +// Object-literal instruction opcodes. An object literal is constructed by a +// straight-line sequence of these ops, each adding one property to the +// object. +enum class ObjLiteralOpcode : uint8_t { + INVALID = 0, + + ConstValue = 1, // numeric types only. + ConstString = 2, + Null = 3, + Undefined = 4, + True = 5, + False = 6, + + MAX = False, +}; + +// The kind of GC thing constructed by the ObjLiteral framework and stored in +// the script data. +enum class ObjLiteralKind : uint8_t { + // Construct an ArrayObject from a list of dense elements. + Array, + + // Construct an ArrayObject (the call site object) for a tagged template call + // from a list of dense elements for the cooked array followed by the dense + // elements for the `.raw` array. + CallSiteObj, + + // Construct a PlainObject from a list of property keys/values. + Object, + + // Construct a PlainObject Shape from a list of property keys. + Shape, + + // Invalid sentinel value. Must be the last enum value. + Invalid +}; + +// Flags that are associated with a sequence of object-literal instructions. +// (These become bitflags by wrapping with EnumSet below.) +enum class ObjLiteralFlag : uint8_t { + // If set, this object contains index property, or duplicate non-index + // property. + // This flag is valid only if the ObjLiteralKind is not Array. + HasIndexOrDuplicatePropName = 1 << 0, + + // Note: at most 6 flags are currently supported. See ObjLiteralKindAndFlags. +}; + +using ObjLiteralFlags = EnumFlags; + +// Helper class to encode ObjLiteralKind and ObjLiteralFlags in a single byte. +class ObjLiteralKindAndFlags { + uint8_t bits_ = 0; + + static constexpr size_t KindBits = 3; + static constexpr size_t KindMask = BitMask(KindBits); + + static_assert(size_t(ObjLiteralKind::Invalid) <= KindMask, + "ObjLiteralKind needs more bits"); + + public: + ObjLiteralKindAndFlags() = default; + + ObjLiteralKindAndFlags(ObjLiteralKind kind, ObjLiteralFlags flags) + : bits_(size_t(kind) | (flags.toRaw() << KindBits)) { + MOZ_ASSERT(this->kind() == kind); + MOZ_ASSERT(this->flags() == flags); + } + + ObjLiteralKind kind() const { return ObjLiteralKind(bits_ & KindMask); } + ObjLiteralFlags flags() const { + ObjLiteralFlags res; + res.setRaw(bits_ >> KindBits); + return res; + } + + uint8_t toRaw() const { return bits_; } + void setRaw(uint8_t bits) { bits_ = bits; } +}; + +inline bool ObjLiteralOpcodeHasValueArg(ObjLiteralOpcode op) { + return op == ObjLiteralOpcode::ConstValue; +} + +inline bool ObjLiteralOpcodeHasAtomArg(ObjLiteralOpcode op) { + return op == ObjLiteralOpcode::ConstString; +} + +struct ObjLiteralReaderBase; + +// Property name (as TaggedParserAtomIndex) or an integer index. Only used for +// object-type literals; array literals do not require the index (the sequence +// is always dense, with no holes, so the index is implicit). For the latter +// case, we have a `None` placeholder. +struct ObjLiteralKey { + private: + uint32_t value_; + + enum ObjLiteralKeyType { + None, + AtomIndex, + ArrayIndex, + }; + + ObjLiteralKeyType type_; + + ObjLiteralKey(uint32_t value, ObjLiteralKeyType ty) + : value_(value), type_(ty) {} + + public: + ObjLiteralKey() : ObjLiteralKey(0, None) {} + ObjLiteralKey(uint32_t value, bool isArrayIndex) + : ObjLiteralKey(value, isArrayIndex ? ArrayIndex : AtomIndex) {} + ObjLiteralKey(const ObjLiteralKey& other) = default; + + static ObjLiteralKey fromPropName(frontend::TaggedParserAtomIndex atomIndex) { + return ObjLiteralKey(atomIndex.rawData(), false); + } + static ObjLiteralKey fromArrayIndex(uint32_t index) { + return ObjLiteralKey(index, true); + } + static ObjLiteralKey none() { return ObjLiteralKey(); } + + bool isNone() const { return type_ == None; } + bool isAtomIndex() const { return type_ == AtomIndex; } + bool isArrayIndex() const { return type_ == ArrayIndex; } + + frontend::TaggedParserAtomIndex getAtomIndex() const { + MOZ_ASSERT(isAtomIndex()); + return frontend::TaggedParserAtomIndex::fromRaw(value_); + } + uint32_t getArrayIndex() const { + MOZ_ASSERT(isArrayIndex()); + return value_; + } + + uint32_t rawIndex() const { return value_; } +}; + +struct ObjLiteralWriterBase { + protected: + friend struct ObjLiteralReaderBase; // for access to mask and shift. + static const uint32_t ATOM_INDEX_MASK = 0x7fffffff; + // If set, the atom index field is an array index, not an atom index. + static const uint32_t INDEXED_PROP = 0x80000000; + + public: + using CodeVector = Vector; + + protected: + CodeVector code_; + + public: + ObjLiteralWriterBase() = default; + + uint32_t curOffset() const { return code_.length(); } + + private: + [[nodiscard]] bool pushByte(FrontendContext* fc, uint8_t data) { + if (!code_.append(data)) { + js::ReportOutOfMemory(fc); + return false; + } + return true; + } + + [[nodiscard]] bool prepareBytes(FrontendContext* fc, size_t len, + uint8_t** p) { + size_t offset = code_.length(); + if (!code_.growByUninitialized(len)) { + js::ReportOutOfMemory(fc); + return false; + } + *p = &code_[offset]; + return true; + } + + template + [[nodiscard]] bool pushRawData(FrontendContext* fc, T data) { + uint8_t* p = nullptr; + if (!prepareBytes(fc, sizeof(T), &p)) { + return false; + } + memcpy(p, &data, sizeof(T)); + return true; + } + + protected: + [[nodiscard]] bool pushOpAndName(FrontendContext* fc, ObjLiteralOpcode op, + ObjLiteralKey key) { + uint8_t opdata = static_cast(op); + uint32_t data = key.rawIndex() | (key.isArrayIndex() ? INDEXED_PROP : 0); + return pushByte(fc, opdata) && pushRawData(fc, data); + } + + [[nodiscard]] bool pushValueArg(FrontendContext* fc, const JS::Value& value) { + MOZ_ASSERT(value.isNumber() || value.isNullOrUndefined() || + value.isBoolean()); + uint64_t data = value.asRawBits(); + return pushRawData(fc, data); + } + + [[nodiscard]] bool pushAtomArg(FrontendContext* fc, + frontend::TaggedParserAtomIndex atomIndex) { + return pushRawData(fc, atomIndex.rawData()); + } +}; + +// An object-literal instruction writer. This class, held by the bytecode +// emitter, keeps a sequence of object-literal instructions emitted as object +// literal expressions are parsed. It allows the user to 'begin' and 'end' +// straight-line sequences, returning the offsets for this range of instructions +// within the writer. +struct ObjLiteralWriter : private ObjLiteralWriterBase { + public: + ObjLiteralWriter() = default; + + void clear() { code_.clear(); } + + using CodeVector = typename ObjLiteralWriterBase::CodeVector; + + bool checkForDuplicatedNames(FrontendContext* fc); + mozilla::Span getCode() const { return code_; } + ObjLiteralKind getKind() const { return kind_; } + ObjLiteralFlags getFlags() const { return flags_; } + uint32_t getPropertyCount() const { return propertyCount_; } + + void beginArray(JSOp op) { + MOZ_ASSERT(JOF_OPTYPE(op) == JOF_OBJECT); + MOZ_ASSERT(op == JSOp::Object); + kind_ = ObjLiteralKind::Array; + } + void beginCallSiteObj(JSOp op) { + MOZ_ASSERT(JOF_OPTYPE(op) == JOF_OBJECT); + MOZ_ASSERT(op == JSOp::CallSiteObj); + kind_ = ObjLiteralKind::CallSiteObj; + } + void beginObject(JSOp op) { + MOZ_ASSERT(JOF_OPTYPE(op) == JOF_OBJECT); + MOZ_ASSERT(op == JSOp::Object); + kind_ = ObjLiteralKind::Object; + } + void beginShape(JSOp op) { + MOZ_ASSERT(JOF_OPTYPE(op) == JOF_SHAPE); + MOZ_ASSERT(op == JSOp::NewObject); + kind_ = ObjLiteralKind::Shape; + } + + bool setPropName(frontend::ParserAtomsTable& parserAtoms, + const frontend::TaggedParserAtomIndex propName) { + // Only valid in object-mode. + setPropNameNoDuplicateCheck(parserAtoms, propName); + + if (flags_.hasFlag(ObjLiteralFlag::HasIndexOrDuplicatePropName)) { + return true; + } + + // OK to early return if we've already discovered a potential duplicate. + if (mightContainDuplicatePropertyNames_) { + return true; + } + + // Check bloom filter for duplicate, and add if not already represented. + if (propNamesFilter_.mightContain(propName.rawData())) { + mightContainDuplicatePropertyNames_ = true; + } else { + propNamesFilter_.add(propName.rawData()); + } + return true; + } + void setPropNameNoDuplicateCheck( + frontend::ParserAtomsTable& parserAtoms, + const frontend::TaggedParserAtomIndex propName) { + MOZ_ASSERT(kind_ == ObjLiteralKind::Object || + kind_ == ObjLiteralKind::Shape); + parserAtoms.markUsedByStencil(propName, frontend::ParserAtom::Atomize::Yes); + nextKey_ = ObjLiteralKey::fromPropName(propName); + } + void setPropIndex(uint32_t propIndex) { + MOZ_ASSERT(kind_ == ObjLiteralKind::Object); + MOZ_ASSERT(propIndex <= ATOM_INDEX_MASK); + nextKey_ = ObjLiteralKey::fromArrayIndex(propIndex); + flags_.setFlag(ObjLiteralFlag::HasIndexOrDuplicatePropName); + } + void beginDenseArrayElements() { + MOZ_ASSERT(kind_ == ObjLiteralKind::Array || + kind_ == ObjLiteralKind::CallSiteObj); + // Dense array element sequences do not use the keys; the indices are + // implicit. + nextKey_ = ObjLiteralKey::none(); + } + + [[nodiscard]] bool propWithConstNumericValue(FrontendContext* fc, + const JS::Value& value) { + MOZ_ASSERT(kind_ != ObjLiteralKind::Shape); + propertyCount_++; + MOZ_ASSERT(value.isNumber()); + return pushOpAndName(fc, ObjLiteralOpcode::ConstValue, nextKey_) && + pushValueArg(fc, value); + } + [[nodiscard]] bool propWithAtomValue( + FrontendContext* fc, frontend::ParserAtomsTable& parserAtoms, + const frontend::TaggedParserAtomIndex value) { + MOZ_ASSERT(kind_ != ObjLiteralKind::Shape); + propertyCount_++; + parserAtoms.markUsedByStencil(value, frontend::ParserAtom::Atomize::No); + return pushOpAndName(fc, ObjLiteralOpcode::ConstString, nextKey_) && + pushAtomArg(fc, value); + } + [[nodiscard]] bool propWithNullValue(FrontendContext* fc) { + MOZ_ASSERT(kind_ != ObjLiteralKind::Shape); + propertyCount_++; + return pushOpAndName(fc, ObjLiteralOpcode::Null, nextKey_); + } + [[nodiscard]] bool propWithUndefinedValue(FrontendContext* fc) { + propertyCount_++; + return pushOpAndName(fc, ObjLiteralOpcode::Undefined, nextKey_); + } + [[nodiscard]] bool propWithTrueValue(FrontendContext* fc) { + MOZ_ASSERT(kind_ != ObjLiteralKind::Shape); + propertyCount_++; + return pushOpAndName(fc, ObjLiteralOpcode::True, nextKey_); + } + [[nodiscard]] bool propWithFalseValue(FrontendContext* fc) { + MOZ_ASSERT(kind_ != ObjLiteralKind::Shape); + propertyCount_++; + return pushOpAndName(fc, ObjLiteralOpcode::False, nextKey_); + } + + static bool arrayIndexInRange(int32_t i) { + return i >= 0 && static_cast(i) <= ATOM_INDEX_MASK; + } + +#if defined(DEBUG) || defined(JS_JITSPEW) + void dump() const; + void dump(JSONPrinter& json, + const frontend::CompilationStencil* stencil) const; + void dumpFields(JSONPrinter& json, + const frontend::CompilationStencil* stencil) const; +#endif + + private: + // Set to true if we've found possible duplicate names while building. + // This field is placed next to `flags_` field, to reduce padding. + bool mightContainDuplicatePropertyNames_ = false; + + ObjLiteralKind kind_ = ObjLiteralKind::Invalid; + ObjLiteralFlags flags_; + ObjLiteralKey nextKey_; + uint32_t propertyCount_ = 0; + + // Duplicate property names detection is performed in the following way: + // * while emitting code, add each property names with + // `propNamesFilter_` + // * if possible duplicate property name is detected, set + // `mightContainDuplicatePropertyNames_` to true + // * in `checkForDuplicatedNames` method, + // if `mightContainDuplicatePropertyNames_` is true, + // check the duplicate property names with `HashSet`, and if it exists, + // set HasIndexOrDuplicatePropName flag. + mozilla::BitBloomFilter<12, frontend::TaggedParserAtomIndex> propNamesFilter_; +}; + +struct ObjLiteralReaderBase { + private: + mozilla::Span data_; + size_t cursor_; + + [[nodiscard]] bool readByte(uint8_t* b) { + if (cursor_ + 1 > data_.Length()) { + return false; + } + *b = *data_.From(cursor_).data(); + cursor_ += 1; + return true; + } + + [[nodiscard]] bool readBytes(size_t size, const uint8_t** p) { + if (cursor_ + size > data_.Length()) { + return false; + } + *p = data_.From(cursor_).data(); + cursor_ += size; + return true; + } + + template + [[nodiscard]] bool readRawData(T* data) { + const uint8_t* p = nullptr; + if (!readBytes(sizeof(T), &p)) { + return false; + } + memcpy(data, p, sizeof(T)); + return true; + } + + public: + explicit ObjLiteralReaderBase(mozilla::Span data) + : data_(data), cursor_(0) {} + + [[nodiscard]] bool readOpAndKey(ObjLiteralOpcode* op, ObjLiteralKey* key) { + uint8_t opbyte; + if (!readByte(&opbyte)) { + return false; + } + if (MOZ_UNLIKELY(opbyte > static_cast(ObjLiteralOpcode::MAX))) { + return false; + } + *op = static_cast(opbyte); + + uint32_t data; + if (!readRawData(&data)) { + return false; + } + bool isArray = data & ObjLiteralWriterBase::INDEXED_PROP; + uint32_t rawIndex = data & ~ObjLiteralWriterBase::INDEXED_PROP; + *key = ObjLiteralKey(rawIndex, isArray); + return true; + } + + [[nodiscard]] bool readValueArg(JS::Value* value) { + uint64_t data; + if (!readRawData(&data)) { + return false; + } + *value = JS::Value::fromRawBits(data); + return true; + } + + [[nodiscard]] bool readAtomArg(frontend::TaggedParserAtomIndex* atomIndex) { + return readRawData(atomIndex->rawDataRef()); + } + + size_t cursor() const { return cursor_; } +}; + +// A single object-literal instruction, creating one property on an object. +struct ObjLiteralInsn { + private: + ObjLiteralOpcode op_; + ObjLiteralKey key_; + union Arg { + explicit Arg(uint64_t raw_) : raw(raw_) {} + + JS::Value constValue; + frontend::TaggedParserAtomIndex atomIndex; + uint64_t raw; + } arg_; + + public: + ObjLiteralInsn() : op_(ObjLiteralOpcode::INVALID), arg_(0) {} + ObjLiteralInsn(ObjLiteralOpcode op, ObjLiteralKey key) + : op_(op), key_(key), arg_(0) { + MOZ_ASSERT(!hasConstValue()); + MOZ_ASSERT(!hasAtomIndex()); + } + ObjLiteralInsn(ObjLiteralOpcode op, ObjLiteralKey key, const JS::Value& value) + : op_(op), key_(key), arg_(0) { + MOZ_ASSERT(hasConstValue()); + MOZ_ASSERT(!hasAtomIndex()); + arg_.constValue = value; + } + ObjLiteralInsn(ObjLiteralOpcode op, ObjLiteralKey key, + frontend::TaggedParserAtomIndex atomIndex) + : op_(op), key_(key), arg_(0) { + MOZ_ASSERT(!hasConstValue()); + MOZ_ASSERT(hasAtomIndex()); + arg_.atomIndex = atomIndex; + } + ObjLiteralInsn(const ObjLiteralInsn& other) : ObjLiteralInsn() { + *this = other; + } + ObjLiteralInsn& operator=(const ObjLiteralInsn& other) { + op_ = other.op_; + key_ = other.key_; + arg_.raw = other.arg_.raw; + return *this; + } + + bool isValid() const { + return op_ > ObjLiteralOpcode::INVALID && op_ <= ObjLiteralOpcode::MAX; + } + + ObjLiteralOpcode getOp() const { + MOZ_ASSERT(isValid()); + return op_; + } + const ObjLiteralKey& getKey() const { + MOZ_ASSERT(isValid()); + return key_; + } + + bool hasConstValue() const { + MOZ_ASSERT(isValid()); + return ObjLiteralOpcodeHasValueArg(op_); + } + bool hasAtomIndex() const { + MOZ_ASSERT(isValid()); + return ObjLiteralOpcodeHasAtomArg(op_); + } + + JS::Value getConstValue() const { + MOZ_ASSERT(isValid()); + MOZ_ASSERT(hasConstValue()); + return arg_.constValue; + } + frontend::TaggedParserAtomIndex getAtomIndex() const { + MOZ_ASSERT(isValid()); + MOZ_ASSERT(hasAtomIndex()); + return arg_.atomIndex; + }; +}; + +// A reader that parses a sequence of object-literal instructions out of the +// encoded form. +struct ObjLiteralReader : private ObjLiteralReaderBase { + public: + explicit ObjLiteralReader(mozilla::Span data) + : ObjLiteralReaderBase(data) {} + + [[nodiscard]] bool readInsn(ObjLiteralInsn* insn) { + ObjLiteralOpcode op; + ObjLiteralKey key; + if (!readOpAndKey(&op, &key)) { + return false; + } + if (ObjLiteralOpcodeHasValueArg(op)) { + JS::Value value; + if (!readValueArg(&value)) { + return false; + } + *insn = ObjLiteralInsn(op, key, value); + return true; + } + if (ObjLiteralOpcodeHasAtomArg(op)) { + frontend::TaggedParserAtomIndex atomIndex; + if (!readAtomArg(&atomIndex)) { + return false; + } + *insn = ObjLiteralInsn(op, key, atomIndex); + return true; + } + *insn = ObjLiteralInsn(op, key); + return true; + } +}; + +// A class to modify the code, while keeping the structure. +struct ObjLiteralModifier : private ObjLiteralReaderBase { + mozilla::Span mutableData_; + + public: + explicit ObjLiteralModifier(mozilla::Span data) + : ObjLiteralReaderBase(data), mutableData_(data) {} + + private: + // Map `atom` with `map`, and write to `atomCursor` of `mutableData_`. + template + void mapOneAtom(MapT map, frontend::TaggedParserAtomIndex atom, + size_t atomCursor) { + auto atomIndex = map(atom); + memcpy(mutableData_.data() + atomCursor, atomIndex.rawDataRef(), + sizeof(frontend::TaggedParserAtomIndex)); + } + + // Map atoms in single instruction. + // Return true if it successfully maps. + // Return false if there's no more instruction. + template + bool mapInsnAtom(MapT map) { + ObjLiteralOpcode op; + ObjLiteralKey key; + + size_t opCursor = cursor(); + if (!readOpAndKey(&op, &key)) { + return false; + } + if (key.isAtomIndex()) { + static constexpr size_t OpLength = 1; + size_t atomCursor = opCursor + OpLength; + mapOneAtom(map, key.getAtomIndex(), atomCursor); + } + + if (ObjLiteralOpcodeHasValueArg(op)) { + JS::Value value; + if (!readValueArg(&value)) { + return false; + } + } else if (ObjLiteralOpcodeHasAtomArg(op)) { + size_t atomCursor = cursor(); + + frontend::TaggedParserAtomIndex atomIndex; + if (!readAtomArg(&atomIndex)) { + return false; + } + + mapOneAtom(map, atomIndex, atomCursor); + } + + return true; + } + + public: + // Map TaggedParserAtomIndex inside the code in place, with given function. + template + void mapAtom(MapT map) { + while (mapInsnAtom(map)) { + } + } +}; + +class ObjLiteralStencil { + friend class frontend::StencilXDR; + + // CompilationStencil::clone has to update the code pointer. + friend struct frontend::CompilationStencil; + + mozilla::Span code_; + ObjLiteralKindAndFlags kindAndFlags_; + uint32_t propertyCount_ = 0; + + public: + ObjLiteralStencil() = default; + + ObjLiteralStencil(uint8_t* code, size_t length, ObjLiteralKind kind, + const ObjLiteralFlags& flags, uint32_t propertyCount) + : code_(mozilla::Span(code, length)), + kindAndFlags_(kind, flags), + propertyCount_(propertyCount) {} + + JS::GCCellPtr create(JSContext* cx, + const frontend::CompilationAtomCache& atomCache) const; + + mozilla::Span code() const { return code_; } + ObjLiteralKind kind() const { return kindAndFlags_.kind(); } + ObjLiteralFlags flags() const { return kindAndFlags_.flags(); } + uint32_t propertyCount() const { return propertyCount_; } + +#ifdef DEBUG + bool isContainedIn(const LifoAlloc& alloc) const; +#endif + +#if defined(DEBUG) || defined(JS_JITSPEW) + void dump() const; + void dump(JSONPrinter& json, + const frontend::CompilationStencil* stencil) const; + void dumpFields(JSONPrinter& json, + const frontend::CompilationStencil* stencil) const; + +#endif +}; + +} // namespace js +#endif // frontend_ObjLiteral_h diff --git a/js/src/frontend/ObjectEmitter.cpp b/js/src/frontend/ObjectEmitter.cpp new file mode 100644 index 0000000000..6a6c36ee08 --- /dev/null +++ b/js/src/frontend/ObjectEmitter.cpp @@ -0,0 +1,897 @@ +/* -*- 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/ObjectEmitter.h" + +#include "mozilla/Assertions.h" // MOZ_ASSERT + +#include "frontend/BytecodeEmitter.h" // BytecodeEmitter +#include "frontend/IfEmitter.h" // IfEmitter +#include "frontend/ParseNode.h" // AccessorType +#include "frontend/SharedContext.h" // SharedContext +#include "vm/FunctionPrefixKind.h" // FunctionPrefixKind +#include "vm/Opcodes.h" // JSOp + +using namespace js; +using namespace js::frontend; + +using mozilla::Maybe; + +PropertyEmitter::PropertyEmitter(BytecodeEmitter* bce) : bce_(bce) {} + +bool PropertyEmitter::prepareForProtoValue(uint32_t keyPos) { + MOZ_ASSERT(propertyState_ == PropertyState::Start || + propertyState_ == PropertyState::Init); + + // [stack] CTOR? OBJ CTOR? + + if (!bce_->updateSourceCoordNotes(keyPos)) { + return false; + } + +#ifdef DEBUG + propertyState_ = PropertyState::ProtoValue; +#endif + return true; +} + +bool PropertyEmitter::emitMutateProto() { + MOZ_ASSERT(propertyState_ == PropertyState::ProtoValue); + + // [stack] OBJ PROTO + + if (!bce_->emit1(JSOp::MutateProto)) { + // [stack] OBJ + return false; + } + +#ifdef DEBUG + propertyState_ = PropertyState::Init; +#endif + return true; +} + +bool PropertyEmitter::prepareForSpreadOperand(uint32_t spreadPos) { + MOZ_ASSERT(propertyState_ == PropertyState::Start || + propertyState_ == PropertyState::Init); + + // [stack] OBJ + + if (!bce_->updateSourceCoordNotes(spreadPos)) { + return false; + } + if (!bce_->emit1(JSOp::Dup)) { + // [stack] OBJ OBJ + return false; + } + +#ifdef DEBUG + propertyState_ = PropertyState::SpreadOperand; +#endif + return true; +} + +bool PropertyEmitter::emitSpread() { + MOZ_ASSERT(propertyState_ == PropertyState::SpreadOperand); + + // [stack] OBJ OBJ VAL + + if (!bce_->emitCopyDataProperties(BytecodeEmitter::CopyOption::Unfiltered)) { + // [stack] OBJ + return false; + } + +#ifdef DEBUG + propertyState_ = PropertyState::Init; +#endif + return true; +} + +MOZ_ALWAYS_INLINE bool PropertyEmitter::prepareForProp(uint32_t keyPos, + bool isStatic, + bool isIndexOrComputed) { + isStatic_ = isStatic; + isIndexOrComputed_ = isIndexOrComputed; + + // [stack] CTOR? OBJ + + if (!bce_->updateSourceCoordNotes(keyPos)) { + return false; + } + + if (isStatic_) { + if (!bce_->emit1(JSOp::Dup2)) { + // [stack] CTOR HOMEOBJ CTOR HOMEOBJ + return false; + } + if (!bce_->emit1(JSOp::Pop)) { + // [stack] CTOR HOMEOBJ CTOR + return false; + } + } + + return true; +} + +bool PropertyEmitter::prepareForPrivateMethod() { + MOZ_ASSERT(propertyState_ == PropertyState::Start || + propertyState_ == PropertyState::Init); + MOZ_ASSERT(isClass_); + + isStatic_ = false; + isIndexOrComputed_ = false; + +#ifdef DEBUG + propertyState_ = PropertyState::PrivateMethodValue; +#endif + return true; +} + +bool PropertyEmitter::prepareForPrivateStaticMethod(uint32_t keyPos) { + MOZ_ASSERT(propertyState_ == PropertyState::Start || + propertyState_ == PropertyState::Init); + MOZ_ASSERT(isClass_); + + // [stack] CTOR OBJ + + if (!prepareForProp(keyPos, + /* isStatic_ = */ true, + /* isIndexOrComputed = */ true)) { + // [stack] CTOR OBJ CTOR + return false; + } + +#ifdef DEBUG + propertyState_ = PropertyState::PrivateStaticMethod; +#endif + return true; +} + +bool PropertyEmitter::prepareForPropValue(uint32_t keyPos, Kind kind) { + MOZ_ASSERT(propertyState_ == PropertyState::Start || + propertyState_ == PropertyState::Init); + + // [stack] CTOR? OBJ + + if (!prepareForProp(keyPos, + /* isStatic_ = */ kind == Kind::Static, + /* isIndexOrComputed = */ false)) { + // [stack] CTOR? OBJ CTOR? + return false; + } + +#ifdef DEBUG + propertyState_ = PropertyState::PropValue; +#endif + return true; +} + +bool PropertyEmitter::prepareForIndexPropKey(uint32_t keyPos, Kind kind) { + MOZ_ASSERT(propertyState_ == PropertyState::Start || + propertyState_ == PropertyState::Init); + + // [stack] CTOR? OBJ + + if (!prepareForProp(keyPos, + /* isStatic_ = */ kind == Kind::Static, + /* isIndexOrComputed = */ true)) { + // [stack] CTOR? OBJ CTOR? + return false; + } + +#ifdef DEBUG + propertyState_ = PropertyState::IndexKey; +#endif + return true; +} + +bool PropertyEmitter::prepareForIndexPropValue() { + MOZ_ASSERT(propertyState_ == PropertyState::IndexKey); + + // [stack] CTOR? OBJ CTOR? KEY + +#ifdef DEBUG + propertyState_ = PropertyState::IndexValue; +#endif + return true; +} + +bool PropertyEmitter::prepareForComputedPropKey(uint32_t keyPos, Kind kind) { + MOZ_ASSERT(propertyState_ == PropertyState::Start || + propertyState_ == PropertyState::Init); + + // [stack] CTOR? OBJ + + if (!prepareForProp(keyPos, + /* isStatic_ = */ kind == Kind::Static, + /* isIndexOrComputed = */ true)) { + // [stack] CTOR? OBJ CTOR? + return false; + } + +#ifdef DEBUG + propertyState_ = PropertyState::ComputedKey; +#endif + return true; +} + +bool PropertyEmitter::prepareForComputedPropValue() { + MOZ_ASSERT(propertyState_ == PropertyState::ComputedKey); + + // [stack] CTOR? OBJ CTOR? KEY + + if (!bce_->emit1(JSOp::ToPropertyKey)) { + // [stack] CTOR? OBJ CTOR? KEY + return false; + } + +#ifdef DEBUG + propertyState_ = PropertyState::ComputedValue; +#endif + return true; +} + +bool PropertyEmitter::emitInitHomeObject() { + MOZ_ASSERT(propertyState_ == PropertyState::PropValue || + propertyState_ == PropertyState::PrivateMethodValue || + propertyState_ == PropertyState::PrivateStaticMethod || + propertyState_ == PropertyState::IndexValue || + propertyState_ == PropertyState::ComputedValue); + + // [stack] CTOR? HOMEOBJ CTOR? KEY? FUN + + // There are the following values on the stack conditionally, between + // HOMEOBJ and FUN: + // * the 2nd CTOR if isStatic_ + // * KEY if isIndexOrComputed_ + // + // JSOp::InitHomeObject uses one of the following: + // * HOMEOBJ if !isStatic_ + // (`super.foo` points the super prototype property) + // * the 2nd CTOR if isStatic_ + // (`super.foo` points the super constructor property) + if (!bce_->emitDupAt(1 + isIndexOrComputed_)) { + // [stack] # non-static method + // [stack] CTOR? HOMEOBJ CTOR KEY? FUN CTOR + // [stack] # static method + // [stack] CTOR? HOMEOBJ KEY? FUN HOMEOBJ + return false; + } + if (!bce_->emit1(JSOp::InitHomeObject)) { + // [stack] CTOR? HOMEOBJ CTOR? KEY? FUN + return false; + } + +#ifdef DEBUG + if (propertyState_ == PropertyState::PropValue) { + propertyState_ = PropertyState::InitHomeObj; + } else if (propertyState_ == PropertyState::PrivateMethodValue) { + propertyState_ = PropertyState::InitHomeObjForPrivateMethod; + } else if (propertyState_ == PropertyState::PrivateStaticMethod) { + propertyState_ = PropertyState::InitHomeObjForPrivateStaticMethod; + } else if (propertyState_ == PropertyState::IndexValue) { + propertyState_ = PropertyState::InitHomeObjForIndex; + } else { + propertyState_ = PropertyState::InitHomeObjForComputed; + } +#endif + return true; +} + +bool PropertyEmitter::emitInit(AccessorType accessorType, + TaggedParserAtomIndex key) { + switch (accessorType) { + case AccessorType::None: + return emitInit(isClass_ ? JSOp::InitHiddenProp : JSOp::InitProp, key); + case AccessorType::Getter: + return emitInit( + isClass_ ? JSOp::InitHiddenPropGetter : JSOp::InitPropGetter, key); + case AccessorType::Setter: + return emitInit( + isClass_ ? JSOp::InitHiddenPropSetter : JSOp::InitPropSetter, key); + } + MOZ_CRASH("Invalid op"); +} + +bool PropertyEmitter::emitInitIndexOrComputed(AccessorType accessorType) { + switch (accessorType) { + case AccessorType::None: + return emitInitIndexOrComputed(isClass_ ? JSOp::InitHiddenElem + : JSOp::InitElem); + case AccessorType::Getter: + return emitInitIndexOrComputed(isClass_ ? JSOp::InitHiddenElemGetter + : JSOp::InitElemGetter); + case AccessorType::Setter: + return emitInitIndexOrComputed(isClass_ ? JSOp::InitHiddenElemSetter + : JSOp::InitElemSetter); + } + MOZ_CRASH("Invalid op"); +} + +bool PropertyEmitter::emitPrivateStaticMethod(AccessorType accessorType) { + MOZ_ASSERT(isClass_); + + switch (accessorType) { + case AccessorType::None: + return emitInitIndexOrComputed(JSOp::InitLockedElem); + case AccessorType::Getter: + return emitInitIndexOrComputed(JSOp::InitHiddenElemGetter); + case AccessorType::Setter: + return emitInitIndexOrComputed(JSOp::InitHiddenElemSetter); + } + MOZ_CRASH("Invalid op"); +} + +bool PropertyEmitter::emitInit(JSOp op, TaggedParserAtomIndex key) { + MOZ_ASSERT(propertyState_ == PropertyState::PropValue || + propertyState_ == PropertyState::InitHomeObj); + + MOZ_ASSERT(op == JSOp::InitProp || op == JSOp::InitHiddenProp || + op == JSOp::InitPropGetter || op == JSOp::InitHiddenPropGetter || + op == JSOp::InitPropSetter || op == JSOp::InitHiddenPropSetter); + + // [stack] CTOR? OBJ CTOR? VAL + + if (!bce_->emitAtomOp(op, key)) { + // [stack] CTOR? OBJ CTOR? + return false; + } + + if (!emitPopClassConstructor()) { + return false; + } + +#ifdef DEBUG + propertyState_ = PropertyState::Init; +#endif + return true; +} + +bool PropertyEmitter::skipInit() { + MOZ_ASSERT(propertyState_ == PropertyState::PrivateMethodValue || + propertyState_ == PropertyState::InitHomeObjForPrivateMethod); +#ifdef DEBUG + propertyState_ = PropertyState::Init; +#endif + return true; +} + +bool PropertyEmitter::emitInitIndexOrComputed(JSOp op) { + MOZ_ASSERT(propertyState_ == PropertyState::IndexValue || + propertyState_ == PropertyState::InitHomeObjForIndex || + propertyState_ == PropertyState::ComputedValue || + propertyState_ == PropertyState::InitHomeObjForComputed || + propertyState_ == PropertyState::PrivateStaticMethod || + propertyState_ == + PropertyState::InitHomeObjForPrivateStaticMethod); + + MOZ_ASSERT(op == JSOp::InitElem || op == JSOp::InitHiddenElem || + op == JSOp::InitLockedElem || op == JSOp::InitElemGetter || + op == JSOp::InitHiddenElemGetter || op == JSOp::InitElemSetter || + op == JSOp::InitHiddenElemSetter); + + // [stack] CTOR? OBJ CTOR? KEY VAL + + if (!bce_->emit1(op)) { + // [stack] CTOR? OBJ CTOR? + return false; + } + + if (!emitPopClassConstructor()) { + return false; + } + +#ifdef DEBUG + propertyState_ = PropertyState::Init; +#endif + return true; +} + +bool PropertyEmitter::emitPopClassConstructor() { + if (isStatic_) { + // [stack] CTOR HOMEOBJ CTOR + + if (!bce_->emit1(JSOp::Pop)) { + // [stack] CTOR HOMEOBJ + return false; + } + } + + return true; +} + +ObjectEmitter::ObjectEmitter(BytecodeEmitter* bce) : PropertyEmitter(bce) {} + +bool ObjectEmitter::emitObject(size_t propertyCount) { + MOZ_ASSERT(propertyState_ == PropertyState::Start); + MOZ_ASSERT(objectState_ == ObjectState::Start); + + // [stack] + + // Emit code for {p:a, '%q':b, 2:c} that is equivalent to constructing + // a new object and defining (in source order) each property on the object + // (or mutating the object's [[Prototype]], in the case of __proto__). + if (!bce_->emit1(JSOp::NewInit)) { + // [stack] OBJ + return false; + } + +#ifdef DEBUG + objectState_ = ObjectState::Object; +#endif + return true; +} + +bool ObjectEmitter::emitObjectWithTemplateOnStack() { + MOZ_ASSERT(propertyState_ == PropertyState::Start); + MOZ_ASSERT(objectState_ == ObjectState::Start); + +#ifdef DEBUG + objectState_ = ObjectState::Object; +#endif + return true; +} + +bool ObjectEmitter::emitEnd() { + MOZ_ASSERT(propertyState_ == PropertyState::Start || + propertyState_ == PropertyState::Init); + MOZ_ASSERT(objectState_ == ObjectState::Object); + + // [stack] OBJ + +#ifdef DEBUG + objectState_ = ObjectState::End; +#endif + return true; +} + +AutoSaveLocalStrictMode::AutoSaveLocalStrictMode(SharedContext* sc) : sc_(sc) { + savedStrictness_ = sc_->setLocalStrictMode(true); +} + +AutoSaveLocalStrictMode::~AutoSaveLocalStrictMode() { + if (sc_) { + restore(); + } +} + +void AutoSaveLocalStrictMode::restore() { + MOZ_ALWAYS_TRUE(sc_->setLocalStrictMode(savedStrictness_)); + sc_ = nullptr; +} + +ClassEmitter::ClassEmitter(BytecodeEmitter* bce) + : PropertyEmitter(bce), strictMode_(bce->sc) { + isClass_ = true; +} + +bool ClassEmitter::emitScope(LexicalScope::ParserData* scopeBindings) { + MOZ_ASSERT(propertyState_ == PropertyState::Start); + MOZ_ASSERT(classState_ == ClassState::Start); + + tdzCache_.emplace(bce_); + + innerScope_.emplace(bce_); + if (!innerScope_->enterLexical(bce_, ScopeKind::Lexical, scopeBindings)) { + return false; + } + +#ifdef DEBUG + classState_ = ClassState::Scope; +#endif + + return true; +} + +bool ClassEmitter::emitBodyScope(ClassBodyScope::ParserData* scopeBindings) { + MOZ_ASSERT(propertyState_ == PropertyState::Start); + MOZ_ASSERT(classState_ == ClassState::Start || + classState_ == ClassState::Scope); + + bodyTdzCache_.emplace(bce_); + + bodyScope_.emplace(bce_); + if (!bodyScope_->enterClassBody(bce_, ScopeKind::ClassBody, scopeBindings)) { + return false; + } + +#ifdef DEBUG + classState_ = ClassState::BodyScope; +#endif + + return true; +} + +bool ClassEmitter::emitClass(TaggedParserAtomIndex name, + TaggedParserAtomIndex nameForAnonymousClass, + bool hasNameOnStack) { + MOZ_ASSERT(propertyState_ == PropertyState::Start); + MOZ_ASSERT(classState_ == ClassState::Start || + classState_ == ClassState::Scope || + classState_ == ClassState::BodyScope); + MOZ_ASSERT_IF(nameForAnonymousClass || hasNameOnStack, !name); + MOZ_ASSERT(!(nameForAnonymousClass && hasNameOnStack)); + + // [stack] + + name_ = name; + nameForAnonymousClass_ = nameForAnonymousClass; + hasNameOnStack_ = hasNameOnStack; + isDerived_ = false; + + if (!bce_->emit1(JSOp::NewInit)) { + // [stack] HOMEOBJ + return false; + } + +#ifdef DEBUG + classState_ = ClassState::Class; +#endif + return true; +} + +bool ClassEmitter::emitDerivedClass(TaggedParserAtomIndex name, + TaggedParserAtomIndex nameForAnonymousClass, + bool hasNameOnStack) { + MOZ_ASSERT(propertyState_ == PropertyState::Start); + MOZ_ASSERT(classState_ == ClassState::Start || + classState_ == ClassState::Scope || + classState_ == ClassState::BodyScope); + MOZ_ASSERT_IF(nameForAnonymousClass || hasNameOnStack, !name); + MOZ_ASSERT(!nameForAnonymousClass || !hasNameOnStack); + + // [stack] HERITAGE + + name_ = name; + nameForAnonymousClass_ = nameForAnonymousClass; + hasNameOnStack_ = hasNameOnStack; + isDerived_ = true; + + InternalIfEmitter ifThenElse(bce_); + + // Heritage must be null or a non-generator constructor + if (!bce_->emit1(JSOp::CheckClassHeritage)) { + // [stack] HERITAGE + return false; + } + + // [IF] (heritage !== null) + if (!bce_->emit1(JSOp::Dup)) { + // [stack] HERITAGE HERITAGE + return false; + } + if (!bce_->emit1(JSOp::Null)) { + // [stack] HERITAGE HERITAGE NULL + return false; + } + if (!bce_->emit1(JSOp::StrictNe)) { + // [stack] HERITAGE NE + return false; + } + + // [THEN] funProto = heritage, objProto = heritage.prototype + if (!ifThenElse.emitThenElse()) { + return false; + } + if (!bce_->emit1(JSOp::Dup)) { + // [stack] HERITAGE HERITAGE + return false; + } + if (!bce_->emitAtomOp(JSOp::GetProp, + TaggedParserAtomIndex::WellKnown::prototype())) { + // [stack] HERITAGE PROTO + return false; + } + + // [ELSE] funProto = %FunctionPrototype%, objProto = null + if (!ifThenElse.emitElse()) { + return false; + } + if (!bce_->emit1(JSOp::Pop)) { + // [stack] + return false; + } + if (!bce_->emitBuiltinObject(BuiltinObjectKind::FunctionPrototype)) { + // [stack] PROTO + return false; + } + if (!bce_->emit1(JSOp::Null)) { + // [stack] PROTO NULL + return false; + } + + // [ENDIF] + if (!ifThenElse.emitEnd()) { + return false; + } + + if (!bce_->emit1(JSOp::ObjWithProto)) { + // [stack] HERITAGE HOMEOBJ + return false; + } + if (!bce_->emit1(JSOp::Swap)) { + // [stack] HOMEOBJ HERITAGE + return false; + } + +#ifdef DEBUG + classState_ = ClassState::Class; +#endif + return true; +} + +bool ClassEmitter::emitInitConstructor(bool needsHomeObject) { + MOZ_ASSERT(propertyState_ == PropertyState::Start); + MOZ_ASSERT(classState_ == ClassState::Class || + classState_ == ClassState::InstanceMemberInitializersEnd); + + // [stack] HOMEOBJ CTOR + + if (needsHomeObject) { + if (!bce_->emitDupAt(1)) { + // [stack] HOMEOBJ CTOR HOMEOBJ + return false; + } + if (!bce_->emit1(JSOp::InitHomeObject)) { + // [stack] HOMEOBJ CTOR + return false; + } + } + + if (!initProtoAndCtor()) { + // [stack] CTOR HOMEOBJ + return false; + } + +#ifdef DEBUG + classState_ = ClassState::InitConstructor; +#endif + return true; +} + +bool ClassEmitter::initProtoAndCtor() { + // [stack] NAME? HOMEOBJ CTOR + + if (hasNameOnStack_) { + if (!bce_->emitDupAt(2)) { + // [stack] NAME HOMEOBJ CTOR NAME + return false; + } + if (!bce_->emit2(JSOp::SetFunName, uint8_t(FunctionPrefixKind::None))) { + // [stack] NAME HOMEOBJ CTOR + return false; + } + } + + if (!bce_->emit1(JSOp::Swap)) { + // [stack] NAME? CTOR HOMEOBJ + return false; + } + if (!bce_->emit1(JSOp::Dup2)) { + // [stack] NAME? CTOR HOMEOBJ CTOR HOMEOBJ + return false; + } + if (!bce_->emitAtomOp(JSOp::InitLockedProp, + TaggedParserAtomIndex::WellKnown::prototype())) { + // [stack] NAME? CTOR HOMEOBJ CTOR + return false; + } + if (!bce_->emitAtomOp(JSOp::InitHiddenProp, + TaggedParserAtomIndex::WellKnown::constructor())) { + // [stack] NAME? CTOR HOMEOBJ + return false; + } + + return true; +} + +bool ClassEmitter::prepareForMemberInitializers(size_t numInitializers, + bool isStatic) { + MOZ_ASSERT_IF(!isStatic, classState_ == ClassState::Class); + MOZ_ASSERT_IF(isStatic, classState_ == ClassState::InitConstructor); + MOZ_ASSERT(memberState_ == MemberState::Start); + + // .initializers is a variable that stores an array of lambdas containing + // code (the initializer) for each field. Upon an object's construction, + // these lambdas will be called, defining the values. + auto initializers = + isStatic ? TaggedParserAtomIndex::WellKnown::dotStaticInitializers() + : TaggedParserAtomIndex::WellKnown::dotInitializers(); + initializersAssignment_.emplace(bce_, initializers, + NameOpEmitter::Kind::Initialize); + if (!initializersAssignment_->prepareForRhs()) { + return false; + } + + if (!bce_->emitUint32Operand(JSOp::NewArray, numInitializers)) { + // [stack] ARRAY + return false; + } + + initializerIndex_ = 0; +#ifdef DEBUG + if (isStatic) { + classState_ = ClassState::StaticMemberInitializers; + } else { + classState_ = ClassState::InstanceMemberInitializers; + } + numInitializers_ = numInitializers; +#endif + return true; +} + +bool ClassEmitter::prepareForMemberInitializer() { + MOZ_ASSERT(classState_ == ClassState::InstanceMemberInitializers || + classState_ == ClassState::StaticMemberInitializers); + MOZ_ASSERT(memberState_ == MemberState::Start); + +#ifdef DEBUG + memberState_ = MemberState::Initializer; +#endif + return true; +} + +bool ClassEmitter::emitMemberInitializerHomeObject(bool isStatic) { + MOZ_ASSERT(memberState_ == MemberState::Initializer); + // [stack] OBJ HERITAGE? ARRAY METHOD + // or: + // [stack] CTOR HOMEOBJ ARRAY METHOD + + if (isStatic) { + if (!bce_->emitDupAt(3)) { + // [stack] CTOR HOMEOBJ ARRAY METHOD CTOR + return false; + } + } else { + if (!bce_->emitDupAt(isDerived_ ? 3 : 2)) { + // [stack] OBJ HERITAGE? ARRAY METHOD OBJ + return false; + } + } + if (!bce_->emit1(JSOp::InitHomeObject)) { + // [stack] OBJ HERITAGE? ARRAY METHOD + // or: + // [stack] CTOR HOMEOBJ ARRAY METHOD + return false; + } + +#ifdef DEBUG + memberState_ = MemberState::InitializerWithHomeObject; +#endif + return true; +} + +bool ClassEmitter::emitStoreMemberInitializer() { + MOZ_ASSERT(memberState_ == MemberState::Initializer || + memberState_ == MemberState::InitializerWithHomeObject); + MOZ_ASSERT(initializerIndex_ < numInitializers_); + // [stack] HOMEOBJ HERITAGE? ARRAY METHOD + + if (!bce_->emitUint32Operand(JSOp::InitElemArray, initializerIndex_)) { + // [stack] HOMEOBJ HERITAGE? ARRAY + return false; + } + + initializerIndex_++; +#ifdef DEBUG + memberState_ = MemberState::Start; +#endif + return true; +} + +bool ClassEmitter::emitMemberInitializersEnd() { + MOZ_ASSERT(propertyState_ == PropertyState::Start || + propertyState_ == PropertyState::Init); + MOZ_ASSERT(classState_ == ClassState::InstanceMemberInitializers || + classState_ == ClassState::StaticMemberInitializers); + MOZ_ASSERT(memberState_ == MemberState::Start); + MOZ_ASSERT(initializerIndex_ == numInitializers_); + + if (!initializersAssignment_->emitAssignment()) { + // [stack] HOMEOBJ HERITAGE? ARRAY + return false; + } + initializersAssignment_.reset(); + + if (!bce_->emit1(JSOp::Pop)) { + // [stack] HOMEOBJ HERITAGE? + return false; + } + +#ifdef DEBUG + if (classState_ == ClassState::InstanceMemberInitializers) { + classState_ = ClassState::InstanceMemberInitializersEnd; + } else { + classState_ = ClassState::StaticMemberInitializersEnd; + } +#endif + return true; +} + +bool ClassEmitter::emitBinding() { + MOZ_ASSERT(propertyState_ == PropertyState::Start || + propertyState_ == PropertyState::Init); + MOZ_ASSERT(classState_ == ClassState::InitConstructor || + classState_ == ClassState::InstanceMemberInitializersEnd || + classState_ == ClassState::StaticMemberInitializersEnd); + // [stack] CTOR HOMEOBJ + + if (!bce_->emit1(JSOp::Pop)) { + // [stack] CTOR + return false; + } + + if (name_) { + MOZ_ASSERT(innerScope_.isSome()); + + if (!bce_->emitLexicalInitialization(name_)) { + // [stack] CTOR + return false; + } + } + + // [stack] CTOR + +#ifdef DEBUG + classState_ = ClassState::BoundName; +#endif + return true; +} + +bool ClassEmitter::emitEnd(Kind kind) { + MOZ_ASSERT(classState_ == ClassState::BoundName); + // [stack] CTOR + + if (bodyScope_.isSome()) { + MOZ_ASSERT(bodyTdzCache_.isSome()); + + if (!bodyScope_->leave(bce_)) { + return false; + } + bodyScope_.reset(); + bodyTdzCache_.reset(); + } + + if (innerScope_.isSome()) { + MOZ_ASSERT(tdzCache_.isSome()); + + if (!innerScope_->leave(bce_)) { + return false; + } + innerScope_.reset(); + tdzCache_.reset(); + } else { + MOZ_ASSERT(kind == Kind::Expression); + MOZ_ASSERT(tdzCache_.isNothing()); + } + + if (kind == Kind::Declaration) { + MOZ_ASSERT(name_); + + if (!bce_->emitLexicalInitialization(name_)) { + // [stack] CTOR + return false; + } + // Only class statements make outer bindings, and they do not leave + // themselves on the stack. + if (!bce_->emit1(JSOp::Pop)) { + // [stack] + return false; + } + } + + // [stack] # class declaration + // [stack] + // [stack] # class expression + // [stack] CTOR + + strictMode_.restore(); + +#ifdef DEBUG + classState_ = ClassState::End; +#endif + return true; +} diff --git a/js/src/frontend/ObjectEmitter.h b/js/src/frontend/ObjectEmitter.h new file mode 100644 index 0000000000..67f11f57c3 --- /dev/null +++ b/js/src/frontend/ObjectEmitter.h @@ -0,0 +1,849 @@ +/* -*- 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/. */ + +#ifndef frontend_ObjectEmitter_h +#define frontend_ObjectEmitter_h + +#include "mozilla/Attributes.h" // MOZ_STACK_CLASS, MOZ_ALWAYS_INLINE, MOZ_RAII +#include "mozilla/Maybe.h" // Maybe + +#include // size_t +#include // uint32_t + +#include "frontend/EmitterScope.h" // EmitterScope +#include "frontend/NameOpEmitter.h" // NameOpEmitter +#include "frontend/ParseNode.h" // AccessorType +#include "frontend/ParserAtom.h" // TaggedParserAtomIndex +#include "frontend/TDZCheckCache.h" // TDZCheckCache +#include "vm/Opcodes.h" // JSOp +#include "vm/Scope.h" // LexicalScope + +namespace js { + +namespace frontend { + +struct BytecodeEmitter; +class SharedContext; + +// Class for emitting bytecode for object and class properties. +// See ObjectEmitter and ClassEmitter for usage. +class MOZ_STACK_CLASS PropertyEmitter { + public: + enum class Kind { + // Prototype property. + Prototype, + + // Class static property. + Static + }; + + protected: + BytecodeEmitter* bce_; + + // True if the object is class. + // Set by ClassEmitter. + bool isClass_ = false; + + // True if the property is class static method. + bool isStatic_ = false; + + // True if the property has computed or index key. + bool isIndexOrComputed_ = false; + +#ifdef DEBUG + // The state of this emitter. + // + // +-------+ + // | Start |-+ + // +-------+ | + // | + // +---------+ + // | + // | +------------------------------------------------------------+ + // | | | + // | | [normal property/method/accessor] | + // | v prepareForPropValue +-----------+ +------+ | + // +->+----------------------->| PropValue |-+ +->| Init |-+ + // | +-----------+ | | +------+ + // | | | + // | +-----------------------------------+ +-----------+ + // | | | + // | +-+---------------------------------------+ | + // | | | | + // | | [method with super] | | + // | | emitInitHomeObject +-------------+ v | + // | +--------------------->| InitHomeObj |->+ | + // | +-------------+ | | + // | | | + // | +-------------------------------------- + | + // | | | + // | | emitInit | + // | +------------------------------------------------------>+ + // | ^ + // | [optimized private non-static method] | + // | prepareForPrivateMethod +--------------------+ | + // +---------------------------->| PrivateMethodValue |-+ | + // | +--------------------+ | | + // | | | + // | +-------------------------------------------------+ | + // | | | + // | +-+---------------------------------------------+ | + // | | | | + // | | [method with super | | + // | | emitInitHomeObject +-----------------+ v | + // | +--------------------->| InitHomeObjFor- |----+ | + // | | PrivateMethod | | | + // | +-----------------+ | | + // | | | + // | +---------------------------------------------+ | + // | | | + // | | skipInit | + // | +------------------------------------------------------>+ + // | ^ + // | [private static method] | + // | prepareForPrivateStaticMethod +---------------------+ | + // +--------------------------------->| PrivateStaticMethod |-+ | + // | +---------------------+ | | + // | | | + // | +-------------------------------------------------------+ | + // | | | + // | +-+-------------------------------------------------+ | + // | | | | + // | | [method with super | | + // | | emitInitHomeObject +---------------------+ v | + // | +--------------------->| InitHomeObjFor- |----+ | + // | | PrivateStaticMethod | | | + // | +---------------------+ | | + // | | | + // | +-----------------------------------------------+ | + // | | | + // | | emitPrivateStaticMethod | + // | +---------------------------------------------------->+ + // | ^ + // | [index property/method/accessor] | + // | prepareForIndexPropKey +----------+ | + // +-------------------------->| IndexKey |-+ | + // | +----------+ | | + // | | | + // | +-------------------------------------+ | + // | | | + // | | prepareForIndexPropValue +------------+ | + // | +------------------------->| IndexValue |-+ | + // | +------------+ | | + // | | | + // | +---------------------------------------+ | + // | | | + // | +-+--------------------------------------------------+ | + // | | | | + // | | [method with super] | | + // | | emitInitHomeObject +---------------------+ v | + // | +--------------------->| InitHomeObjForIndex |---->+ | + // | +---------------------+ | | + // | | | + // | +--------------------------------------------------+ | + // | | | + // | | emitInitIndexOrComputed | + // | +---------------------------------------------------->+ + // | ^ + // | [computed property/method/accessor] | + // | prepareForComputedPropKey +-------------+ | + // +----------------------------->| ComputedKey |-+ | + // | +-------------+ | | + // | | | + // | +-------------------------------------------+ | + // | | | + // | | prepareForComputedPropValue +---------------+ | + // | +---------------------------->| ComputedValue |-+ | + // | +---------------+ | | + // | | | + // | +---------------------------------------------+ | + // | | | + // | +-+--------------------------------------------------+ | + // | | | | + // | | [method with super] | | + // | | emitInitHomeObject +------------------------+ v | + // | +--------------------->| InitHomeObjForComputed |->+ | + // | +------------------------+ | | + // | | | + // | +--------------------------------------------------+ | + // | | | + // | | emitInitIndexOrComputed | + // | +---------------------------------------------------->+ + // | ^ + // | | + // | [__proto__] | + // | prepareForProtoValue +------------+ emitMutateProto | + // +------------------------>| ProtoValue |-------------------->+ + // | +------------+ ^ + // | | + // | [...prop] | + // | prepareForSpreadOperand +---------------+ emitSpread | + // +-------------------------->| SpreadOperand |----------------+ + // +---------------+ + enum class PropertyState { + // The initial state. + Start, + + // After calling prepareForPropValue. + PropValue, + + // After calling emitInitHomeObject, from PropValue. + InitHomeObj, + + // After calling prepareForPrivateMethod. + PrivateMethodValue, + + // After calling emitInitHomeObject, from PrivateMethod. + InitHomeObjForPrivateMethod, + + // After calling prepareForPrivateStaticMethod. + PrivateStaticMethod, + + // After calling emitInitHomeObject, from PrivateStaticMethod. + InitHomeObjForPrivateStaticMethod, + + // After calling prepareForIndexPropKey. + IndexKey, + + // prepareForIndexPropValue. + IndexValue, + + // After calling emitInitHomeObject, from IndexValue. + InitHomeObjForIndex, + + // After calling prepareForComputedPropKey. + ComputedKey, + + // prepareForComputedPropValue. + ComputedValue, + + // After calling emitInitHomeObject, from ComputedValue. + InitHomeObjForComputed, + + // After calling prepareForProtoValue. + ProtoValue, + + // After calling prepareForSpreadOperand. + SpreadOperand, + + // After calling one of emitInit, emitInitIndexOrComputed, emitMutateProto, + // or emitSpread. + Init, + }; + PropertyState propertyState_ = PropertyState::Start; +#endif + + public: + explicit PropertyEmitter(BytecodeEmitter* bce); + + // Parameters are the offset in the source code for each character below: + // + // { __proto__: protoValue } + // ^ + // | + // keyPos + [[nodiscard]] bool prepareForProtoValue(uint32_t keyPos); + [[nodiscard]] bool emitMutateProto(); + + // { ...obj } + // ^ + // | + // spreadPos + [[nodiscard]] bool prepareForSpreadOperand(uint32_t spreadPos); + [[nodiscard]] bool emitSpread(); + + // { key: value } + // ^ + // | + // keyPos + [[nodiscard]] bool prepareForPropValue(uint32_t keyPos, Kind kind); + + [[nodiscard]] bool prepareForPrivateMethod(); + + [[nodiscard]] bool prepareForPrivateStaticMethod(uint32_t keyPos); + + // { 1: value } + // ^ + // | + // keyPos + [[nodiscard]] bool prepareForIndexPropKey(uint32_t keyPos, Kind kind); + [[nodiscard]] bool prepareForIndexPropValue(); + + // { [ key ]: value } + // ^ + // | + // keyPos + [[nodiscard]] bool prepareForComputedPropKey(uint32_t keyPos, Kind kind); + [[nodiscard]] bool prepareForComputedPropValue(); + + [[nodiscard]] bool emitInitHomeObject(); + + // @param key + // Property key + [[nodiscard]] bool emitInit(AccessorType accessorType, + TaggedParserAtomIndex key); + + [[nodiscard]] bool emitInitIndexOrComputed(AccessorType accessorType); + + [[nodiscard]] bool emitPrivateStaticMethod(AccessorType accessorType); + + [[nodiscard]] bool skipInit(); + + private: + [[nodiscard]] MOZ_ALWAYS_INLINE bool prepareForProp(uint32_t keyPos, + bool isStatic, + bool isComputed); + + // @param op + // Opcode for initializing property + // @param key + // Atom of the property if the property key is not computed + [[nodiscard]] bool emitInit(JSOp op, TaggedParserAtomIndex key); + [[nodiscard]] bool emitInitIndexOrComputed(JSOp op); + + [[nodiscard]] bool emitPopClassConstructor(); +}; + +// Class for emitting bytecode for object literal. +// +// Usage: (check for the return value is omitted for simplicity) +// +// `{}` +// ObjectEmitter oe(this); +// oe.emitObject(0); +// oe.emitEnd(); +// +// `{ prop: 10 }` +// ObjectEmitter oe(this); +// oe.emitObject(1); +// +// oe.prepareForPropValue(offset_of_prop); +// emit(10); +// oe.emitInitProp(atom_of_prop); +// +// oe.emitEnd(); +// +// `{ prop: function() {} }`, when property value is anonymous function +// ObjectEmitter oe(this); +// oe.emitObject(1); +// +// oe.prepareForPropValue(offset_of_prop); +// emit(function); +// oe.emitInitProp(atom_of_prop); +// +// oe.emitEnd(); +// +// `{ get prop() { ... }, set prop(v) { ... } }` +// ObjectEmitter oe(this); +// oe.emitObject(2); +// +// oe.prepareForPropValue(offset_of_prop); +// emit(function_for_getter); +// oe.emitInitGetter(atom_of_prop); +// +// oe.prepareForPropValue(offset_of_prop); +// emit(function_for_setter); +// oe.emitInitSetter(atom_of_prop); +// +// oe.emitEnd(); +// +// `{ 1: 10, get 2() { ... }, set 3(v) { ... } }` +// ObjectEmitter oe(this); +// oe.emitObject(3); +// +// oe.prepareForIndexPropKey(offset_of_prop); +// emit(1); +// oe.prepareForIndexPropValue(); +// emit(10); +// oe.emitInitIndexedProp(); +// +// oe.prepareForIndexPropKey(offset_of_opening_bracket); +// emit(2); +// oe.prepareForIndexPropValue(); +// emit(function_for_getter); +// oe.emitInitIndexGetter(); +// +// oe.prepareForIndexPropKey(offset_of_opening_bracket); +// emit(3); +// oe.prepareForIndexPropValue(); +// emit(function_for_setter); +// oe.emitInitIndexSetter(); +// +// oe.emitEnd(); +// +// `{ [prop1]: 10, get [prop2]() { ... }, set [prop3](v) { ... } }` +// ObjectEmitter oe(this); +// oe.emitObject(3); +// +// oe.prepareForComputedPropKey(offset_of_opening_bracket); +// emit(prop1); +// oe.prepareForComputedPropValue(); +// emit(10); +// oe.emitInitComputedProp(); +// +// oe.prepareForComputedPropKey(offset_of_opening_bracket); +// emit(prop2); +// oe.prepareForComputedPropValue(); +// emit(function_for_getter); +// oe.emitInitComputedGetter(); +// +// oe.prepareForComputedPropKey(offset_of_opening_bracket); +// emit(prop3); +// oe.prepareForComputedPropValue(); +// emit(function_for_setter); +// oe.emitInitComputedSetter(); +// +// oe.emitEnd(); +// +// `{ __proto__: obj }` +// ObjectEmitter oe(this); +// oe.emitObject(1); +// oe.prepareForProtoValue(offset_of___proto__); +// emit(obj); +// oe.emitMutateProto(); +// oe.emitEnd(); +// +// `{ ...obj }` +// ObjectEmitter oe(this); +// oe.emitObject(1); +// oe.prepareForSpreadOperand(offset_of_triple_dots); +// emit(obj); +// oe.emitSpread(); +// oe.emitEnd(); +// +class MOZ_STACK_CLASS ObjectEmitter : public PropertyEmitter { + private: +#ifdef DEBUG + // The state of this emitter. + // + // +-------+ emitObject +--------+ + // | Start |----------->| Object |-+ + // +-------+ +--------+ | + // | + // +-----------------------------+ + // | + // | (do PropertyEmitter operation) emitEnd +-----+ + // +-------------------------------+--------->| End | + // +-----+ + enum class ObjectState { + // The initial state. + Start, + + // After calling emitObject. + Object, + + // After calling emitEnd. + End, + }; + ObjectState objectState_ = ObjectState::Start; +#endif + + public: + explicit ObjectEmitter(BytecodeEmitter* bce); + + [[nodiscard]] bool emitObject(size_t propertyCount); + // Same as `emitObject()`, but start with an empty template object already on + // the stack. + [[nodiscard]] bool emitObjectWithTemplateOnStack(); + [[nodiscard]] bool emitEnd(); +}; + +// Save and restore the strictness. +// Used by class declaration/expression to temporarily enable strict mode. +class MOZ_RAII AutoSaveLocalStrictMode { + SharedContext* sc_; + bool savedStrictness_; + + public: + explicit AutoSaveLocalStrictMode(SharedContext* sc); + ~AutoSaveLocalStrictMode(); + + // Force restore the strictness now. + void restore(); +}; + +// Class for emitting bytecode for JS class. +// +// Usage: (check for the return value is omitted for simplicity) +// +// `class { constructor() { ... } }` +// ClassEmitter ce(this); +// ce.emitScope(scopeBindings); +// ce.emitClass(nullptr, nullptr, false); +// +// emit(function_for_constructor); +// ce.emitInitConstructor(/* needsHomeObject = */ false); +// +// ce.emitEnd(ClassEmitter::Kind::Expression); +// +// `class X { constructor() { ... } }` +// ClassEmitter ce(this); +// ce.emitScope(scopeBindings); +// ce.emitClass(atom_of_X, nullptr, false); +// +// emit(function_for_constructor); +// ce.emitInitConstructor(/* needsHomeObject = */ false); +// +// ce.emitEnd(ClassEmitter::Kind::Expression); +// +// `class X extends Y { constructor() { ... } }` +// ClassEmitter ce(this); +// ce.emitScope(scopeBindings); +// +// emit(Y); +// ce.emitDerivedClass(atom_of_X, nullptr, false); +// +// emit(function_for_constructor); +// ce.emitInitConstructor(/* needsHomeObject = */ false); +// +// ce.emitEnd(ClassEmitter::Kind::Expression); +// +// `class X extends Y { constructor() { ... super.f(); ... } }` +// ClassEmitter ce(this); +// ce.emitScope(scopeBindings); +// +// emit(Y); +// ce.emitDerivedClass(atom_of_X, nullptr, false); +// +// emit(function_for_constructor); +// // pass true if constructor contains super.prop access +// ce.emitInitConstructor(/* needsHomeObject = */ true); +// +// ce.emitEnd(ClassEmitter::Kind::Expression); +// +// `class X extends Y { field0 = expr0; ... }` +// ClassEmitter ce(this); +// ce.emitScope(scopeBindings); +// emit(Y); +// ce.emitDerivedClass(atom_of_X, nullptr, false); +// +// ce.prepareForMemberInitializers(fields.length()); +// for (auto field : fields) { +// emit(field.initializer_method()); +// ce.emitStoreMemberInitializer(); +// } +// ce.emitMemberInitializersEnd(); +// +// emit(function_for_constructor); +// ce.emitInitConstructor(/* needsHomeObject = */ false); +// ce.emitEnd(ClassEmitter::Kind::Expression); +// +// `class X { field0 = super.method(); ... }` +// // after emitClass/emitDerivedClass +// ce.prepareForMemberInitializers(1); +// for (auto field : fields) { +// emit(field.initializer_method()); +// if (field.initializer_contains_super_or_eval()) { +// ce.emitMemberInitializerHomeObject(); +// } +// ce.emitStoreMemberInitializer(); +// } +// ce.emitMemberInitializersEnd(); +// +// `m() {}` in class +// // after emitInitConstructor +// ce.prepareForPropValue(offset_of_m); +// emit(function_for_m); +// ce.emitInitProp(atom_of_m); +// +// `m() { super.f(); }` in class +// // after emitInitConstructor +// ce.prepareForPropValue(offset_of_m); +// emit(function_for_m); +// ce.emitInitHomeObject(); +// ce.emitInitProp(atom_of_m); +// +// `async m() { super.f(); }` in class +// // after emitInitConstructor +// ce.prepareForPropValue(offset_of_m); +// emit(function_for_m); +// ce.emitInitHomeObject(); +// ce.emitInitProp(atom_of_m); +// +// `get p() { super.f(); }` in class +// // after emitInitConstructor +// ce.prepareForPropValue(offset_of_p); +// emit(function_for_p); +// ce.emitInitHomeObject(); +// ce.emitInitGetter(atom_of_m); +// +// `static m() {}` in class +// // after emitInitConstructor +// ce.prepareForPropValue(offset_of_m, +// PropertyEmitter::Kind::Static); +// emit(function_for_m); +// ce.emitInitProp(atom_of_m); +// +// `static get [p]() { super.f(); }` in class +// // after emitInitConstructor +// ce.prepareForComputedPropValue(offset_of_m, +// PropertyEmitter::Kind::Static); +// emit(p); +// ce.prepareForComputedPropValue(); +// emit(function_for_m); +// ce.emitInitHomeObject(); +// ce.emitInitComputedGetter(); +// +class MOZ_STACK_CLASS ClassEmitter : public PropertyEmitter { + public: + enum class Kind { + // Class expression. + Expression, + + // Class declaration. + Declaration, + }; + + private: + // Pseudocode for class declarations: + // + // class extends BaseExpression { + // constructor() { ... } + // ... + // } + // + // + // if defined { + // let heritage = BaseExpression; + // + // if (heritage !== null) { + // funProto = heritage; + // objProto = heritage.prototype; + // } else { + // funProto = %FunctionPrototype%; + // objProto = null; + // } + // } else { + // objProto = %ObjectPrototype%; + // } + // + // let homeObject = ObjectCreate(objProto); + // + // if defined { + // if defined { + // cons = DefineMethod(, proto=homeObject, + // funProto=funProto); + // } else { + // cons = DefineMethod(, proto=homeObject); + // } + // } else { + // if defined { + // cons = DefaultDerivedConstructor(proto=homeObject, + // funProto=funProto); + // } else { + // cons = DefaultConstructor(proto=homeObject); + // } + // } + // + // cons.prototype = homeObject; + // homeObject.constructor = cons; + // + // EmitPropertyList(...) + + bool isDerived_ = false; + + mozilla::Maybe tdzCache_; + mozilla::Maybe innerScope_; + mozilla::Maybe bodyTdzCache_; + mozilla::Maybe bodyScope_; + AutoSaveLocalStrictMode strictMode_; + +#ifdef DEBUG + // The state of this emitter. + // + // clang-format off + // +-------+ + // | Start |-+------------------------>+--+------------------------------>+--+ + // +-------+ | ^ | ^ | + // | [has scope] | | [has body scope] | | + // | emitScope +-------+ | | emitBodyScope +-----------+ | | + // +-------------->| Scope |-+ +---------------->| BodyScope |-+ | + // +-------+ +-----------+ | + // | + // +-----------------------------------------------------------------------+ + // | + // | emitClass +-------+ + // +-+----------------->+->| Class |-+ + // | ^ +-------+ | + // | emitDerivedClass | | + // +------------------+ | + // | + // +-------------------------------+ + // | + // | + // | prepareForMemberInitializers(isStatic = false) + // +---------------+ + // | | + // | +--------v-------------------+ + // | | InstanceMemberInitializers | + // | +----------------------------+ + // | | + // | emitMemberInitializersEnd + // | | + // | +--------v----------------------+ + // | | InstanceMemberInitializersEnd | + // | +-------------------------------+ + // | | + // +<--------------+ + // | + // | emitInitConstructor +-----------------+ + // +-------------------------------->| InitConstructor |-+ + // +-----------------+ | + // | + // | + // | + // +-----------------------------------------------------+ + // | + // | prepareForMemberInitializers(isStatic = true) + // +---------------+ + // | | + // | +--------v-----------------+ + // | | StaticMemberInitializers | + // | +--------------------------+ + // | | + // | | emitMemberInitializersEnd + // | | + // | +--------v--------------------+ + // | | StaticMemberInitializersEnd | + // | +-----------------------------+ + // | | + // +<--------------+ + // | + // | (do PropertyEmitter operation) + // +--------------------------------+ + // | + // +-------------+ emitBinding | + // | BoundName |<-----------------+ + // +--+----------+ + // | + // | emitEnd + // | + // +--v----+ + // | End | + // +-------+ + // + // clang-format on + enum class ClassState { + // The initial state. + Start, + + // After calling emitScope. + Scope, + + // After calling emitBodyScope. + BodyScope, + + // After calling emitClass or emitDerivedClass. + Class, + + // After calling emitInitConstructor. + InitConstructor, + + // After calling prepareForMemberInitializers(isStatic = false). + InstanceMemberInitializers, + + // After calling emitMemberInitializersEnd. + InstanceMemberInitializersEnd, + + // After calling prepareForMemberInitializers(isStatic = true). + StaticMemberInitializers, + + // After calling emitMemberInitializersEnd. + StaticMemberInitializersEnd, + + // After calling emitBinding. + BoundName, + + // After calling emitEnd. + End, + }; + ClassState classState_ = ClassState::Start; + + // The state of the members emitter. + // + // clang-format off + // + // +-------+ + // | Start +<-----------------------------+ + // +-------+ | + // | | + // | prepareForMemberInitializer | emitStoreMemberInitializer + // v | + // +-------------+ | + // | Initializer +------------------------->+ + // +-------------+ | + // | | + // | emitMemberInitializerHomeObject | + // v | + // +---------------------------+ | + // | InitializerWithHomeObject +------------+ + // +---------------------------+ + // + // clang-format on + enum class MemberState { + // After calling prepareForMemberInitializers + // and 0 or more calls to emitStoreMemberInitializer. + Start, + + // After calling prepareForMemberInitializer + Initializer, + + // After calling emitMemberInitializerHomeObject + InitializerWithHomeObject, + }; + MemberState memberState_ = MemberState::Start; + + size_t numInitializers_ = 0; +#endif + + TaggedParserAtomIndex name_; + TaggedParserAtomIndex nameForAnonymousClass_; + bool hasNameOnStack_ = false; + mozilla::Maybe initializersAssignment_; + size_t initializerIndex_ = 0; + + public: + explicit ClassEmitter(BytecodeEmitter* bce); + + bool emitScope(LexicalScope::ParserData* scopeBindings); + bool emitBodyScope(ClassBodyScope::ParserData* scopeBindings); + + // @param name + // Name of the class (nullptr if this is anonymous class) + // @param nameForAnonymousClass + // Statically inferred name of the class (only for anonymous classes) + // @param hasNameOnStack + // If true the name is on the stack (only for anonymous classes) + [[nodiscard]] bool emitClass(TaggedParserAtomIndex name, + TaggedParserAtomIndex nameForAnonymousClass, + bool hasNameOnStack); + [[nodiscard]] bool emitDerivedClass( + TaggedParserAtomIndex name, TaggedParserAtomIndex nameForAnonymousClass, + bool hasNameOnStack); + + // @param needsHomeObject + // True if the constructor contains `super.foo` + [[nodiscard]] bool emitInitConstructor(bool needsHomeObject); + + [[nodiscard]] bool prepareForMemberInitializers(size_t numInitializers, + bool isStatic); + [[nodiscard]] bool prepareForMemberInitializer(); + [[nodiscard]] bool emitMemberInitializerHomeObject(bool isStatic); + [[nodiscard]] bool emitStoreMemberInitializer(); + [[nodiscard]] bool emitMemberInitializersEnd(); + + [[nodiscard]] bool emitBinding(); + + [[nodiscard]] bool emitEnd(Kind kind); + + private: + [[nodiscard]] bool initProtoAndCtor(); +}; + +} /* namespace frontend */ +} /* namespace js */ + +#endif /* frontend_ObjectEmitter_h */ diff --git a/js/src/frontend/OptionalEmitter.cpp b/js/src/frontend/OptionalEmitter.cpp new file mode 100644 index 0000000000..6fe5924099 --- /dev/null +++ b/js/src/frontend/OptionalEmitter.cpp @@ -0,0 +1,142 @@ +/* -*- 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/OptionalEmitter.h" + +#include "frontend/BytecodeEmitter.h" +#include "frontend/IfEmitter.h" // IfEmitter, InternalIfEmitter, CondEmitter +#include "vm/Opcodes.h" + +using namespace js; +using namespace js::frontend; + +OptionalEmitter::OptionalEmitter(BytecodeEmitter* bce, int32_t initialDepth) + : bce_(bce), tdzCache_(bce), initialDepth_(initialDepth) {} + +bool OptionalEmitter::emitJumpShortCircuit() { + MOZ_ASSERT(state_ == State::Start || state_ == State::ShortCircuit || + state_ == State::ShortCircuitForCall); + MOZ_ASSERT(initialDepth_ + 1 == bce_->bytecodeSection().stackDepth()); + + if (!bce_->emit1(JSOp::IsNullOrUndefined)) { + // [stack] OBJ NULL-OR-UNDEF + return false; + } + if (!bce_->emitJump(JSOp::JumpIfTrue, &jumpShortCircuit_)) { + // [stack] OBJ + return false; + } + +#ifdef DEBUG + state_ = State::ShortCircuit; +#endif + return true; +} + +bool OptionalEmitter::emitJumpShortCircuitForCall() { + MOZ_ASSERT(state_ == State::Start || state_ == State::ShortCircuit || + state_ == State::ShortCircuitForCall); + int32_t depth = bce_->bytecodeSection().stackDepth(); + MOZ_ASSERT(initialDepth_ + 2 == depth); + if (!bce_->emit1(JSOp::Swap)) { + // [stack] THIS CALLEE + return false; + } + + InternalIfEmitter ifEmitter(bce_); + if (!bce_->emit1(JSOp::IsNullOrUndefined)) { + // [stack] THIS CALLEE NULL-OR-UNDEF + return false; + } + + if (!ifEmitter.emitThen()) { + // [stack] THIS CALLEE + return false; + } + + if (!bce_->emit1(JSOp::Pop)) { + // [stack] THIS + return false; + } + + if (!bce_->emitJump(JSOp::Goto, &jumpShortCircuit_)) { + // [stack] THIS + return false; + } + + if (!ifEmitter.emitEnd()) { + return false; + } + + bce_->bytecodeSection().setStackDepth(depth); + + if (!bce_->emit1(JSOp::Swap)) { + // [stack] THIS CALLEE + return false; + } +#ifdef DEBUG + state_ = State::ShortCircuitForCall; +#endif + return true; +} + +bool OptionalEmitter::emitOptionalJumpTarget(JSOp op, + Kind kind /* = Kind::Other */) { +#ifdef DEBUG + int32_t depth = bce_->bytecodeSection().stackDepth(); +#endif + MOZ_ASSERT(state_ == State::ShortCircuit || + state_ == State::ShortCircuitForCall); + + // if we get to this point, it means that the optional chain did not short + // circuit, so we should skip the short circuiting bytecode. + if (!bce_->emitJump(JSOp::Goto, &jumpFinish_)) { + // [stack] RESULT + return false; + } + + if (!bce_->emitJumpTargetAndPatch(jumpShortCircuit_)) { + // [stack] # if call + // [stack] THIS + // [stack] # otherwise + // [stack] OBJ + return false; + } + + // reset stack depth to the depth when we jumped + bce_->bytecodeSection().setStackDepth(initialDepth_ + 1); + + if (!bce_->emit1(JSOp::Pop)) { + // [stack] + return false; + } + + if (!bce_->emit1(op)) { + // [stack] JSOP + return false; + } + + if (kind == Kind::Reference) { + if (!bce_->emit1(op)) { + // [stack] JSOP JSOP + return false; + } + } + + MOZ_ASSERT(depth == bce_->bytecodeSection().stackDepth()); + + if (!bce_->emitJumpTargetAndPatch(jumpFinish_)) { + // [stack] # if call + // [stack] CALLEE THIS + // [stack] # otherwise + // [stack] VAL + return false; + } +#ifdef DEBUG + state_ = State::JumpEnd; +#endif + return true; +} diff --git a/js/src/frontend/OptionalEmitter.h b/js/src/frontend/OptionalEmitter.h new file mode 100644 index 0000000000..4a4caa05c3 --- /dev/null +++ b/js/src/frontend/OptionalEmitter.h @@ -0,0 +1,220 @@ +/* -*- 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/. */ + +#ifndef frontend_OptionalEmitter_h +#define frontend_OptionalEmitter_h + +#include "mozilla/Attributes.h" + +#include "frontend/JumpList.h" +#include "frontend/TDZCheckCache.h" + +namespace js { +namespace frontend { + +struct BytecodeEmitter; + +// Class for emitting bytecode for optional expressions. +// +// Usage: (check for the return value is omitted for simplicity) +// +// `obj?.prop;` +// OptionalEmitter oe(this); +// PropOpEmitter poe(this, +// PropOpEmitter::Kind::Get, +// PropOpEmitter::ObjKind::Other); +// poe.prepareForObj(); +// emit(obj); +// oe.emitJumpShortCircuit(); +// poe.emitGet(atom_of_prop); +// oe.emitOptionalJumpTarget(JSOp::Undefined); +// +// `delete obj?.prop;` +// OptionalEmitter oe(this); +// OptionalPropOpEmitter poe(this, +// PropOpEmitter::Kind::Delete, +// PropOpEmitter::ObjKind::Other); +// poe.prepareForObj(); +// emit(obj); +// oe.emitJumpShortCircuit(); +// poe.emitDelete(atom_of_prop); +// oe.emitOptionalJumpTarget(JSOp:True); +// +// `obj?.[key];` +// OptionalEmitter oe(this); +// ElemOpEmitter eoe(this, +// ElemOpEmitter::Kind::Get, +// ElemOpEmitter::ObjKind::Other); +// eoe.prepareForObj(); +// emit(obj); +// oe.emitJumpShortCircuit(); +// eoe.prepareForKey(); +// emit(key); +// eoe.emitGet(); +// oe.emitOptionalJumpTarget(JSOp::Undefined); +// +// `delete obj?.[key];` +// OptionalEmitter oe(this); +// ElemOpEmitter eoe(this, +// ElemOpEmitter::Kind::Delete, +// ElemOpEmitter::ObjKind::Other); +// eoe.prepareForObj(); +// emit(obj); +// oe.emitJumpShortCircuit(); +// eoe.prepareForKey(); +// emit(key); +// eoe.emitDelete(); +// oe.emitOptionalJumpTarget(JSOp::True); +// +// `print?.(arg);` +// OptionalEmitter oe(this); +// CallOrNewEmitter cone(this, JSOp::Call, +// CallOrNewEmitter::ArgumentsKind::Other, +// ValueUsage::WantValue); +// cone.emitNameCallee(print); +// cone.emitThis(); +// oe.emitShortCircuitForCall(); +// cone.prepareForNonSpreadArguments(); +// emit(arg); +// cone.emitEnd(1, offset_of_callee); +// oe.emitOptionalJumpTarget(JSOp::Undefined); +// +// `callee.prop?.(arg1, arg2);` +// OptionalEmitter oe(this); +// CallOrNewEmitter cone(this, JSOp::Call, +// CallOrNewEmitter::ArgumentsKind::Other, +// ValueUsage::WantValue); +// PropOpEmitter& poe = cone.prepareForPropCallee(false); +// ... emit `callee.prop` with `poe` here... +// cone.emitThis(); +// oe.emitShortCircuitForCall(); +// cone.prepareForNonSpreadArguments(); +// emit(arg1); +// emit(arg2); +// cone.emitEnd(2, offset_of_callee); +// oe.emitOptionalJumpTarget(JSOp::Undefined); +// +// `callee[key]?.(arg);` +// OptionalEmitter oe(this); +// CallOrNewEmitter cone(this, JSOp::Call, +// CallOrNewEmitter::ArgumentsKind::Other, +// ValueUsage::WantValue); +// ElemOpEmitter& eoe = cone.prepareForElemCallee(false); +// ... emit `callee[key]` with `eoe` here... +// cone.emitThis(); +// oe.emitShortCircuitForCall(); +// cone.prepareForNonSpreadArguments(); +// emit(arg); +// cone.emitEnd(1, offset_of_callee); +// oe.emitOptionalJumpTarget(JSOp::Undefined); +// +// `(function() { ... })?.(arg);` +// OptionalEmitter oe(this); +// CallOrNewEmitter cone(this, JSOp::Call, +// CallOrNewEmitter::ArgumentsKind::Other, +// ValueUsage::WantValue); +// cone.prepareForFunctionCallee(); +// emit(function); +// cone.emitThis(); +// oe.emitShortCircuitForCall(); +// cone.prepareForNonSpreadArguments(); +// emit(arg); +// cone.emitEnd(1, offset_of_callee); +// oe.emitOptionalJumpTarget(JSOp::Undefined); +// +// `(a?b)();` +// OptionalEmitter oe(this); +// CallOrNewEmitter cone(this, JSOP_CALL, +// CallOrNewEmitter::ArgumentsKind::Other, +// ValueUsage::WantValue); +// cone.prepareForFunctionCallee(); +// emit(optionalChain); +// cone.emitThis(); +// oe.emitOptionalJumpTarget(JSOp::Undefined, +// OptionalEmitter::Kind::Reference); +// oe.emitShortCircuitForCall(); +// cone.prepareForNonSpreadArguments(); +// emit(arg); +// cone.emitEnd(1, offset_of_callee); +// oe.emitOptionalJumpTarget(JSOp::Undefined); +// +class MOZ_RAII OptionalEmitter { + public: + OptionalEmitter(BytecodeEmitter* bce, int32_t initialDepth); + + private: + BytecodeEmitter* bce_; + + TDZCheckCache tdzCache_; + + // jumptarget for ShortCircuiting code, which has null or undefined values + JumpList jumpShortCircuit_; + + // jumpTarget for code that does not shortCircuit + JumpList jumpFinish_; + + // jumpTarget for code that does not shortCircuit + int32_t initialDepth_; + + // The state of this emitter. + // + // +-------+ emitJumpShortCircuit +--------------+ + // | Start |-+---------------------------->| ShortCircuit |-----------+ + // +-------+ | +--------------+ | + // +----->| | + // | | emitJumpShortCircuitForCall +---------------------+ v + // | +---------------------------->| ShortCircuitForCall |--->+ + // | +---------------------+ | + // | | + // ---------------------------------------------------------------+ + // | + // | + // +------------------------------------------------------------------+ + // | + // | emitOptionalJumpTarget +---------+ + // +----------------------->| JumpEnd | + // +---------+ + // +#ifdef DEBUG + enum class State { + // The initial state. + Start, + + // for shortcircuiting in most cases. + ShortCircuit, + + // for shortcircuiting from references, which have two items on + // the stack. For example function calls. + ShortCircuitForCall, + + // internally used, end of the jump code + JumpEnd + }; + + State state_ = State::Start; +#endif + + public: + enum class Kind { + // Requires two values on the stack + Reference, + // Requires one value on the stack + Other + }; + + [[nodiscard]] bool emitJumpShortCircuit(); + [[nodiscard]] bool emitJumpShortCircuitForCall(); + + // JSOp is the op code to be emitted, Kind is if we are dealing with a + // reference (in which case we need two elements on the stack) or other value + // (which needs one element on the stack) + [[nodiscard]] bool emitOptionalJumpTarget(JSOp op, Kind kind = Kind::Other); +}; + +} /* namespace frontend */ +} /* namespace js */ + +#endif /* frontend_OptionalEmitter_h */ diff --git a/js/src/frontend/ParseContext-inl.h b/js/src/frontend/ParseContext-inl.h new file mode 100644 index 0000000000..ddaf56c8c2 --- /dev/null +++ b/js/src/frontend/ParseContext-inl.h @@ -0,0 +1,177 @@ +/* -*- 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/. */ + +#ifndef frontend_ParseContext_inl_h +#define frontend_ParseContext_inl_h + +#include "frontend/ParseContext.h" + +#include "frontend/Parser.h" + +namespace js { +namespace frontend { + +template <> +inline bool ParseContext::Statement::is() const { + return kind_ == StatementKind::Label; +} + +template <> +inline bool ParseContext::Statement::is() const { + return kind_ == StatementKind::Class; +} + +template +inline T& ParseContext::Statement::as() { + MOZ_ASSERT(is()); + return static_cast(*this); +} + +inline ParseContext::Scope::BindingIter ParseContext::Scope::bindings( + ParseContext* pc) { + // In function scopes with parameter expressions, function special names + // (like '.this') are declared as vars in the function scope, despite its + // not being the var scope. + return BindingIter(*this, pc->varScope_ == this || + pc->functionScope_.ptrOr(nullptr) == this); +} + +inline ParseContext::Scope::Scope(ParserBase* parser) + : Nestable(&parser->pc_->innermostScope_), + declared_(parser->fc_->nameCollectionPool()), + possibleAnnexBFunctionBoxes_(parser->fc_->nameCollectionPool()), + id_(parser->usedNames_.nextScopeId()) {} + +inline ParseContext::Scope::Scope(FrontendContext* fc, ParseContext* pc, + UsedNameTracker& usedNames) + : Nestable(&pc->innermostScope_), + declared_(fc->nameCollectionPool()), + possibleAnnexBFunctionBoxes_(fc->nameCollectionPool()), + id_(usedNames.nextScopeId()) {} + +inline ParseContext::VarScope::VarScope(ParserBase* parser) : Scope(parser) { + useAsVarScope(parser->pc_); +} + +inline ParseContext::VarScope::VarScope(FrontendContext* fc, ParseContext* pc, + UsedNameTracker& usedNames) + : Scope(fc, pc, usedNames) { + useAsVarScope(pc); +} + +inline JS::Result +ParseContext::checkBreakStatement(TaggedParserAtomIndex 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(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 +ParseContext::checkContinueStatement(TaggedParserAtomIndex 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()) { + if (stmt->as().label() == label) { + return Ok(); + } + + stmt = stmt->enclosing(); + } + } +} + +template +inline void RedeclareVar(DeclaredNamePtrT ptr, DeclarationKind kind) { +#ifdef DEBUG + DeclarationKind declaredKind = ptr->value()->kind(); + MOZ_ASSERT(DeclarationKindIsVar(declaredKind)); +#endif + + // Any vars that are redeclared as body-level functions must + // be recorded as body-level functions. + // + // In the case of global and eval scripts, GlobalDeclaration- + // Instantiation [1] and EvalDeclarationInstantiation [2] + // check for the declarability of global var and function + // bindings via CanDeclareVar [3] and CanDeclareGlobal- + // Function [4]. CanDeclareGlobalFunction is strictly more + // restrictive than CanDeclareGlobalVar, so record the more + // restrictive kind. These semantics are implemented in + // CheckCanDeclareGlobalBinding. + // + // VarForAnnexBLexicalFunction declarations are declared when + // the var scope exits. It is not possible for a var to be + // previously declared as VarForAnnexBLexicalFunction and + // checked for redeclaration. + // + // [1] ES 15.1.11 + // [2] ES 18.2.1.3 + // [3] ES 8.1.1.4.15 + // [4] ES 8.1.1.4.16 + if (kind == DeclarationKind::BodyLevelFunction) { + MOZ_ASSERT(declaredKind != DeclarationKind::VarForAnnexBLexicalFunction); + ptr->value()->alterKind(kind); + } +} + +} // namespace frontend +} // namespace js + +#endif // frontend_ParseContext_inl_h diff --git a/js/src/frontend/ParseContext.cpp b/js/src/frontend/ParseContext.cpp new file mode 100644 index 0000000000..f6a7723d07 --- /dev/null +++ b/js/src/frontend/ParseContext.cpp @@ -0,0 +1,723 @@ +/* -*- 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/ParseContext-inl.h" + +#include "frontend/CompilationStencil.h" // ScopeContext +#include "frontend/Parser.h" // ParserBase +#include "js/friend/ErrorMessages.h" // JSMSG_* +#include "vm/WellKnownAtom.h" // js_*_str + +using mozilla::Maybe; +using mozilla::Nothing; +using mozilla::Some; + +namespace js { +namespace frontend { + +using AddDeclaredNamePtr = ParseContext::Scope::AddDeclaredNamePtr; +using DeclaredNamePtr = ParseContext::Scope::DeclaredNamePtr; + +const char* DeclarationKindString(DeclarationKind kind) { + switch (kind) { + case DeclarationKind::PositionalFormalParameter: + case DeclarationKind::FormalParameter: + return "formal parameter"; + case DeclarationKind::CoverArrowParameter: + return "cover arrow parameter"; + case DeclarationKind::Var: + return "var"; + case DeclarationKind::Let: + return "let"; + case DeclarationKind::Const: + return "const"; + case DeclarationKind::Class: + return "class"; + case DeclarationKind::Import: + return "import"; + case DeclarationKind::BodyLevelFunction: + case DeclarationKind::ModuleBodyLevelFunction: + case DeclarationKind::LexicalFunction: + case DeclarationKind::SloppyLexicalFunction: + return "function"; + case DeclarationKind::VarForAnnexBLexicalFunction: + return "annex b var"; + case DeclarationKind::SimpleCatchParameter: + case DeclarationKind::CatchParameter: + return "catch parameter"; + case DeclarationKind::PrivateName: + return "private name"; + case DeclarationKind::Synthetic: + return "synthetic"; + case DeclarationKind::PrivateMethod: + return "private method"; + } + + MOZ_CRASH("Bad DeclarationKind"); +} + +bool DeclarationKindIsVar(DeclarationKind kind) { + return kind == DeclarationKind::Var || + kind == DeclarationKind::BodyLevelFunction || + kind == DeclarationKind::VarForAnnexBLexicalFunction; +} + +bool DeclarationKindIsParameter(DeclarationKind kind) { + return kind == DeclarationKind::PositionalFormalParameter || + kind == DeclarationKind::FormalParameter; +} + +bool UsedNameTracker::noteUse(FrontendContext* fc, TaggedParserAtomIndex name, + NameVisibility visibility, uint32_t scriptId, + uint32_t scopeId, + mozilla::Maybe tokenPosition) { + if (UsedNameMap::AddPtr p = map_.lookupForAdd(name)) { + p->value().maybeUpdatePos(tokenPosition); + + if (!p->value().noteUsedInScope(scriptId, scopeId)) { + return false; + } + } else { + // We need a token position precisely where we have private visibility. + MOZ_ASSERT(tokenPosition.isSome() == + (visibility == NameVisibility::Private)); + + if (visibility == NameVisibility::Private) { + // We have seen at least one private name + hasPrivateNames_ = true; + } + + UsedNameInfo info(fc, visibility, tokenPosition); + + if (!info.noteUsedInScope(scriptId, scopeId)) { + return false; + } + if (!map_.add(p, name, std::move(info))) { + return false; + } + } + + return true; +} + +bool UsedNameTracker::getUnboundPrivateNames( + Vector& unboundPrivateNames) { + // We never saw any private names, so can just return early + if (!hasPrivateNames_) { + return true; + } + + for (auto iter = map_.iter(); !iter.done(); iter.next()) { + // Don't care about public; + if (iter.get().value().isPublic()) { + continue; + } + + // empty list means all bound + if (iter.get().value().empty()) { + continue; + } + + if (!unboundPrivateNames.emplaceBack(iter.get().key(), + *iter.get().value().pos())) { + return false; + } + } + + // Return a sorted list in ascendng order of position. + auto comparePosition = [](const auto& a, const auto& b) { + return a.position < b.position; + }; + std::sort(unboundPrivateNames.begin(), unboundPrivateNames.end(), + comparePosition); + + return true; +} + +bool UsedNameTracker::hasUnboundPrivateNames( + FrontendContext* fc, mozilla::Maybe& maybeUnboundName) { + // We never saw any private names, so can just return early + if (!hasPrivateNames_) { + return true; + } + + Vector unboundPrivateNames(fc); + if (!getUnboundPrivateNames(unboundPrivateNames)) { + return false; + } + + if (unboundPrivateNames.empty()) { + return true; + } + + // GetUnboundPrivateNames returns the list sorted. + maybeUnboundName.emplace(unboundPrivateNames[0]); + return true; +} + +void UsedNameTracker::UsedNameInfo::resetToScope(uint32_t scriptId, + uint32_t scopeId) { + while (!uses_.empty()) { + Use& innermost = uses_.back(); + if (innermost.scopeId < scopeId) { + break; + } + MOZ_ASSERT(innermost.scriptId >= scriptId); + uses_.popBack(); + } +} + +void UsedNameTracker::rewind(RewindToken token) { + scriptCounter_ = token.scriptId; + scopeCounter_ = token.scopeId; + + for (UsedNameMap::Range r = map_.all(); !r.empty(); r.popFront()) { + r.front().value().resetToScope(token.scriptId, token.scopeId); + } +} + +void ParseContext::Scope::dump(ParseContext* pc, ParserBase* parser) { + fprintf(stdout, "ParseScope %p", this); + + fprintf(stdout, "\n decls:\n"); + for (DeclaredNameMap::Range r = declared_->all(); !r.empty(); r.popFront()) { + auto index = r.front().key(); + UniqueChars bytes = parser->parserAtoms().toPrintableString(index); + if (!bytes) { + ReportOutOfMemory(pc->sc()->fc_); + return; + } + DeclaredNameInfo& info = r.front().value().wrapped; + fprintf(stdout, " %s %s%s\n", DeclarationKindString(info.kind()), + bytes.get(), info.closedOver() ? " (closed over)" : ""); + } + + fprintf(stdout, "\n"); +} + +bool ParseContext::Scope::addPossibleAnnexBFunctionBox(ParseContext* pc, + FunctionBox* funbox) { + if (!possibleAnnexBFunctionBoxes_) { + if (!possibleAnnexBFunctionBoxes_.acquire(pc->sc()->fc_)) { + return false; + } + } + + return maybeReportOOM(pc, possibleAnnexBFunctionBoxes_->append(funbox)); +} + +bool ParseContext::Scope::propagateAndMarkAnnexBFunctionBoxes( + ParseContext* pc, ParserBase* parser) { + // Strict mode doesn't have wack Annex B function semantics. + if (pc->sc()->strict() || !possibleAnnexBFunctionBoxes_ || + possibleAnnexBFunctionBoxes_->empty()) { + return true; + } + + if (this == &pc->varScope()) { + // Base case: actually declare the Annex B vars and mark applicable + // function boxes as Annex B. + Maybe redeclaredKind; + uint32_t unused; + for (FunctionBox* funbox : *possibleAnnexBFunctionBoxes_) { + bool annexBApplies; + if (!pc->computeAnnexBAppliesToLexicalFunctionInInnermostScope( + funbox, parser, &annexBApplies)) { + return false; + } + if (annexBApplies) { + if (!pc->tryDeclareVar(funbox->explicitName(), parser, + DeclarationKind::VarForAnnexBLexicalFunction, + DeclaredNameInfo::npos, &redeclaredKind, + &unused)) { + return false; + } + + MOZ_ASSERT(!redeclaredKind); + funbox->isAnnexB = true; + } + } + } else { + // Inner scope case: propagate still applicable function boxes to the + // enclosing scope. + for (FunctionBox* funbox : *possibleAnnexBFunctionBoxes_) { + bool annexBApplies; + if (!pc->computeAnnexBAppliesToLexicalFunctionInInnermostScope( + funbox, parser, &annexBApplies)) { + return false; + } + if (annexBApplies) { + if (!enclosing()->addPossibleAnnexBFunctionBox(pc, funbox)) { + return false; + } + } + } + } + + return true; +} + +static bool DeclarationKindIsCatchParameter(DeclarationKind kind) { + return kind == DeclarationKind::SimpleCatchParameter || + kind == DeclarationKind::CatchParameter; +} + +bool ParseContext::Scope::addCatchParameters(ParseContext* pc, + Scope& catchParamScope) { + if (pc->useAsmOrInsideUseAsm()) { + return true; + } + + for (DeclaredNameMap::Range r = catchParamScope.declared_->all(); !r.empty(); + r.popFront()) { + DeclarationKind kind = r.front().value()->kind(); + uint32_t pos = r.front().value()->pos(); + MOZ_ASSERT(DeclarationKindIsCatchParameter(kind)); + auto name = r.front().key(); + AddDeclaredNamePtr p = lookupDeclaredNameForAdd(name); + MOZ_ASSERT(!p); + if (!addDeclaredName(pc, p, name, kind, pos)) { + return false; + } + } + + return true; +} + +void ParseContext::Scope::removeCatchParameters(ParseContext* pc, + Scope& catchParamScope) { + if (pc->useAsmOrInsideUseAsm()) { + return; + } + + for (DeclaredNameMap::Range r = catchParamScope.declared_->all(); !r.empty(); + r.popFront()) { + auto name = r.front().key(); + DeclaredNamePtr p = declared_->lookup(name); + MOZ_ASSERT(p); + + // This check is needed because the catch body could have declared + // vars, which would have been added to catchParamScope. + if (DeclarationKindIsCatchParameter(r.front().value()->kind())) { + declared_->remove(p); + } + } +} + +ParseContext::ParseContext(FrontendContext* fc, ParseContext*& parent, + SharedContext* sc, ErrorReporter& errorReporter, + CompilationState& compilationState, + Directives* newDirectives, bool isFull) + : Nestable(&parent), + sc_(sc), + errorReporter_(errorReporter), + innermostStatement_(nullptr), + innermostScope_(nullptr), + varScope_(nullptr), + positionalFormalParameterNames_(fc->nameCollectionPool()), + closedOverBindingsForLazy_(fc->nameCollectionPool()), + innerFunctionIndexesForLazy(sc->fc_), + newDirectives(newDirectives), + lastYieldOffset(NoYieldOffset), + lastAwaitOffset(NoAwaitOffset), + scriptId_(compilationState.usedNames.nextScriptId()), + superScopeNeedsHomeObject_(false) { + if (isFunctionBox()) { + if (functionBox()->isNamedLambda()) { + namedLambdaScope_.emplace(fc, parent, compilationState.usedNames); + } + functionScope_.emplace(fc, parent, compilationState.usedNames); + } +} + +bool ParseContext::init() { + if (scriptId_ == UINT32_MAX) { + errorReporter_.errorNoOffset(JSMSG_NEED_DIET, js_script_str); + return false; + } + + FrontendContext* fc = sc()->fc_; + + if (isFunctionBox()) { + // Named lambdas always need a binding for their own name. If this + // binding is closed over when we finish parsing the function iNn + // finishFunctionScopes, the function box needs to be marked as + // needing a dynamic DeclEnv object. + if (functionBox()->isNamedLambda()) { + if (!namedLambdaScope_->init(this)) { + return false; + } + AddDeclaredNamePtr p = namedLambdaScope_->lookupDeclaredNameForAdd( + functionBox()->explicitName()); + MOZ_ASSERT(!p); + if (!namedLambdaScope_->addDeclaredName( + this, p, functionBox()->explicitName(), DeclarationKind::Const, + DeclaredNameInfo::npos)) { + return false; + } + } + + if (!functionScope_->init(this)) { + return false; + } + + if (!positionalFormalParameterNames_.acquire(fc)) { + return false; + } + } + + if (!closedOverBindingsForLazy_.acquire(fc)) { + return false; + } + + return true; +} + +bool ParseContext::computeAnnexBAppliesToLexicalFunctionInInnermostScope( + FunctionBox* funbox, ParserBase* parser, bool* annexBApplies) { + MOZ_ASSERT(!sc()->strict()); + + TaggedParserAtomIndex name = funbox->explicitName(); + Maybe redeclaredKind; + if (!isVarRedeclaredInInnermostScope( + name, parser, DeclarationKind::VarForAnnexBLexicalFunction, + &redeclaredKind)) { + return false; + } + + if (!redeclaredKind && isFunctionBox()) { + Scope& funScope = functionScope(); + if (&funScope != &varScope()) { + // Annex B.3.3.1 disallows redeclaring parameter names. In the + // presence of parameter expressions, parameter names are on the + // function scope, which encloses the var scope. This means the + // isVarRedeclaredInInnermostScope call above would not catch this + // case, so test it manually. + if (DeclaredNamePtr p = funScope.lookupDeclaredName(name)) { + DeclarationKind declaredKind = p->value()->kind(); + if (DeclarationKindIsParameter(declaredKind)) { + redeclaredKind = Some(declaredKind); + } else { + MOZ_ASSERT(FunctionScope::isSpecialName(name)); + } + } + } + } + + // If an early error would have occurred already, this function should not + // exhibit Annex B.3.3 semantics. + *annexBApplies = !redeclaredKind; + return true; +} + +bool ParseContext::isVarRedeclaredInInnermostScope( + TaggedParserAtomIndex name, ParserBase* parser, DeclarationKind kind, + mozilla::Maybe* out) { + uint32_t unused; + return tryDeclareVarHelper( + name, parser, kind, DeclaredNameInfo::npos, out, &unused); +} + +bool ParseContext::isVarRedeclaredInEval(TaggedParserAtomIndex name, + ParserBase* parser, + DeclarationKind kind, + Maybe* out) { + auto maybeKind = parser->getCompilationState() + .scopeContext.lookupLexicalBindingInEnclosingScope(name); + if (!maybeKind) { + *out = Nothing(); + return true; + } + + switch (*maybeKind) { + case ScopeContext::EnclosingLexicalBindingKind::Let: + *out = Some(DeclarationKind::Let); + break; + case ScopeContext::EnclosingLexicalBindingKind::Const: + *out = Some(DeclarationKind::Const); + break; + case ScopeContext::EnclosingLexicalBindingKind::CatchParameter: + *out = Some(DeclarationKind::CatchParameter); + break; + case ScopeContext::EnclosingLexicalBindingKind::Synthetic: + *out = Some(DeclarationKind::Synthetic); + break; + case ScopeContext::EnclosingLexicalBindingKind::PrivateMethod: + *out = Some(DeclarationKind::PrivateMethod); + break; + } + return true; +} + +bool ParseContext::tryDeclareVar(TaggedParserAtomIndex name, ParserBase* parser, + DeclarationKind kind, uint32_t beginPos, + Maybe* redeclaredKind, + uint32_t* prevPos) { + return tryDeclareVarHelper(name, parser, kind, beginPos, + redeclaredKind, prevPos); +} + +template +bool ParseContext::tryDeclareVarHelper(TaggedParserAtomIndex name, + ParserBase* parser, DeclarationKind kind, + uint32_t beginPos, + Maybe* redeclaredKind, + uint32_t* prevPos) { + MOZ_ASSERT(DeclarationKindIsVar(kind)); + + // It is an early error if a 'var' declaration appears inside a + // scope contour that has a lexical declaration of the same name. For + // example, the following are early errors: + // + // { let x; var x; } + // { { var x; } let x; } + // + // And the following are not: + // + // { var x; var x; } + // { { let x; } var x; } + + for (ParseContext::Scope* scope = innermostScope(); + scope != varScope().enclosing(); scope = scope->enclosing()) { + if (AddDeclaredNamePtr p = scope->lookupDeclaredNameForAdd(name)) { + DeclarationKind declaredKind = p->value()->kind(); + if (DeclarationKindIsVar(declaredKind)) { + if (dryRunOption == NotDryRun) { + RedeclareVar(p, kind); + } + } else if (!DeclarationKindIsParameter(declaredKind)) { + // Annex B.3.5 allows redeclaring simple (non-destructured) + // catch parameters with var declarations. + bool annexB35Allowance = + declaredKind == DeclarationKind::SimpleCatchParameter; + + // Annex B.3.3 allows redeclaring functions in the same block. + bool annexB33Allowance = + declaredKind == DeclarationKind::SloppyLexicalFunction && + kind == DeclarationKind::VarForAnnexBLexicalFunction && + scope == innermostScope(); + + if (!annexB35Allowance && !annexB33Allowance) { + *redeclaredKind = Some(declaredKind); + *prevPos = p->value()->pos(); + return true; + } + } else if (kind == DeclarationKind::VarForAnnexBLexicalFunction) { + MOZ_ASSERT(DeclarationKindIsParameter(declaredKind)); + + // Annex B.3.3.1 disallows redeclaring parameter names. + // We don't need to set *prevPos here since this case is not + // an error. + *redeclaredKind = Some(declaredKind); + return true; + } + } else if (dryRunOption == NotDryRun) { + if (!scope->addDeclaredName(this, p, name, kind, beginPos)) { + return false; + } + } + + // DryRunOption is used for propagating Annex B functions: we don't + // want to declare the synthesized Annex B vars until we exit the var + // scope and know that no early errors would have occurred. In order + // to avoid quadratic search, we only check for var redeclarations in + // the innermost scope when doing a dry run. + if (dryRunOption == DryRunInnermostScopeOnly) { + break; + } + } + + if (!sc()->strict() && sc()->isEvalContext() && + (dryRunOption == NotDryRun || innermostScope() == &varScope())) { + if (!isVarRedeclaredInEval(name, parser, kind, redeclaredKind)) { + return false; + } + // We don't have position information at runtime. + *prevPos = DeclaredNameInfo::npos; + } + + return true; +} + +bool ParseContext::hasUsedName(const UsedNameTracker& usedNames, + TaggedParserAtomIndex name) { + if (auto p = usedNames.lookup(name)) { + return p->value().isUsedInScript(scriptId()); + } + return false; +} + +bool ParseContext::hasUsedFunctionSpecialName(const UsedNameTracker& usedNames, + TaggedParserAtomIndex name) { + MOZ_ASSERT(name == TaggedParserAtomIndex::WellKnown::arguments() || + name == TaggedParserAtomIndex::WellKnown::dotThis() || + name == TaggedParserAtomIndex::WellKnown::dotNewTarget()); + return hasUsedName(usedNames, name) || + functionBox()->bindingsAccessedDynamically(); +} + +bool ParseContext::declareFunctionThis(const UsedNameTracker& usedNames, + bool canSkipLazyClosedOverBindings) { + // The asm.js validator does all its own symbol-table management so, as an + // optimization, avoid doing any work here. + if (useAsmOrInsideUseAsm()) { + return true; + } + + // Derived class constructors emit JSOp::CheckReturn, which requires + // '.this' to be bound. Class field initializers implicitly read `.this`. + // Therefore we unconditionally declare `.this` in all class constructors. + FunctionBox* funbox = functionBox(); + auto dotThis = TaggedParserAtomIndex::WellKnown::dotThis(); + + bool declareThis; + if (canSkipLazyClosedOverBindings) { + declareThis = funbox->functionHasThisBinding(); + } else { + declareThis = hasUsedFunctionSpecialName(usedNames, dotThis) || + funbox->isClassConstructor(); + } + + if (declareThis) { + ParseContext::Scope& funScope = functionScope(); + AddDeclaredNamePtr p = funScope.lookupDeclaredNameForAdd(dotThis); + MOZ_ASSERT(!p); + if (!funScope.addDeclaredName(this, p, dotThis, DeclarationKind::Var, + DeclaredNameInfo::npos)) { + return false; + } + funbox->setFunctionHasThisBinding(); + } + + return true; +} + +bool ParseContext::declareFunctionArgumentsObject( + const UsedNameTracker& usedNames, bool canSkipLazyClosedOverBindings) { + FunctionBox* funbox = functionBox(); + ParseContext::Scope& funScope = functionScope(); + ParseContext::Scope& _varScope = varScope(); + + bool usesArguments = false; + bool hasExtraBodyVarScope = &funScope != &_varScope; + + // Time to implement the odd semantics of 'arguments'. + auto argumentsName = TaggedParserAtomIndex::WellKnown::arguments(); + + bool tryDeclareArguments; + if (canSkipLazyClosedOverBindings) { + tryDeclareArguments = funbox->shouldDeclareArguments(); + } else { + tryDeclareArguments = hasUsedFunctionSpecialName(usedNames, argumentsName); + } + + // ES 9.2.12 steps 19 and 20 say formal parameters, lexical bindings, + // and body-level functions named 'arguments' shadow the arguments + // object. + // + // So even if there wasn't a free use of 'arguments' but there is a var + // binding of 'arguments', we still might need the arguments object. + // + // If we have an extra var scope due to parameter expressions and the body + // declared 'var arguments', we still need to declare 'arguments' in the + // function scope. + DeclaredNamePtr p = _varScope.lookupDeclaredName(argumentsName); + if (p && p->value()->kind() == DeclarationKind::Var) { + if (hasExtraBodyVarScope) { + tryDeclareArguments = true; + } else { + usesArguments = true; + } + } + + if (tryDeclareArguments) { + AddDeclaredNamePtr p = funScope.lookupDeclaredNameForAdd(argumentsName); + if (!p) { + if (!funScope.addDeclaredName(this, p, argumentsName, + DeclarationKind::Var, + DeclaredNameInfo::npos)) { + return false; + } + funbox->setShouldDeclareArguments(); + usesArguments = true; + } else if (hasExtraBodyVarScope) { + // Formal parameters shadow the arguments object. + return true; + } + } + + if (usesArguments) { + funbox->setNeedsArgsObj(); + } + + return true; +} + +bool ParseContext::declareNewTarget(const UsedNameTracker& usedNames, + bool canSkipLazyClosedOverBindings) { + // The asm.js validator does all its own symbol-table management so, as an + // optimization, avoid doing any work here. + if (useAsmOrInsideUseAsm()) { + return true; + } + + FunctionBox* funbox = functionBox(); + auto dotNewTarget = TaggedParserAtomIndex::WellKnown::dotNewTarget(); + + bool declareNewTarget; + if (canSkipLazyClosedOverBindings) { + declareNewTarget = funbox->functionHasNewTargetBinding(); + } else { + declareNewTarget = hasUsedFunctionSpecialName(usedNames, dotNewTarget); + } + + if (declareNewTarget) { + ParseContext::Scope& funScope = functionScope(); + AddDeclaredNamePtr p = funScope.lookupDeclaredNameForAdd(dotNewTarget); + MOZ_ASSERT(!p); + if (!funScope.addDeclaredName(this, p, dotNewTarget, DeclarationKind::Var, + DeclaredNameInfo::npos)) { + return false; + } + funbox->setFunctionHasNewTargetBinding(); + } + + return true; +} + +bool ParseContext::declareDotGeneratorName() { + // The special '.generator' binding must be on the function scope, and must + // be marked closed-over, as generators expect to find it on the CallObject. + ParseContext::Scope& funScope = functionScope(); + auto dotGenerator = TaggedParserAtomIndex::WellKnown::dotGenerator(); + AddDeclaredNamePtr p = funScope.lookupDeclaredNameForAdd(dotGenerator); + if (!p) { + if (!funScope.addDeclaredName(this, p, dotGenerator, DeclarationKind::Var, + DeclaredNameInfo::npos, ClosedOver::Yes)) { + return false; + } + } + return true; +} + +bool ParseContext::declareTopLevelDotGeneratorName() { + // Provide a .generator binding on the module scope for compatibility with + // generator code, which expect to find it on the CallObject for normal + // generators. + MOZ_ASSERT( + sc()->isModuleContext(), + "Tried to declare top level dot generator in a non-module context."); + ParseContext::Scope& modScope = varScope(); + auto dotGenerator = TaggedParserAtomIndex::WellKnown::dotGenerator(); + AddDeclaredNamePtr p = modScope.lookupDeclaredNameForAdd(dotGenerator); + return p || + modScope.addDeclaredName(this, p, dotGenerator, DeclarationKind::Var, + DeclaredNameInfo::npos, ClosedOver::Yes); +} + +} // namespace frontend + +} // namespace js diff --git a/js/src/frontend/ParseContext.h b/js/src/frontend/ParseContext.h new file mode 100644 index 0000000000..a4c9b5f04d --- /dev/null +++ b/js/src/frontend/ParseContext.h @@ -0,0 +1,622 @@ +/* -*- 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/. */ + +#ifndef frontend_ParseContext_h +#define frontend_ParseContext_h + +#include "ds/Nestable.h" +#include "frontend/ErrorReporter.h" +#include "frontend/NameAnalysisTypes.h" // DeclaredNameInfo, FunctionBoxVector +#include "frontend/NameCollections.h" +#include "frontend/ParserAtom.h" // TaggedParserAtomIndex +#include "frontend/ScriptIndex.h" // ScriptIndex +#include "frontend/SharedContext.h" +#include "js/friend/ErrorMessages.h" // JSMSG_* +#include "vm/GeneratorAndAsyncKind.h" // js::GeneratorKind, js::FunctionAsyncKind +#include "vm/WellKnownAtom.h" // js_*_str + +namespace js { + +namespace frontend { + +class ParserBase; +class UsedNameTracker; + +struct CompilationState; + +const char* DeclarationKindString(DeclarationKind kind); + +// Returns true if the declaration is `var` or equivalent. +bool DeclarationKindIsVar(DeclarationKind kind); + +bool DeclarationKindIsParameter(DeclarationKind kind); + +/* + * The struct ParseContext stores information about the current parsing context, + * which is part of the parser state (see the field Parser::pc). The current + * parsing context is either the global context, or the function currently being + * parsed. When the parser encounters a function definition, it creates a new + * ParseContext, makes it the new current context. + */ +class ParseContext : public Nestable { + public: + // The intra-function statement stack. + // + // Used for early error checking that depend on the nesting structure of + // statements, such as continue/break targets, labels, and unbraced + // lexical declarations. + class Statement : public Nestable { + StatementKind kind_; + + public: + using Nestable::enclosing; + using Nestable::findNearest; + + Statement(ParseContext* pc, StatementKind kind) + : Nestable(&pc->innermostStatement_), kind_(kind) {} + + template + inline bool is() const; + template + inline T& as(); + + StatementKind kind() const { return kind_; } + + void refineForKind(StatementKind newForKind) { + MOZ_ASSERT(kind_ == StatementKind::ForLoop); + MOZ_ASSERT(newForKind == StatementKind::ForInLoop || + newForKind == StatementKind::ForOfLoop); + kind_ = newForKind; + } + }; + + class LabelStatement : public Statement { + TaggedParserAtomIndex label_; + + public: + LabelStatement(ParseContext* pc, TaggedParserAtomIndex label) + : Statement(pc, StatementKind::Label), label_(label) {} + + TaggedParserAtomIndex label() const { return label_; } + }; + + struct ClassStatement : public Statement { + FunctionBox* constructorBox; + + explicit ClassStatement(ParseContext* pc) + : Statement(pc, StatementKind::Class), constructorBox(nullptr) {} + }; + + // The intra-function scope stack. + // + // Tracks declared and used names within a scope. + class Scope : public Nestable { + // Names declared in this scope. Corresponds to the union of + // VarDeclaredNames and LexicallyDeclaredNames in the ES spec. + // + // A 'var' declared name is a member of the declared name set of every + // scope in its scope contour. + // + // A lexically declared name is a member only of the declared name set of + // the scope in which it is declared. + PooledMapPtr declared_; + + // FunctionBoxes in this scope that need to be considered for Annex + // B.3.3 semantics. This is checked on Scope exit, as by then we have + // all the declared names and would know if Annex B.3.3 is applicable. + PooledVectorPtr possibleAnnexBFunctionBoxes_; + + // Monotonically increasing id. + uint32_t id_; + + // Scope size info, relevant for scopes in generators and async functions + // only. During parsing, this is the estimated number of slots needed for + // nested scopes inside this one. When the parser leaves a scope, this is + // set to UINT32_MAX if there are too many bindings overrall to store them + // in stack frames, and 0 otherwise. + uint32_t sizeBits_ = 0; + + bool maybeReportOOM(ParseContext* pc, bool result) { + if (!result) { + ReportOutOfMemory(pc->sc()->fc_); + } + return result; + } + + public: + using DeclaredNamePtr = DeclaredNameMap::Ptr; + using AddDeclaredNamePtr = DeclaredNameMap::AddPtr; + + using Nestable::enclosing; + + explicit inline Scope(ParserBase* parser); + explicit inline Scope(FrontendContext* fc, ParseContext* pc, + UsedNameTracker& usedNames); + + void dump(ParseContext* pc, ParserBase* parser); + + uint32_t id() const { return id_; } + + [[nodiscard]] bool init(ParseContext* pc) { + if (id_ == UINT32_MAX) { + pc->errorReporter_.errorNoOffset(JSMSG_NEED_DIET, js_script_str); + return false; + } + + return declared_.acquire(pc->sc()->fc_); + } + + bool isEmpty() const { return declared_->all().empty(); } + + uint32_t declaredCount() const { + size_t count = declared_->count(); + MOZ_ASSERT(count <= UINT32_MAX); + return uint32_t(count); + } + + DeclaredNamePtr lookupDeclaredName(TaggedParserAtomIndex name) { + return declared_->lookup(name); + } + + AddDeclaredNamePtr lookupDeclaredNameForAdd(TaggedParserAtomIndex name) { + return declared_->lookupForAdd(name); + } + + [[nodiscard]] bool addDeclaredName(ParseContext* pc, AddDeclaredNamePtr& p, + TaggedParserAtomIndex name, + DeclarationKind kind, uint32_t pos, + ClosedOver closedOver = ClosedOver::No) { + return maybeReportOOM( + pc, declared_->add(p, name, DeclaredNameInfo(kind, pos, closedOver))); + } + + // Add a FunctionBox as a possible candidate for Annex B.3.3 semantics. + [[nodiscard]] bool addPossibleAnnexBFunctionBox(ParseContext* pc, + FunctionBox* funbox); + + // Check if the candidate function boxes for Annex B.3.3 should in + // fact get Annex B semantics. Checked on Scope exit. + [[nodiscard]] bool propagateAndMarkAnnexBFunctionBoxes(ParseContext* pc, + ParserBase* parser); + + // Add and remove catch parameter names. Used to implement the odd + // semantics of catch bodies. + bool addCatchParameters(ParseContext* pc, Scope& catchParamScope); + void removeCatchParameters(ParseContext* pc, Scope& catchParamScope); + + void useAsVarScope(ParseContext* pc) { + MOZ_ASSERT(!pc->varScope_); + pc->varScope_ = this; + } + + // Maximum number of fixed stack slots in a generator or async function + // script. If a script would have more, we instead store some variables in + // heap EnvironmentObjects. + // + // This limit is a performance heuristic. Stack slots reduce allocations, + // and `Local` opcodes are a bit faster than `AliasedVar` ones; but at each + // `yield` or `await` the stack slots must be memcpy'd into a + // GeneratorObject. At some point the memcpy is too much. The limit is + // plenty for typical human-authored code. + static constexpr uint32_t FixedSlotLimit = 256; + + // This is called as we leave a function, var, or lexical scope in a + // generator or async function. `ownSlotCount` is the number of `bindings_` + // that are not closed over. + void setOwnStackSlotCount(uint32_t ownSlotCount) { + // Determine if this scope is too big to optimize bindings into stack + // slots. The meaning of sizeBits_ changes from "maximum nested slot + // count" to "UINT32_MAX if too big". + uint32_t slotCount = ownSlotCount + sizeBits_; + if (slotCount > FixedSlotLimit) { + slotCount = sizeBits_; + sizeBits_ = UINT32_MAX; + } else { + sizeBits_ = 0; + } + + // Propagate total size to enclosing scope. + if (Scope* parent = enclosing()) { + if (slotCount > parent->sizeBits_) { + parent->sizeBits_ = slotCount; + } + } + } + + bool tooBigToOptimize() const { + MOZ_ASSERT(sizeBits_ == 0 || sizeBits_ == UINT32_MAX, + "call this only after the parser leaves the scope"); + return sizeBits_ != 0; + } + + // An iterator for the set of names a scope binds: the set of all + // declared names for 'var' scopes, and the set of lexically declared + // names, plus synthetic names, for non-'var' scopes. + class BindingIter { + friend class Scope; + + DeclaredNameMap::Range declaredRange_; + mozilla::DebugOnly count_; + bool isVarScope_; + + BindingIter(Scope& scope, bool isVarScope) + : declaredRange_(scope.declared_->all()), + count_(0), + isVarScope_(isVarScope) { + settle(); + } + + bool isLexicallyDeclared() { + return BindingKindIsLexical(kind()) || + kind() == BindingKind::Synthetic || + kind() == BindingKind::PrivateMethod; + } + + void settle() { + // Both var and lexically declared names are binding in a var + // scope. + if (isVarScope_) { + return; + } + + // Otherwise, only lexically declared names are binding. Pop the range + // until we find such a name. + while (!declaredRange_.empty()) { + if (isLexicallyDeclared()) { + break; + } + declaredRange_.popFront(); + } + } + + public: + bool done() const { return declaredRange_.empty(); } + + explicit operator bool() const { return !done(); } + + TaggedParserAtomIndex name() { + MOZ_ASSERT(!done()); + return declaredRange_.front().key(); + } + + DeclarationKind declarationKind() { + MOZ_ASSERT(!done()); + return declaredRange_.front().value()->kind(); + } + + BindingKind kind() { + return DeclarationKindToBindingKind(declarationKind()); + } + + bool closedOver() { + MOZ_ASSERT(!done()); + return declaredRange_.front().value()->closedOver(); + } + + void setClosedOver() { + MOZ_ASSERT(!done()); + return declaredRange_.front().value()->setClosedOver(); + } + + void operator++(int) { + MOZ_ASSERT(!done()); + MOZ_ASSERT(count_ != UINT32_MAX); + declaredRange_.popFront(); + settle(); + } + }; + + inline BindingIter bindings(ParseContext* pc); + }; + + class VarScope : public Scope { + public: + explicit inline VarScope(ParserBase* parser); + explicit inline VarScope(FrontendContext* fc, ParseContext* pc, + UsedNameTracker& usedNames); + }; + + private: + // Context shared between parsing and bytecode generation. + SharedContext* sc_; + + // A mechanism used for error reporting. + ErrorReporter& errorReporter_; + + // The innermost statement, i.e., top of the statement stack. + Statement* innermostStatement_; + + // The innermost scope, i.e., top of the scope stack. + // + // The outermost scope in the stack is usually varScope_. In the case of + // functions, the outermost scope is functionScope_, which may be + // varScope_. See comment above functionScope_. + Scope* innermostScope_; + + // If isFunctionBox() and the function is a named lambda, the DeclEnv + // scope for named lambdas. + mozilla::Maybe namedLambdaScope_; + + // If isFunctionBox(), the scope for the function. If there are no + // parameter expressions, this is scope for the entire function. If there + // are parameter expressions, this holds the special function names + // ('.this', 'arguments') and the formal parameters. + mozilla::Maybe functionScope_; + + // The body-level scope. This always exists, but not necessarily at the + // beginning of parsing the script in the case of functions with parameter + // expressions. + Scope* varScope_; + + // Simple formal parameter names, in order of appearance. Only used when + // isFunctionBox(). + PooledVectorPtr positionalFormalParameterNames_; + + // Closed over binding names, in order of appearance. Null-delimited + // between scopes. Only used when syntax parsing. + PooledVectorPtr closedOverBindingsForLazy_; + + public: + // All inner functions in this context. Only used when syntax parsing. + // The Functions (or FunctionCreateionDatas) are traced as part of the + // CompilationStencil function vector. + Vector innerFunctionIndexesForLazy; + + // In a function context, points to a Directive struct that can be updated + // to reflect new directives encountered in the Directive Prologue that + // require reparsing the function. In global/module/generator-tail contexts, + // we don't need to reparse when encountering a DirectivePrologue so this + // pointer may be nullptr. + Directives* newDirectives; + + // lastYieldOffset stores the offset of the last yield that was parsed. + // NoYieldOffset is its initial value. + static const uint32_t NoYieldOffset = UINT32_MAX; + uint32_t lastYieldOffset; + + // lastAwaitOffset stores the offset of the last await that was parsed. + // NoAwaitOffset is its initial value. + static const uint32_t NoAwaitOffset = UINT32_MAX; + uint32_t lastAwaitOffset; + + private: + // Monotonically increasing id. + uint32_t scriptId_; + + // Set when encountering a super.property inside a method. We need to mark + // the nearest super scope as needing a home object. + bool superScopeNeedsHomeObject_; + + public: + ParseContext(FrontendContext* fc, ParseContext*& parent, SharedContext* sc, + ErrorReporter& errorReporter, CompilationState& compilationState, + Directives* newDirectives, bool isFull); + + [[nodiscard]] 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(); } + + Statement* innermostStatement() { return innermostStatement_; } + + Scope* innermostScope() { + // There is always at least one scope: the 'var' scope. + MOZ_ASSERT(innermostScope_); + return innermostScope_; + } + + Scope& namedLambdaScope() { + MOZ_ASSERT(functionBox()->isNamedLambda()); + return *namedLambdaScope_; + } + + Scope& functionScope() { + MOZ_ASSERT(isFunctionBox()); + return *functionScope_; + } + + Scope& varScope() { + MOZ_ASSERT(varScope_); + return *varScope_; + } + + bool isFunctionExtraBodyVarScopeInnermost() { + return isFunctionBox() && functionBox()->hasParameterExprs && + innermostScope() == varScope_; + } + + template bool */> + Statement* findInnermostStatement(Predicate predicate) { + return Statement::findNearest(innermostStatement_, predicate); + } + + template bool */> + T* findInnermostStatement(Predicate predicate) { + return Statement::findNearest(innermostStatement_, predicate); + } + + template + T* findInnermostStatement() { + return Statement::findNearest(innermostStatement_); + } + + AtomVector& positionalFormalParameterNames() { + return *positionalFormalParameterNames_; + } + + AtomVector& closedOverBindingsForLazy() { + return *closedOverBindingsForLazy_; + } + + enum class BreakStatementError : uint8_t { + // Unlabeled break must be inside loop or switch. + ToughBreak, + LabelNotFound, + }; + + // Return Err(true) if we have encountered at least one loop, + // Err(false) otherwise. + [[nodiscard]] inline JS::Result checkBreakStatement( + TaggedParserAtomIndex label); + + enum class ContinueStatementError : uint8_t { + NotInALoop, + LabelNotFound, + }; + [[nodiscard]] inline JS::Result + checkContinueStatement(TaggedParserAtomIndex 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() { return !innermostStatement_; } + + bool atGlobalLevel() { return atBodyLevel() && sc_->isGlobalContext(); } + + // True if we are at the topmost level of a module only. + bool atModuleLevel() { return atBodyLevel() && sc_->isModuleContext(); } + + // True if we are at the topmost level of an entire script or module. For + // example, in the comment on |atBodyLevel()| above, we would encounter |f1| + // and the outermost |if (cond)| at top level, and everything else would not + // be at top level. + bool atTopLevel() { return atBodyLevel() && sc_->isTopLevelContext(); } + + bool atModuleTopLevel() { + // True if we are at the topmost level of an entire module. + // + // For example, this is used to determine if an await statement should + // mark a module as an async module during parsing. + // + // Example module: + // import x from "y"; + // + // await x.foo(); // mark as Top level await. + // + // if (cond) { + // await x.bar(); // mark as Top level await. + // } + // + // async function z() { + // await x.baz(); // do not mark as Top level await. + // } + return sc_->isModuleContext() && sc_->isTopLevelContext(); + } + + // True if this is the outermost ParserContext for current compile. For + // delazification, this lets us identify if the lazy PrivateScriptData is for + // current parser context. + bool isOutermostOfCurrentCompile() const { + MOZ_ASSERT(!!enclosing() == !!scriptId()); + return (scriptId() == 0); + } + + void setSuperScopeNeedsHomeObject() { + MOZ_ASSERT(sc_->allowSuperProperty()); + superScopeNeedsHomeObject_ = true; + } + + bool superScopeNeedsHomeObject() const { return superScopeNeedsHomeObject_; } + + bool useAsmOrInsideUseAsm() const { + return sc_->isFunctionBox() && sc_->asFunctionBox()->useAsmOrInsideUseAsm(); + } + + // A generator is marked as a generator before its body is parsed. + GeneratorKind generatorKind() const { + return sc_->isFunctionBox() ? sc_->asFunctionBox()->generatorKind() + : GeneratorKind::NotGenerator; + } + + bool isGenerator() const { + return generatorKind() == GeneratorKind::Generator; + } + + bool isAsync() const { + return sc_->isSuspendableContext() && + sc_->asSuspendableContext()->isAsync(); + } + + bool isGeneratorOrAsync() const { return isGenerator() || isAsync(); } + + bool needsDotGeneratorName() const { return isGeneratorOrAsync(); } + + FunctionAsyncKind asyncKind() const { + return isAsync() ? FunctionAsyncKind::AsyncFunction + : FunctionAsyncKind::SyncFunction; + } + + bool isArrowFunction() const { + return sc_->isFunctionBox() && sc_->asFunctionBox()->isArrow(); + } + + bool isMethod() const { + return sc_->isFunctionBox() && sc_->asFunctionBox()->isMethod(); + } + + bool isGetterOrSetter() const { + return sc_->isFunctionBox() && (sc_->asFunctionBox()->isGetter() || + sc_->asFunctionBox()->isSetter()); + } + + bool allowReturn() const { + return sc_->isFunctionBox() && sc_->asFunctionBox()->allowReturn(); + } + + uint32_t scriptId() const { return scriptId_; } + + bool computeAnnexBAppliesToLexicalFunctionInInnermostScope( + FunctionBox* funbox, ParserBase* parser, bool* annexBApplies); + + bool tryDeclareVar(TaggedParserAtomIndex name, ParserBase* parser, + DeclarationKind kind, uint32_t beginPos, + mozilla::Maybe* redeclaredKind, + uint32_t* prevPos); + + bool hasUsedName(const UsedNameTracker& usedNames, + TaggedParserAtomIndex name); + bool hasUsedFunctionSpecialName(const UsedNameTracker& usedNames, + TaggedParserAtomIndex name); + + bool declareFunctionThis(const UsedNameTracker& usedNames, + bool canSkipLazyClosedOverBindings); + bool declareFunctionArgumentsObject(const UsedNameTracker& usedNames, + bool canSkipLazyClosedOverBindings); + bool declareNewTarget(const UsedNameTracker& usedNames, + bool canSkipLazyClosedOverBindings); + bool declareDotGeneratorName(); + bool declareTopLevelDotGeneratorName(); + + private: + [[nodiscard]] bool isVarRedeclaredInInnermostScope( + TaggedParserAtomIndex name, ParserBase* parser, DeclarationKind kind, + mozilla::Maybe* out); + + [[nodiscard]] bool isVarRedeclaredInEval( + TaggedParserAtomIndex name, ParserBase* parser, DeclarationKind kind, + mozilla::Maybe* out); + + enum DryRunOption { NotDryRun, DryRunInnermostScopeOnly }; + template + bool tryDeclareVarHelper(TaggedParserAtomIndex name, ParserBase* parser, + DeclarationKind kind, uint32_t beginPos, + mozilla::Maybe* redeclaredKind, + uint32_t* prevPos); +}; + +} // namespace frontend + +} // namespace js + +#endif // frontend_ParseContext_h diff --git a/js/src/frontend/ParseNode.cpp b/js/src/frontend/ParseNode.cpp new file mode 100644 index 0000000000..b9d16f0be8 --- /dev/null +++ b/js/src/frontend/ParseNode.cpp @@ -0,0 +1,430 @@ +/* -*- 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/ParseNode.h" + +#include "mozilla/FloatingPoint.h" + +#include "jsnum.h" + +#include "frontend/CompilationStencil.h" // ExtensibleCompilationStencil +#include "frontend/FullParseHandler.h" +#include "frontend/ParseContext.h" +#include "frontend/Parser.h" // ParserBase +#include "frontend/ParserAtom.h" // ParserAtomsTable, TaggedParserAtomIndex +#include "frontend/SharedContext.h" +#include "js/Printer.h" +#include "vm/Scope.h" // GetScopeDataTrailingNames + +using namespace js; +using namespace js::frontend; + +#ifdef DEBUG +void ListNode::checkConsistency() const { + ParseNode* const* tailNode; + uint32_t actualCount = 0; + if (const ParseNode* last = head()) { + const ParseNode* pn = last; + while (pn) { + last = pn; + pn = pn->pn_next; + actualCount++; + } + + tailNode = &last->pn_next; + } else { + tailNode = &head_; + } + MOZ_ASSERT(tail() == tailNode); + MOZ_ASSERT(count() == actualCount); +} +#endif + +/* + * Allocate a ParseNode from parser's node freelist or, failing that, from + * cx's temporary arena. + */ +void* ParseNodeAllocator::allocNode(size_t size) { + LifoAlloc::AutoFallibleScope fallibleAllocator(&alloc); + void* p = alloc.alloc(size); + if (!p) { + ReportOutOfMemory(fc); + } + return p; +} + +ParseNode* ParseNode::appendOrCreateList(ParseNodeKind kind, ParseNode* left, + ParseNode* right, + FullParseHandler* handler, + ParseContext* pc) { + // The asm.js specification is written in ECMAScript grammar terms that + // specify *only* a binary tree. It's a royal pain to implement the asm.js + // spec to act upon n-ary lists as created below. So for asm.js, form a + // binary tree of lists exactly as ECMAScript would by skipping the + // following optimization. + if (!pc->useAsmOrInsideUseAsm()) { + // Left-associative trees of a given operator (e.g. |a + b + c|) are + // binary trees in the spec: (+ (+ a b) c) in Lisp terms. Recursively + // processing such a tree, exactly implemented that way, would blow the + // the stack. We use a list node that uses O(1) stack to represent + // such operations: (+ a b c). + // + // (**) is right-associative; per spec |a ** b ** c| parses as + // (** a (** b c)). But we treat this the same way, creating a list + // node: (** a b c). All consumers must understand that this must be + // processed with a right fold, whereas the list (+ a b c) must be + // processed with a left fold because (+) is left-associative. + // + if (left->isKind(kind) && + (kind == ParseNodeKind::PowExpr ? !left->isInParens() + : left->isBinaryOperation())) { + ListNode* list = &left->as(); + + list->append(right); + list->pn_pos.end = right->pn_pos.end; + + return list; + } + } + + ListNode* list = handler->new_(kind, left); + if (!list) { + return nullptr; + } + + list->append(right); + return list; +} + +const ParseNode::TypeCode ParseNode::typeCodeTable[] = { +#define TYPE_CODE(_name, type) type::classTypeCode(), + FOR_EACH_PARSE_NODE_KIND(TYPE_CODE) +#undef TYPE_CODE +}; + +#ifdef DEBUG + +const size_t ParseNode::sizeTable[] = { +# define NODE_SIZE(_name, type) sizeof(type), + FOR_EACH_PARSE_NODE_KIND(NODE_SIZE) +# undef NODE_SIZE +}; + +static const char* const parseNodeNames[] = { +# define STRINGIFY(name, _type) #name, + FOR_EACH_PARSE_NODE_KIND(STRINGIFY) +# undef STRINGIFY +}; + +static void DumpParseTree(const ParserAtomsTable* parserAtoms, ParseNode* pn, + GenericPrinter& out, int indent) { + if (pn == nullptr) { + out.put("#NULL"); + } else { + pn->dump(parserAtoms, out, indent); + } +} + +void frontend::DumpParseTree(ParserBase* parser, ParseNode* pn, + GenericPrinter& out, int indent) { + ParserAtomsTable* parserAtoms = parser ? &parser->parserAtoms() : nullptr; + ::DumpParseTree(parserAtoms, pn, out, indent); +} + +static void IndentNewLine(GenericPrinter& out, int indent) { + out.putChar('\n'); + for (int i = 0; i < indent; ++i) { + out.putChar(' '); + } +} + +void ParseNode::dump() { dump(nullptr); } + +void ParseNode::dump(const ParserAtomsTable* parserAtoms) { + js::Fprinter out(stderr); + dump(parserAtoms, out); +} + +void ParseNode::dump(const ParserAtomsTable* parserAtoms, GenericPrinter& out) { + dump(parserAtoms, out, 0); + out.putChar('\n'); +} + +void ParseNode::dump(const ParserAtomsTable* parserAtoms, GenericPrinter& out, + int indent) { + switch (getKind()) { +# define DUMP(K, T) \ + case ParseNodeKind::K: \ + as().dumpImpl(parserAtoms, out, indent); \ + break; + FOR_EACH_PARSE_NODE_KIND(DUMP) +# undef DUMP + default: + out.printf("#", (void*)this, unsigned(getKind())); + } +} + +void NullaryNode::dumpImpl(const ParserAtomsTable* parserAtoms, + GenericPrinter& out, int indent) { + switch (getKind()) { + case ParseNodeKind::TrueExpr: + out.put("#true"); + break; + case ParseNodeKind::FalseExpr: + out.put("#false"); + break; + case ParseNodeKind::NullExpr: + out.put("#null"); + break; + case ParseNodeKind::RawUndefinedExpr: + out.put("#undefined"); + break; + + default: + out.printf("(%s)", parseNodeNames[getKindAsIndex()]); + } +} + +void NumericLiteral::dumpImpl(const ParserAtomsTable* parserAtoms, + GenericPrinter& out, int indent) { + ToCStringBuf cbuf; + const char* cstr = NumberToCString(&cbuf, value()); + MOZ_ASSERT(cstr); + if (!std::isfinite(value())) { + out.put("#"); + } + out.printf("%s", cstr); +} + +void BigIntLiteral::dumpImpl(const ParserAtomsTable* parserAtoms, + GenericPrinter& out, int indent) { + out.printf("(%s)", parseNodeNames[getKindAsIndex()]); +} + +void RegExpLiteral::dumpImpl(const ParserAtomsTable* parserAtoms, + GenericPrinter& out, int indent) { + out.printf("(%s)", parseNodeNames[getKindAsIndex()]); +} + +static void DumpCharsNoNewline(const ParserAtomsTable* parserAtoms, + TaggedParserAtomIndex index, + GenericPrinter& out) { + out.put("\""); + if (parserAtoms) { + parserAtoms->dumpCharsNoQuote(out, index); + } else { + DumpTaggedParserAtomIndexNoQuote(out, index, nullptr); + } + out.put("\""); +} + +void LoopControlStatement::dumpImpl(const ParserAtomsTable* parserAtoms, + GenericPrinter& out, int indent) { + const char* name = parseNodeNames[getKindAsIndex()]; + out.printf("(%s", name); + if (label_) { + out.printf(" "); + DumpCharsNoNewline(parserAtoms, label_, out); + } + out.printf(")"); +} + +void UnaryNode::dumpImpl(const ParserAtomsTable* parserAtoms, + GenericPrinter& out, int indent) { + const char* name = parseNodeNames[getKindAsIndex()]; + out.printf("(%s ", name); + indent += strlen(name) + 2; + ::DumpParseTree(parserAtoms, kid(), out, indent); + out.printf(")"); +} + +void BinaryNode::dumpImpl(const ParserAtomsTable* parserAtoms, + GenericPrinter& out, int indent) { + if (isKind(ParseNodeKind::DotExpr)) { + out.put("(."); + + ::DumpParseTree(parserAtoms, right(), out, indent + 2); + + out.putChar(' '); + if (as().isSuper()) { + out.put("super"); + } else { + ::DumpParseTree(parserAtoms, left(), out, indent + 2); + } + + out.printf(")"); + return; + } + + const char* name = parseNodeNames[getKindAsIndex()]; + out.printf("(%s ", name); + indent += strlen(name) + 2; + ::DumpParseTree(parserAtoms, left(), out, indent); + IndentNewLine(out, indent); + ::DumpParseTree(parserAtoms, right(), out, indent); + out.printf(")"); +} + +void TernaryNode::dumpImpl(const ParserAtomsTable* parserAtoms, + GenericPrinter& out, int indent) { + const char* name = parseNodeNames[getKindAsIndex()]; + out.printf("(%s ", name); + indent += strlen(name) + 2; + ::DumpParseTree(parserAtoms, kid1(), out, indent); + IndentNewLine(out, indent); + ::DumpParseTree(parserAtoms, kid2(), out, indent); + IndentNewLine(out, indent); + ::DumpParseTree(parserAtoms, kid3(), out, indent); + out.printf(")"); +} + +void FunctionNode::dumpImpl(const ParserAtomsTable* parserAtoms, + GenericPrinter& out, int indent) { + const char* name = parseNodeNames[getKindAsIndex()]; + out.printf("(%s ", name); + indent += strlen(name) + 2; + ::DumpParseTree(parserAtoms, body(), out, indent); + out.printf(")"); +} + +void ModuleNode::dumpImpl(const ParserAtomsTable* parserAtoms, + GenericPrinter& out, int indent) { + const char* name = parseNodeNames[getKindAsIndex()]; + out.printf("(%s ", name); + indent += strlen(name) + 2; + ::DumpParseTree(parserAtoms, body(), out, indent); + out.printf(")"); +} + +void ListNode::dumpImpl(const ParserAtomsTable* parserAtoms, + GenericPrinter& out, int indent) { + const char* name = parseNodeNames[getKindAsIndex()]; + out.printf("(%s [", name); + if (ParseNode* listHead = head()) { + indent += strlen(name) + 3; + ::DumpParseTree(parserAtoms, listHead, out, indent); + for (ParseNode* item : contentsFrom(listHead->pn_next)) { + IndentNewLine(out, indent); + ::DumpParseTree(parserAtoms, item, out, indent); + } + } + out.printf("])"); +} + +void NameNode::dumpImpl(const ParserAtomsTable* parserAtoms, + GenericPrinter& out, int indent) { + switch (getKind()) { + case ParseNodeKind::StringExpr: + case ParseNodeKind::TemplateStringExpr: + case ParseNodeKind::ObjectPropertyName: + DumpCharsNoNewline(parserAtoms, atom_, out); + return; + + case ParseNodeKind::Name: + case ParseNodeKind::PrivateName: // atom() already includes the '#', no + // need to specially include it. + case ParseNodeKind::PropertyNameExpr: + if (!atom_) { + out.put("#"); + } else if (parserAtoms) { + if (atom_ == TaggedParserAtomIndex::WellKnown::empty()) { + out.put("#"); + } else { + parserAtoms->dumpCharsNoQuote(out, atom_); + } + } else { + DumpTaggedParserAtomIndexNoQuote(out, atom_, nullptr); + } + return; + + case ParseNodeKind::LabelStmt: { + this->as().dumpImpl(parserAtoms, out, indent); + return; + } + + default: { + const char* name = parseNodeNames[getKindAsIndex()]; + out.printf("(%s)", name); + return; + } + } +} + +void LabeledStatement::dumpImpl(const ParserAtomsTable* parserAtoms, + GenericPrinter& out, int indent) { + const char* name = parseNodeNames[getKindAsIndex()]; + out.printf("(%s ", name); + DumpCharsNoNewline(parserAtoms, label(), out); + indent += strlen(name) + 2; + IndentNewLine(out, indent); + ::DumpParseTree(parserAtoms, statement(), out, indent); + out.printf(")"); +} + +template +void BaseScopeNode::dumpImpl( + const ParserAtomsTable* parserAtoms, GenericPrinter& out, int indent) { + const char* name = parseNodeNames[getKindAsIndex()]; + out.printf("(%s [", name); + int nameIndent = indent + strlen(name) + 3; + if (!isEmptyScope()) { + typename ScopeType::ParserData* bindings = scopeBindings(); + auto names = GetScopeDataTrailingNames(bindings); + for (uint32_t i = 0; i < names.size(); i++) { + auto index = names[i].name(); + if (parserAtoms) { + if (index == TaggedParserAtomIndex::WellKnown::empty()) { + out.put("#"); + } else { + parserAtoms->dumpCharsNoQuote(out, index); + } + } else { + DumpTaggedParserAtomIndexNoQuote(out, index, nullptr); + } + if (i < names.size() - 1) { + IndentNewLine(out, nameIndent); + } + } + } + out.putChar(']'); + indent += 2; + IndentNewLine(out, indent); + ::DumpParseTree(parserAtoms, scopeBody(), out, indent); + out.printf(")"); +} +#endif + +TaggedParserAtomIndex NumericLiteral::toAtom( + FrontendContext* fc, ParserAtomsTable& parserAtoms) const { + return NumberToParserAtom(fc, parserAtoms, value()); +} + +RegExpObject* RegExpLiteral::create( + JSContext* cx, FrontendContext* fc, ParserAtomsTable& parserAtoms, + CompilationAtomCache& atomCache, + ExtensibleCompilationStencil& stencil) const { + return stencil.regExpData[index_].createRegExpAndEnsureAtom( + cx, fc, parserAtoms, atomCache); +} + +bool js::frontend::IsAnonymousFunctionDefinition(ParseNode* pn) { + // ES 2017 draft + // 12.15.2 (ArrowFunction, AsyncArrowFunction). + // 14.1.12 (FunctionExpression). + // 14.4.8 (Generatoression). + // 14.6.8 (AsyncFunctionExpression) + if (pn->is() && + !pn->as().funbox()->explicitName()) { + return true; + } + + // 14.5.8 (ClassExpression) + if (pn->is() && !pn->as().names()) { + return true; + } + + return false; +} diff --git a/js/src/frontend/ParseNode.h b/js/src/frontend/ParseNode.h new file mode 100644 index 0000000000..959cfe0b0c --- /dev/null +++ b/js/src/frontend/ParseNode.h @@ -0,0 +1,2556 @@ +/* -*- 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/. */ + +#ifndef frontend_ParseNode_h +#define frontend_ParseNode_h + +#include "mozilla/Assertions.h" + +#include +#include +#include + +#include "jstypes.h" // js::Bit + +#include "frontend/FunctionSyntaxKind.h" // FunctionSyntaxKind +#include "frontend/NameAnalysisTypes.h" // PrivateNameKind +#include "frontend/ParserAtom.h" // TaggedParserAtomIndex +#include "frontend/Stencil.h" // BigIntStencil +#include "frontend/Token.h" +#include "js/TypeDecls.h" +#include "vm/Opcodes.h" +#include "vm/Scope.h" +#include "vm/ScopeKind.h" + +// [SMDOC] ParseNode tree lifetime information +// +// - 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::ParserData` and `ClassBodyScope::ParserData` +// MUST BE allocated as instances of `ParseNode`, in the same `LifoAlloc`. +// They are bulk-deallocated alongside the rest of the tree. + +struct JSContext; + +namespace js { + +class JS_PUBLIC_API GenericPrinter; +class LifoAlloc; +class RegExpObject; + +namespace frontend { + +class ParserAtomsTable; +class ParserBase; +class ParseContext; +struct ExtensibleCompilationStencil; +class ParserSharedBase; +class FullParseHandler; + +class FunctionBox; + +#define FOR_EACH_PARSE_NODE_KIND(F) \ + F(EmptyStmt, NullaryNode) \ + F(ExpressionStmt, UnaryNode) \ + F(CommaExpr, ListNode) \ + F(ConditionalExpr, ConditionalExpression) \ + F(PropertyDefinition, PropertyDefinition) \ + F(Shorthand, BinaryNode) \ + F(PosExpr, UnaryNode) \ + F(NegExpr, UnaryNode) \ + F(PreIncrementExpr, UnaryNode) \ + F(PostIncrementExpr, UnaryNode) \ + F(PreDecrementExpr, UnaryNode) \ + F(PostDecrementExpr, UnaryNode) \ + F(PropertyNameExpr, NameNode) \ + F(DotExpr, PropertyAccess) \ + F(ElemExpr, PropertyByValue) \ + F(PrivateMemberExpr, PrivateMemberAccess) \ + F(OptionalDotExpr, OptionalPropertyAccess) \ + F(OptionalChain, UnaryNode) \ + F(OptionalElemExpr, OptionalPropertyByValue) \ + F(OptionalPrivateMemberExpr, OptionalPrivateMemberAccess) \ + F(OptionalCallExpr, BinaryNode) \ + F(ArrayExpr, ListNode) \ + F(Elision, NullaryNode) \ + F(StatementList, ListNode) \ + F(LabelStmt, LabeledStatement) \ + F(ObjectExpr, ListNode) \ + F(CallExpr, BinaryNode) \ + F(Arguments, ListNode) \ + F(Name, NameNode) \ + F(ObjectPropertyName, NameNode) \ + F(PrivateName, NameNode) \ + F(ComputedName, UnaryNode) \ + F(NumberExpr, NumericLiteral) \ + F(BigIntExpr, BigIntLiteral) \ + F(StringExpr, NameNode) \ + F(TemplateStringListExpr, ListNode) \ + F(TemplateStringExpr, NameNode) \ + F(TaggedTemplateExpr, BinaryNode) \ + F(CallSiteObj, CallSiteNode) \ + F(RegExpExpr, RegExpLiteral) \ + F(TrueExpr, BooleanLiteral) \ + F(FalseExpr, BooleanLiteral) \ + F(NullExpr, NullLiteral) \ + F(RawUndefinedExpr, RawUndefinedLiteral) \ + F(ThisExpr, UnaryNode) \ + IF_RECORD_TUPLE(F(RecordExpr, ListNode)) \ + IF_RECORD_TUPLE(F(TupleExpr, ListNode)) \ + F(Function, FunctionNode) \ + F(Module, ModuleNode) \ + F(IfStmt, TernaryNode) \ + F(SwitchStmt, SwitchStatement) \ + F(Case, CaseClause) \ + F(WhileStmt, BinaryNode) \ + F(DoWhileStmt, BinaryNode) \ + F(ForStmt, ForNode) \ + F(BreakStmt, BreakStatement) \ + F(ContinueStmt, ContinueStatement) \ + F(VarStmt, DeclarationListNode) \ + F(ConstDecl, DeclarationListNode) \ + F(WithStmt, BinaryNode) \ + F(ReturnStmt, UnaryNode) \ + F(NewExpr, BinaryNode) \ + IF_DECORATORS(F(DecoratorList, ListNode)) \ + /* Delete operations. These must be sequential. */ \ + F(DeleteNameExpr, UnaryNode) \ + F(DeletePropExpr, UnaryNode) \ + F(DeleteElemExpr, UnaryNode) \ + F(DeleteOptionalChainExpr, UnaryNode) \ + F(DeleteExpr, UnaryNode) \ + F(TryStmt, TernaryNode) \ + F(Catch, BinaryNode) \ + F(ThrowStmt, UnaryNode) \ + F(DebuggerStmt, DebuggerStatement) \ + F(Generator, NullaryNode) \ + F(InitialYield, UnaryNode) \ + F(YieldExpr, UnaryNode) \ + F(YieldStarExpr, UnaryNode) \ + F(LexicalScope, LexicalScopeNode) \ + F(LetDecl, DeclarationListNode) \ + F(ImportDecl, BinaryNode) \ + F(ImportSpecList, ListNode) \ + F(ImportSpec, BinaryNode) \ + F(ImportNamespaceSpec, UnaryNode) \ + F(ImportAssertionList, ListNode) \ + F(ImportAssertion, BinaryNode) \ + F(ImportModuleRequest, BinaryNode) \ + F(ExportStmt, UnaryNode) \ + F(ExportFromStmt, BinaryNode) \ + F(ExportDefaultStmt, BinaryNode) \ + F(ExportSpecList, ListNode) \ + F(ExportSpec, BinaryNode) \ + F(ExportNamespaceSpec, UnaryNode) \ + F(ExportBatchSpecStmt, NullaryNode) \ + F(ForIn, TernaryNode) \ + F(ForOf, TernaryNode) \ + F(ForHead, TernaryNode) \ + F(ParamsBody, ParamsBodyNode) \ + F(Spread, UnaryNode) \ + F(MutateProto, UnaryNode) \ + F(ClassDecl, ClassNode) \ + F(DefaultConstructor, ClassMethod) \ + F(ClassBodyScope, ClassBodyScopeNode) \ + F(ClassMethod, ClassMethod) \ + F(StaticClassBlock, StaticClassBlock) \ + F(ClassField, ClassField) \ + F(ClassMemberList, ListNode) \ + F(ClassNames, ClassNames) \ + F(NewTargetExpr, NewTargetNode) \ + F(PosHolder, NullaryNode) \ + F(SuperBase, UnaryNode) \ + F(SuperCallExpr, BinaryNode) \ + F(SetThis, BinaryNode) \ + F(ImportMetaExpr, BinaryNode) \ + F(CallImportExpr, BinaryNode) \ + F(CallImportSpec, BinaryNode) \ + F(InitExpr, BinaryNode) \ + \ + /* Unary operators. */ \ + F(TypeOfNameExpr, UnaryNode) \ + F(TypeOfExpr, UnaryNode) \ + F(VoidExpr, UnaryNode) \ + F(NotExpr, UnaryNode) \ + F(BitNotExpr, UnaryNode) \ + F(AwaitExpr, UnaryNode) \ + \ + /* \ + * Binary operators. \ + * This list must be kept in the same order in several places: \ + * - The binary operators in ParseNode.h \ + * - the binary operators in TokenKind.h \ + * - the precedence list in Parser.cpp \ + * - the JSOp code list in BytecodeEmitter.cpp \ + */ \ + F(CoalesceExpr, ListNode) \ + F(OrExpr, ListNode) \ + F(AndExpr, ListNode) \ + F(BitOrExpr, ListNode) \ + F(BitXorExpr, ListNode) \ + F(BitAndExpr, ListNode) \ + F(StrictEqExpr, ListNode) \ + F(EqExpr, ListNode) \ + F(StrictNeExpr, ListNode) \ + F(NeExpr, ListNode) \ + F(LtExpr, ListNode) \ + F(LeExpr, ListNode) \ + F(GtExpr, ListNode) \ + F(GeExpr, ListNode) \ + F(InstanceOfExpr, ListNode) \ + F(InExpr, ListNode) \ + F(PrivateInExpr, ListNode) \ + F(LshExpr, ListNode) \ + F(RshExpr, ListNode) \ + F(UrshExpr, ListNode) \ + F(AddExpr, ListNode) \ + F(SubExpr, ListNode) \ + F(MulExpr, ListNode) \ + F(DivExpr, ListNode) \ + F(ModExpr, ListNode) \ + F(PowExpr, ListNode) \ + \ + /* Assignment operators (= += -= etc.). */ \ + /* AssignmentNode::test assumes all these are consecutive. */ \ + F(AssignExpr, AssignmentNode) \ + F(AddAssignExpr, AssignmentNode) \ + F(SubAssignExpr, AssignmentNode) \ + F(CoalesceAssignExpr, AssignmentNode) \ + F(OrAssignExpr, AssignmentNode) \ + F(AndAssignExpr, AssignmentNode) \ + F(BitOrAssignExpr, AssignmentNode) \ + F(BitXorAssignExpr, AssignmentNode) \ + F(BitAndAssignExpr, AssignmentNode) \ + F(LshAssignExpr, AssignmentNode) \ + F(RshAssignExpr, AssignmentNode) \ + F(UrshAssignExpr, AssignmentNode) \ + F(MulAssignExpr, AssignmentNode) \ + F(DivAssignExpr, AssignmentNode) \ + F(ModAssignExpr, AssignmentNode) \ + F(PowAssignExpr, AssignmentNode) + +/* + * Parsing builds a tree of nodes that directs code generation. This tree is + * not a concrete syntax tree in all respects (for example, || and && are left + * associative, but (A && B && C) translates into the right-associated tree + * > so that code generation can emit a left-associative branch + * around when A is false). Nodes are labeled by kind. + * + * The long comment after this enum block describes the kinds in detail. + */ +enum class ParseNodeKind : uint16_t { + // These constants start at 1001, the better to catch + LastUnused = 1000, +#define EMIT_ENUM(name, _type) name, + FOR_EACH_PARSE_NODE_KIND(EMIT_ENUM) +#undef EMIT_ENUM + Limit, + Start = LastUnused + 1, + BinOpFirst = ParseNodeKind::CoalesceExpr, + BinOpLast = ParseNodeKind::PowExpr, + AssignmentStart = ParseNodeKind::AssignExpr, + AssignmentLast = ParseNodeKind::PowAssignExpr, +}; + +inline bool IsDeleteKind(ParseNodeKind kind) { + return ParseNodeKind::DeleteNameExpr <= kind && + kind <= ParseNodeKind::DeleteExpr; +} + +inline bool IsTypeofKind(ParseNodeKind kind) { + return ParseNodeKind::TypeOfNameExpr <= kind && + kind <= ParseNodeKind::TypeOfExpr; +} + +/* + * + * Function (FunctionNode) + * funbox: ptr to js::FunctionBox + * body: ParamsBody or null for lazily-parsed function + * syntaxKind: the syntax of the function + * ParamsBody (ListNode) + * head: list of formal parameters with + * * Name node with non-empty name for SingleNameBinding without + * Initializer + * * AssignExpr node for SingleNameBinding with Initializer + * * Name node with empty name for destructuring + * expr: Array or Object for BindingPattern without + * Initializer, Assign for BindingPattern with + * Initializer + * followed by: + * * LexicalScopeNode + * count: number of formal parameters + 1 + * Spread (UnaryNode) + * kid: expression being spread + * ClassDecl (ClassNode) + * kid1: ClassNames for class name. can be null for anonymous class. + * kid2: expression after `extends`. null if no expression + * kid3: either of + * * ClassMemberList, if anonymous class + * * LexicalScopeNode which contains ClassMemberList as scopeBody, + * if named class + * ClassNames (ClassNames) + * left: Name node for outer binding, or null if the class is an expression + * that doesn't create an outer binding + * right: Name node for inner binding + * ClassMemberList (ListNode) + * head: list of N ClassMethod, ClassField or StaticClassBlock nodes + * count: N >= 0 + * DefaultConstructor (ClassMethod) + * name: propertyName + * method: methodDefinition + * ClassMethod (ClassMethod) + * name: propertyName + * method: methodDefinition + * initializerIfPrivate: initializer to stamp private method onto instance + * Module (ModuleNode) + * body: statement list of the module + * + * + * StatementList (ListNode) + * head: list of N statements + * count: N >= 0 + * IfStmt (TernaryNode) + * kid1: cond + * kid2: then + * kid3: else or null + * SwitchStmt (SwitchStatement) + * left: discriminant + * right: LexicalScope node that contains the list of Case nodes, with at + * most one default node. + * hasDefault: true if there's a default case + * Case (CaseClause) + * left: case-expression if CaseClause, or null if DefaultClause + * right: StatementList node for this case's statements + * WhileStmt (BinaryNode) + * left: cond + * right: body + * DoWhileStmt (BinaryNode) + * left: body + * right: cond + * ForStmt (ForNode) + * left: one of + * * ForIn: for (x in y) ... + * * ForOf: for (x of x) ... + * * ForHead: for (;;) ... + * right: body + * ForIn (TernaryNode) + * kid1: declaration or expression to left of 'in' + * kid2: null + * kid3: object expr to right of 'in' + * ForOf (TernaryNode) + * kid1: declaration or expression to left of 'of' + * kid2: null + * kid3: expr to right of 'of' + * ForHead (TernaryNode) + * kid1: init expr before first ';' or nullptr + * kid2: cond expr before second ';' or nullptr + * kid3: update expr after second ';' or nullptr + * ThrowStmt (UnaryNode) + * kid: thrown exception + * TryStmt (TernaryNode) + * kid1: try block + * kid2: null or LexicalScope for catch-block with scopeBody pointing to a + * Catch node + * kid3: null or finally block + * Catch (BinaryNode) + * left: Name, Array, or Object catch var node + * (Array or Object if destructuring), + * or null if optional catch binding + * right: catch block statements + * BreakStmt (BreakStatement) + * label: label or null + * ContinueStmt (ContinueStatement) + * label: label or null + * WithStmt (BinaryNode) + * left: head expr + * right: body + * VarStmt, LetDecl, ConstDecl (DeclarationListNode) + * head: list of N Name or AssignExpr nodes + * each name node has either + * atom: variable name + * expr: initializer or null + * or + * atom: variable name + * each assignment node has + * left: pattern + * right: initializer + * count: N > 0 + * ReturnStmt (UnaryNode) + * kid: returned expression, or null if none + * ExpressionStmt (UnaryNode) + * kid: expr + * EmptyStmt (NullaryNode) + * (no fields) + * LabelStmt (LabeledStatement) + * atom: label + * expr: labeled statement + * ImportDecl (BinaryNode) + * left: ImportSpecList import specifiers + * right: String module specifier + * ImportSpecList (ListNode) + * head: list of N ImportSpec nodes + * count: N >= 0 (N = 0 for `import {} from ...`) + * ImportSpec (BinaryNode) + * left: import name + * right: local binding name + * ImportNamespaceSpec (UnaryNode) + * kid: local binding name + * ExportStmt (UnaryNode) + * kid: declaration expression + * ExportFromStmt (BinaryNode) + * left: ExportSpecList export specifiers + * right: String module specifier + * ExportSpecList (ListNode) + * head: list of N ExportSpec nodes + * count: N >= 0 (N = 0 for `export {}`) + * ExportSpec (BinaryNode) + * left: local binding name + * right: export name + * ExportNamespaceSpec (UnaryNode) + * kid: export name + * ExportDefaultStmt (BinaryNode) + * left: export default declaration or expression + * right: Name node for assignment + * + * + * The `Expr` suffix is used for nodes that can appear anywhere an expression + * could appear. It is not used on a few weird kinds like Arguments and + * CallSiteObj that are always the child node of an expression node, but which + * can't stand alone. + * + * All left-associated binary trees of the same type are optimized into lists + * to avoid recursion when processing expression chains. + * + * CommaExpr (ListNode) + * head: list of N comma-separated exprs + * count: N >= 2 + * AssignExpr (BinaryNode) + * left: target of assignment + * right: value to assign + * AddAssignExpr, SubAssignExpr, CoalesceAssignExpr, OrAssignExpr, + * AndAssignExpr, BitOrAssignExpr, BitXorAssignExpr, BitAndAssignExpr, + * LshAssignExpr, RshAssignExpr, UrshAssignExpr, MulAssignExpr, DivAssignExpr, + * ModAssignExpr, PowAssignExpr (AssignmentNode) + * left: target of assignment + * right: value to assign + * ConditionalExpr (ConditionalExpression) + * (cond ? thenExpr : elseExpr) + * kid1: cond + * kid2: thenExpr + * kid3: elseExpr + * CoalesceExpr, OrExpr, AndExpr, BitOrExpr, BitXorExpr, + * BitAndExpr, StrictEqExpr, EqExpr, StrictNeExpr, NeExpr, LtExpr, LeExpr, + * GtExpr, GeExpr, InstanceOfExpr, InExpr, LshExpr, RshExpr, UrshExpr, AddExpr, + * SubExpr, MulExpr, DivExpr, ModExpr, PowExpr (ListNode) + * head: list of N subexpressions + * All of these operators are left-associative except Pow which is + * right-associative, but still forms a list (see comments in + * ParseNode::appendOrCreateList). + * count: N >= 2 + * PosExpr, NegExpr, VoidExpr, NotExpr, BitNotExpr, TypeOfNameExpr, + * TypeOfExpr (UnaryNode) + * kid: unary expr + * PreIncrementExpr, PostIncrementExpr, PreDecrementExpr, + * PostDecrementExpr (UnaryNode) + * kid: member expr + * NewExpr (BinaryNode) + * left: ctor expression on the left of the '(' + * right: Arguments + * DecoratorList (ListNode) + * head: list of N nodes, each item is one of: + * * NameNode (DecoratorMemberExpression) + * * CallNode (DecoratorCallExpression) + * * Node (DecoratorParenthesizedExpression) + * count: N > 0 + * DeleteNameExpr, DeletePropExpr, DeleteElemExpr, DeleteExpr (UnaryNode) + * kid: expression that's evaluated, then the overall delete evaluates to + * true; can't be a kind for a more-specific ParseNodeKind::Delete* + * unless constant folding (or a similar parse tree manipulation) has + * occurred + * * DeleteNameExpr: Name expr + * * DeletePropExpr: Dot expr + * * DeleteElemExpr: Elem expr + * * DeleteOptionalChainExpr: Member expr + * * DeleteExpr: Member expr + * DeleteOptionalChainExpr (UnaryNode) + * kid: expression that's evaluated, then the overall delete evaluates to + * true; If constant folding occurs, Elem expr may become Dot expr. + * OptionalElemExpr does not get folded into OptionalDot. + * OptionalChain (UnaryNode) + * kid: expression that is evaluated as a chain. An Optional chain contains + * one or more optional nodes. It's first node (kid) is always an + * optional node, for example: an OptionalElemExpr, OptionalDotExpr, or + * OptionalCall. An OptionalChain will shortcircuit and return + * Undefined without evaluating the rest of the expression if any of the + * optional nodes it contains are nullish. An optionalChain also can + * contain nodes such as DotExpr, ElemExpr, NameExpr CallExpr, etc. + * These are evaluated normally. + * * OptionalDotExpr: Dot expr with jump + * * OptionalElemExpr: Elem expr with jump + * * OptionalCallExpr: Call expr with jump + * * DotExpr: Dot expr without jump + * * ElemExpr: Elem expr without jump + * * CallExpr: Call expr without jump + * PropertyNameExpr (NameNode) + * atom: property name being accessed + * privateNameKind: kind of the name if private + * DotExpr (PropertyAccess) + * left: MEMBER expr to left of '.' + * right: PropertyName to right of '.' + * OptionalDotExpr (OptionalPropertyAccess) + * left: MEMBER expr to left of '.', short circuits back to OptionalChain + * if nullish. + * right: PropertyName to right of '.' + * ElemExpr (PropertyByValue) + * left: MEMBER expr to left of '[' + * right: expr between '[' and ']' + * OptionalElemExpr (OptionalPropertyByValue) + * left: MEMBER expr to left of '[', short circuits back to OptionalChain + * if nullish. + * right: expr between '[' and ']' + * CallExpr (BinaryNode) + * left: callee expression on the left of the '(' + * right: Arguments + * OptionalCallExpr (BinaryNode) + * left: callee expression on the left of the '(', short circuits back to + * OptionalChain if nullish. + * right: Arguments + * Arguments (ListNode) + * head: list of arg1, arg2, ... argN + * count: N >= 0 + * ArrayExpr (ListNode) + * head: list of N array element expressions + * holes ([,,]) are represented by Elision nodes, + * spread elements ([...X]) are represented by Spread nodes + * count: N >= 0 + * ObjectExpr (ListNode) + * head: list of N nodes, each item is one of: + * * MutateProto + * * PropertyDefinition + * * Shorthand + * * Spread + * count: N >= 0 + * PropertyDefinition (PropertyDefinition) + * key-value pair in object initializer or destructuring lhs + * left: property id + * right: value + * Shorthand (BinaryNode) + * Same fields as PropertyDefinition. This is used for object literal + * properties using shorthand ({x}). + * ComputedName (UnaryNode) + * ES6 ComputedPropertyName. + * kid: the AssignmentExpression inside the square brackets + * Name (NameNode) + * atom: name, or object atom + * StringExpr (NameNode) + * atom: string + * TemplateStringListExpr (ListNode) + * head: list of alternating expr and template strings + * TemplateString [, expression, TemplateString]+ + * there's at least one expression. If the template literal contains + * no ${}-delimited expression, it's parsed as a single TemplateString + * TemplateStringExpr (NameNode) + * atom: template string atom + * TaggedTemplateExpr (BinaryNode) + * left: tag expression + * right: Arguments, with the first being the call site object, then + * arg1, arg2, ... argN + * CallSiteObj (CallSiteNode) + * head: an Array of raw TemplateString, then corresponding cooked + * TemplateString nodes + * Array [, cooked TemplateString]+ + * where the Array is + * [raw TemplateString]+ + * RegExpExpr (RegExpLiteral) + * regexp: RegExp model object + * NumberExpr (NumericLiteral) + * value: double value of numeric literal + * BigIntExpr (BigIntLiteral) + * stencil: script compilation struct that has |bigIntData| vector + * index: index into the script compilation's |bigIntData| vector + * TrueExpr, FalseExpr (BooleanLiteral) + * NullExpr (NullLiteral) + * RawUndefinedExpr (RawUndefinedLiteral) + * + * ThisExpr (UnaryNode) + * kid: '.this' Name if function `this`, else nullptr + * SuperBase (UnaryNode) + * kid: '.this' Name + * SuperCallExpr (BinaryNode) + * left: SuperBase + * right: Arguments + * SetThis (BinaryNode) + * left: '.this' Name + * right: SuperCall + * + * LexicalScope (LexicalScopeNode) + * scopeBindings: scope bindings + * scopeBody: scope body + * Generator (NullaryNode) + * InitialYield (UnaryNode) + * kid: generator object + * YieldExpr, YieldStarExpr, AwaitExpr (UnaryNode) + * kid: expr or null + */ + +// FIXME: Remove `*Type` (bug 1489008) +#define FOR_EACH_PARSENODE_SUBCLASS(MACRO) \ + MACRO(BinaryNode, BinaryNodeType, asBinary) \ + MACRO(AssignmentNode, AssignmentNodeType, asAssignment) \ + MACRO(CaseClause, CaseClauseType, asCaseClause) \ + MACRO(ClassMethod, ClassMethodType, asClassMethod) \ + MACRO(ClassField, ClassFieldType, asClassField) \ + MACRO(StaticClassBlock, StaticClassBlockType, asStaticClassBlock) \ + MACRO(PropertyDefinition, PropertyDefinitionType, asPropertyDefinition) \ + MACRO(ClassNames, ClassNamesType, asClassNames) \ + MACRO(ForNode, ForNodeType, asFor) \ + MACRO(PropertyAccess, PropertyAccessType, asPropertyAccess) \ + MACRO(OptionalPropertyAccess, OptionalPropertyAccessType, \ + asOptionalPropertyAccess) \ + MACRO(PropertyByValue, PropertyByValueType, asPropertyByValue) \ + MACRO(OptionalPropertyByValue, OptionalPropertyByValueType, \ + asOptionalPropertyByValue) \ + MACRO(PrivateMemberAccess, PrivateMemberAccessType, asPrivateMemberAccess) \ + MACRO(OptionalPrivateMemberAccess, OptionalPrivateMemberAccessType, \ + asOptionalPrivateMemberAccess) \ + MACRO(NewTargetNode, NewTargetNodeType, asNewTargetNode) \ + MACRO(SwitchStatement, SwitchStatementType, asSwitchStatement) \ + MACRO(DeclarationListNode, DeclarationListNodeType, asDeclarationList) \ + \ + MACRO(ParamsBodyNode, ParamsBodyNodeType, asParamsBody) \ + MACRO(FunctionNode, FunctionNodeType, asFunction) \ + MACRO(ModuleNode, ModuleNodeType, asModule) \ + \ + MACRO(LexicalScopeNode, LexicalScopeNodeType, asLexicalScope) \ + MACRO(ClassBodyScopeNode, ClassBodyScopeNodeType, asClassBodyScope) \ + \ + MACRO(ListNode, ListNodeType, asList) \ + MACRO(CallSiteNode, CallSiteNodeType, asCallSite) \ + MACRO(CallNode, CallNodeType, asCallNode) \ + MACRO(CallNode, OptionalCallNodeType, asOptionalCallNode) \ + \ + MACRO(LoopControlStatement, LoopControlStatementType, \ + asLoopControlStatement) \ + MACRO(BreakStatement, BreakStatementType, asBreakStatement) \ + MACRO(ContinueStatement, ContinueStatementType, asContinueStatement) \ + \ + MACRO(NameNode, NameNodeType, asName) \ + MACRO(LabeledStatement, LabeledStatementType, asLabeledStatement) \ + \ + MACRO(NullaryNode, NullaryNodeType, asNullary) \ + MACRO(BooleanLiteral, BooleanLiteralType, asBooleanLiteral) \ + MACRO(DebuggerStatement, DebuggerStatementType, asDebuggerStatement) \ + MACRO(NullLiteral, NullLiteralType, asNullLiteral) \ + MACRO(RawUndefinedLiteral, RawUndefinedLiteralType, asRawUndefinedLiteral) \ + \ + MACRO(NumericLiteral, NumericLiteralType, asNumericLiteral) \ + MACRO(BigIntLiteral, BigIntLiteralType, asBigIntLiteral) \ + \ + MACRO(RegExpLiteral, RegExpLiteralType, asRegExpLiteral) \ + \ + MACRO(TernaryNode, TernaryNodeType, asTernary) \ + MACRO(ClassNode, ClassNodeType, asClass) \ + MACRO(ConditionalExpression, ConditionalExpressionType, \ + asConditionalExpression) \ + MACRO(TryNode, TryNodeType, asTry) \ + \ + MACRO(UnaryNode, UnaryNodeType, asUnary) \ + MACRO(ThisLiteral, ThisLiteralType, asThisLiteral) + +#define DECLARE_CLASS(typeName, longTypeName, asMethodName) class typeName; +FOR_EACH_PARSENODE_SUBCLASS(DECLARE_CLASS) +#undef DECLARE_CLASS + +enum class AccessorType { None, Getter, Setter }; + +static inline bool IsConstructorKind(FunctionSyntaxKind kind) { + return kind == FunctionSyntaxKind::ClassConstructor || + kind == FunctionSyntaxKind::DerivedClassConstructor; +} + +static inline bool IsMethodDefinitionKind(FunctionSyntaxKind kind) { + return IsConstructorKind(kind) || kind == FunctionSyntaxKind::Method || + kind == FunctionSyntaxKind::FieldInitializer || + kind == FunctionSyntaxKind::Getter || + kind == FunctionSyntaxKind::Setter; +} + +// To help diagnose sporadic crashes in the frontend, a few assertions are +// enabled in early beta builds. (Most are not; those still use MOZ_ASSERT.) +// See bug 1547561. +#if defined(EARLY_BETA_OR_EARLIER) +# define JS_PARSE_NODE_ASSERT MOZ_RELEASE_ASSERT +#else +# define JS_PARSE_NODE_ASSERT MOZ_ASSERT +#endif + +class ParseNode { + const ParseNodeKind pn_type; /* ParseNodeKind::PNK_* type */ + + bool pn_parens : 1; /* this expr was enclosed in parens */ + bool pn_rhs_anon_fun : 1; /* this expr is anonymous function or class that + * is a direct RHS of ParseNodeKind::Assign or + * ParseNodeKind::PropertyDefinition of property, + * that needs SetFunctionName. */ + + protected: + // Used by ComputedName to indicate if the ComputedName is a + // a synthetic construct. This allows us to avoid needing to + // compute ToString on uncommon property values such as BigInt. + // Instead we parse as though they were computed names. + // + // We need this bit to distinguish a synthetic computed name like + // this however to undo this transformation in Reflect.parse and + // name guessing. + bool pn_synthetic_computed : 1; + + ParseNode(const ParseNode& other) = delete; + void operator=(const ParseNode& other) = delete; + + public: + explicit ParseNode(ParseNodeKind kind) + : pn_type(kind), + pn_parens(false), + pn_rhs_anon_fun(false), + pn_synthetic_computed(false), + pn_pos(0, 0), + pn_next(nullptr) { + JS_PARSE_NODE_ASSERT(ParseNodeKind::Start <= kind); + JS_PARSE_NODE_ASSERT(kind < ParseNodeKind::Limit); + } + + ParseNode(ParseNodeKind kind, const TokenPos& pos) + : pn_type(kind), + pn_parens(false), + pn_rhs_anon_fun(false), + pn_synthetic_computed(false), + pn_pos(pos), + pn_next(nullptr) { + JS_PARSE_NODE_ASSERT(ParseNodeKind::Start <= kind); + JS_PARSE_NODE_ASSERT(kind < ParseNodeKind::Limit); + } + + ParseNodeKind getKind() const { + JS_PARSE_NODE_ASSERT(ParseNodeKind::Start <= pn_type); + JS_PARSE_NODE_ASSERT(pn_type < ParseNodeKind::Limit); + return pn_type; + } + bool isKind(ParseNodeKind kind) const { return getKind() == kind; } + + protected: + size_t getKindAsIndex() const { + return size_t(getKind()) - size_t(ParseNodeKind::Start); + } + + // Used to implement test() on a few ParseNodes efficiently. + // (This enum doesn't fully reflect the ParseNode class hierarchy, + // so don't use it for anything else.) + enum class TypeCode : uint8_t { + Nullary, + Unary, + Binary, + Ternary, + List, + Name, + Other + }; + + // typeCodeTable[getKindAsIndex()] is the type code of a ParseNode of kind + // pnk. + static const TypeCode typeCodeTable[]; + + private: +#ifdef DEBUG + static const size_t sizeTable[]; +#endif + + public: + TypeCode typeCode() const { return typeCodeTable[getKindAsIndex()]; } + + bool isBinaryOperation() const { + ParseNodeKind kind = getKind(); + return ParseNodeKind::BinOpFirst <= kind && + kind <= ParseNodeKind::BinOpLast; + } + inline bool isName(TaggedParserAtomIndex name) const; + + /* Boolean attributes. */ + bool isInParens() const { return pn_parens; } + bool isLikelyIIFE() const { return isInParens(); } + void setInParens(bool enabled) { pn_parens = enabled; } + + bool isDirectRHSAnonFunction() const { return pn_rhs_anon_fun; } + void setDirectRHSAnonFunction(bool enabled) { pn_rhs_anon_fun = enabled; } + + TokenPos pn_pos; /* two 16-bit pairs here, for 64 bits */ + ParseNode* pn_next; /* intrinsic link in parent PN_LIST */ + + public: + /* + * 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); + + /* True if pn is a parsenode representing a literal constant. */ + bool isLiteral() const { + return isKind(ParseNodeKind::NumberExpr) || + isKind(ParseNodeKind::BigIntExpr) || + isKind(ParseNodeKind::StringExpr) || + isKind(ParseNodeKind::TrueExpr) || + isKind(ParseNodeKind::FalseExpr) || + isKind(ParseNodeKind::NullExpr) || + isKind(ParseNodeKind::RawUndefinedExpr); + } + + inline bool isConstant(); + + template + inline bool is() const { + return NodeType::test(*this); + } + + /* Casting operations. */ + template + inline NodeType& as() { + MOZ_ASSERT(NodeType::test(*this)); + return *static_cast(this); + } + + template + inline const NodeType& as() const { + MOZ_ASSERT(NodeType::test(*this)); + return *static_cast(this); + } + +#ifdef DEBUG + // Debugger-friendly stderr printer. + void dump(); + void dump(const ParserAtomsTable* parserAtoms); + void dump(const ParserAtomsTable* parserAtoms, GenericPrinter& out); + void dump(const ParserAtomsTable* parserAtoms, GenericPrinter& out, + int indent); + + // The size of this node, in bytes. + size_t size() const { return sizeTable[getKindAsIndex()]; } +#endif +}; + +// Remove a ParseNode, **pnp, from a parse tree, putting another ParseNode, +// *pn, in its place. +// +// pnp points to a ParseNode pointer. This must be the only pointer that points +// to the parse node being replaced. The replacement, *pn, is unchanged except +// for its pn_next pointer; updating that is necessary if *pn's new parent is a +// list node. +inline void ReplaceNode(ParseNode** pnp, ParseNode* pn) { + pn->pn_next = (*pnp)->pn_next; + *pnp = pn; +} + +class NullaryNode : public ParseNode { + public: + NullaryNode(ParseNodeKind kind, const TokenPos& pos) : ParseNode(kind, pos) { + MOZ_ASSERT(is()); + } + + static bool test(const ParseNode& node) { + return node.typeCode() == TypeCode::Nullary; + } + + static constexpr TypeCode classTypeCode() { return TypeCode::Nullary; } + + template + bool accept(Visitor& visitor) { + return true; + } + +#ifdef DEBUG + void dumpImpl(const ParserAtomsTable* parserAtoms, GenericPrinter& out, + int indent); +#endif +}; + +class NameNode : public ParseNode { + TaggedParserAtomIndex atom_; /* lexical name or label atom */ + PrivateNameKind privateNameKind_ = PrivateNameKind::None; + + public: + NameNode(ParseNodeKind kind, TaggedParserAtomIndex atom, const TokenPos& pos) + : ParseNode(kind, pos), atom_(atom) { + MOZ_ASSERT(atom); + MOZ_ASSERT(is()); + } + + static bool test(const ParseNode& node) { + return node.typeCode() == TypeCode::Name; + } + + static constexpr TypeCode classTypeCode() { return TypeCode::Name; } + + template + bool accept(Visitor& visitor) { + return true; + } + +#ifdef DEBUG + void dumpImpl(const ParserAtomsTable* parserAtoms, GenericPrinter& out, + int indent); +#endif + + TaggedParserAtomIndex atom() const { return atom_; } + + TaggedParserAtomIndex name() const { + MOZ_ASSERT(isKind(ParseNodeKind::Name) || + isKind(ParseNodeKind::PrivateName)); + return atom_; + } + + void setAtom(TaggedParserAtomIndex atom) { atom_ = atom; } + + void setPrivateNameKind(PrivateNameKind privateNameKind) { + privateNameKind_ = privateNameKind; + } + + PrivateNameKind privateNameKind() { return privateNameKind_; } +}; + +inline bool ParseNode::isName(TaggedParserAtomIndex name) const { + return getKind() == ParseNodeKind::Name && as().name() == name; +} + +class UnaryNode : public ParseNode { + ParseNode* kid_; + + public: + UnaryNode(ParseNodeKind kind, const TokenPos& pos, ParseNode* kid) + : ParseNode(kind, pos), kid_(kid) { + MOZ_ASSERT(is()); + } + + static bool test(const ParseNode& node) { + return node.typeCode() == TypeCode::Unary; + } + + static constexpr TypeCode classTypeCode() { return TypeCode::Unary; } + + template + bool accept(Visitor& visitor) { + if (kid_) { + if (!visitor.visit(kid_)) { + return false; + } + } + return true; + } + +#ifdef DEBUG + void dumpImpl(const ParserAtomsTable* parserAtoms, GenericPrinter& out, + int indent); +#endif + + ParseNode* kid() const { return kid_; } + + /* + * Non-null if this is a statement node which could be a member of a + * Directive Prologue: an expression statement consisting of a single + * string literal. + * + * This considers only the node and its children, not its context. After + * parsing, check the node's prologue flag to see if it is indeed part of + * a directive prologue. + * + * Note that a Directive Prologue can contain statements that cannot + * themselves be directives (string literals that include escape sequences + * or escaped newlines, say). This member function returns true for such + * nodes; we use it to determine the extent of the prologue. + */ + TaggedParserAtomIndex isStringExprStatement() const { + if (isKind(ParseNodeKind::ExpressionStmt)) { + if (kid()->isKind(ParseNodeKind::StringExpr) && !kid()->isInParens()) { + return kid()->as().atom(); + } + } + return TaggedParserAtomIndex::null(); + } + + // Methods used by FoldConstants.cpp. + ParseNode** unsafeKidReference() { return &kid_; } + + void setSyntheticComputedName() { pn_synthetic_computed = true; } + bool isSyntheticComputedName() { + MOZ_ASSERT(isKind(ParseNodeKind::ComputedName)); + return pn_synthetic_computed; + } +}; + +class BinaryNode : public ParseNode { + ParseNode* left_; + ParseNode* right_; + + public: + BinaryNode(ParseNodeKind kind, const TokenPos& pos, ParseNode* left, + ParseNode* right) + : ParseNode(kind, pos), left_(left), right_(right) { + MOZ_ASSERT(is()); + } + + BinaryNode(ParseNodeKind kind, ParseNode* left, ParseNode* right) + : ParseNode(kind, TokenPos::box(left->pn_pos, right->pn_pos)), + left_(left), + right_(right) { + MOZ_ASSERT(is()); + } + + static bool test(const ParseNode& node) { + return node.typeCode() == TypeCode::Binary; + } + + static constexpr TypeCode classTypeCode() { return TypeCode::Binary; } + + template + bool accept(Visitor& visitor) { + if (left_) { + if (!visitor.visit(left_)) { + return false; + } + } + if (right_) { + if (!visitor.visit(right_)) { + return false; + } + } + return true; + } + +#ifdef DEBUG + void dumpImpl(const ParserAtomsTable* parserAtoms, GenericPrinter& out, + int indent); +#endif + + ParseNode* left() const { return left_; } + + ParseNode* right() const { return right_; } + + // Methods used by FoldConstants.cpp. + // callers are responsible for keeping the list consistent. + ParseNode** unsafeLeftReference() { return &left_; } + + ParseNode** unsafeRightReference() { return &right_; } +}; + +class AssignmentNode : public BinaryNode { + public: + AssignmentNode(ParseNodeKind kind, ParseNode* left, ParseNode* right) + : BinaryNode(kind, TokenPos(left->pn_pos.begin, right->pn_pos.end), left, + right) { + MOZ_ASSERT(is()); + } + + static bool test(const ParseNode& node) { + ParseNodeKind kind = node.getKind(); + bool match = ParseNodeKind::AssignmentStart <= kind && + kind <= ParseNodeKind::AssignmentLast; + MOZ_ASSERT_IF(match, node.is()); + return match; + } +}; + +class ForNode : public BinaryNode { + unsigned iflags_; /* JSITER_* flags */ + + public: + ForNode(const TokenPos& pos, ParseNode* forHead, ParseNode* body, + unsigned iflags) + : BinaryNode(ParseNodeKind::ForStmt, pos, forHead, body), + iflags_(iflags) { + MOZ_ASSERT(forHead->isKind(ParseNodeKind::ForIn) || + forHead->isKind(ParseNodeKind::ForOf) || + forHead->isKind(ParseNodeKind::ForHead)); + } + + static bool test(const ParseNode& node) { + bool match = node.isKind(ParseNodeKind::ForStmt); + MOZ_ASSERT_IF(match, node.is()); + return match; + } + + TernaryNode* head() const { return &left()->as(); } + + ParseNode* body() const { return right(); } + + unsigned iflags() const { return iflags_; } +}; + +class TernaryNode : public ParseNode { + ParseNode* kid1_; /* condition, discriminant, etc. */ + ParseNode* kid2_; /* then-part, case list, etc. */ + ParseNode* kid3_; /* else-part, default case, etc. */ + + public: + TernaryNode(ParseNodeKind kind, ParseNode* kid1, ParseNode* kid2, + ParseNode* kid3) + : TernaryNode(kind, kid1, kid2, kid3, + TokenPos((kid1 ? kid1 + : kid2 ? kid2 + : kid3) + ->pn_pos.begin, + (kid3 ? kid3 + : kid2 ? kid2 + : kid1) + ->pn_pos.end)) {} + + TernaryNode(ParseNodeKind kind, ParseNode* kid1, ParseNode* kid2, + ParseNode* kid3, const TokenPos& pos) + : ParseNode(kind, pos), kid1_(kid1), kid2_(kid2), kid3_(kid3) { + MOZ_ASSERT(is()); + } + + static bool test(const ParseNode& node) { + return node.typeCode() == TypeCode::Ternary; + } + + static constexpr TypeCode classTypeCode() { return TypeCode::Ternary; } + + template + bool accept(Visitor& visitor) { + if (kid1_) { + if (!visitor.visit(kid1_)) { + return false; + } + } + if (kid2_) { + if (!visitor.visit(kid2_)) { + return false; + } + } + if (kid3_) { + if (!visitor.visit(kid3_)) { + return false; + } + } + return true; + } + +#ifdef DEBUG + void dumpImpl(const ParserAtomsTable* parserAtoms, GenericPrinter& out, + int indent); +#endif + + ParseNode* kid1() const { return kid1_; } + + ParseNode* kid2() const { return kid2_; } + + ParseNode* kid3() const { return kid3_; } + + // Methods used by FoldConstants.cpp. + ParseNode** unsafeKid1Reference() { return &kid1_; } + + ParseNode** unsafeKid2Reference() { return &kid2_; } + + ParseNode** unsafeKid3Reference() { return &kid3_; } +}; + +class ListNode : public ParseNode { + ParseNode* head_; /* first node in list */ + ParseNode** tail_; /* ptr to last node's pn_next in list */ + uint32_t count_; /* number of nodes in list */ + uint32_t xflags; + + private: + // xflags bits. + + // Statement list has top-level function statements. + static constexpr uint32_t hasTopLevelFunctionDeclarationsBit = Bit(0); + + // Array/Object/Class initializer has non-constants. + // * array has holes + // * array has spread node + // * array has element which is known not to be constant + // * array has no element + // * object/class has __proto__ + // * object/class has property which is known not to be constant + // * object/class shorthand property + // * object/class spread property + // * object/class has method + // * object/class has computed property + static constexpr uint32_t hasNonConstInitializerBit = Bit(1); + + // Flag set by the emitter after emitting top-level function statements. + static constexpr uint32_t emittedTopLevelFunctionDeclarationsBit = Bit(2); + + public: + ListNode(ParseNodeKind kind, const TokenPos& pos) + : ParseNode(kind, pos), + head_(nullptr), + tail_(&head_), + count_(0), + xflags(0) { + MOZ_ASSERT(is()); + } + + ListNode(ParseNodeKind kind, ParseNode* kid) + : ParseNode(kind, kid->pn_pos), + head_(kid), + tail_(&kid->pn_next), + count_(1), + xflags(0) { + if (kid->pn_pos.begin < pn_pos.begin) { + pn_pos.begin = kid->pn_pos.begin; + } + pn_pos.end = kid->pn_pos.end; + + MOZ_ASSERT(is()); + } + + static bool test(const ParseNode& node) { + return node.typeCode() == TypeCode::List; + } + + static constexpr TypeCode classTypeCode() { return TypeCode::List; } + + template + bool accept(Visitor& visitor) { + ParseNode** listp = &head_; + for (; *listp; listp = &(*listp)->pn_next) { + // Don't use PN*& because we want to check if it changed, so we can use + // ReplaceNode + ParseNode* pn = *listp; + if (!visitor.visit(pn)) { + return false; + } + if (pn != *listp) { + ReplaceNode(listp, pn); + } + } + unsafeReplaceTail(listp); + return true; + } + +#ifdef DEBUG + void dumpImpl(const ParserAtomsTable* parserAtoms, GenericPrinter& out, + int indent); +#endif + + ParseNode* head() const { return head_; } + + ParseNode** tail() const { return tail_; } + + uint32_t count() const { return count_; } + + bool empty() const { return count() == 0; } + + void checkConsistency() const +#ifndef DEBUG + {} +#endif + ; + + [[nodiscard]] bool hasTopLevelFunctionDeclarations() const { + MOZ_ASSERT(isKind(ParseNodeKind::StatementList)); + return xflags & hasTopLevelFunctionDeclarationsBit; + } + + [[nodiscard]] bool emittedTopLevelFunctionDeclarations() const { + MOZ_ASSERT(isKind(ParseNodeKind::StatementList)); + MOZ_ASSERT(hasTopLevelFunctionDeclarations()); + return xflags & emittedTopLevelFunctionDeclarationsBit; + } + + [[nodiscard]] bool hasNonConstInitializer() const { + MOZ_ASSERT(isKind(ParseNodeKind::ArrayExpr) || + isKind(ParseNodeKind::ObjectExpr)); + return xflags & hasNonConstInitializerBit; + } + + void setHasTopLevelFunctionDeclarations() { + MOZ_ASSERT(isKind(ParseNodeKind::StatementList)); + xflags |= hasTopLevelFunctionDeclarationsBit; + } + + void setEmittedTopLevelFunctionDeclarations() { + MOZ_ASSERT(isKind(ParseNodeKind::StatementList)); + MOZ_ASSERT(hasTopLevelFunctionDeclarations()); + xflags |= emittedTopLevelFunctionDeclarationsBit; + } + + void setHasNonConstInitializer() { + MOZ_ASSERT(isKind(ParseNodeKind::ArrayExpr) || + isKind(ParseNodeKind::ObjectExpr) || + IF_RECORD_TUPLE(isKind(ParseNodeKind::TupleExpr), false) || + IF_RECORD_TUPLE(isKind(ParseNodeKind::RecordExpr), false)); + xflags |= hasNonConstInitializerBit; + } + + void unsetHasNonConstInitializer() { + MOZ_ASSERT(isKind(ParseNodeKind::ArrayExpr) || + isKind(ParseNodeKind::ObjectExpr) || + IF_RECORD_TUPLE(isKind(ParseNodeKind::TupleExpr), false) || + IF_RECORD_TUPLE(isKind(ParseNodeKind::RecordExpr), false)); + xflags &= ~hasNonConstInitializerBit; + } + + /* + * Compute a pointer to the last element in a singly-linked list. NB: list + * must be non-empty -- this is asserted! + */ + ParseNode* last() const { + MOZ_ASSERT(!empty()); + // + // ParseNode ParseNode + // +-----+---------+-----+ +-----+---------+-----+ + // | ... | pn_next | ... | +-...->| ... | pn_next | ... | + // +-----+---------+-----+ | +-----+---------+-----+ + // ^ | | ^ ^ + // | +---------------+ | | + // | | tail() + // | | + // head() last() + // + return (ParseNode*)(uintptr_t(tail()) - offsetof(ParseNode, pn_next)); + } + + void replaceLast(ParseNode* node) { + MOZ_ASSERT(!empty()); + pn_pos.end = node->pn_pos.end; + + ParseNode* item = head(); + ParseNode* lastNode = last(); + MOZ_ASSERT(item); + if (item == lastNode) { + head_ = node; + } else { + while (item->pn_next != lastNode) { + MOZ_ASSERT(item->pn_next); + item = item->pn_next; + } + item->pn_next = node; + } + tail_ = &node->pn_next; + } + + void append(ParseNode* item) { + MOZ_ASSERT(item->pn_pos.begin >= pn_pos.begin); + pn_pos.end = item->pn_pos.end; + *tail_ = item; + tail_ = &item->pn_next; + count_++; + } + + void prepend(ParseNode* item) { + item->pn_next = head_; + head_ = item; + if (tail_ == &head_) { + tail_ = &item->pn_next; + } + count_++; + } + + // Methods used by FoldConstants.cpp. + // Caller is responsible for keeping the list consistent. + ParseNode** unsafeHeadReference() { return &head_; } + + void unsafeReplaceTail(ParseNode** newTail) { + tail_ = newTail; + checkConsistency(); + } + + void unsafeDecrementCount() { + MOZ_ASSERT(count() > 1); + count_--; + } + + private: + // Classes to iterate over ListNode contents: + // + // Usage: + // ListNode* list; + // for (ParseNode* item : list->contents()) { + // // item is ParseNode* typed. + // } + class iterator { + private: + ParseNode* node_; + + friend class ListNode; + explicit iterator(ParseNode* node) : node_(node) {} + + public: + // Implement std::iterator_traits. + using iterator_category = std::input_iterator_tag; + using value_type = ParseNode*; + using difference_type = ptrdiff_t; + using pointer = ParseNode**; + using reference = ParseNode*&; + + bool operator==(const iterator& other) const { + return node_ == other.node_; + } + + bool operator!=(const iterator& other) const { return !(*this == other); } + + iterator& operator++() { + node_ = node_->pn_next; + return *this; + } + + ParseNode* operator*() { return node_; } + + const ParseNode* operator*() const { return node_; } + }; + + class range { + private: + ParseNode* begin_; + ParseNode* end_; + + friend class ListNode; + range(ParseNode* begin, ParseNode* end) : begin_(begin), end_(end) {} + + public: + iterator begin() { return iterator(begin_); } + + iterator end() { return iterator(end_); } + + const iterator begin() const { return iterator(begin_); } + + const iterator end() const { return iterator(end_); } + + const iterator cbegin() const { return begin(); } + + const iterator cend() const { return end(); } + }; + +#ifdef DEBUG + [[nodiscard]] bool contains(ParseNode* target) const { + MOZ_ASSERT(target); + for (ParseNode* node : contents()) { + if (target == node) { + return true; + } + } + return false; + } +#endif + + public: + range contents() { return range(head(), nullptr); } + + const range contents() const { return range(head(), nullptr); } + + range contentsFrom(ParseNode* begin) { + MOZ_ASSERT_IF(begin, contains(begin)); + return range(begin, nullptr); + } + + const range contentsFrom(ParseNode* begin) const { + MOZ_ASSERT_IF(begin, contains(begin)); + return range(begin, nullptr); + } + + range contentsTo(ParseNode* end) { + MOZ_ASSERT_IF(end, contains(end)); + return range(head(), end); + } + + const range contentsTo(ParseNode* end) const { + MOZ_ASSERT_IF(end, contains(end)); + return range(head(), end); + } +}; + +class DeclarationListNode : public ListNode { + public: + DeclarationListNode(ParseNodeKind kind, const TokenPos& pos) + : ListNode(kind, pos) { + MOZ_ASSERT(is()); + } + + static bool test(const ParseNode& node) { + bool match = node.isKind(ParseNodeKind::VarStmt) || + node.isKind(ParseNodeKind::LetDecl) || + node.isKind(ParseNodeKind::ConstDecl); + MOZ_ASSERT_IF(match, node.is()); + return match; + } + + auto* singleBinding() const { + MOZ_ASSERT(count() == 1); + return head(); + } +}; + +class ParamsBodyNode : public ListNode { + public: + explicit ParamsBodyNode(const TokenPos& pos) + : ListNode(ParseNodeKind::ParamsBody, pos) { + MOZ_ASSERT(is()); + } + + static bool test(const ParseNode& node) { + bool match = node.isKind(ParseNodeKind::ParamsBody); + MOZ_ASSERT_IF(match, node.is()); + return match; + } + + auto parameters() const { + MOZ_ASSERT(last()->is()); + return contentsTo(last()); + } + + auto* body() const { + MOZ_ASSERT(last()->is()); + return &last()->as(); + } +}; + +class FunctionNode : public ParseNode { + FunctionBox* funbox_; + ParseNode* body_; + FunctionSyntaxKind syntaxKind_; + + public: + FunctionNode(FunctionSyntaxKind syntaxKind, const TokenPos& pos) + : ParseNode(ParseNodeKind::Function, pos), + funbox_(nullptr), + body_(nullptr), + syntaxKind_(syntaxKind) { + MOZ_ASSERT(!body_); + MOZ_ASSERT(!funbox_); + MOZ_ASSERT(is()); + } + + static bool test(const ParseNode& node) { + return node.isKind(ParseNodeKind::Function); + } + + static constexpr TypeCode classTypeCode() { return TypeCode::Other; } + + template + bool accept(Visitor& visitor) { + // Note: body is null for lazily-parsed functions. + if (body_) { + if (!visitor.visit(body_)) { + return false; + } + MOZ_ASSERT(body_->is()); + } + return true; + } + +#ifdef DEBUG + void dumpImpl(const ParserAtomsTable* parserAtoms, GenericPrinter& out, + int indent); +#endif + + FunctionBox* funbox() const { return funbox_; } + + ParamsBodyNode* body() const { + return body_ ? &body_->as() : nullptr; + } + + void setFunbox(FunctionBox* funbox) { funbox_ = funbox; } + + void setBody(ParamsBodyNode* body) { body_ = body; } + + FunctionSyntaxKind syntaxKind() const { return syntaxKind_; } + + bool functionIsHoisted() const { + return syntaxKind() == FunctionSyntaxKind::Statement; + } +}; + +class ModuleNode : public ParseNode { + ParseNode* body_; + + public: + explicit ModuleNode(const TokenPos& pos) + : ParseNode(ParseNodeKind::Module, pos), body_(nullptr) { + MOZ_ASSERT(!body_); + MOZ_ASSERT(is()); + } + + static bool test(const ParseNode& node) { + return node.isKind(ParseNodeKind::Module); + } + + static constexpr TypeCode classTypeCode() { return TypeCode::Other; } + + template + bool accept(Visitor& visitor) { + return visitor.visit(body_); + } + +#ifdef DEBUG + void dumpImpl(const ParserAtomsTable* parserAtoms, GenericPrinter& out, + int indent); +#endif + + ListNode* body() const { return &body_->as(); } + + void setBody(ListNode* body) { body_ = body; } +}; + +class NumericLiteral : public ParseNode { + double value_; /* aligned numeric literal value */ + DecimalPoint decimalPoint_; /* Whether the number has a decimal point */ + + public: + NumericLiteral(double value, DecimalPoint decimalPoint, const TokenPos& pos) + : ParseNode(ParseNodeKind::NumberExpr, pos), + value_(value), + decimalPoint_(decimalPoint) {} + + static bool test(const ParseNode& node) { + return node.isKind(ParseNodeKind::NumberExpr); + } + + static constexpr TypeCode classTypeCode() { return TypeCode::Other; } + + template + bool accept(Visitor& visitor) { + return true; + } + +#ifdef DEBUG + void dumpImpl(const ParserAtomsTable* parserAtoms, GenericPrinter& out, + int indent); +#endif + + double value() const { return value_; } + + DecimalPoint decimalPoint() const { return decimalPoint_; } + + // Return the decimal string representation of this numeric literal. + TaggedParserAtomIndex toAtom(FrontendContext* fc, + ParserAtomsTable& parserAtoms) const; +}; + +class BigIntLiteral : public ParseNode { + BigIntIndex index_; + bool isZero_; + + public: + BigIntLiteral(BigIntIndex index, bool isZero, const TokenPos& pos) + : ParseNode(ParseNodeKind::BigIntExpr, pos), + index_(index), + isZero_(isZero) {} + + static bool test(const ParseNode& node) { + return node.isKind(ParseNodeKind::BigIntExpr); + } + + static constexpr TypeCode classTypeCode() { return TypeCode::Other; } + + template + bool accept(Visitor& visitor) { + return true; + } + +#ifdef DEBUG + void dumpImpl(const ParserAtomsTable* parserAtoms, GenericPrinter& out, + int indent); +#endif + + BigIntIndex index() { return index_; } + + bool isZero() const { return isZero_; } +}; + +template +class BaseScopeNode : public ParseNode { + using ParserData = typename ScopeType::ParserData; + ParserData* bindings; + ParseNode* body; + ScopeKind kind_; + + public: + BaseScopeNode(ParserData* bindings, ParseNode* body, + ScopeKind kind = ScopeKind::Lexical) + : ParseNode(NodeKind, body->pn_pos), + bindings(bindings), + body(body), + kind_(kind) {} + + static bool test(const ParseNode& node) { return node.isKind(NodeKind); } + + static constexpr TypeCode classTypeCode() { return TypeCode::Other; } + + template + bool accept(Visitor& visitor) { + return visitor.visit(body); + } + +#ifdef DEBUG + void dumpImpl(const ParserAtomsTable* parserAtoms, GenericPrinter& out, + int indent); +#endif + + ParserData* scopeBindings() const { + MOZ_ASSERT(!isEmptyScope()); + return bindings; + } + + ParseNode* scopeBody() const { return body; } + + void setScopeBody(ParseNode* body) { this->body = body; } + + bool isEmptyScope() const { return !bindings; } + + ScopeKind kind() const { return kind_; } +}; + +class LexicalScopeNode + : public BaseScopeNode { + public: + LexicalScopeNode(LexicalScope::ParserData* bindings, ParseNode* body, + ScopeKind kind = ScopeKind::Lexical) + : BaseScopeNode(bindings, body, kind) {} +}; + +class ClassBodyScopeNode + : public BaseScopeNode { + public: + ClassBodyScopeNode(ClassBodyScope::ParserData* bindings, ListNode* memberList) + : BaseScopeNode(bindings, memberList, ScopeKind::ClassBody) { + MOZ_ASSERT(memberList->isKind(ParseNodeKind::ClassMemberList)); + } + + ListNode* memberList() const { + ListNode* list = &scopeBody()->as(); + MOZ_ASSERT(list->isKind(ParseNodeKind::ClassMemberList)); + return list; + } +}; + +class LabeledStatement : public NameNode { + ParseNode* statement_; + + public: + LabeledStatement(TaggedParserAtomIndex label, ParseNode* stmt, uint32_t begin) + : NameNode(ParseNodeKind::LabelStmt, label, + TokenPos(begin, stmt->pn_pos.end)), + statement_(stmt) {} + + TaggedParserAtomIndex label() const { return atom(); } + + ParseNode* statement() const { return statement_; } + + static bool test(const ParseNode& node) { + return node.isKind(ParseNodeKind::LabelStmt); + } + + template + bool accept(Visitor& visitor) { + if (statement_) { + if (!visitor.visit(statement_)) { + return false; + } + } + return true; + } + +#ifdef DEBUG + void dumpImpl(const ParserAtomsTable* parserAtoms, GenericPrinter& out, + int indent); +#endif +}; + +// Inside a switch statement, a CaseClause is a case-label and the subsequent +// statements. The same node type is used for DefaultClauses. The only +// difference is that their caseExpression() is null. +class CaseClause : public BinaryNode { + public: + CaseClause(ParseNode* expr, ParseNode* stmts, uint32_t begin) + : BinaryNode(ParseNodeKind::Case, TokenPos(begin, stmts->pn_pos.end), + expr, stmts) {} + + ParseNode* caseExpression() const { return left(); } + + bool isDefault() const { return !caseExpression(); } + + ListNode* statementList() const { return &right()->as(); } + + static bool test(const ParseNode& node) { + bool match = node.isKind(ParseNodeKind::Case); + MOZ_ASSERT_IF(match, node.is()); + return match; + } +}; + +class LoopControlStatement : public ParseNode { + TaggedParserAtomIndex label_; /* target of break/continue statement */ + + protected: + LoopControlStatement(ParseNodeKind kind, TaggedParserAtomIndex label, + const TokenPos& pos) + : ParseNode(kind, pos), label_(label) { + MOZ_ASSERT(kind == ParseNodeKind::BreakStmt || + kind == ParseNodeKind::ContinueStmt); + MOZ_ASSERT(is()); + } + + public: + /* Label associated with this break/continue statement, if any. */ + TaggedParserAtomIndex label() const { return label_; } + +#ifdef DEBUG + void dumpImpl(const ParserAtomsTable* parserAtoms, GenericPrinter& out, + int indent); +#endif + + static bool test(const ParseNode& node) { + return node.isKind(ParseNodeKind::BreakStmt) || + node.isKind(ParseNodeKind::ContinueStmt); + } + + static constexpr TypeCode classTypeCode() { return TypeCode::Other; } + + template + bool accept(Visitor& visitor) { + return true; + } +}; + +class BreakStatement : public LoopControlStatement { + public: + BreakStatement(TaggedParserAtomIndex label, const TokenPos& pos) + : LoopControlStatement(ParseNodeKind::BreakStmt, label, pos) {} + + static bool test(const ParseNode& node) { + bool match = node.isKind(ParseNodeKind::BreakStmt); + MOZ_ASSERT_IF(match, node.is()); + return match; + } +}; + +class ContinueStatement : public LoopControlStatement { + public: + ContinueStatement(TaggedParserAtomIndex label, const TokenPos& pos) + : LoopControlStatement(ParseNodeKind::ContinueStmt, label, pos) {} + + static bool test(const ParseNode& node) { + bool match = node.isKind(ParseNodeKind::ContinueStmt); + MOZ_ASSERT_IF(match, node.is()); + return match; + } +}; + +class DebuggerStatement : public NullaryNode { + public: + explicit DebuggerStatement(const TokenPos& pos) + : NullaryNode(ParseNodeKind::DebuggerStmt, pos) {} + + static bool test(const ParseNode& node) { + bool match = node.isKind(ParseNodeKind::DebuggerStmt); + MOZ_ASSERT_IF(match, node.is()); + return match; + } +}; + +class ConditionalExpression : public TernaryNode { + public: + ConditionalExpression(ParseNode* condition, ParseNode* thenExpr, + ParseNode* elseExpr) + : TernaryNode(ParseNodeKind::ConditionalExpr, condition, thenExpr, + elseExpr, + TokenPos(condition->pn_pos.begin, elseExpr->pn_pos.end)) { + MOZ_ASSERT(condition); + MOZ_ASSERT(thenExpr); + MOZ_ASSERT(elseExpr); + } + + ParseNode& condition() const { return *kid1(); } + + ParseNode& thenExpression() const { return *kid2(); } + + ParseNode& elseExpression() const { return *kid3(); } + + static bool test(const ParseNode& node) { + bool match = node.isKind(ParseNodeKind::ConditionalExpr); + MOZ_ASSERT_IF(match, node.is()); + return match; + } +}; + +class TryNode : public TernaryNode { + public: + TryNode(uint32_t begin, ParseNode* body, LexicalScopeNode* catchScope, + ParseNode* finallyBlock) + : TernaryNode( + ParseNodeKind::TryStmt, body, catchScope, finallyBlock, + TokenPos(begin, + (finallyBlock ? finallyBlock : catchScope)->pn_pos.end)) { + MOZ_ASSERT(body); + MOZ_ASSERT(catchScope || finallyBlock); + } + + static bool test(const ParseNode& node) { + bool match = node.isKind(ParseNodeKind::TryStmt); + MOZ_ASSERT_IF(match, node.is()); + return match; + } + + ParseNode* body() const { return kid1(); } + + LexicalScopeNode* catchScope() const { + return kid2() ? &kid2()->as() : nullptr; + } + + ParseNode* finallyBlock() const { return kid3(); } +}; + +class ThisLiteral : public UnaryNode { + public: + ThisLiteral(const TokenPos& pos, ParseNode* thisName) + : UnaryNode(ParseNodeKind::ThisExpr, pos, thisName) {} + + static bool test(const ParseNode& node) { + bool match = node.isKind(ParseNodeKind::ThisExpr); + MOZ_ASSERT_IF(match, node.is()); + return match; + } +}; + +class NullLiteral : public NullaryNode { + public: + explicit NullLiteral(const TokenPos& pos) + : NullaryNode(ParseNodeKind::NullExpr, pos) {} + + static bool test(const ParseNode& node) { + bool match = node.isKind(ParseNodeKind::NullExpr); + MOZ_ASSERT_IF(match, node.is()); + return match; + } +}; + +// This is only used internally, currently just for tagged templates and the +// initial value of fields without initializers. It represents the value +// 'undefined' (aka `void 0`), like NullLiteral represents the value 'null'. +class RawUndefinedLiteral : public NullaryNode { + public: + explicit RawUndefinedLiteral(const TokenPos& pos) + : NullaryNode(ParseNodeKind::RawUndefinedExpr, pos) {} + + static bool test(const ParseNode& node) { + bool match = node.isKind(ParseNodeKind::RawUndefinedExpr); + MOZ_ASSERT_IF(match, node.is()); + return match; + } +}; + +class BooleanLiteral : public NullaryNode { + public: + BooleanLiteral(bool b, const TokenPos& pos) + : NullaryNode(b ? ParseNodeKind::TrueExpr : ParseNodeKind::FalseExpr, + pos) {} + + static bool test(const ParseNode& node) { + bool match = node.isKind(ParseNodeKind::TrueExpr) || + node.isKind(ParseNodeKind::FalseExpr); + MOZ_ASSERT_IF(match, node.is()); + return match; + } +}; + +class RegExpLiteral : public ParseNode { + RegExpIndex index_; + + public: + RegExpLiteral(RegExpIndex dataIndex, const TokenPos& pos) + : ParseNode(ParseNodeKind::RegExpExpr, pos), index_(dataIndex) {} + + // Create a RegExp object of this RegExp literal. + RegExpObject* create(JSContext* cx, FrontendContext* fc, + ParserAtomsTable& parserAtoms, + CompilationAtomCache& atomCache, + ExtensibleCompilationStencil& stencil) const; + +#ifdef DEBUG + void dumpImpl(const ParserAtomsTable* parserAtoms, GenericPrinter& out, + int indent); +#endif + + static bool test(const ParseNode& node) { + return node.isKind(ParseNodeKind::RegExpExpr); + } + + static constexpr TypeCode classTypeCode() { return TypeCode::Other; } + + template + bool accept(Visitor& visitor) { + return true; + } + + RegExpIndex index() { return index_; } +}; + +class PropertyAccessBase : public BinaryNode { + public: + /* + * PropertyAccess nodes can have any expression/'super' as left-hand + * side, but the name must be a ParseNodeKind::PropertyName node. + */ + PropertyAccessBase(ParseNodeKind kind, ParseNode* lhs, NameNode* name, + uint32_t begin, uint32_t end) + : BinaryNode(kind, TokenPos(begin, end), lhs, name) { + MOZ_ASSERT(lhs); + MOZ_ASSERT(name); + } + + ParseNode& expression() const { return *left(); } + + static bool test(const ParseNode& node) { + bool match = node.isKind(ParseNodeKind::DotExpr) || + node.isKind(ParseNodeKind::OptionalDotExpr); + MOZ_ASSERT_IF(match, node.is()); + MOZ_ASSERT_IF(match, node.as().right()->isKind( + ParseNodeKind::PropertyNameExpr)); + return match; + } + + NameNode& key() const { return right()->as(); } + + // Method used by BytecodeEmitter::emitPropLHS for optimization. + // Those methods allow expression to temporarily be nullptr for + // optimization purpose. + ParseNode* maybeExpression() const { return left(); } + + void setExpression(ParseNode* pn) { *unsafeLeftReference() = pn; } + + TaggedParserAtomIndex name() const { return right()->as().atom(); } +}; + +class PropertyAccess : public PropertyAccessBase { + public: + PropertyAccess(ParseNode* lhs, NameNode* name, uint32_t begin, uint32_t end) + : PropertyAccessBase(ParseNodeKind::DotExpr, lhs, name, begin, end) { + MOZ_ASSERT(lhs); + MOZ_ASSERT(name); + } + + static bool test(const ParseNode& node) { + bool match = node.isKind(ParseNodeKind::DotExpr); + MOZ_ASSERT_IF(match, node.is()); + return match; + } + + bool isSuper() const { + // ParseNodeKind::SuperBase cannot result from any expression syntax. + return expression().isKind(ParseNodeKind::SuperBase); + } +}; + +class OptionalPropertyAccess : public PropertyAccessBase { + public: + OptionalPropertyAccess(ParseNode* lhs, NameNode* name, uint32_t begin, + uint32_t end) + : PropertyAccessBase(ParseNodeKind::OptionalDotExpr, lhs, name, begin, + end) { + MOZ_ASSERT(lhs); + MOZ_ASSERT(name); + } + + static bool test(const ParseNode& node) { + bool match = node.isKind(ParseNodeKind::OptionalDotExpr); + MOZ_ASSERT_IF(match, node.is()); + return match; + } +}; + +class PropertyByValueBase : public BinaryNode { + public: + PropertyByValueBase(ParseNodeKind kind, ParseNode* lhs, ParseNode* propExpr, + uint32_t begin, uint32_t end) + : BinaryNode(kind, TokenPos(begin, end), lhs, propExpr) {} + + static bool test(const ParseNode& node) { + bool match = node.isKind(ParseNodeKind::ElemExpr) || + node.isKind(ParseNodeKind::OptionalElemExpr); + MOZ_ASSERT_IF(match, node.is()); + return match; + } + + ParseNode& expression() const { return *left(); } + + ParseNode& key() const { return *right(); } +}; + +class PropertyByValue : public PropertyByValueBase { + public: + PropertyByValue(ParseNode* lhs, ParseNode* propExpr, uint32_t begin, + uint32_t end) + : PropertyByValueBase(ParseNodeKind::ElemExpr, lhs, propExpr, begin, + end) {} + + static bool test(const ParseNode& node) { + bool match = node.isKind(ParseNodeKind::ElemExpr); + MOZ_ASSERT_IF(match, node.is()); + return match; + } + + bool isSuper() const { return left()->isKind(ParseNodeKind::SuperBase); } +}; + +class OptionalPropertyByValue : public PropertyByValueBase { + public: + OptionalPropertyByValue(ParseNode* lhs, ParseNode* propExpr, uint32_t begin, + uint32_t end) + : PropertyByValueBase(ParseNodeKind::OptionalElemExpr, lhs, propExpr, + begin, end) {} + + static bool test(const ParseNode& node) { + bool match = node.isKind(ParseNodeKind::OptionalElemExpr); + MOZ_ASSERT_IF(match, node.is()); + return match; + } +}; + +class PrivateMemberAccessBase : public BinaryNode { + public: + PrivateMemberAccessBase(ParseNodeKind kind, ParseNode* lhs, NameNode* name, + uint32_t begin, uint32_t end) + : BinaryNode(kind, TokenPos(begin, end), lhs, name) { + MOZ_ASSERT(name->isKind(ParseNodeKind::PrivateName)); + } + + ParseNode& expression() const { return *left(); } + + NameNode& privateName() const { + NameNode& name = right()->as(); + MOZ_ASSERT(name.isKind(ParseNodeKind::PrivateName)); + return name; + } + + static bool test(const ParseNode& node) { + bool match = node.isKind(ParseNodeKind::PrivateMemberExpr) || + node.isKind(ParseNodeKind::OptionalPrivateMemberExpr); + MOZ_ASSERT_IF(match, node.is()); + MOZ_ASSERT_IF(match, node.as().right()->isKind( + ParseNodeKind::PrivateName)); + return match; + } +}; + +class PrivateMemberAccess : public PrivateMemberAccessBase { + public: + PrivateMemberAccess(ParseNode* lhs, NameNode* name, uint32_t begin, + uint32_t end) + : PrivateMemberAccessBase(ParseNodeKind::PrivateMemberExpr, lhs, name, + begin, end) {} + + static bool test(const ParseNode& node) { + return node.isKind(ParseNodeKind::PrivateMemberExpr); + } +}; + +class OptionalPrivateMemberAccess : public PrivateMemberAccessBase { + public: + OptionalPrivateMemberAccess(ParseNode* lhs, NameNode* name, uint32_t begin, + uint32_t end) + : PrivateMemberAccessBase(ParseNodeKind::OptionalPrivateMemberExpr, lhs, + name, begin, end) {} + + static bool test(const ParseNode& node) { + return node.isKind(ParseNodeKind::OptionalPrivateMemberExpr); + } +}; + +class NewTargetNode : public TernaryNode { + public: + NewTargetNode(NullaryNode* newHolder, NullaryNode* targetHolder, + NameNode* newTargetName) + : TernaryNode(ParseNodeKind::NewTargetExpr, newHolder, targetHolder, + newTargetName) {} + + static bool test(const ParseNode& node) { + bool match = node.isKind(ParseNodeKind::NewTargetExpr); + MOZ_ASSERT_IF(match, node.is()); + return match; + } + + auto* newHolder() const { return &kid1()->as(); } + auto* targetHolder() const { return &kid2()->as(); } + auto* newTargetName() const { return &kid3()->as(); } +}; + +/* + * A CallSiteNode represents the implicit call site object argument in a + * TaggedTemplate. + */ +class CallSiteNode : public ListNode { + public: + explicit CallSiteNode(uint32_t begin) + : ListNode(ParseNodeKind::CallSiteObj, TokenPos(begin, begin + 1)) {} + + static bool test(const ParseNode& node) { + bool match = node.isKind(ParseNodeKind::CallSiteObj); + MOZ_ASSERT_IF(match, node.is()); + return match; + } + + ListNode* rawNodes() const { + MOZ_ASSERT(head()); + return &head()->as(); + } +}; + +class CallNode : public BinaryNode { + const JSOp callOp_; + + public: + CallNode(ParseNodeKind kind, JSOp callOp, ParseNode* left, ParseNode* right) + : CallNode(kind, callOp, TokenPos(left->pn_pos.begin, right->pn_pos.end), + left, right) {} + + CallNode(ParseNodeKind kind, JSOp callOp, TokenPos pos, ParseNode* left, + ParseNode* right) + : BinaryNode(kind, pos, left, right), callOp_(callOp) { + MOZ_ASSERT(is()); + } + + static bool test(const ParseNode& node) { + bool match = node.isKind(ParseNodeKind::CallExpr) || + node.isKind(ParseNodeKind::SuperCallExpr) || + node.isKind(ParseNodeKind::OptionalCallExpr) || + node.isKind(ParseNodeKind::TaggedTemplateExpr) || + node.isKind(ParseNodeKind::CallImportExpr) || + node.isKind(ParseNodeKind::NewExpr); + MOZ_ASSERT_IF(match, node.is()); + return match; + } + + JSOp callOp() { return callOp_; } +}; + +class ClassMethod : public BinaryNode { + bool isStatic_; + AccessorType accessorType_; + FunctionNode* initializerIfPrivate_; + +#ifdef ENABLE_DECORATORS + ListNode* decorators_; +#endif + + public: + /* + * Method definitions often keep a name and function body that overlap, + * so explicitly define the beginning and end here. + */ + ClassMethod(ParseNodeKind kind, ParseNode* name, ParseNode* body, + AccessorType accessorType, bool isStatic, + FunctionNode* initializerIfPrivate +#ifdef ENABLE_DECORATORS + , + ListNode* decorators +#endif + ) + : BinaryNode(kind, TokenPos(name->pn_pos.begin, body->pn_pos.end), name, + body), + isStatic_(isStatic), + accessorType_(accessorType), + initializerIfPrivate_(initializerIfPrivate) +#ifdef ENABLE_DECORATORS + , + decorators_(decorators) +#endif + { + MOZ_ASSERT(kind == ParseNodeKind::DefaultConstructor || + kind == ParseNodeKind::ClassMethod); + } + + static bool test(const ParseNode& node) { + bool match = node.isKind(ParseNodeKind::DefaultConstructor) || + node.isKind(ParseNodeKind::ClassMethod); + MOZ_ASSERT_IF(match, node.is()); + return match; + } + + ParseNode& name() const { return *left(); } + + FunctionNode& method() const { return right()->as(); } + + bool isStatic() const { return isStatic_; } + + AccessorType accessorType() const { return accessorType_; } + + FunctionNode* initializerIfPrivate() const { return initializerIfPrivate_; } + +#ifdef ENABLE_DECORATORS + ListNode* decorators() const { return decorators_; } +#endif +}; + +class ClassField : public BinaryNode { + bool isStatic_; +#ifdef ENABLE_DECORATORS + bool hasAccessor_; + ListNode* decorators_; +#endif + + public: + ClassField(ParseNode* name, ParseNode* initializer, bool isStatic +#ifdef ENABLE_DECORATORS + , + ListNode* decorators, bool hasAccessor +#endif + ) + : BinaryNode(ParseNodeKind::ClassField, initializer->pn_pos, name, + initializer), + isStatic_(isStatic) +#ifdef ENABLE_DECORATORS + , + hasAccessor_(hasAccessor), + decorators_(decorators) +#endif + { + } + + static bool test(const ParseNode& node) { + bool match = node.isKind(ParseNodeKind::ClassField); + MOZ_ASSERT_IF(match, node.is()); + return match; + } + + ParseNode& name() const { return *left(); } + + FunctionNode* initializer() const { return &right()->as(); } + + bool isStatic() const { return isStatic_; } + +#ifdef ENABLE_DECORATORS + ListNode* decorators() const { return decorators_; } + bool hasAccessor() const { return hasAccessor_; } +#endif +}; + +// Hold onto the function generated for a class static block like +// +// class A { +// static { /* this static block */ } +// } +// +class StaticClassBlock : public UnaryNode { + public: + explicit StaticClassBlock(FunctionNode* function) + : UnaryNode(ParseNodeKind::StaticClassBlock, function->pn_pos, function) { + } + + static bool test(const ParseNode& node) { + bool match = node.isKind(ParseNodeKind::StaticClassBlock); + MOZ_ASSERT_IF(match, node.is()); + return match; + } + FunctionNode* function() const { return &kid()->as(); } +}; + +class PropertyDefinition : public BinaryNode { + AccessorType accessorType_; + + public: + PropertyDefinition(ParseNode* name, ParseNode* value, + AccessorType accessorType) + : BinaryNode(ParseNodeKind::PropertyDefinition, + TokenPos(name->pn_pos.begin, value->pn_pos.end), name, + value), + accessorType_(accessorType) {} + + static bool test(const ParseNode& node) { + bool match = node.isKind(ParseNodeKind::PropertyDefinition); + MOZ_ASSERT_IF(match, node.is()); + return match; + } + + AccessorType accessorType() { return accessorType_; } +}; + +class SwitchStatement : public BinaryNode { + bool hasDefault_; /* only for ParseNodeKind::Switch */ + + public: + SwitchStatement(uint32_t begin, ParseNode* discriminant, + LexicalScopeNode* lexicalForCaseList, bool hasDefault) + : BinaryNode(ParseNodeKind::SwitchStmt, + TokenPos(begin, lexicalForCaseList->pn_pos.end), + discriminant, lexicalForCaseList), + hasDefault_(hasDefault) { +#ifdef DEBUG + ListNode* cases = &lexicalForCaseList->scopeBody()->as(); + MOZ_ASSERT(cases->isKind(ParseNodeKind::StatementList)); + bool found = false; + for (ParseNode* item : cases->contents()) { + CaseClause* caseNode = &item->as(); + if (caseNode->isDefault()) { + found = true; + break; + } + } + MOZ_ASSERT(found == hasDefault); +#endif + } + + static bool test(const ParseNode& node) { + bool match = node.isKind(ParseNodeKind::SwitchStmt); + MOZ_ASSERT_IF(match, node.is()); + return match; + } + + ParseNode& discriminant() const { return *left(); } + + LexicalScopeNode& lexicalForCaseList() const { + return right()->as(); + } + + bool hasDefault() const { return hasDefault_; } +}; + +class ClassNames : public BinaryNode { + public: + ClassNames(ParseNode* outerBinding, ParseNode* innerBinding, + const TokenPos& pos) + : BinaryNode(ParseNodeKind::ClassNames, pos, outerBinding, innerBinding) { + MOZ_ASSERT_IF(outerBinding, outerBinding->isKind(ParseNodeKind::Name)); + MOZ_ASSERT(innerBinding->isKind(ParseNodeKind::Name)); + MOZ_ASSERT_IF(outerBinding, innerBinding->as().atom() == + outerBinding->as().atom()); + } + + static bool test(const ParseNode& node) { + bool match = node.isKind(ParseNodeKind::ClassNames); + MOZ_ASSERT_IF(match, node.is()); + return match; + } + + /* + * Classes require two definitions: The first "outer" binding binds the + * class into the scope in which it was declared. the outer binding is a + * mutable lexial binding. The second "inner" binding binds the class by + * name inside a block in which the methods are evaulated. It is immutable, + * giving the methods access to the static members of the class even if + * the outer binding has been overwritten. + */ + NameNode* outerBinding() const { + if (ParseNode* binding = left()) { + return &binding->as(); + } + return nullptr; + } + + NameNode* innerBinding() const { return &right()->as(); } +}; + +class ClassNode : public TernaryNode { + private: + LexicalScopeNode* innerScope() const { + return &kid3()->as(); + } + + ClassBodyScopeNode* bodyScope() const { + return &innerScope()->scopeBody()->as(); + } + +#ifdef ENABLE_DECORATORS + ListNode* decorators_; +#endif + + public: + ClassNode(ParseNode* names, ParseNode* heritage, + LexicalScopeNode* memberBlock, +#ifdef ENABLE_DECORATORS + ListNode* decorators, +#endif + const TokenPos& pos) + : TernaryNode(ParseNodeKind::ClassDecl, names, heritage, memberBlock, pos) +#ifdef ENABLE_DECORATORS + , + decorators_(decorators) +#endif + { + MOZ_ASSERT(innerScope()->scopeBody()->is()); + MOZ_ASSERT_IF(names, names->is()); + } + + static bool test(const ParseNode& node) { + bool match = node.isKind(ParseNodeKind::ClassDecl); + MOZ_ASSERT_IF(match, node.is()); + return match; + } + + ClassNames* names() const { + return kid1() ? &kid1()->as() : nullptr; + } + + ParseNode* heritage() const { return kid2(); } + + ListNode* memberList() const { return bodyScope()->memberList(); } + + LexicalScopeNode* scopeBindings() const { + LexicalScopeNode* scope = innerScope(); + return scope->isEmptyScope() ? nullptr : scope; + } + + ClassBodyScopeNode* bodyScopeBindings() const { + ClassBodyScopeNode* scope = bodyScope(); + return scope->isEmptyScope() ? nullptr : scope; + } +#ifdef ENABLE_DECORATORS + ListNode* decorators() const { return decorators_; } +#endif +}; + +#ifdef DEBUG +void DumpParseTree(ParserBase* parser, ParseNode* pn, GenericPrinter& out, + int indent = 0); +#endif + +class ParseNodeAllocator { + public: + explicit ParseNodeAllocator(FrontendContext* fc, LifoAlloc& alloc) + : fc(fc), alloc(alloc) {} + + void* allocNode(size_t size); + + private: + FrontendContext* fc; + LifoAlloc& alloc; +}; + +inline bool ParseNode::isConstant() { + switch (pn_type) { + case ParseNodeKind::NumberExpr: + case ParseNodeKind::StringExpr: + case ParseNodeKind::TemplateStringExpr: + case ParseNodeKind::NullExpr: + case ParseNodeKind::RawUndefinedExpr: + case ParseNodeKind::FalseExpr: + case ParseNodeKind::TrueExpr: + return true; + case ParseNodeKind::ArrayExpr: + case ParseNodeKind::ObjectExpr: + return !as().hasNonConstInitializer(); + default: + return false; + } +} + +bool IsAnonymousFunctionDefinition(ParseNode* pn); + +} /* namespace frontend */ +} /* namespace js */ + +#endif /* frontend_ParseNode_h */ diff --git a/js/src/frontend/ParseNodeVerify.cpp b/js/src/frontend/ParseNodeVerify.cpp new file mode 100644 index 0000000000..b23a60649e --- /dev/null +++ b/js/src/frontend/ParseNodeVerify.cpp @@ -0,0 +1,50 @@ +/* -*- 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/ParseNodeVerify.h" + +#include "frontend/ParseNodeVisitor.h" + +using namespace js; + +#ifdef DEBUG + +namespace js { +namespace frontend { + +class ParseNodeVerifier : public ParseNodeVisitor { + using Base = ParseNodeVisitor; + + const LifoAlloc& alloc_; + + public: + ParseNodeVerifier(FrontendContext* fc, const LifoAlloc& alloc) + : Base(fc), alloc_(alloc) {} + + [[nodiscard]] bool visit(ParseNode* pn) { + // pn->size() asserts that pn->pn_kind is valid, so we don't redundantly + // assert that here. + JS_PARSE_NODE_ASSERT(alloc_.contains(pn), + "start of parse node is in alloc"); + JS_PARSE_NODE_ASSERT(alloc_.contains((unsigned char*)pn + pn->size()), + "end of parse node is in alloc"); + if (pn->is()) { + pn->as().checkConsistency(); + } + return Base::visit(pn); + } +}; + +} // namespace frontend +} // namespace js + +bool frontend::CheckParseTree(FrontendContext* fc, const LifoAlloc& alloc, + ParseNode* pn) { + ParseNodeVerifier verifier(fc, alloc); + return verifier.visit(pn); +} + +#endif // DEBUG diff --git a/js/src/frontend/ParseNodeVerify.h b/js/src/frontend/ParseNodeVerify.h new file mode 100644 index 0000000000..1898e122d4 --- /dev/null +++ b/js/src/frontend/ParseNodeVerify.h @@ -0,0 +1,48 @@ +/* -*- 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/. */ + +#ifndef frontend_ParseNodeVerify_h +#define frontend_ParseNodeVerify_h + +#include "frontend/SyntaxParseHandler.h" // SyntaxParseHandler::Node + +namespace js { + +class FrontendContext; +class LifoAlloc; + +namespace frontend { + +class ParseNode; + +// In most builds, examine the given ParseNode and crash if it's not +// well-formed. (In late beta and shipping builds of Firefox, this does +// nothing.) +// +// This returns true on success, and false only if we hit the recursion limit. +// If the ParseNode is actually bad, we crash. + +#ifdef DEBUG +[[nodiscard]] extern bool CheckParseTree(FrontendContext* fc, + const LifoAlloc& alloc, ParseNode* pn); +#else +[[nodiscard]] inline bool CheckParseTree(FrontendContext* fc, + const LifoAlloc& alloc, + ParseNode* pn) { + return true; +} +#endif + +[[nodiscard]] inline bool CheckParseTree(FrontendContext* fc, + const LifoAlloc& alloc, + SyntaxParseHandler::Node pn) { + return true; +} + +} /* namespace frontend */ +} /* namespace js */ + +#endif // frontend_ParseNodeVerify_h diff --git a/js/src/frontend/ParseNodeVisitor.h b/js/src/frontend/ParseNodeVisitor.h new file mode 100644 index 0000000000..18e57d68e6 --- /dev/null +++ b/js/src/frontend/ParseNodeVisitor.h @@ -0,0 +1,136 @@ +/* -*- 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/. */ + +#ifndef frontend_ParseNodeVisitor_h +#define frontend_ParseNodeVisitor_h + +#include "mozilla/Assertions.h" + +#include "frontend/ParseNode.h" +#include "js/friend/StackLimits.h" // js::AutoCheckRecursionLimit + +namespace js { + +class FrontendContext; + +namespace frontend { + +/** + * Utility class for walking a JS AST. + * + * Simple usage: + * + * class HowTrueVisitor : public ParseNodeVisitor { + * public: + * bool visitTrueExpr(BooleanLiteral* pn) { + * std::cout << "How true.\n"; + * return true; + * } + * bool visitClassDecl(ClassNode* pn) { + * // The base-class implementation of each visit method + * // simply visits the node's children. So the subclass + * // gets to decide whether to descend into a subtree + * // and can do things either before or after: + * std::cout << "How classy.\n"; + * return ParseNodeVisitor::visitClassDecl(pn); + * } + * }; + * + * HowTrueVisitor v; + * v.visit(programRootNode); // walks the entire tree + * + * A ParseNodeVisitor can modify nodes, but it can't replace the current node + * with a different one; for that, use a RewritingParseNodeVisitor. + * + * Note that the Curiously Recurring Template Pattern is used for performance, + * as it eliminates the need for virtual method calls. Some rough testing shows + * about a 12% speedup in the FoldConstants.cpp pass. + * https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern + */ +template +class ParseNodeVisitor { + public: + FrontendContext* fc_; + + explicit ParseNodeVisitor(FrontendContext* fc) : fc_(fc) {} + + [[nodiscard]] bool visit(ParseNode* pn) { + AutoCheckRecursionLimit recursion(fc_); + if (!recursion.check(fc_)) { + return false; + } + + switch (pn->getKind()) { +#define VISIT_CASE(KIND, TYPE) \ + case ParseNodeKind::KIND: \ + return static_cast(this)->visit##KIND(&pn->as()); + FOR_EACH_PARSE_NODE_KIND(VISIT_CASE) +#undef VISIT_CASE + default: + MOZ_CRASH("invalid node kind"); + } + } + + // using static_cast here allows plain visit() to be overridden. +#define VISIT_METHOD(KIND, TYPE) \ + [[nodiscard]] bool visit##KIND(TYPE* pn) { /* NOLINT */ \ + return pn->accept(*static_cast(this)); \ + } + FOR_EACH_PARSE_NODE_KIND(VISIT_METHOD) +#undef VISIT_METHOD +}; + +/* + * Utility class for walking a JS AST that allows for replacing nodes. + * + * The API is the same as ParseNodeVisitor, except that visit methods take an + * argument of type `ParseNode*&`, a reference to the field in the parent node + * that points down to the node being visited. Methods can replace the current + * node by assigning to this reference. + * + * All visit methods take a `ParseNode*&` rather than more specific types like + * `BinaryNode*&`, to allow replacing the current node with one of a different + * type. Constant folding makes use of this. + */ +template +class RewritingParseNodeVisitor { + public: + FrontendContext* fc_; + + explicit RewritingParseNodeVisitor(FrontendContext* fc) : fc_(fc) {} + + [[nodiscard]] bool visit(ParseNode*& pn) { + AutoCheckRecursionLimit recursion(fc_); + if (!recursion.check(fc_)) { + return false; + } + + switch (pn->getKind()) { +#define VISIT_CASE(KIND, _type) \ + case ParseNodeKind::KIND: \ + return static_cast(this)->visit##KIND(pn); + FOR_EACH_PARSE_NODE_KIND(VISIT_CASE) +#undef VISIT_CASE + default: + MOZ_CRASH("invalid node kind"); + } + } + + // using static_cast here allows plain visit() to be overridden. +#define VISIT_METHOD(KIND, TYPE) \ + [[nodiscard]] bool visit##KIND(ParseNode*& pn) { \ + MOZ_ASSERT(pn->is(), \ + "Node of kind " #KIND " was not of type " #TYPE); \ + return pn->as().accept(*static_cast(this)); \ + } + FOR_EACH_PARSE_NODE_KIND(VISIT_METHOD) +#undef VISIT_METHOD +}; + +} // namespace frontend +} // namespace js + +#endif // frontend_ParseNodeVisitor_h diff --git a/js/src/frontend/Parser.cpp b/js/src/frontend/Parser.cpp new file mode 100644 index 0000000000..b4d0e76cb7 --- /dev/null +++ b/js/src/frontend/Parser.cpp @@ -0,0 +1,13078 @@ +/* -*- 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/Assertions.h" +#include "mozilla/Casting.h" +#include "mozilla/Range.h" +#include "mozilla/Sprintf.h" +#include "mozilla/Utf8.h" +#include "mozilla/Variant.h" + +#include +#include +#include + +#include "jsnum.h" +#include "jstypes.h" + +#include "frontend/BytecodeCompiler.h" +#include "frontend/FoldConstants.h" +#include "frontend/FunctionSyntaxKind.h" // FunctionSyntaxKind +#include "frontend/ModuleSharedContext.h" +#include "frontend/ParseNode.h" +#include "frontend/ParseNodeVerify.h" +#include "frontend/ParserAtom.h" // TaggedParserAtomIndex, ParserAtomsTable, ParserAtom +#include "frontend/ScriptIndex.h" // ScriptIndex +#include "frontend/TokenStream.h" +#include "irregexp/RegExpAPI.h" +#include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_* +#include "js/HashTable.h" +#include "js/RegExpFlags.h" // JS::RegExpFlags +#include "js/Stack.h" // JS::NativeStackLimit +#include "util/StringBuffer.h" // StringBuffer +#include "vm/BytecodeUtil.h" +#include "vm/FunctionFlags.h" // js::FunctionFlags +#include "vm/GeneratorAndAsyncKind.h" // js::GeneratorKind, js::FunctionAsyncKind +#include "vm/JSContext.h" +#include "vm/JSScript.h" +#include "vm/ModuleBuilder.h" // js::ModuleBuilder +#include "vm/Scope.h" // GetScopeDataTrailingNames +#include "vm/WellKnownAtom.h" // js_*_str +#include "wasm/AsmJS.h" + +#include "frontend/ParseContext-inl.h" +#include "frontend/SharedContext-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::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; + +static inline void PropagateTransitiveParseFlags(const FunctionBox* inner, + SharedContext* 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 +inline typename GeneralParser::FinalParser* +GeneralParser::asFinalParser() { + static_assert( + std::is_base_of_v, FinalParser>, + "inheritance relationship required by the static_cast<> below"); + + return static_cast(this); +} + +template +inline const typename GeneralParser::FinalParser* +GeneralParser::asFinalParser() const { + static_assert( + std::is_base_of_v, FinalParser>, + "inheritance relationship required by the static_cast<> below"); + + return static_cast(this); +} + +template +template +bool GeneralParser::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(FrontendContext* fc, + CompilationState& compilationState, + Kind kind) + : fc_(fc), + alloc_(compilationState.parserAllocScope.alloc()), + compilationState_(compilationState), + pc_(nullptr), + usedNames_(compilationState.usedNames) { + fc_->nameCollectionPool().addActiveCompilation(); +} + +ParserSharedBase::~ParserSharedBase() { + fc_->nameCollectionPool().removeActiveCompilation(); +} + +#if defined(DEBUG) || defined(JS_JITSPEW) +void ParserSharedBase::dumpAtom(TaggedParserAtomIndex index) const { + parserAtoms().dump(index); +} +#endif + +ParserBase::ParserBase(FrontendContext* fc, + const ReadOnlyCompileOptions& options, + bool foldConstants, CompilationState& compilationState) + : ParserSharedBase(fc, compilationState, ParserSharedBase::Kind::Parser), + anyChars(fc, 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_); } + +JSAtom* ParserBase::liftParserAtomToJSAtom(TaggedParserAtomIndex index) { + JSContext* cx = fc_->maybeCurrentJSContext(); + MOZ_ASSERT(cx); + return parserAtoms().toJSAtom(cx, fc_, index, + compilationState_.input.atomCache); +} + +template +PerHandlerParser::PerHandlerParser( + FrontendContext* fc, const ReadOnlyCompileOptions& options, + bool foldConstants, CompilationState& compilationState, + void* internalSyntaxParser) + : ParserBase(fc, options, foldConstants, compilationState), + handler_(fc, compilationState), + internalSyntaxParser_(internalSyntaxParser) { + MOZ_ASSERT(compilationState.isInitialStencil() == + compilationState.input.isInitialStencil()); +} + +template +GeneralParser::GeneralParser( + FrontendContext* fc, const ReadOnlyCompileOptions& options, + const Unit* units, size_t length, bool foldConstants, + CompilationState& compilationState, SyntaxParser* syntaxParser) + : Base(fc, options, foldConstants, compilationState, syntaxParser), + tokenStream(fc, &compilationState.parserAtoms, options, units, length) {} + +template +void Parser::setAwaitHandling( + AwaitHandling awaitHandling) { + this->awaitHandling_ = awaitHandling; +} + +template +void Parser::setAwaitHandling( + AwaitHandling awaitHandling) { + this->awaitHandling_ = awaitHandling; + if (SyntaxParser* syntaxParser = getSyntaxParser()) { + syntaxParser->setAwaitHandling(awaitHandling); + } +} + +template +inline void GeneralParser::setAwaitHandling( + AwaitHandling awaitHandling) { + asFinalParser()->setAwaitHandling(awaitHandling); +} + +template +void Parser::setInParametersOfAsyncFunction( + bool inParameters) { + this->inParametersOfAsyncFunction_ = inParameters; +} + +template +void Parser::setInParametersOfAsyncFunction( + bool inParameters) { + this->inParametersOfAsyncFunction_ = inParameters; + if (SyntaxParser* syntaxParser = getSyntaxParser()) { + syntaxParser->setInParametersOfAsyncFunction(inParameters); + } +} + +template +inline void GeneralParser::setInParametersOfAsyncFunction( + bool inParameters) { + asFinalParser()->setInParametersOfAsyncFunction(inParameters); +} + +template +FunctionBox* PerHandlerParser::newFunctionBox( + FunctionNodeType funNode, TaggedParserAtomIndex explicitName, + FunctionFlags flags, uint32_t toStringStart, Directives inheritedDirectives, + GeneratorKind generatorKind, FunctionAsyncKind asyncKind) { + MOZ_ASSERT(funNode); + + ScriptIndex index = ScriptIndex(compilationState_.scriptData.length()); + if (uint32_t(index) >= TaggedScriptThingIndex::IndexLimit) { + ReportAllocationOverflow(fc_); + return nullptr; + } + if (!compilationState_.appendScriptStencilAndData(fc_)) { + return nullptr; + } + + bool isInitialStencil = compilationState_.isInitialStencil(); + + // 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_( + fc_, extent, compilationState_, inheritedDirectives, generatorKind, + asyncKind, isInitialStencil, explicitName, flags, index); + if (!funbox) { + ReportOutOfMemory(fc_); + return nullptr; + } + + handler_.setFunctionBox(funNode, funbox); + + return funbox; +} + +template +FunctionBox* PerHandlerParser::newFunctionBox( + FunctionNodeType funNode, const ScriptStencil& cachedScriptData, + const ScriptStencilExtra& cachedScriptExtra) { + MOZ_ASSERT(funNode); + + ScriptIndex index = ScriptIndex(compilationState_.scriptData.length()); + if (uint32_t(index) >= TaggedScriptThingIndex::IndexLimit) { + ReportAllocationOverflow(fc_); + return nullptr; + } + if (!compilationState_.appendScriptStencilAndData(fc_)) { + return nullptr; + } + + /* + * 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_( + fc_, cachedScriptExtra.extent, compilationState_, + Directives(/* strict = */ false), cachedScriptExtra.generatorKind(), + cachedScriptExtra.asyncKind(), compilationState_.isInitialStencil(), + cachedScriptData.functionAtom, cachedScriptData.functionFlags, index); + if (!funbox) { + ReportOutOfMemory(fc_); + return nullptr; + } + + handler_.setFunctionBox(funNode, funbox); + funbox->initFromScriptStencilExtra(cachedScriptExtra); + + 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(fc_, anyChars.displayURL())) { + return false; + } + } + + if (anyChars.hasSourceMapURL()) { + MOZ_ASSERT(!ss->hasSourceMapURL()); + if (!ss->setSourceMapURL(fc_, 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(fc_, options().sourceMapURL())) { + return false; + } + } + + return true; +} + +/* + * Parse a top-level JS script. + */ +template +typename ParseHandler::ListNodeType GeneralParser::parse() { + MOZ_ASSERT(checkOptionsCalled_); + + SourceExtent extent = SourceExtent::makeGlobalExtent( + /* len = */ 0, options().lineno, options().column); + Directives directives(options().forceStrictMode()); + GlobalSharedContext globalsc(this->fc_, ScopeKind::Global, options(), + 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(this->fc_, 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(this->fc_, this->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(TaggedParserAtomIndex name) { + TokenKind tt = ReservedWordTokenKind(name); + if (tt == TokenKind::Limit) { + return name != TaggedParserAtomIndex::WellKnown::eval() && + name != TaggedParserAtomIndex::WellKnown::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)) { + return false; + } + } + return true; +} + +template +void GeneralParser::reportMissingClosing( + unsigned errorNumber, unsigned noteNumber, uint32_t openedPos) { + auto notes = MakeUnique(); + if (!notes) { + ReportOutOfMemory(this->fc_); + 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(this->fc_, getFilename(), 0, line, column, + GetErrorMessage, nullptr, noteNumber, lineNumber, + columnNumber)) { + return; + } + + errorWithNotes(std::move(notes), errorNumber); +} + +template +void GeneralParser::reportRedeclaration( + TaggedParserAtomIndex name, DeclarationKind prevKind, TokenPos pos, + uint32_t prevPos) { + UniqueChars bytes = this->parserAtoms().toPrintableString(name); + if (!bytes) { + ReportOutOfMemory(this->fc_); + return; + } + + if (prevPos == DeclaredNameInfo::npos) { + errorAt(pos.begin, JSMSG_REDECLARED_VAR, DeclarationKindString(prevKind), + bytes.get()); + return; + } + + auto notes = MakeUnique(); + if (!notes) { + ReportOutOfMemory(this->fc_); + 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(this->fc_, 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 +bool GeneralParser::notePositionalFormalParameter( + FunctionNodeType funNode, TaggedParserAtomIndex 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 = this->parserAtoms().toPrintableString(name); + if (!bytes) { + ReportOutOfMemory(this->fc_); + 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( + TrivialTaggedParserAtomIndex::from(name))) { + ReportOutOfMemory(this->fc_); + return false; + } + + NameNodeType paramNode = newName(name); + if (!paramNode) { + return false; + } + + handler_.addFunctionFormalParameter(funNode, paramNode); + return true; +} + +template +bool PerHandlerParser::noteDestructuredPositionalFormalParameter( + FunctionNodeType funNode, Node destruct) { + // Append an empty name to the positional formals vector to keep track of + // argument slots when making FunctionScope::ParserData. + if (!pc_->positionalFormalParameterNames().append( + TrivialTaggedParserAtomIndex::null())) { + ReportOutOfMemory(fc_); + return false; + } + + handler_.addFunctionFormalParameter(funNode, destruct); + return true; +} + +template +bool GeneralParser::noteDeclaredName( + TaggedParserAtomIndex name, DeclarationKind kind, TokenPos pos, + ClosedOver isClosedOver) { + // 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 redeclaredKind; + uint32_t prevPos; + if (!pc_->tryDeclareVar(name, this, 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, + isClosedOver)) { + 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, + isClosedOver)) { + return false; + } + + break; + } + + case DeclarationKind::LexicalFunction: + case DeclarationKind::PrivateName: + case DeclarationKind::Synthetic: + case DeclarationKind::PrivateMethod: { + 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, + isClosedOver)) { + 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, + isClosedOver)) { + 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 == TaggedParserAtomIndex::WellKnown::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 != TaggedParserAtomIndex::WellKnown::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, + isClosedOver)) { + 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 +bool GeneralParser::noteDeclaredPrivateName( + Node nameNode, TaggedParserAtomIndex name, PropertyType propType, + FieldPlacement placement, TokenPos pos) { + ParseContext::Scope* scope = pc_->innermostScope(); + AddDeclaredNamePtr p = scope->lookupDeclaredNameForAdd(name); + + DeclarationKind declKind = DeclarationKind::PrivateName; + ClosedOver closedOver = ClosedOver::No; + PrivateNameKind kind; + switch (propType) { + case PropertyType::Field: + case PropertyType::FieldWithAccessor: + kind = PrivateNameKind::Field; + break; + case PropertyType::Method: + case PropertyType::GeneratorMethod: + case PropertyType::AsyncMethod: + case PropertyType::AsyncGeneratorMethod: + if (placement == FieldPlacement::Instance) { + // Optimized private method. Non-optimized paths still get + // DeclarationKind::Synthetic. + declKind = DeclarationKind::PrivateMethod; + } + + // Methods must be marked closed-over so that + // EmitterScope::lookupPrivate() works even if the method is used, but not + // within any method (from a computed property name, or debugger frame) + closedOver = ClosedOver::Yes; + 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)) { + // Private methods demands that + // + // class A { + // static set #x(_) {} + // get #x() { } + // } + // + // Report a SyntaxError. + if (placement == p->value()->placement()) { + 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, declKind, pos.begin, closedOver)) { + return false; + } + + DeclaredNamePtr declared = scope->lookupDeclaredName(name); + declared->value()->setPrivateNameKind(kind); + declared->value()->setFieldPlacement(placement); + handler_.setPrivateNameKind(nameNode, kind); + + return true; +} + +bool ParserBase::noteUsedNameInternal(TaggedParserAtomIndex name, + NameVisibility visibility, + mozilla::Maybe 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(fc_, name, visibility, pc_->scriptId(), scope->id(), + tokenPosition); +} + +template +bool PerHandlerParser:: + 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_, this)) { + return false; + } + + if (handler_.reuseClosedOverBindings()) { + MOZ_ASSERT(pc_->isOutermostOfCurrentCompile()); + + // Closed over bindings for all scopes are stored in a contiguous array, in + // the same order as the order in which scopes are visited, and seprated by + // TaggedParserAtomIndex::null(). + uint32_t slotCount = scope.declaredCount(); + while (auto parserAtom = handler_.nextLazyClosedOverBinding()) { + scope.lookupDeclaredName(parserAtom)->value()->setClosedOver(); + MOZ_ASSERT(slotCount > 0); + slotCount--; + } + + if (pc_->isGeneratorOrAsync()) { + scope.setOwnStackSlotCount(slotCount); + } + return true; + } + + constexpr bool isSyntaxParser = + std::is_same_v; + uint32_t scriptId = pc_->scriptId(); + uint32_t scopeId = scope.id(); + + uint32_t slotCount = 0; + for (BindingIter bi = scope.bindings(pc_); bi; bi++) { + bool closedOver = false; + if (UsedNamePtr p = usedNames_.lookup(bi.name())) { + p->value().noteBoundInScope(scriptId, scopeId, &closedOver); + if (closedOver) { + bi.setClosedOver(); + + if constexpr (isSyntaxParser) { + if (!pc_->closedOverBindingsForLazy().append( + TrivialTaggedParserAtomIndex::from(bi.name()))) { + ReportOutOfMemory(fc_); + return false; + } + } + } + } + + if constexpr (!isSyntaxParser) { + if (!closedOver) { + 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( + TrivialTaggedParserAtomIndex::null())) { + ReportOutOfMemory(fc_); + return false; + } + } + + return true; +} + +template +bool Parser::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::ParserData* NewEmptyBindingData(FrontendContext* fc, + LifoAlloc& alloc, + uint32_t numBindings) { + using Data = typename ScopeT::ParserData; + size_t allocSize = SizeOfScopeData(numBindings); + auto* bindings = alloc.newWithSize(allocSize, numBindings); + if (!bindings) { + ReportOutOfMemory(fc); + } + return bindings; +} + +GlobalScope::ParserData* NewEmptyGlobalScopeData(FrontendContext* fc, + LifoAlloc& alloc, + uint32_t numBindings) { + return NewEmptyBindingData(fc, alloc, numBindings); +} + +LexicalScope::ParserData* NewEmptyLexicalScopeData(FrontendContext* fc, + LifoAlloc& alloc, + uint32_t numBindings) { + return NewEmptyBindingData(fc, alloc, numBindings); +} + +FunctionScope::ParserData* NewEmptyFunctionScopeData(FrontendContext* fc, + LifoAlloc& alloc, + uint32_t numBindings) { + return NewEmptyBindingData(fc, alloc, numBindings); +} + +namespace detail { + +template +static MOZ_ALWAYS_INLINE ParserBindingName* InitializeIndexedBindings( + SlotInfo& slotInfo, ParserBindingName* start, ParserBindingName* cursor) { + return cursor; +} + +template +static MOZ_ALWAYS_INLINE ParserBindingName* InitializeIndexedBindings( + SlotInfo& slotInfo, ParserBindingName* start, ParserBindingName* cursor, + UnsignedInteger SlotInfo::*field, const ParserBindingNameVector& bindings, + Step&&... step) { + slotInfo.*field = + AssertedCast(PointerRangeSize(start, cursor)); + + ParserBindingName* newCursor = + std::uninitialized_copy(bindings.begin(), bindings.end(), cursor); + + return InitializeIndexedBindings(slotInfo, start, newCursor, + std::forward(step)...); +} + +} // namespace detail + +// Initialize the trailing name bindings of |data|, then set |data->length| to +// the count of bindings added (which must equal |count|). +// +// First, |firstBindings| are added to the trailing names. 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 +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 = GetScopeDataTrailingNamesPointer(data); + ParserBindingName* cursor = std::uninitialized_copy( + firstBindings.begin(), firstBindings.end(), start); + +#ifdef DEBUG + ParserBindingName* end = +#endif + detail::InitializeIndexedBindings(data->slotInfo, start, cursor, + std::forward(step)...); + + MOZ_ASSERT(PointerRangeSize(start, end) == count); + data->length = count; +} + +Maybe NewGlobalScopeData(FrontendContext* fc, + ParseContext::Scope& scope, + LifoAlloc& alloc, + ParseContext* pc) { + ParserBindingNameVector vars(fc); + ParserBindingNameVector lets(fc); + ParserBindingNameVector consts(fc); + + 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"); + } + } + + GlobalScope::ParserData* bindings = nullptr; + uint32_t numBindings = vars.length() + lets.length() + consts.length(); + + if (numBindings > 0) { + bindings = NewEmptyBindingData(fc, alloc, numBindings); + if (!bindings) { + return Nothing(); + } + + // The ordering here is important. See comments in GlobalScope. + InitializeBindingData(bindings, numBindings, vars, + &ParserGlobalScopeSlotInfo::letStart, lets, + &ParserGlobalScopeSlotInfo::constStart, consts); + } + + return Some(bindings); +} + +Maybe ParserBase::newGlobalScopeData( + ParseContext::Scope& scope) { + return NewGlobalScopeData(fc_, scope, stencilAlloc(), pc_); +} + +Maybe NewModuleScopeData(FrontendContext* fc, + ParseContext::Scope& scope, + LifoAlloc& alloc, + ParseContext* pc) { + ParserBindingNameVector imports(fc); + ParserBindingNameVector vars(fc); + ParserBindingNameVector lets(fc); + ParserBindingNameVector consts(fc); + + bool allBindingsClosedOver = + pc->sc()->allBindingsClosedOver() || scope.tooBigToOptimize(); + + 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"); + } + } + + ModuleScope::ParserData* bindings = nullptr; + uint32_t numBindings = + imports.length() + vars.length() + lets.length() + consts.length(); + + if (numBindings > 0) { + bindings = NewEmptyBindingData(fc, alloc, numBindings); + if (!bindings) { + return Nothing(); + } + + // The ordering here is important. See comments in ModuleScope. + InitializeBindingData(bindings, numBindings, imports, + &ParserModuleScopeSlotInfo::varStart, vars, + &ParserModuleScopeSlotInfo::letStart, lets, + &ParserModuleScopeSlotInfo::constStart, consts); + } + + return Some(bindings); +} + +Maybe ParserBase::newModuleScopeData( + ParseContext::Scope& scope) { + return NewModuleScopeData(fc_, scope, stencilAlloc(), pc_); +} + +Maybe NewEvalScopeData(FrontendContext* fc, + ParseContext::Scope& scope, + LifoAlloc& alloc, + ParseContext* pc) { + ParserBindingNameVector vars(fc); + + // Treat all bindings as closed over in non-strict eval. + bool allBindingsClosedOver = + !pc->sc()->strict() || pc->sc()->allBindingsClosedOver(); + for (BindingIter bi = scope.bindings(pc); bi; bi++) { + // Eval scopes only contain 'var' bindings. + MOZ_ASSERT(bi.kind() == BindingKind::Var); + bool isTopLevelFunction = + bi.declarationKind() == DeclarationKind::BodyLevelFunction; + bool closedOver = allBindingsClosedOver || bi.closedOver(); + + ParserBindingName binding(bi.name(), closedOver, isTopLevelFunction); + if (!vars.append(binding)) { + return Nothing(); + } + } + + EvalScope::ParserData* bindings = nullptr; + uint32_t numBindings = vars.length(); + + if (numBindings > 0) { + bindings = NewEmptyBindingData(fc, alloc, numBindings); + if (!bindings) { + return Nothing(); + } + + InitializeBindingData(bindings, numBindings, vars); + } + + return Some(bindings); +} + +Maybe ParserBase::newEvalScopeData( + ParseContext::Scope& scope) { + return NewEvalScopeData(fc_, scope, stencilAlloc(), pc_); +} + +Maybe NewFunctionScopeData( + FrontendContext* fc, ParseContext::Scope& scope, bool hasParameterExprs, + LifoAlloc& alloc, ParseContext* pc) { + ParserBindingNameVector positionalFormals(fc); + ParserBindingNameVector formals(fc); + ParserBindingNameVector vars(fc); + + 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++) { + TaggedParserAtomIndex 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 (TaggedParserAtomIndex(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(bi.name())); + if (!vars.append(binding)) { + return Nothing(); + } + break; + case BindingKind::Let: + case BindingKind::Const: + break; + default: + MOZ_CRASH("bad function scope BindingKind"); + break; + } + } + + FunctionScope::ParserData* bindings = nullptr; + uint32_t numBindings = + positionalFormals.length() + formals.length() + vars.length(); + + if (numBindings > 0) { + bindings = NewEmptyBindingData(fc, alloc, numBindings); + if (!bindings) { + return Nothing(); + } + + // The ordering here is important. See comments in FunctionScope. + InitializeBindingData( + bindings, numBindings, positionalFormals, + &ParserFunctionScopeSlotInfo::nonPositionalFormalStart, formals, + &ParserFunctionScopeSlotInfo::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 ParserBase::newFunctionScopeData( + ParseContext::Scope& scope, bool hasParameterExprs) { + return NewFunctionScopeData(fc_, scope, hasParameterExprs, stencilAlloc(), + pc_); +} + +VarScope::ParserData* NewEmptyVarScopeData(FrontendContext* fc, + LifoAlloc& alloc, + uint32_t numBindings) { + return NewEmptyBindingData(fc, alloc, numBindings); +} + +Maybe NewVarScopeData(FrontendContext* fc, + ParseContext::Scope& scope, + LifoAlloc& alloc, + ParseContext* pc) { + ParserBindingNameVector vars(fc); + + 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(); + } + } else { + MOZ_ASSERT( + bi.kind() == BindingKind::Let || bi.kind() == BindingKind::Const, + "bad var scope BindingKind"); + } + } + + VarScope::ParserData* bindings = nullptr; + uint32_t numBindings = vars.length(); + + if (numBindings > 0) { + bindings = NewEmptyBindingData(fc, 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 ParserBase::newVarScopeData( + ParseContext::Scope& scope) { + return NewVarScopeData(fc_, scope, stencilAlloc(), pc_); +} + +Maybe NewLexicalScopeData(FrontendContext* fc, + ParseContext::Scope& scope, + LifoAlloc& alloc, + ParseContext* pc) { + ParserBindingNameVector lets(fc); + ParserBindingNameVector consts(fc); + + 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; + case BindingKind::Var: + case BindingKind::FormalParameter: + break; + default: + MOZ_CRASH("Bad lexical scope BindingKind"); + break; + } + } + + LexicalScope::ParserData* bindings = nullptr; + uint32_t numBindings = lets.length() + consts.length(); + + if (numBindings > 0) { + bindings = NewEmptyBindingData(fc, alloc, numBindings); + if (!bindings) { + return Nothing(); + } + + // The ordering here is important. See comments in LexicalScope. + InitializeBindingData(bindings, numBindings, lets, + &ParserLexicalScopeSlotInfo::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 ParserBase::newLexicalScopeData( + ParseContext::Scope& scope) { + return NewLexicalScopeData(fc_, scope, stencilAlloc(), pc_); +} + +Maybe NewClassBodyScopeData( + FrontendContext* fc, ParseContext::Scope& scope, LifoAlloc& alloc, + ParseContext* pc) { + ParserBindingNameVector privateBrand(fc); + ParserBindingNameVector synthetics(fc); + ParserBindingNameVector privateMethods(fc); + + 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::Synthetic: + if (bi.name() == TaggedParserAtomIndex::WellKnown::dotPrivateBrand()) { + MOZ_ASSERT(privateBrand.empty()); + if (!privateBrand.append(binding)) { + return Nothing(); + } + } else { + if (!synthetics.append(binding)) { + return Nothing(); + } + } + break; + + case BindingKind::PrivateMethod: + if (!privateMethods.append(binding)) { + return Nothing(); + } + break; + + default: + MOZ_CRASH("bad class body scope BindingKind"); + break; + } + } + + // We should have zero or one private brands. + MOZ_ASSERT(privateBrand.length() == 0 || privateBrand.length() == 1); + + ClassBodyScope::ParserData* bindings = nullptr; + uint32_t numBindings = + privateBrand.length() + synthetics.length() + privateMethods.length(); + + if (numBindings > 0) { + bindings = NewEmptyBindingData(fc, alloc, numBindings); + if (!bindings) { + return Nothing(); + } + // To simplify initialization of the bindings, we concatenate the + // synthetics+privateBrand vector such that the private brand is always the + // first element, as ordering is important. See comments in ClassBodyScope. + ParserBindingNameVector brandAndSynthetics(fc); + if (!brandAndSynthetics.appendAll(privateBrand)) { + return Nothing(); + } + if (!brandAndSynthetics.appendAll(synthetics)) { + return Nothing(); + } + + // The ordering here is important. See comments in ClassBodyScope. + InitializeBindingData(bindings, numBindings, brandAndSynthetics, + &ParserClassBodyScopeSlotInfo::privateMethodStart, + privateMethods); + } + + // `EmitterScope::lookupPrivate()` requires `.privateBrand` to be stored in a + // predictable slot: the first slot available in the environment object, + // `ClassBodyLexicalEnvironmentObject::privateBrandSlot()`. We assume that + // if `.privateBrand` is first in the scope, it will be stored there. + MOZ_ASSERT_IF(!privateBrand.empty(), + GetScopeDataTrailingNames(bindings)[0].name() == + TaggedParserAtomIndex::WellKnown::dotPrivateBrand()); + + return Some(bindings); +} + +Maybe ParserBase::newClassBodyScopeData( + ParseContext::Scope& scope) { + return NewClassBodyScopeData(fc_, scope, stencilAlloc(), pc_); +} + +template <> +SyntaxParseHandler::LexicalScopeNodeType +PerHandlerParser::finishLexicalScope( + ParseContext::Scope& scope, Node body, ScopeKind kind) { + if (!propagateFreeNamesAndMarkClosedOverBindings(scope)) { + return null(); + } + + return handler_.newLexicalScope(body); +} + +template <> +LexicalScopeNode* PerHandlerParser::finishLexicalScope( + ParseContext::Scope& scope, ParseNode* body, ScopeKind kind) { + if (!propagateFreeNamesAndMarkClosedOverBindings(scope)) { + return nullptr; + } + + Maybe bindings = newLexicalScopeData(scope); + if (!bindings) { + return nullptr; + } + + return handler_.newLexicalScope(*bindings, body, kind); +} + +template <> +SyntaxParseHandler::ClassBodyScopeNodeType +PerHandlerParser::finishClassBodyScope( + ParseContext::Scope& scope, ListNodeType body) { + if (!propagateFreeNamesAndMarkClosedOverBindings(scope)) { + return null(); + } + + return handler_.newClassBodyScope(body); +} + +template <> +ClassBodyScopeNode* PerHandlerParser::finishClassBodyScope( + ParseContext::Scope& scope, ListNode* body) { + if (!propagateFreeNamesAndMarkClosedOverBindings(scope)) { + return nullptr; + } + + Maybe bindings = newClassBodyScopeData(scope); + if (!bindings) { + return nullptr; + } + + return handler_.newClassBodyScope(*bindings, body); +} + +template +bool PerHandlerParser::checkForUndefinedPrivateFields( + EvalSharedContext* evalSc) { + if (!this->compilationState_.isInitialStencil()) { + // We're delazifying -- so we already checked private names during first + // parse. + return true; + } + + Vector unboundPrivateNames(fc_); + if (!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 = this->parserAtoms().toPrintableString(minimum.atom); + if (!str) { + ReportOutOfMemory(this->fc_); + return false; + } + + errorAt(minimum.position.begin, JSMSG_MISSING_PRIVATE_DECL, str.get()); + return false; + } + + // It's important that the unbound private names are sorted, as we + // want our errors to always be issued to the first textually. + for (UnboundPrivateName unboundName : unboundPrivateNames) { + // If the enclosingScope is non-syntactic, then we are in a + // Debugger.Frame.prototype.eval call. In order to find the declared private + // names, we must use the effective scope that was determined when creating + // the scopeContext. + if (!this->compilationState_.scopeContext + .effectiveScopePrivateFieldCacheHas(unboundName.atom)) { + UniqueChars str = this->parserAtoms().toPrintableString(unboundName.atom); + if (!str) { + ReportOutOfMemory(this->fc_); + return false; + } + errorAt(unboundName.position.begin, JSMSG_MISSING_PRIVATE_DECL, + str.get()); + return false; + } + } + + return true; +} + +template +LexicalScopeNode* Parser::evalBody( + EvalSharedContext* evalsc) { + SourceParseContext evalpc(this, evalsc, /* newDirectives = */ nullptr); + if (!evalpc.init()) { + return nullptr; + } + + ParseContext::VarScope varScope(this); + if (!varScope.init(pc_)) { + return nullptr; + } + + LexicalScopeNode* body; + { + // All evals have an implicit non-extensible lexical scope. + ParseContext::Scope lexicalScope(this); + if (!lexicalScope.init(pc_)) { + return nullptr; + } + + ListNode* list = statementList(YieldIsName); + if (!list) { + return nullptr; + } + + if (!checkStatementsEOF()) { + return nullptr; + } + + // Private names not lexically defined must trigger a syntax error. + if (!checkForUndefinedPrivateFields(evalsc)) { + return nullptr; + } + + body = finishLexicalScope(lexicalScope, list); + if (!body) { + return nullptr; + } + } + +#ifdef DEBUG + if (evalpc.superScopeNeedsHomeObject() && + !this->compilationState_.input.enclosingScope.isNull()) { + // If superScopeNeedsHomeObject_ is set and we are an entry-point + // ParseContext, then we must be emitting an eval script, and the + // outer function must already be marked as needing a home object + // since it contains an eval. + MOZ_ASSERT( + this->compilationState_.scopeContext.hasFunctionNeedsHomeObjectOnChain, + "Eval must have found an enclosing function box scope that " + "allows super.property"); + } +#endif + + if (!CheckParseTree(this->fc_, alloc_, body)) { + return null(); + } + + ParseNode* node = body; + // 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(this->fc_, this->parserAtoms(), &node, &handler_)) { + return null(); + } + } + body = handler_.asLexicalScope(node); + + if (!this->setSourceMapInfo()) { + return nullptr; + } + + if (pc_->sc()->strict()) { + if (!propagateFreeNamesAndMarkClosedOverBindings(varScope)) { + return nullptr; + } + } else { + // For non-strict eval scripts, since all bindings are automatically + // considered closed over, we don't need to call propagateFreeNames- + // AndMarkClosedOverBindings. However, Annex B.3.3 functions still need to + // be marked. + if (!varScope.propagateAndMarkAnnexBFunctionBoxes(pc_, this)) { + return nullptr; + } + } + + Maybe bindings = newEvalScopeData(pc_->varScope()); + if (!bindings) { + return nullptr; + } + evalsc->bindings = *bindings; + + return body; +} + +template +ListNode* Parser::globalBody( + GlobalSharedContext* globalsc) { + SourceParseContext globalpc(this, globalsc, /* newDirectives = */ nullptr); + if (!globalpc.init()) { + return nullptr; + } + + ParseContext::VarScope varScope(this); + if (!varScope.init(pc_)) { + return nullptr; + } + + ListNode* body = statementList(YieldIsName); + if (!body) { + return nullptr; + } + + if (!checkStatementsEOF()) { + return nullptr; + } + + if (!CheckParseTree(this->fc_, alloc_, body)) { + return null(); + } + + if (!checkForUndefinedPrivateFields()) { + return null(); + } + + ParseNode* node = body; + // 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(this->fc_, this->parserAtoms(), &node, &handler_)) { + return null(); + } + } + body = &node->as(); + + if (!this->setSourceMapInfo()) { + return nullptr; + } + + // For global scripts, whether bindings are closed over or not doesn't + // matter, so no need to call propagateFreeNamesAndMarkClosedOver- + // Bindings. However, Annex B.3.3 functions still need to be marked. + if (!varScope.propagateAndMarkAnnexBFunctionBoxes(pc_, this)) { + return nullptr; + } + + Maybe bindings = + newGlobalScopeData(pc_->varScope()); + if (!bindings) { + return nullptr; + } + globalsc->bindings = *bindings; + + return body; +} + +template +ModuleNode* Parser::moduleBody( + ModuleSharedContext* modulesc) { + MOZ_ASSERT(checkOptionsCalled_); + + this->compilationState_.moduleMetadata = + fc_->getAllocator()->template new_(); + if (!this->compilationState_.moduleMetadata) { + return null(); + } + + SourceParseContext modulepc(this, modulesc, nullptr); + if (!modulepc.init()) { + return null(); + } + + ParseContext::VarScope varScope(this); + if (!varScope.init(pc_)) { + return null(); + } + + ModuleNodeType moduleNode = handler_.newModule(pos()); + if (!moduleNode) { + return null(); + } + + AutoAwaitIsKeyword awaitIsKeyword( + this, AwaitIsModuleKeyword); + ListNode* stmtList = statementList(YieldIsName); + if (!stmtList) { + return null(); + } + + MOZ_ASSERT(stmtList->isKind(ParseNodeKind::StatementList)); + moduleNode->setBody(&stmtList->as()); + + if (pc_->isAsync()) { + if (!noteUsedName(TaggedParserAtomIndex::WellKnown::dotGenerator())) { + return null(); + } + + if (!pc_->declareTopLevelDotGeneratorName()) { + return null(); + } + } + + TokenKind tt; + if (!tokenStream.getToken(&tt, TokenStream::SlashIsRegExp)) { + return null(); + } + if (tt != TokenKind::Eof) { + error(JSMSG_GARBAGE_AFTER_INPUT, "module", TokenKindToDesc(tt)); + return null(); + } + + // Set the module to async if an await keyword was found at the top level. + if (pc_->isAsync()) { + pc_->sc()->asModuleContext()->builder.noteAsync( + *this->compilationState_.moduleMetadata); + } + + // Generate the Import/Export tables and store in CompilationState. + if (!modulesc->builder.buildTables(*this->compilationState_.moduleMetadata)) { + return null(); + } + + // Check exported local bindings exist and mark them as closed over. + StencilModuleMetadata& moduleMetadata = + *this->compilationState_.moduleMetadata; + for (auto entry : moduleMetadata.localExportEntries) { + DeclaredNamePtr p = modulepc.varScope().lookupDeclaredName(entry.localName); + if (!p) { + UniqueChars str = this->parserAtoms().toPrintableString(entry.localName); + if (!str) { + ReportOutOfMemory(this->fc_); + return null(); + } + + errorNoOffset(JSMSG_MISSING_EXPORT, str.get()); + return null(); + } + + p->value()->setClosedOver(); + } + + // Reserve an environment slot for a "*namespace*" psuedo-binding and mark as + // closed-over. We do not know until module linking if this will be used. + if (!noteDeclaredName(TaggedParserAtomIndex::WellKnown::starNamespaceStar(), + DeclarationKind::Const, pos())) { + return nullptr; + } + modulepc.varScope() + .lookupDeclaredName(TaggedParserAtomIndex::WellKnown::starNamespaceStar()) + ->value() + ->setClosedOver(); + + if (options().deoptimizeModuleGlobalVars) { + for (BindingIter bi = modulepc.varScope().bindings(pc_); bi; bi++) { + bi.setClosedOver(); + } + } + + if (!CheckParseTree(this->fc_, alloc_, stmtList)) { + return null(); + } + + ParseNode* 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(this->fc_, this->parserAtoms(), &node, &handler_)) { + return null(); + } + } + stmtList = &node->as(); + + if (!this->setSourceMapInfo()) { + return null(); + } + + // Private names not lexically defined must trigger a syntax error. + if (!checkForUndefinedPrivateFields()) { + return null(); + } + + if (!propagateFreeNamesAndMarkClosedOverBindings(modulepc.varScope())) { + return null(); + } + + Maybe bindings = + newModuleScopeData(modulepc.varScope()); + if (!bindings) { + return nullptr; + } + + modulesc->bindings = *bindings; + return moduleNode; +} + +template +SyntaxParseHandler::ModuleNodeType Parser::moduleBody( + ModuleSharedContext* modulesc) { + MOZ_ALWAYS_FALSE(abortIfSyntaxParser()); + return SyntaxParseHandler::NodeFailure; +} + +template +typename ParseHandler::NameNodeType +PerHandlerParser::newInternalDotName(TaggedParserAtomIndex name) { + NameNodeType nameNode = newName(name); + if (!nameNode) { + return null(); + } + if (!noteUsedName(name)) { + return null(); + } + return nameNode; +} + +template +typename ParseHandler::NameNodeType +PerHandlerParser::newThisName() { + return newInternalDotName(TaggedParserAtomIndex::WellKnown::dotThis()); +} + +template +typename ParseHandler::NameNodeType +PerHandlerParser::newNewTargetName() { + return newInternalDotName(TaggedParserAtomIndex::WellKnown::dotNewTarget()); +} + +template +typename ParseHandler::NameNodeType +PerHandlerParser::newDotGeneratorName() { + return newInternalDotName(TaggedParserAtomIndex::WellKnown::dotGenerator()); +} + +template +bool PerHandlerParser::finishFunctionScopes( + bool isStandaloneFunction) { + FunctionBox* funbox = pc_->functionBox(); + + if (funbox->hasParameterExprs) { + if (!propagateFreeNamesAndMarkClosedOverBindings(pc_->functionScope())) { + return false; + } + + // Functions with parameter expressions utilize the FunctionScope for vars + // generated by sloppy-direct-evals, as well as arguments (which are + // lexicals bindings). If the function body has var bindings (or has a + // sloppy-direct-eval that might), then an extra VarScope must be created + // for them. + if (VarScopeHasBindings(pc_) || + funbox->needsExtraBodyVarEnvironmentRegardlessOfBindings()) { + funbox->setFunctionHasExtraBodyVarScope(); + } + } + + // See: JSFunction::needsCallObject() + if (FunctionScopeHasClosedOverBindings(pc_) || + funbox->needsCallObjectRegardlessOfBindings()) { + funbox->setNeedsFunctionEnvironmentObjects(); + } + + if (funbox->isNamedLambda() && !isStandaloneFunction) { + if (!propagateFreeNamesAndMarkClosedOverBindings(pc_->namedLambdaScope())) { + return false; + } + + // See: JSFunction::needsNamedLambdaEnvironment() + if (LexicalScopeHasClosedOverBindings(pc_, pc_->namedLambdaScope())) { + funbox->setNeedsFunctionEnvironmentObjects(); + } + } + + return true; +} + +template <> +bool PerHandlerParser::finishFunction( + bool isStandaloneFunction /* = false */) { + if (!finishFunctionScopes(isStandaloneFunction)) { + return false; + } + + FunctionBox* funbox = pc_->functionBox(); + ScriptStencil& script = funbox->functionStencil(); + + if (funbox->isInterpreted()) { + // BCE will need to generate bytecode for this. + funbox->emitBytecode = true; + this->compilationState_.nonLazyFunctionCount++; + } + + bool hasParameterExprs = funbox->hasParameterExprs; + + if (hasParameterExprs) { + Maybe bindings = newVarScopeData(pc_->varScope()); + if (!bindings) { + return false; + } + funbox->setExtraVarScopeBindings(*bindings); + + MOZ_ASSERT(bool(*bindings) == VarScopeHasBindings(pc_)); + MOZ_ASSERT_IF(!funbox->needsExtraBodyVarEnvironmentRegardlessOfBindings(), + bool(*bindings) == funbox->functionHasExtraBodyVarScope()); + } + + { + Maybe bindings = + newFunctionScopeData(pc_->functionScope(), hasParameterExprs); + if (!bindings) { + return false; + } + funbox->setFunctionScopeBindings(*bindings); + } + + if (funbox->isNamedLambda() && !isStandaloneFunction) { + Maybe bindings = + newLexicalScopeData(pc_->namedLambdaScope()); + if (!bindings) { + return false; + } + funbox->setNamedLambdaBindings(*bindings); + } + + funbox->finishScriptFlags(); + funbox->copyFunctionFields(script); + + if (this->compilationState_.isInitialStencil()) { + ScriptStencilExtra& scriptExtra = funbox->functionExtraStencil(); + funbox->copyFunctionExtraFields(scriptExtra); + funbox->copyScriptExtraFields(scriptExtra); + } + + return true; +} + +template <> +bool PerHandlerParser::finishFunction( + bool isStandaloneFunction /* = false */) { + // The BaseScript for a lazily parsed function needs to know its set of + // free variables and inner functions so that when it is fully parsed, we + // can skip over any already syntax parsed inner functions and still + // retain correct scope information. + + if (!finishFunctionScopes(isStandaloneFunction)) { + return false; + } + + FunctionBox* funbox = pc_->functionBox(); + ScriptStencil& script = funbox->functionStencil(); + + funbox->finishScriptFlags(); + funbox->copyFunctionFields(script); + + ScriptStencilExtra& scriptExtra = funbox->functionExtraStencil(); + funbox->copyFunctionExtraFields(scriptExtra); + funbox->copyScriptExtraFields(scriptExtra); + + // Elide nullptr sentinels from end of binding list. These are inserted for + // each scope regardless of if any bindings are actually closed over. + { + AtomVector& closedOver = pc_->closedOverBindingsForLazy(); + while (!closedOver.empty() && !closedOver.back()) { + closedOver.popBack(); + } + } + + // Check if we will overflow the `ngcthings` field later. + mozilla::CheckedUint32 ngcthings = + mozilla::CheckedUint32(pc_->innerFunctionIndexesForLazy.length()) + + mozilla::CheckedUint32(pc_->closedOverBindingsForLazy().length()); + if (!ngcthings.isValid()) { + ReportAllocationOverflow(fc_); + return false; + } + + // If there are no script-things, we can return early without allocating. + if (ngcthings.value() == 0) { + MOZ_ASSERT(!script.hasGCThings()); + return true; + } + + TaggedScriptThingIndex* cursor = nullptr; + if (!this->compilationState_.allocateGCThingsUninitialized( + fc_, funbox->index(), ngcthings.value(), &cursor)) { + return false; + } + + // Copy inner-function and closed-over-binding info for the stencil. The order + // is important here. We emit functions first, followed by the bindings info. + // The bindings list uses nullptr as delimiter to separates the bindings per + // scope. + // + // See: FullParseHandler::nextLazyInnerFunction(), + // FullParseHandler::nextLazyClosedOverBinding() + for (const ScriptIndex& index : pc_->innerFunctionIndexesForLazy) { + void* raw = &(*cursor++); + new (raw) TaggedScriptThingIndex(index); + } + for (auto binding : pc_->closedOverBindingsForLazy()) { + void* raw = &(*cursor++); + if (binding) { + this->parserAtoms().markUsedByStencil(binding, ParserAtom::Atomize::Yes); + new (raw) TaggedScriptThingIndex(binding); + } else { + new (raw) TaggedScriptThingIndex(); + } + } + + return true; +} + +static YieldHandling GetYieldHandling(GeneratorKind generatorKind) { + if (generatorKind == GeneratorKind::NotGenerator) { + return YieldIsName; + } + return YieldIsKeyword; +} + +static AwaitHandling GetAwaitHandling(FunctionAsyncKind asyncKind) { + if (asyncKind == FunctionAsyncKind::SyncFunction) { + return AwaitIsName; + } + return AwaitIsKeyword; +} + +static FunctionFlags InitialFunctionFlags(FunctionSyntaxKind kind, + GeneratorKind generatorKind, + FunctionAsyncKind asyncKind, + bool isSelfHosting) { + FunctionFlags flags = {}; + + switch (kind) { + case FunctionSyntaxKind::Expression: + flags = (generatorKind == GeneratorKind::NotGenerator && + asyncKind == FunctionAsyncKind::SyncFunction + ? FunctionFlags::INTERPRETED_LAMBDA + : FunctionFlags::INTERPRETED_LAMBDA_GENERATOR_OR_ASYNC); + break; + case FunctionSyntaxKind::Arrow: + flags = FunctionFlags::INTERPRETED_LAMBDA_ARROW; + break; + case FunctionSyntaxKind::Method: + case FunctionSyntaxKind::FieldInitializer: + case FunctionSyntaxKind::StaticClassBlock: + flags = FunctionFlags::INTERPRETED_METHOD; + break; + case FunctionSyntaxKind::ClassConstructor: + case FunctionSyntaxKind::DerivedClassConstructor: + flags = FunctionFlags::INTERPRETED_CLASS_CTOR; + break; + case FunctionSyntaxKind::Getter: + flags = FunctionFlags::INTERPRETED_GETTER; + break; + case FunctionSyntaxKind::Setter: + flags = FunctionFlags::INTERPRETED_SETTER; + break; + default: + MOZ_ASSERT(kind == FunctionSyntaxKind::Statement); + flags = (generatorKind == GeneratorKind::NotGenerator && + asyncKind == FunctionAsyncKind::SyncFunction + ? FunctionFlags::INTERPRETED_NORMAL + : FunctionFlags::INTERPRETED_GENERATOR_OR_ASYNC); + } + + if (isSelfHosting) { + flags.setIsSelfHostedBuiltin(); + } + + return flags; +} + +template +FunctionNode* Parser::standaloneFunction( + const Maybe& parameterListEnd, FunctionSyntaxKind syntaxKind, + GeneratorKind generatorKind, FunctionAsyncKind asyncKind, + Directives inheritedDirectives, Directives* newDirectives) { + MOZ_ASSERT(checkOptionsCalled_); + // Skip prelude. + TokenKind tt; + if (!tokenStream.getToken(&tt, TokenStream::SlashIsRegExp)) { + return null(); + } + if (asyncKind == FunctionAsyncKind::AsyncFunction) { + MOZ_ASSERT(tt == TokenKind::Async); + if (!tokenStream.getToken(&tt, TokenStream::SlashIsRegExp)) { + return null(); + } + } + MOZ_ASSERT(tt == TokenKind::Function); + + if (!tokenStream.getToken(&tt)) { + return null(); + } + if (generatorKind == GeneratorKind::Generator) { + MOZ_ASSERT(tt == TokenKind::Mul); + if (!tokenStream.getToken(&tt)) { + return null(); + } + } + + // Skip function name, if present. + TaggedParserAtomIndex explicitName; + if (TokenKindIsPossibleIdentifierName(tt)) { + explicitName = anyChars.currentName(); + } else { + anyChars.ungetToken(); + } + + FunctionNodeType funNode = handler_.newFunction(syntaxKind, pos()); + if (!funNode) { + return null(); + } + + ParamsBodyNodeType argsbody = handler_.newParamsBody(pos()); + if (!argsbody) { + return null(); + } + funNode->setBody(argsbody); + + bool isSelfHosting = options().selfHostingMode; + FunctionFlags flags = + InitialFunctionFlags(syntaxKind, generatorKind, asyncKind, isSelfHosting); + FunctionBox* funbox = + newFunctionBox(funNode, explicitName, flags, /* toStringStart = */ 0, + inheritedDirectives, generatorKind, asyncKind); + if (!funbox) { + return null(); + } + + // Function is not syntactically part of another script. + MOZ_ASSERT(funbox->index() == CompilationStencil::TopLevelIndex); + + funbox->initStandalone(this->compilationState_.scopeContext, syntaxKind); + + SourceParseContext funpc(this, funbox, newDirectives); + if (!funpc.init()) { + return null(); + } + + YieldHandling yieldHandling = GetYieldHandling(generatorKind); + AwaitHandling awaitHandling = GetAwaitHandling(asyncKind); + AutoAwaitIsKeyword awaitIsKeyword(this, + awaitHandling); + if (!functionFormalParametersAndBody(InAllowed, yieldHandling, &funNode, + syntaxKind, parameterListEnd, + /* isStandaloneFunction = */ true)) { + return null(); + } + + if (!tokenStream.getToken(&tt, TokenStream::SlashIsRegExp)) { + return null(); + } + if (tt != TokenKind::Eof) { + error(JSMSG_GARBAGE_AFTER_INPUT, "function body", TokenKindToDesc(tt)); + return null(); + } + + if (!CheckParseTree(this->fc_, alloc_, funNode)) { + return null(); + } + + ParseNode* node = funNode; + // 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(this->fc_, this->parserAtoms(), &node, &handler_)) { + return null(); + } + } + funNode = &node->as(); + + if (!checkForUndefinedPrivateFields(nullptr)) { + return null(); + } + + if (!this->setSourceMapInfo()) { + return null(); + } + + return funNode; +} + +template +typename ParseHandler::LexicalScopeNodeType +GeneralParser::functionBody(InHandling inHandling, + YieldHandling yieldHandling, + FunctionSyntaxKind kind, + FunctionBodyType type) { + MOZ_ASSERT(pc_->isFunctionBox()); + +#ifdef DEBUG + uint32_t startYieldOffset = pc_->lastYieldOffset; +#endif + + Node body; + if (type == StatementListBody) { + bool inheritedStrict = pc_->sc()->strict(); + body = statementList(yieldHandling); + if (!body) { + return null(); + } + + // When we transitioned from non-strict to strict mode, we need to + // validate that all parameter names are valid strict mode names. + if (!inheritedStrict && pc_->sc()->strict()) { + MOZ_ASSERT(pc_->sc()->hasExplicitUseStrict(), + "strict mode should only change when a 'use strict' directive " + "is present"); + if (!hasValidSimpleStrictParameterNames()) { + // Request that this function be reparsed as strict to report + // the invalid parameter name at the correct source location. + pc_->newDirectives->setStrict(); + return null(); + } + } + } else { + MOZ_ASSERT(type == ExpressionBody); + + // Async functions are implemented as generators, and generators are + // assumed to be statement lists, to prepend initial `yield`. + ListNodeType stmtList = null(); + if (pc_->isAsync()) { + stmtList = handler_.newStatementList(pos()); + if (!stmtList) { + return null(); + } + } + + Node kid = assignExpr(inHandling, yieldHandling, TripledotProhibited); + if (!kid) { + return null(); + } + + body = handler_.newExpressionBody(kid); + if (!body) { + return null(); + } + + if (pc_->isAsync()) { + handler_.addStatementToList(stmtList, body); + body = stmtList; + } + } + + MOZ_ASSERT_IF(!pc_->isGenerator() && !pc_->isAsync(), + pc_->lastYieldOffset == startYieldOffset); + MOZ_ASSERT_IF(pc_->isGenerator(), kind != FunctionSyntaxKind::Arrow); + MOZ_ASSERT_IF(pc_->isGenerator(), type == StatementListBody); + + if (pc_->needsDotGeneratorName()) { + MOZ_ASSERT_IF(!pc_->isAsync(), type == StatementListBody); + if (!pc_->declareDotGeneratorName()) { + return null(); + } + if (pc_->isGenerator()) { + NameNodeType generator = newDotGeneratorName(); + if (!generator) { + return null(); + } + if (!handler_.prependInitialYield(handler_.asList(body), generator)) { + return null(); + } + } + } + + // Declare the 'arguments', 'this', and 'new.target' bindings if necessary + // before finishing up the scope so these special bindings get marked as + // closed over if necessary. Arrow functions don't have these bindings. + if (kind != FunctionSyntaxKind::Arrow) { + bool canSkipLazyClosedOverBindings = handler_.reuseClosedOverBindings(); + if (!pc_->declareFunctionArgumentsObject(usedNames_, + canSkipLazyClosedOverBindings)) { + return null(); + } + if (!pc_->declareFunctionThis(usedNames_, canSkipLazyClosedOverBindings)) { + return null(); + } + if (!pc_->declareNewTarget(usedNames_, canSkipLazyClosedOverBindings)) { + return null(); + } + } + + return finishLexicalScope(pc_->varScope(), body, ScopeKind::FunctionLexical); +} + +template +bool GeneralParser::matchOrInsertSemicolon( + Modifier modifier /* = TokenStream::SlashIsRegExp */) { + TokenKind tt = TokenKind::Eof; + if (!tokenStream.peekTokenSameLine(&tt, modifier)) { + return false; + } + if (tt != TokenKind::Eof && tt != TokenKind::Eol && tt != TokenKind::Semi && + tt != TokenKind::RightCurly) { + /* + * When current token is `await` and it's outside of async function, + * it's possibly intended to be an await expression. + * + * await f(); + * ^ + * | + * tried to insert semicolon here + * + * Detect this situation and throw an understandable error. Otherwise + * we'd throw a confusing "unexpected token: (unexpected token)" error. + */ + if (!pc_->isAsync() && anyChars.currentToken().type == TokenKind::Await) { + error(JSMSG_AWAIT_OUTSIDE_ASYNC_OR_MODULE); + return false; + } + if (!yieldExpressionsSupported() && + anyChars.currentToken().type == TokenKind::Yield) { + error(JSMSG_YIELD_OUTSIDE_GENERATOR); + return false; + } + + /* Advance the scanner for proper error location reporting. */ + tokenStream.consumeKnownToken(tt, modifier); + error(JSMSG_UNEXPECTED_TOKEN_NO_EXPECT, TokenKindToDesc(tt)); + return false; + } + bool matched; + return tokenStream.matchToken(&matched, TokenKind::Semi, modifier); +} + +bool ParserBase::leaveInnerFunction(ParseContext* outerpc) { + MOZ_ASSERT(pc_ != outerpc); + + MOZ_ASSERT_IF(outerpc->isFunctionBox(), + outerpc->functionBox()->index() < pc_->functionBox()->index()); + + // If the current function allows super.property but cannot have a home + // object, i.e., it is an arrow function, we need to propagate the flag to + // the outer ParseContext. + if (pc_->superScopeNeedsHomeObject()) { + if (!pc_->isArrowFunction()) { + MOZ_ASSERT(pc_->functionBox()->needsHomeObject()); + } else { + outerpc->setSuperScopeNeedsHomeObject(); + } + } + + // Lazy functions inner to another lazy function need to be remembered by + // the inner function so that if the outer function is eventually parsed + // we do not need any further parsing or processing of the inner function. + // + // Append the inner function index here unconditionally; the vector is only + // used if the Parser using outerpc is a syntax parsing. See + // GeneralParser::finishFunction. + if (!outerpc->innerFunctionIndexesForLazy.append( + pc_->functionBox()->index())) { + return false; + } + + PropagateTransitiveParseFlags(pc_->functionBox(), outerpc->sc()); + + return true; +} + +TaggedParserAtomIndex ParserBase::prefixAccessorName( + PropertyType propType, TaggedParserAtomIndex propAtom) { + StringBuffer prefixed(fc_); + if (propType == PropertyType::Setter) { + if (!prefixed.append("set ")) { + return TaggedParserAtomIndex::null(); + } + } else { + if (!prefixed.append("get ")) { + return TaggedParserAtomIndex::null(); + } + } + if (!prefixed.append(this->parserAtoms(), propAtom)) { + return TaggedParserAtomIndex::null(); + } + return prefixed.finishParserAtom(this->parserAtoms(), fc_); +} + +template +void GeneralParser::setFunctionStartAtPosition( + FunctionBox* funbox, TokenPos pos) const { + uint32_t startLine, startColumn; + tokenStream.computeLineAndColumn(pos.begin, &startLine, &startColumn); + + // NOTE: `Debugger::CallData::findScripts` relies on sourceStart and + // lineno/column referring to the same location. + funbox->setStart(pos.begin, startLine, startColumn); +} + +template +void GeneralParser::setFunctionStartAtCurrentToken( + FunctionBox* funbox) const { + setFunctionStartAtPosition(funbox, anyChars.currentToken().pos); +} + +template +bool GeneralParser::functionArguments( + YieldHandling yieldHandling, FunctionSyntaxKind kind, + FunctionNodeType funNode) { + FunctionBox* funbox = pc_->functionBox(); + + // Modifier for the following tokens. + // TokenStream::SlashIsDiv for the following cases: + // async a => 1 + // ^ + // + // (a) => 1 + // ^ + // + // async (a) => 1 + // ^ + // + // function f(a) {} + // ^ + // + // TokenStream::SlashIsRegExp for the following case: + // a => 1 + // ^ + Modifier firstTokenModifier = + kind != FunctionSyntaxKind::Arrow || funbox->isAsync() + ? TokenStream::SlashIsDiv + : TokenStream::SlashIsRegExp; + TokenKind tt; + if (!tokenStream.getToken(&tt, firstTokenModifier)) { + return false; + } + + if (kind == FunctionSyntaxKind::Arrow && TokenKindIsPossibleIdentifier(tt)) { + // Record the start of function source (for FunctionToString). + setFunctionStartAtCurrentToken(funbox); + + ParamsBodyNodeType argsbody = handler_.newParamsBody(pos()); + if (!argsbody) { + return false; + } + handler_.setFunctionFormalParametersAndBody(funNode, argsbody); + + TaggedParserAtomIndex name = bindingIdentifier(yieldHandling); + if (!name) { + return false; + } + + constexpr bool disallowDuplicateParams = true; + bool duplicatedParam = false; + if (!notePositionalFormalParameter(funNode, name, pos().begin, + disallowDuplicateParams, + &duplicatedParam)) { + return false; + } + MOZ_ASSERT(!duplicatedParam); + MOZ_ASSERT(pc_->positionalFormalParameterNames().length() == 1); + + funbox->setLength(1); + funbox->setArgCount(1); + return true; + } + + if (tt != TokenKind::LeftParen) { + error(kind == FunctionSyntaxKind::Arrow ? JSMSG_BAD_ARROW_ARGS + : JSMSG_PAREN_BEFORE_FORMAL); + return false; + } + + // Record the start of function source (for FunctionToString). + setFunctionStartAtCurrentToken(funbox); + + ParamsBodyNodeType argsbody = handler_.newParamsBody(pos()); + if (!argsbody) { + return false; + } + handler_.setFunctionFormalParametersAndBody(funNode, argsbody); + + bool matched; + if (!tokenStream.matchToken(&matched, TokenKind::RightParen, + TokenStream::SlashIsRegExp)) { + return false; + } + if (!matched) { + bool hasRest = false; + bool hasDefault = false; + bool duplicatedParam = false; + bool disallowDuplicateParams = + kind == FunctionSyntaxKind::Arrow || + kind == FunctionSyntaxKind::Method || + kind == FunctionSyntaxKind::FieldInitializer || + kind == FunctionSyntaxKind::ClassConstructor; + AtomVector& positionalFormals = pc_->positionalFormalParameterNames(); + + if (kind == FunctionSyntaxKind::Getter) { + error(JSMSG_ACCESSOR_WRONG_ARGS, "getter", "no", "s"); + return false; + } + + while (true) { + if (hasRest) { + error(JSMSG_PARAMETER_AFTER_REST); + return false; + } + + TokenKind tt; + if (!tokenStream.getToken(&tt, TokenStream::SlashIsRegExp)) { + return false; + } + + if (tt == TokenKind::TripleDot) { + if (kind == FunctionSyntaxKind::Setter) { + error(JSMSG_ACCESSOR_WRONG_ARGS, "setter", "one", ""); + return false; + } + + disallowDuplicateParams = true; + if (duplicatedParam) { + // Has duplicated args before the rest parameter. + error(JSMSG_BAD_DUP_ARGS); + return false; + } + + hasRest = true; + funbox->setHasRest(); + + if (!tokenStream.getToken(&tt)) { + return false; + } + + if (!TokenKindIsPossibleIdentifier(tt) && + tt != TokenKind::LeftBracket && tt != TokenKind::LeftCurly) { + error(JSMSG_NO_REST_NAME); + return false; + } + } + + switch (tt) { + case TokenKind::LeftBracket: + case TokenKind::LeftCurly: { + disallowDuplicateParams = true; + if (duplicatedParam) { + // Has duplicated args before the destructuring parameter. + error(JSMSG_BAD_DUP_ARGS); + return false; + } + + funbox->hasDestructuringArgs = true; + + Node destruct = destructuringDeclarationWithoutYieldOrAwait( + DeclarationKind::FormalParameter, yieldHandling, tt); + if (!destruct) { + return false; + } + + if (!noteDestructuredPositionalFormalParameter(funNode, destruct)) { + return false; + } + + break; + } + + default: { + if (!TokenKindIsPossibleIdentifier(tt)) { + error(JSMSG_MISSING_FORMAL); + return false; + } + + TaggedParserAtomIndex name = bindingIdentifier(yieldHandling); + if (!name) { + return false; + } + + if (!notePositionalFormalParameter(funNode, name, pos().begin, + disallowDuplicateParams, + &duplicatedParam)) { + return false; + } + if (duplicatedParam) { + funbox->hasDuplicateParameters = true; + } + + break; + } + } + + if (positionalFormals.length() >= ARGNO_LIMIT) { + error(JSMSG_TOO_MANY_FUN_ARGS); + return false; + } + + bool matched; + if (!tokenStream.matchToken(&matched, TokenKind::Assign, + TokenStream::SlashIsRegExp)) { + return false; + } + if (matched) { + if (hasRest) { + error(JSMSG_REST_WITH_DEFAULT); + return false; + } + disallowDuplicateParams = true; + if (duplicatedParam) { + error(JSMSG_BAD_DUP_ARGS); + return false; + } + + if (!hasDefault) { + hasDefault = true; + + // The Function.length property is the number of formals + // before the first default argument. + funbox->setLength(positionalFormals.length() - 1); + } + funbox->hasParameterExprs = true; + + Node def_expr = assignExprWithoutYieldOrAwait(yieldHandling); + if (!def_expr) { + return false; + } + if (!handler_.setLastFunctionFormalParameterDefault(funNode, + def_expr)) { + return false; + } + } + + // Setter syntax uniquely requires exactly one argument. + if (kind == FunctionSyntaxKind::Setter) { + break; + } + + if (!tokenStream.matchToken(&matched, TokenKind::Comma, + TokenStream::SlashIsRegExp)) { + return false; + } + if (!matched) { + break; + } + + if (!hasRest) { + if (!tokenStream.peekToken(&tt, TokenStream::SlashIsRegExp)) { + return false; + } + if (tt == TokenKind::RightParen) { + break; + } + } + } + + TokenKind tt; + if (!tokenStream.getToken(&tt, TokenStream::SlashIsRegExp)) { + return false; + } + if (tt != TokenKind::RightParen) { + if (kind == FunctionSyntaxKind::Setter) { + error(JSMSG_ACCESSOR_WRONG_ARGS, "setter", "one", ""); + return false; + } + + error(JSMSG_PAREN_AFTER_FORMAL); + return false; + } + + if (!hasDefault) { + funbox->setLength(positionalFormals.length() - hasRest); + } + + funbox->setArgCount(positionalFormals.length()); + } else if (kind == FunctionSyntaxKind::Setter) { + error(JSMSG_ACCESSOR_WRONG_ARGS, "setter", "one", ""); + return false; + } + + return true; +} + +template +bool Parser::skipLazyInnerFunction( + FunctionNode* funNode, uint32_t toStringStart, bool tryAnnexB) { + // When a lazily-parsed function is called, we only fully parse (and emit) + // that function, not any of its nested children. The initial syntax-only + // parse recorded the free variables of nested functions and their extents, + // so we can skip over them after accounting for their free variables. + + MOZ_ASSERT(pc_->isOutermostOfCurrentCompile()); + handler_.nextLazyInnerFunction(); + const ScriptStencil& cachedData = handler_.cachedScriptData(); + const ScriptStencilExtra& cachedExtra = handler_.cachedScriptExtra(); + MOZ_ASSERT(toStringStart == cachedExtra.extent.toStringStart); + + FunctionBox* funbox = newFunctionBox(funNode, cachedData, cachedExtra); + if (!funbox) { + return false; + } + + ScriptStencil& script = funbox->functionStencil(); + funbox->copyFunctionFields(script); + + // If the inner lazy function is class constructor, connect it to the class + // statement/expression we are parsing. + if (funbox->isClassConstructor()) { + auto classStmt = + pc_->template findInnermostStatement(); + MOZ_ASSERT(!classStmt->constructorBox); + classStmt->constructorBox = funbox; + } + + MOZ_ASSERT_IF(pc_->isFunctionBox(), + pc_->functionBox()->index() < funbox->index()); + + PropagateTransitiveParseFlags(funbox, pc_->sc()); + + if (!tokenStream.advance(funbox->extent().sourceEnd)) { + return false; + } + + // Append possible Annex B function box only upon successfully parsing. + if (tryAnnexB && + !pc_->innermostScope()->addPossibleAnnexBFunctionBox(pc_, funbox)) { + return false; + } + + return true; +} + +template +bool Parser::skipLazyInnerFunction( + FunctionNodeType funNode, uint32_t toStringStart, bool tryAnnexB) { + MOZ_CRASH("Cannot skip lazy inner functions when syntax parsing"); +} + +template +bool GeneralParser::skipLazyInnerFunction( + FunctionNodeType funNode, uint32_t toStringStart, bool tryAnnexB) { + return asFinalParser()->skipLazyInnerFunction(funNode, toStringStart, + tryAnnexB); +} + +template +bool GeneralParser::addExprAndGetNextTemplStrToken( + YieldHandling yieldHandling, ListNodeType nodeList, TokenKind* ttp) { + Node pn = expr(InAllowed, yieldHandling, TripledotProhibited); + if (!pn) { + return false; + } + handler_.addList(nodeList, pn); + + TokenKind tt; + if (!tokenStream.getToken(&tt, TokenStream::SlashIsRegExp)) { + return false; + } + if (tt != TokenKind::RightCurly) { + error(JSMSG_TEMPLSTR_UNTERM_EXPR); + return false; + } + + return tokenStream.getTemplateToken(ttp); +} + +template +bool GeneralParser::taggedTemplate( + YieldHandling yieldHandling, ListNodeType tagArgsList, TokenKind tt) { + CallSiteNodeType callSiteObjNode = handler_.newCallSiteObject(pos().begin); + if (!callSiteObjNode) { + return false; + } + handler_.addList(tagArgsList, callSiteObjNode); + + pc_->sc()->setHasCallSiteObj(); + + while (true) { + if (!appendToCallSiteObj(callSiteObjNode)) { + return false; + } + if (tt != TokenKind::TemplateHead) { + break; + } + + if (!addExprAndGetNextTemplStrToken(yieldHandling, tagArgsList, &tt)) { + return false; + } + } + handler_.setEndPosition(tagArgsList, callSiteObjNode); + return true; +} + +template +typename ParseHandler::ListNodeType +GeneralParser::templateLiteral( + YieldHandling yieldHandling) { + NameNodeType literal = noSubstitutionUntaggedTemplate(); + if (!literal) { + return null(); + } + + ListNodeType nodeList = + handler_.newList(ParseNodeKind::TemplateStringListExpr, literal); + if (!nodeList) { + return null(); + } + + TokenKind tt; + do { + if (!addExprAndGetNextTemplStrToken(yieldHandling, nodeList, &tt)) { + return null(); + } + + literal = noSubstitutionUntaggedTemplate(); + if (!literal) { + return null(); + } + + handler_.addList(nodeList, literal); + } while (tt == TokenKind::TemplateHead); + return nodeList; +} + +template +typename ParseHandler::FunctionNodeType +GeneralParser::functionDefinition( + FunctionNodeType funNode, uint32_t toStringStart, InHandling inHandling, + YieldHandling yieldHandling, TaggedParserAtomIndex funName, + FunctionSyntaxKind kind, GeneratorKind generatorKind, + FunctionAsyncKind asyncKind, bool tryAnnexB /* = false */) { + MOZ_ASSERT_IF(kind == FunctionSyntaxKind::Statement, funName); + + // If we see any inner function, note it on our current context. The bytecode + // emitter may eliminate the function later, but we use a conservative + // definition for consistency between lazy and full parsing. + pc_->sc()->setHasInnerFunctions(); + + // When fully parsing a lazy script, we do not fully reparse its inner + // functions, which are also lazy. Instead, their free variables and source + // extents are recorded and may be skipped. + if (handler_.reuseLazyInnerFunctions()) { + if (!skipLazyInnerFunction(funNode, toStringStart, tryAnnexB)) { + return null(); + } + + return funNode; + } + + bool isSelfHosting = options().selfHostingMode; + FunctionFlags flags = + InitialFunctionFlags(kind, generatorKind, asyncKind, isSelfHosting); + + // Self-hosted functions with special function names require extended slots + // for various purposes. + bool forceExtended = + isSelfHosting && funName && + this->parserAtoms().isExtendedUnclonedSelfHostedFunctionName(funName); + if (forceExtended) { + flags.setIsExtended(); + } + + // Speculatively parse using the directives of the parent parsing context. + // If a directive is encountered (e.g., "use strict") that changes how the + // function should have been parsed, we backup and reparse with the new set + // of directives. + Directives directives(pc_); + Directives newDirectives = directives; + + Position start(tokenStream); + auto startObj = this->compilationState_.getPosition(); + + // Parse the inner function. The following is a loop as we may attempt to + // reparse a function due to failed syntax parsing and encountering new + // "use foo" directives. + while (true) { + if (trySyntaxParseInnerFunction(&funNode, funName, flags, toStringStart, + inHandling, yieldHandling, kind, + generatorKind, asyncKind, tryAnnexB, + directives, &newDirectives)) { + break; + } + + // Return on error. + if (anyChars.hadError() || directives == newDirectives) { + return null(); + } + + // Assignment must be monotonic to prevent infinitely attempting to + // reparse. + MOZ_ASSERT_IF(directives.strict(), newDirectives.strict()); + MOZ_ASSERT_IF(directives.asmJS(), newDirectives.asmJS()); + directives = newDirectives; + + // Rewind to retry parsing with new directives applied. + tokenStream.rewind(start); + this->compilationState_.rewind(startObj); + + // functionFormalParametersAndBody may have already set body before failing. + handler_.setFunctionFormalParametersAndBody(funNode, null()); + } + + return funNode; +} + +template +bool Parser::advancePastSyntaxParsedFunction( + SyntaxParser* syntaxParser) { + MOZ_ASSERT(getSyntaxParser() == syntaxParser); + + // Advance this parser over tokens processed by the syntax parser. + Position currentSyntaxPosition(syntaxParser->tokenStream); + if (!tokenStream.fastForward(currentSyntaxPosition, syntaxParser->anyChars)) { + return false; + } + + anyChars.adoptState(syntaxParser->anyChars); + tokenStream.adoptState(syntaxParser->tokenStream); + return true; +} + +template +bool Parser::trySyntaxParseInnerFunction( + FunctionNode** funNode, TaggedParserAtomIndex explicitName, + FunctionFlags flags, uint32_t toStringStart, InHandling inHandling, + YieldHandling yieldHandling, FunctionSyntaxKind kind, + GeneratorKind generatorKind, FunctionAsyncKind asyncKind, bool tryAnnexB, + Directives inheritedDirectives, Directives* newDirectives) { + // Try a syntax parse for this inner function. + do { + // If we're assuming this function is an IIFE, always perform a full + // parse to avoid the overhead of a lazy syntax-only parse. Although + // the prediction may be incorrect, IIFEs are common enough that it + // pays off for lots of code. + if ((*funNode)->isLikelyIIFE() && + generatorKind == GeneratorKind::NotGenerator && + asyncKind == FunctionAsyncKind::SyncFunction) { + break; + } + + SyntaxParser* syntaxParser = getSyntaxParser(); + if (!syntaxParser) { + break; + } + + UsedNameTracker::RewindToken token = usedNames_.getRewindToken(); + auto statePosition = this->compilationState_.getPosition(); + + // Move the syntax parser to the current position in the stream. In the + // common case this seeks forward, but it'll also seek backward *at least* + // when arrow functions appear inside arrow function argument defaults + // (because we rewind to reparse arrow functions once we're certain they're + // arrow functions): + // + // var x = (y = z => 2) => q; + // // ^ we first seek to here to syntax-parse this function + // // ^ then we seek back to here to syntax-parse the outer function + Position currentPosition(tokenStream); + if (!syntaxParser->tokenStream.seekTo(currentPosition, anyChars)) { + return false; + } + + // Make a FunctionBox before we enter the syntax parser, because |pn| + // still expects a FunctionBox to be attached to it during BCE, and + // the syntax parser cannot attach one to it. + FunctionBox* funbox = + newFunctionBox(*funNode, explicitName, flags, toStringStart, + inheritedDirectives, generatorKind, asyncKind); + if (!funbox) { + return false; + } + funbox->initWithEnclosingParseContext(pc_, kind); + + SyntaxParseHandler::Node syntaxNode = + syntaxParser->innerFunctionForFunctionBox( + SyntaxParseHandler::NodeGeneric, pc_, funbox, inHandling, + yieldHandling, kind, newDirectives); + if (!syntaxNode) { + if (syntaxParser->hadAbortedSyntaxParse()) { + // Try again with a full parse. UsedNameTracker needs to be + // rewound to just before we tried the syntax parse for + // correctness. + syntaxParser->clearAbortedSyntaxParse(); + usedNames_.rewind(token); + this->compilationState_.rewind(statePosition); + MOZ_ASSERT(!fc_->hadErrors()); + break; + } + return false; + } + + if (!advancePastSyntaxParsedFunction(syntaxParser)) { + return false; + } + + // Update the end position of the parse node. + (*funNode)->pn_pos.end = anyChars.currentToken().pos.end; + + // Append possible Annex B function box only upon successfully parsing. + if (tryAnnexB) { + if (!pc_->innermostScope()->addPossibleAnnexBFunctionBox(pc_, funbox)) { + return false; + } + } + + return true; + } while (false); + + // We failed to do a syntax parse above, so do the full parse. + FunctionNodeType innerFunc = + innerFunction(*funNode, pc_, explicitName, flags, toStringStart, + inHandling, yieldHandling, kind, generatorKind, asyncKind, + tryAnnexB, inheritedDirectives, newDirectives); + if (!innerFunc) { + return false; + } + + *funNode = innerFunc; + return true; +} + +template +bool Parser::trySyntaxParseInnerFunction( + FunctionNodeType* funNode, TaggedParserAtomIndex explicitName, + FunctionFlags flags, uint32_t toStringStart, InHandling inHandling, + YieldHandling yieldHandling, FunctionSyntaxKind kind, + GeneratorKind generatorKind, FunctionAsyncKind asyncKind, bool tryAnnexB, + Directives inheritedDirectives, Directives* newDirectives) { + // This is already a syntax parser, so just parse the inner function. + FunctionNodeType innerFunc = + innerFunction(*funNode, pc_, explicitName, flags, toStringStart, + inHandling, yieldHandling, kind, generatorKind, asyncKind, + tryAnnexB, inheritedDirectives, newDirectives); + + if (!innerFunc) { + return false; + } + + *funNode = innerFunc; + return true; +} + +template +inline bool GeneralParser::trySyntaxParseInnerFunction( + FunctionNodeType* funNode, TaggedParserAtomIndex explicitName, + FunctionFlags flags, uint32_t toStringStart, InHandling inHandling, + YieldHandling yieldHandling, FunctionSyntaxKind kind, + GeneratorKind generatorKind, FunctionAsyncKind asyncKind, bool tryAnnexB, + Directives inheritedDirectives, Directives* newDirectives) { + return asFinalParser()->trySyntaxParseInnerFunction( + funNode, explicitName, flags, toStringStart, inHandling, yieldHandling, + kind, generatorKind, asyncKind, tryAnnexB, inheritedDirectives, + newDirectives); +} + +template +typename ParseHandler::FunctionNodeType +GeneralParser::innerFunctionForFunctionBox( + FunctionNodeType funNode, ParseContext* outerpc, FunctionBox* funbox, + InHandling inHandling, YieldHandling yieldHandling, FunctionSyntaxKind kind, + Directives* newDirectives) { + // Note that it is possible for outerpc != this->pc_, as we may be + // attempting to syntax parse an inner function from an outer full + // parser. In that case, outerpc is a SourceParseContext from the full parser + // instead of the current top of the stack of the syntax parser. + + // Push a new ParseContext. + SourceParseContext funpc(this, funbox, newDirectives); + if (!funpc.init()) { + return null(); + } + + if (!functionFormalParametersAndBody(inHandling, yieldHandling, &funNode, + kind)) { + return null(); + } + + if (!leaveInnerFunction(outerpc)) { + return null(); + } + + return funNode; +} + +template +typename ParseHandler::FunctionNodeType +GeneralParser::innerFunction( + FunctionNodeType funNode, ParseContext* outerpc, + TaggedParserAtomIndex explicitName, FunctionFlags flags, + uint32_t toStringStart, InHandling inHandling, YieldHandling yieldHandling, + FunctionSyntaxKind kind, GeneratorKind generatorKind, + FunctionAsyncKind asyncKind, bool tryAnnexB, Directives inheritedDirectives, + Directives* newDirectives) { + // Note that it is possible for outerpc != this->pc_, as we may be + // attempting to syntax parse an inner function from an outer full + // parser. In that case, outerpc is a SourceParseContext from the full parser + // instead of the current top of the stack of the syntax parser. + + FunctionBox* funbox = + newFunctionBox(funNode, explicitName, flags, toStringStart, + inheritedDirectives, generatorKind, asyncKind); + if (!funbox) { + return null(); + } + funbox->initWithEnclosingParseContext(outerpc, kind); + + FunctionNodeType innerFunc = innerFunctionForFunctionBox( + funNode, outerpc, funbox, inHandling, yieldHandling, kind, newDirectives); + if (!innerFunc) { + return null(); + } + + // Append possible Annex B function box only upon successfully parsing. + if (tryAnnexB) { + if (!pc_->innermostScope()->addPossibleAnnexBFunctionBox(pc_, funbox)) { + return null(); + } + } + + return innerFunc; +} + +template +bool GeneralParser::appendToCallSiteObj( + CallSiteNodeType callSiteObj) { + Node cookedNode = noSubstitutionTaggedTemplate(); + if (!cookedNode) { + return false; + } + + auto atom = tokenStream.getRawTemplateStringAtom(); + if (!atom) { + return false; + } + NameNodeType rawNode = handler_.newTemplateStringLiteral(atom, pos()); + if (!rawNode) { + return false; + } + + handler_.addToCallSiteObject(callSiteObj, rawNode, cookedNode); + return true; +} + +template +FunctionNode* Parser::standaloneLazyFunction( + CompilationInput& input, uint32_t toStringStart, bool strict, + GeneratorKind generatorKind, FunctionAsyncKind asyncKind) { + MOZ_ASSERT(checkOptionsCalled_); + + FunctionSyntaxKind syntaxKind = input.functionSyntaxKind(); + FunctionNodeType funNode = handler_.newFunction(syntaxKind, pos()); + if (!funNode) { + return null(); + } + + TaggedParserAtomIndex displayAtom = + this->getCompilationState().previousParseCache.displayAtom(); + + Directives directives(strict); + FunctionBox* funbox = + newFunctionBox(funNode, displayAtom, input.functionFlags(), toStringStart, + directives, generatorKind, asyncKind); + if (!funbox) { + return null(); + } + const ScriptStencilExtra& funExtra = + this->getCompilationState().previousParseCache.funExtra(); + funbox->initFromLazyFunction( + funExtra, this->getCompilationState().scopeContext, syntaxKind); + if (funbox->useMemberInitializers()) { + funbox->setMemberInitializers(funExtra.memberInitializers()); + } + + Directives newDirectives = directives; + SourceParseContext funpc(this, funbox, &newDirectives); + if (!funpc.init()) { + return null(); + } + + // Our tokenStream has no current token, so funNode's position is garbage. + // Substitute the position of the first token in our source. If the + // function is a not-async arrow, use TokenStream::SlashIsRegExp to keep + // verifyConsistentModifier from complaining (we will use + // TokenStream::SlashIsRegExp in functionArguments). + Modifier modifier = (input.functionFlags().isArrow() && + asyncKind == FunctionAsyncKind::SyncFunction) + ? TokenStream::SlashIsRegExp + : TokenStream::SlashIsDiv; + if (!tokenStream.peekTokenPos(&funNode->pn_pos, modifier)) { + return null(); + } + + YieldHandling yieldHandling = GetYieldHandling(generatorKind); + + if (funbox->isSyntheticFunction()) { + // Currently default class constructors are the only synthetic function that + // supports delazification. + MOZ_ASSERT(funbox->isClassConstructor()); + MOZ_ASSERT(funbox->extent().toStringStart == funbox->extent().sourceStart); + + HasHeritage hasHeritage = funbox->isDerivedClassConstructor() + ? HasHeritage::Yes + : HasHeritage::No; + TokenPos synthesizedBodyPos(funbox->extent().toStringStart, + funbox->extent().toStringEnd); + + // Reset pos() to the `class` keyword for predictable results. + tokenStream.consumeKnownToken(TokenKind::Class); + + if (!this->synthesizeConstructorBody(synthesizedBodyPos, hasHeritage, + funNode, funbox)) { + return null(); + } + } else { + if (!functionFormalParametersAndBody(InAllowed, yieldHandling, &funNode, + syntaxKind)) { + MOZ_ASSERT(directives == newDirectives); + return null(); + } + } + + if (!CheckParseTree(this->fc_, alloc_, funNode)) { + return null(); + } + + ParseNode* node = funNode; + // 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(this->fc_, this->parserAtoms(), &node, &handler_)) { + return null(); + } + } + funNode = &node->as(); + + return funNode; +} + +void ParserBase::setFunctionEndFromCurrentToken(FunctionBox* funbox) const { + if (compilationState_.isInitialStencil()) { + MOZ_ASSERT(anyChars.currentToken().type != TokenKind::Eof); + MOZ_ASSERT(anyChars.currentToken().type < TokenKind::Limit); + funbox->setEnd(anyChars.currentToken().pos.end); + } else { + // If we're delazifying an arrow function with expression body and + // the expression is also a function, we arrive here immediately after + // skipping the function by Parser::skipLazyInnerFunction. + // + // a => b => c + // ^ + // | + // we're here + // + // In that case, the current token's type field is either Limit or + // poisoned. + // We shouldn't read the value if it's poisoned. + // See TokenStreamSpecific::advance and + // mfbt/MemoryChecking.h for more details. + // + // Also, in delazification, the FunctionBox should already have the + // correct extent, and we shouldn't overwrite it here. + // See ScriptStencil variant of PerHandlerParser::newFunctionBox. +#if !defined(MOZ_ASAN) && !defined(MOZ_MSAN) && !defined(MOZ_VALGRIND) + MOZ_ASSERT(anyChars.currentToken().type != TokenKind::Eof); +#endif + MOZ_ASSERT(funbox->extent().sourceEnd == anyChars.currentToken().pos.end); + } +} + +template +bool GeneralParser::functionFormalParametersAndBody( + InHandling inHandling, YieldHandling yieldHandling, + FunctionNodeType* funNode, FunctionSyntaxKind kind, + const Maybe& parameterListEnd /* = Nothing() */, + bool isStandaloneFunction /* = false */) { + // Given a properly initialized parse context, try to parse an actual + // function without concern for conversion to strict mode, use of lazy + // parsing and such. + + FunctionBox* funbox = pc_->functionBox(); + + if (kind == FunctionSyntaxKind::ClassConstructor || + kind == FunctionSyntaxKind::DerivedClassConstructor) { + if (!noteUsedName(TaggedParserAtomIndex::WellKnown::dotInitializers())) { + return false; + } + } + + // See below for an explanation why arrow function parameters and arrow + // function bodies are parsed with different yield/await settings. + { + AwaitHandling awaitHandling = + kind == FunctionSyntaxKind::StaticClassBlock ? AwaitIsDisallowed + : (funbox->isAsync() || + (kind == FunctionSyntaxKind::Arrow && awaitIsKeyword())) + ? AwaitIsKeyword + : AwaitIsName; + AutoAwaitIsKeyword awaitIsKeyword(this, awaitHandling); + AutoInParametersOfAsyncFunction inParameters( + this, funbox->isAsync()); + if (!functionArguments(yieldHandling, kind, *funNode)) { + return false; + } + } + + Maybe varScope; + if (funbox->hasParameterExprs) { + varScope.emplace(this); + if (!varScope->init(pc_)) { + return false; + } + } else { + pc_->functionScope().useAsVarScope(pc_); + } + + if (kind == FunctionSyntaxKind::Arrow) { + TokenKind tt; + if (!tokenStream.peekTokenSameLine(&tt)) { + return false; + } + + if (tt == TokenKind::Eol) { + error(JSMSG_UNEXPECTED_TOKEN, + "'=>' on the same line after an argument list", + TokenKindToDesc(tt)); + return false; + } + if (tt != TokenKind::Arrow) { + error(JSMSG_BAD_ARROW_ARGS); + return false; + } + tokenStream.consumeKnownToken(TokenKind::Arrow); + } + + // When parsing something for new Function() we have to make sure to + // only treat a certain part of the source as a parameter list. + if (parameterListEnd.isSome() && parameterListEnd.value() != pos().begin) { + error(JSMSG_UNEXPECTED_PARAMLIST_END); + return false; + } + + // Parse the function body. + FunctionBodyType bodyType = StatementListBody; + TokenKind tt; + if (!tokenStream.getToken(&tt, TokenStream::SlashIsRegExp)) { + return false; + } + uint32_t openedPos = 0; + if (tt != TokenKind::LeftCurly) { + if (kind != FunctionSyntaxKind::Arrow) { + error(JSMSG_CURLY_BEFORE_BODY); + return false; + } + + anyChars.ungetToken(); + bodyType = ExpressionBody; + funbox->setHasExprBody(); + } else { + openedPos = pos().begin; + } + + // Arrow function parameters inherit yieldHandling from the enclosing + // context, but the arrow body doesn't. E.g. in |(a = yield) => yield|, + // |yield| in the parameters is either a name or keyword, depending on + // whether the arrow function is enclosed in a generator function or not. + // Whereas the |yield| in the function body is always parsed as a name. + // The same goes when parsing |await| in arrow functions. + YieldHandling bodyYieldHandling = GetYieldHandling(pc_->generatorKind()); + AwaitHandling bodyAwaitHandling = GetAwaitHandling(pc_->asyncKind()); + bool inheritedStrict = pc_->sc()->strict(); + LexicalScopeNodeType body; + { + AutoAwaitIsKeyword awaitIsKeyword(this, + bodyAwaitHandling); + AutoInParametersOfAsyncFunction inParameters(this, + false); + body = functionBody(inHandling, bodyYieldHandling, kind, bodyType); + if (!body) { + return false; + } + } + + // Revalidate the function name when we transitioned to strict mode. + if ((kind == FunctionSyntaxKind::Statement || + kind == FunctionSyntaxKind::Expression) && + funbox->explicitName() && !inheritedStrict && pc_->sc()->strict()) { + MOZ_ASSERT(pc_->sc()->hasExplicitUseStrict(), + "strict mode should only change when a 'use strict' directive " + "is present"); + + auto propertyName = funbox->explicitName(); + YieldHandling nameYieldHandling; + if (kind == FunctionSyntaxKind::Expression) { + // Named lambda has binding inside it. + nameYieldHandling = bodyYieldHandling; + } else { + // Otherwise YieldHandling cannot be checked at this point + // because of different context. + // It should already be checked before this point. + nameYieldHandling = YieldIsName; + } + + // We already use the correct await-handling at this point, therefore + // we don't need call AutoAwaitIsKeyword here. + + uint32_t nameOffset = handler_.getFunctionNameOffset(*funNode, anyChars); + if (!checkBindingIdentifier(propertyName, nameOffset, nameYieldHandling)) { + return false; + } + } + + if (bodyType == StatementListBody) { + // Cannot use mustMatchToken here because of internal compiler error on + // gcc 6.4.0, with linux 64 SM hazard build. + TokenKind actual; + if (!tokenStream.getToken(&actual, TokenStream::SlashIsRegExp)) { + return false; + } + if (actual != TokenKind::RightCurly) { + reportMissingClosing(JSMSG_CURLY_AFTER_BODY, JSMSG_CURLY_OPENED, + openedPos); + return false; + } + + setFunctionEndFromCurrentToken(funbox); + } else { + MOZ_ASSERT(kind == FunctionSyntaxKind::Arrow); + + if (anyChars.hadError()) { + return false; + } + + setFunctionEndFromCurrentToken(funbox); + + if (kind == FunctionSyntaxKind::Statement) { + if (!matchOrInsertSemicolon()) { + return false; + } + } + } + + if (IsMethodDefinitionKind(kind) && pc_->superScopeNeedsHomeObject()) { + funbox->setNeedsHomeObject(); + } + + if (!finishFunction(isStandaloneFunction)) { + return false; + } + + handler_.setEndPosition(body, pos().begin); + handler_.setEndPosition(*funNode, pos().end); + handler_.setFunctionBody(*funNode, body); + + return true; +} + +template +typename ParseHandler::FunctionNodeType +GeneralParser::functionStmt(uint32_t toStringStart, + YieldHandling yieldHandling, + DefaultHandling defaultHandling, + FunctionAsyncKind asyncKind) { + MOZ_ASSERT(anyChars.isCurrentTokenType(TokenKind::Function)); + + // In sloppy mode, Annex B.3.2 allows labelled function declarations. + // Otherwise it's a parse error. + ParseContext::Statement* declaredInStmt = pc_->innermostStatement(); + if (declaredInStmt && declaredInStmt->kind() == StatementKind::Label) { + MOZ_ASSERT(!pc_->sc()->strict(), + "labeled functions shouldn't be parsed in strict mode"); + + // Find the innermost non-label statement. Report an error if it's + // unbraced: functions can't appear in it. Otherwise the statement + // (or its absence) determines the scope the function's bound in. + while (declaredInStmt && declaredInStmt->kind() == StatementKind::Label) { + declaredInStmt = declaredInStmt->enclosing(); + } + + if (declaredInStmt && !StatementKindIsBraced(declaredInStmt->kind())) { + error(JSMSG_SLOPPY_FUNCTION_LABEL); + return null(); + } + } + + TokenKind tt; + if (!tokenStream.getToken(&tt)) { + return null(); + } + + GeneratorKind generatorKind = GeneratorKind::NotGenerator; + if (tt == TokenKind::Mul) { + generatorKind = GeneratorKind::Generator; + if (!tokenStream.getToken(&tt)) { + return null(); + } + } + + TaggedParserAtomIndex name; + if (TokenKindIsPossibleIdentifier(tt)) { + name = bindingIdentifier(yieldHandling); + if (!name) { + return null(); + } + } else if (defaultHandling == AllowDefaultName) { + name = TaggedParserAtomIndex::WellKnown::default_(); + anyChars.ungetToken(); + } else { + /* Unnamed function expressions are forbidden in statement context. */ + error(JSMSG_UNNAMED_FUNCTION_STMT); + return null(); + } + + // Note the declared name and check for early errors. + DeclarationKind kind; + if (declaredInStmt) { + MOZ_ASSERT(declaredInStmt->kind() != StatementKind::Label); + MOZ_ASSERT(StatementKindIsBraced(declaredInStmt->kind())); + + kind = + (!pc_->sc()->strict() && generatorKind == GeneratorKind::NotGenerator && + asyncKind == FunctionAsyncKind::SyncFunction) + ? DeclarationKind::SloppyLexicalFunction + : DeclarationKind::LexicalFunction; + } else { + kind = pc_->atModuleLevel() ? DeclarationKind::ModuleBodyLevelFunction + : DeclarationKind::BodyLevelFunction; + } + + if (!noteDeclaredName(name, kind, pos())) { + return null(); + } + + FunctionSyntaxKind syntaxKind = FunctionSyntaxKind::Statement; + FunctionNodeType funNode = handler_.newFunction(syntaxKind, pos()); + if (!funNode) { + return null(); + } + + // Under sloppy mode, try Annex B.3.3 semantics. If making an additional + // 'var' binding of the same name does not throw an early error, do so. + // This 'var' binding would be assigned the function object when its + // declaration is reached, not at the start of the block. + // + // This semantics is implemented upon Scope exit in + // Scope::propagateAndMarkAnnexBFunctionBoxes. + bool tryAnnexB = kind == DeclarationKind::SloppyLexicalFunction; + + YieldHandling newYieldHandling = GetYieldHandling(generatorKind); + return functionDefinition(funNode, toStringStart, InAllowed, newYieldHandling, + name, syntaxKind, generatorKind, asyncKind, + tryAnnexB); +} + +template +typename ParseHandler::FunctionNodeType +GeneralParser::functionExpr(uint32_t toStringStart, + InvokedPrediction invoked, + FunctionAsyncKind asyncKind) { + MOZ_ASSERT(anyChars.isCurrentTokenType(TokenKind::Function)); + + AutoAwaitIsKeyword awaitIsKeyword( + this, GetAwaitHandling(asyncKind)); + GeneratorKind generatorKind = GeneratorKind::NotGenerator; + TokenKind tt; + if (!tokenStream.getToken(&tt)) { + return null(); + } + + if (tt == TokenKind::Mul) { + generatorKind = GeneratorKind::Generator; + if (!tokenStream.getToken(&tt)) { + return null(); + } + } + + YieldHandling yieldHandling = GetYieldHandling(generatorKind); + + TaggedParserAtomIndex name; + if (TokenKindIsPossibleIdentifier(tt)) { + name = bindingIdentifier(yieldHandling); + if (!name) { + return null(); + } + } else { + anyChars.ungetToken(); + } + + FunctionSyntaxKind syntaxKind = FunctionSyntaxKind::Expression; + FunctionNodeType funNode = handler_.newFunction(syntaxKind, pos()); + if (!funNode) { + return null(); + } + + if (invoked) { + funNode = handler_.setLikelyIIFE(funNode); + } + + return functionDefinition(funNode, toStringStart, InAllowed, yieldHandling, + name, syntaxKind, generatorKind, asyncKind); +} + +/* + * Return true if this node, known to be an unparenthesized string literal + * that never contain escape sequences, could be the string of a directive in a + * Directive Prologue. Directive strings never contain escape sequences or line + * continuations. + */ +static inline bool IsUseStrictDirective(const TokenPos& pos, + TaggedParserAtomIndex atom) { + // the length of "use strict", including quotation. + static constexpr size_t useStrictLength = 12; + return atom == TaggedParserAtomIndex::WellKnown::useStrict() && + pos.begin + useStrictLength == pos.end; +} +static inline bool IsUseAsmDirective(const TokenPos& pos, + TaggedParserAtomIndex atom) { + // the length of "use asm", including quotation. + static constexpr size_t useAsmLength = 9; + return atom == TaggedParserAtomIndex::WellKnown::useAsm() && + pos.begin + useAsmLength == pos.end; +} + +template +bool Parser::asmJS(ListNodeType list) { + // While asm.js could technically be validated and compiled during syntax + // parsing, we have no guarantee that some later JS wouldn't abort the + // syntax parse and cause us to re-parse (and re-compile) the asm.js module. + // For simplicity, unconditionally abort the syntax parse when "use asm" is + // encountered so that asm.js is always validated/compiled exactly once + // during a full parse. + MOZ_ALWAYS_FALSE(abortIfSyntaxParser()); + return false; +} + +template +bool Parser::asmJS(ListNodeType list) { + // Disable syntax parsing in anything nested inside the asm.js module. + disableSyntaxParser(); + + // We should be encountering the "use asm" directive for the first time; if + // the directive is already, we must have failed asm.js validation and we're + // reparsing. In that case, don't try to validate again. A non-null + // newDirectives means we're not in a normal function. + if (!pc_->newDirectives || pc_->newDirectives->asmJS()) { + return true; + } + + // If there is no ScriptSource, then we are doing a non-compiling parse and + // so we shouldn't (and can't, without a ScriptSource) compile. + if (ss == nullptr) { + return true; + } + + pc_->functionBox()->useAsm = true; + + // Attempt to validate and compile this asm.js module. On success, the + // tokenStream has been advanced to the closing }. On failure, the + // tokenStream is in an indeterminate state and we must reparse the + // function from the beginning. Reparsing is triggered by marking that a + // new directive has been encountered and returning 'false'. + bool validated; + if (!CompileAsmJS(this->fc_, this->parserAtoms(), *this, list, &validated)) { + return false; + } + if (!validated) { + pc_->newDirectives->setAsmJS(); + return false; + } + + return true; +} + +template +inline bool GeneralParser::asmJS(ListNodeType list) { + return asFinalParser()->asmJS(list); +} + +/* + * Recognize Directive Prologue members and directives. Assuming |pn| is a + * candidate for membership in a directive prologue, recognize directives and + * set |pc_|'s flags accordingly. If |pn| is indeed part of a prologue, set its + * |prologue| flag. + * + * Note that the following is a strict mode function: + * + * function foo() { + * "blah" // inserted semi colon + * "blurgh" + * "use\x20loose" + * "use strict" + * } + * + * That is, even though "use\x20loose" can never be a directive, now or in the + * future (because of the hex escape), the Directive Prologue extends through it + * to the "use strict" statement, which is indeed a directive. + */ +template +bool GeneralParser::maybeParseDirective( + ListNodeType list, Node possibleDirective, bool* cont) { + TokenPos directivePos; + TaggedParserAtomIndex directive = + handler_.isStringExprStatement(possibleDirective, &directivePos); + + *cont = !!directive; + if (!*cont) { + return true; + } + + if (IsUseStrictDirective(directivePos, directive)) { + // Functions with non-simple parameter lists (destructuring, + // default or rest parameters) must not contain a "use strict" + // directive. + if (pc_->isFunctionBox()) { + FunctionBox* funbox = pc_->functionBox(); + if (!funbox->hasSimpleParameterList()) { + const char* parameterKind = funbox->hasDestructuringArgs + ? "destructuring" + : funbox->hasParameterExprs ? "default" + : "rest"; + errorAt(directivePos.begin, JSMSG_STRICT_NON_SIMPLE_PARAMS, + parameterKind); + return false; + } + } + + // We're going to be in strict mode. Note that this scope explicitly + // had "use strict"; + pc_->sc()->setExplicitUseStrict(); + if (!pc_->sc()->strict()) { + // Some strict mode violations can appear before a Use Strict Directive + // is applied. (See the |DeprecatedContent| enum initializers.) These + // violations can manifest in two ways. + // + // First, the violation can appear *before* the Use Strict Directive. + // Numeric literals (and therefore octal literals) can only precede a + // Use Strict Directive if this function's parameter list is not simple, + // but we reported an error for non-simple parameter lists above, so + // octal literals present no issue. But octal escapes and \8 and \9 can + // appear in the directive prologue before a Use Strict Directive: + // + // function f() + // { + // "hell\157 world"; // octal escape + // "\8"; "\9"; // NonOctalDecimalEscape + // "use strict"; // retroactively makes all the above errors + // } + // + // Second, the violation can appear *after* the Use Strict Directive but + // *before* the directive is recognized as terminated. This only + // happens when a directive is terminated by ASI, and the next token + // contains a violation: + // + // function a() + // { + // "use strict" // ASI + // 0755; + // } + // function b() + // { + // "use strict" // ASI + // "hell\157 world"; + // } + // function c() + // { + // "use strict" // ASI + // "\8"; + // } + // + // We note such violations when tokenizing. Then, if a violation has + // been observed at the time a "use strict" is applied, we report the + // error. + switch (anyChars.sawDeprecatedContent()) { + case DeprecatedContent::None: + break; + case DeprecatedContent::OctalLiteral: + error(JSMSG_DEPRECATED_OCTAL_LITERAL); + return false; + case DeprecatedContent::OctalEscape: + error(JSMSG_DEPRECATED_OCTAL_ESCAPE); + return false; + case DeprecatedContent::EightOrNineEscape: + error(JSMSG_DEPRECATED_EIGHT_OR_NINE_ESCAPE); + return false; + } + + pc_->sc()->setStrictScript(); + } + } else if (IsUseAsmDirective(directivePos, directive)) { + if (pc_->isFunctionBox()) { + return asmJS(list); + } + return warningAt(directivePos.begin, JSMSG_USE_ASM_DIRECTIVE_FAIL); + } + return true; +} + +template +typename ParseHandler::ListNodeType +GeneralParser::statementList(YieldHandling yieldHandling) { + AutoCheckRecursionLimit recursion(this->fc_); + if (!recursion.check(this->fc_)) { + return null(); + } + + ListNodeType stmtList = handler_.newStatementList(pos()); + if (!stmtList) { + return null(); + } + + bool canHaveDirectives = pc_->atBodyLevel(); + if (canHaveDirectives) { + // Clear flags for deprecated content that might have been seen in an + // enclosing context. + anyChars.clearSawDeprecatedContent(); + } + + bool canHaveHashbangComment = pc_->atTopLevel(); + if (canHaveHashbangComment) { + tokenStream.consumeOptionalHashbangComment(); + } + + bool afterReturn = false; + bool warnedAboutStatementsAfterReturn = false; + uint32_t statementBegin = 0; + for (;;) { + TokenKind tt = TokenKind::Eof; + if (!tokenStream.peekToken(&tt, TokenStream::SlashIsRegExp)) { + if (anyChars.isEOF()) { + isUnexpectedEOF_ = true; + } + return null(); + } + if (tt == TokenKind::Eof || tt == TokenKind::RightCurly) { + TokenPos pos; + if (!tokenStream.peekTokenPos(&pos, TokenStream::SlashIsRegExp)) { + return null(); + } + handler_.setListEndPosition(stmtList, pos); + break; + } + if (afterReturn) { + if (!tokenStream.peekOffset(&statementBegin, + TokenStream::SlashIsRegExp)) { + return null(); + } + } + Node next = statementListItem(yieldHandling, canHaveDirectives); + if (!next) { + if (anyChars.isEOF()) { + isUnexpectedEOF_ = true; + } + return null(); + } + if (!warnedAboutStatementsAfterReturn) { + if (afterReturn) { + if (!handler_.isStatementPermittedAfterReturnStatement(next)) { + if (!warningAt(statementBegin, JSMSG_STMT_AFTER_RETURN)) { + return null(); + } + + warnedAboutStatementsAfterReturn = true; + } + } else if (handler_.isReturnStatement(next)) { + afterReturn = true; + } + } + + if (canHaveDirectives) { + if (!maybeParseDirective(stmtList, next, &canHaveDirectives)) { + return null(); + } + } + + handler_.addStatementToList(stmtList, next); + } + + return stmtList; +} + +template +typename ParseHandler::Node GeneralParser::condition( + InHandling inHandling, YieldHandling yieldHandling) { + if (!mustMatchToken(TokenKind::LeftParen, JSMSG_PAREN_BEFORE_COND)) { + return null(); + } + + Node pn = exprInParens(inHandling, yieldHandling, TripledotProhibited); + if (!pn) { + return null(); + } + + if (!mustMatchToken(TokenKind::RightParen, JSMSG_PAREN_AFTER_COND)) { + return null(); + } + + return pn; +} + +template +bool GeneralParser::matchLabel( + YieldHandling yieldHandling, TaggedParserAtomIndex* labelOut) { + MOZ_ASSERT(labelOut != nullptr); + TokenKind tt = TokenKind::Eof; + if (!tokenStream.peekTokenSameLine(&tt, TokenStream::SlashIsRegExp)) { + return false; + } + + if (TokenKindIsPossibleIdentifier(tt)) { + tokenStream.consumeKnownToken(tt, TokenStream::SlashIsRegExp); + + *labelOut = labelIdentifier(yieldHandling); + if (!*labelOut) { + return false; + } + } else { + *labelOut = TaggedParserAtomIndex::null(); + } + return true; +} + +template +GeneralParser::PossibleError::PossibleError( + GeneralParser& parser) + : parser_(parser) {} + +template +typename GeneralParser::PossibleError::Error& +GeneralParser::PossibleError::error(ErrorKind kind) { + if (kind == ErrorKind::Expression) { + return exprError_; + } + if (kind == ErrorKind::Destructuring) { + return destructuringError_; + } + MOZ_ASSERT(kind == ErrorKind::DestructuringWarning); + return destructuringWarning_; +} + +template +void GeneralParser::PossibleError::setResolved( + ErrorKind kind) { + error(kind).state_ = ErrorState::None; +} + +template +bool GeneralParser::PossibleError::hasError( + ErrorKind kind) { + return error(kind).state_ == ErrorState::Pending; +} + +template +bool GeneralParser::PossibleError::hasPendingDestructuringError() { + return hasError(ErrorKind::Destructuring); +} + +template +void GeneralParser::PossibleError::setPending( + ErrorKind kind, const TokenPos& pos, unsigned errorNumber) { + // Don't overwrite a previously recorded error. + if (hasError(kind)) { + return; + } + + // If we report an error later, we'll do it from the position where we set + // the state to pending. + Error& err = error(kind); + err.offset_ = pos.begin; + err.errorNumber_ = errorNumber; + err.state_ = ErrorState::Pending; +} + +template +void GeneralParser::PossibleError:: + setPendingDestructuringErrorAt(const TokenPos& pos, unsigned errorNumber) { + setPending(ErrorKind::Destructuring, pos, errorNumber); +} + +template +void GeneralParser::PossibleError:: + setPendingDestructuringWarningAt(const TokenPos& pos, + unsigned errorNumber) { + setPending(ErrorKind::DestructuringWarning, pos, errorNumber); +} + +template +void GeneralParser::PossibleError:: + setPendingExpressionErrorAt(const TokenPos& pos, unsigned errorNumber) { + setPending(ErrorKind::Expression, pos, errorNumber); +} + +template +bool GeneralParser::PossibleError::checkForError( + ErrorKind kind) { + if (!hasError(kind)) { + return true; + } + + Error& err = error(kind); + parser_.errorAt(err.offset_, err.errorNumber_); + return false; +} + +template +bool GeneralParser::PossibleError::checkForDestructuringErrorOrWarning() { + // Clear pending expression error, because we're definitely not in an + // expression context. + setResolved(ErrorKind::Expression); + + // Report any pending destructuring error. + return checkForError(ErrorKind::Destructuring); +} + +template +bool GeneralParser::PossibleError::checkForExpressionError() { + // Clear pending destructuring error, because we're definitely not + // in a destructuring context. + setResolved(ErrorKind::Destructuring); + setResolved(ErrorKind::DestructuringWarning); + + // Report any pending expression error. + return checkForError(ErrorKind::Expression); +} + +template +void GeneralParser::PossibleError::transferErrorTo( + ErrorKind kind, PossibleError* other) { + if (hasError(kind) && !other->hasError(kind)) { + Error& err = error(kind); + Error& otherErr = other->error(kind); + otherErr.offset_ = err.offset_; + otherErr.errorNumber_ = err.errorNumber_; + otherErr.state_ = err.state_; + } +} + +template +void GeneralParser::PossibleError::transferErrorsTo( + PossibleError* other) { + MOZ_ASSERT(other); + MOZ_ASSERT(this != other); + MOZ_ASSERT(&parser_ == &other->parser_, + "Can't transfer fields to an instance which belongs to a " + "different parser"); + + transferErrorTo(ErrorKind::Destructuring, other); + transferErrorTo(ErrorKind::Expression, other); +} + +template +typename ParseHandler::BinaryNodeType +GeneralParser::bindingInitializer( + Node lhs, DeclarationKind kind, YieldHandling yieldHandling) { + MOZ_ASSERT(anyChars.isCurrentTokenType(TokenKind::Assign)); + + if (kind == DeclarationKind::FormalParameter) { + pc_->functionBox()->hasParameterExprs = true; + } + + Node rhs = assignExpr(InAllowed, yieldHandling, TripledotProhibited); + if (!rhs) { + return null(); + } + + BinaryNodeType assign = + handler_.newAssignment(ParseNodeKind::AssignExpr, lhs, rhs); + if (!assign) { + return null(); + } + + return assign; +} + +template +typename ParseHandler::NameNodeType +GeneralParser::bindingIdentifier( + DeclarationKind kind, YieldHandling yieldHandling) { + TaggedParserAtomIndex name = bindingIdentifier(yieldHandling); + if (!name) { + return null(); + } + + NameNodeType binding = newName(name); + if (!binding || !noteDeclaredName(name, kind, pos())) { + return null(); + } + + return binding; +} + +template +typename ParseHandler::Node +GeneralParser::bindingIdentifierOrPattern( + DeclarationKind kind, YieldHandling yieldHandling, TokenKind tt) { + if (tt == TokenKind::LeftBracket) { + return arrayBindingPattern(kind, yieldHandling); + } + + if (tt == TokenKind::LeftCurly) { + return objectBindingPattern(kind, yieldHandling); + } + + if (!TokenKindIsPossibleIdentifierName(tt)) { + error(JSMSG_NO_VARIABLE_NAME); + return null(); + } + + return bindingIdentifier(kind, yieldHandling); +} + +template +typename ParseHandler::ListNodeType +GeneralParser::objectBindingPattern( + DeclarationKind kind, YieldHandling yieldHandling) { + MOZ_ASSERT(anyChars.isCurrentTokenType(TokenKind::LeftCurly)); + + AutoCheckRecursionLimit recursion(this->fc_); + if (!recursion.check(this->fc_)) { + return null(); + } + + uint32_t begin = pos().begin; + ListNodeType literal = handler_.newObjectLiteral(begin); + if (!literal) { + return null(); + } + + Maybe declKind = Some(kind); + TaggedParserAtomIndex propAtom; + for (;;) { + TokenKind tt; + if (!tokenStream.peekToken(&tt)) { + return null(); + } + if (tt == TokenKind::RightCurly) { + break; + } + + if (tt == TokenKind::TripleDot) { + tokenStream.consumeKnownToken(TokenKind::TripleDot); + uint32_t begin = pos().begin; + + TokenKind tt; + if (!tokenStream.getToken(&tt)) { + return null(); + } + + if (!TokenKindIsPossibleIdentifierName(tt)) { + error(JSMSG_NO_VARIABLE_NAME); + return null(); + } + + NameNodeType inner = bindingIdentifier(kind, yieldHandling); + if (!inner) { + return null(); + } + + if (!handler_.addSpreadProperty(literal, begin, inner)) { + return null(); + } + } else { + TokenPos namePos = anyChars.nextToken().pos; + + PropertyType propType; + Node propName = + propertyOrMethodName(yieldHandling, PropertyNameInPattern, declKind, + literal, &propType, &propAtom); + if (!propName) { + return null(); + } + + if (propType == PropertyType::Normal) { + // Handle e.g., |var {p: x} = o| and |var {p: x=0} = o|. + + if (!tokenStream.getToken(&tt, TokenStream::SlashIsRegExp)) { + return null(); + } + + Node binding = bindingIdentifierOrPattern(kind, yieldHandling, tt); + if (!binding) { + return null(); + } + + bool hasInitializer; + if (!tokenStream.matchToken(&hasInitializer, TokenKind::Assign, + TokenStream::SlashIsRegExp)) { + return null(); + } + + Node bindingExpr = + hasInitializer ? bindingInitializer(binding, kind, yieldHandling) + : binding; + if (!bindingExpr) { + return null(); + } + + if (!handler_.addPropertyDefinition(literal, propName, bindingExpr)) { + return null(); + } + } else if (propType == PropertyType::Shorthand) { + // Handle e.g., |var {x, y} = o| as destructuring shorthand + // for |var {x: x, y: y} = o|. + MOZ_ASSERT(TokenKindIsPossibleIdentifierName(tt)); + + NameNodeType binding = bindingIdentifier(kind, yieldHandling); + if (!binding) { + return null(); + } + + if (!handler_.addShorthand(literal, handler_.asName(propName), + binding)) { + return null(); + } + } else if (propType == PropertyType::CoverInitializedName) { + // Handle e.g., |var {x=1, y=2} = o| as destructuring + // shorthand with default values. + MOZ_ASSERT(TokenKindIsPossibleIdentifierName(tt)); + + NameNodeType binding = bindingIdentifier(kind, yieldHandling); + if (!binding) { + return null(); + } + + tokenStream.consumeKnownToken(TokenKind::Assign); + + BinaryNodeType bindingExpr = + bindingInitializer(binding, kind, yieldHandling); + if (!bindingExpr) { + return null(); + } + + if (!handler_.addPropertyDefinition(literal, propName, bindingExpr)) { + return null(); + } + } else { + errorAt(namePos.begin, JSMSG_NO_VARIABLE_NAME); + return null(); + } + } + + bool matched; + if (!tokenStream.matchToken(&matched, TokenKind::Comma, + TokenStream::SlashIsInvalid)) { + return null(); + } + if (!matched) { + break; + } + if (tt == TokenKind::TripleDot) { + error(JSMSG_REST_WITH_COMMA); + return null(); + } + } + + if (!mustMatchToken(TokenKind::RightCurly, [this, begin](TokenKind actual) { + this->reportMissingClosing(JSMSG_CURLY_AFTER_LIST, JSMSG_CURLY_OPENED, + begin); + })) { + return null(); + } + + handler_.setEndPosition(literal, pos().end); + return literal; +} + +template +typename ParseHandler::ListNodeType +GeneralParser::arrayBindingPattern( + DeclarationKind kind, YieldHandling yieldHandling) { + MOZ_ASSERT(anyChars.isCurrentTokenType(TokenKind::LeftBracket)); + + AutoCheckRecursionLimit recursion(this->fc_); + if (!recursion.check(this->fc_)) { + return null(); + } + + uint32_t begin = pos().begin; + ListNodeType literal = handler_.newArrayLiteral(begin); + if (!literal) { + return null(); + } + + uint32_t index = 0; + for (;; index++) { + if (index >= NativeObject::MAX_DENSE_ELEMENTS_COUNT) { + error(JSMSG_ARRAY_INIT_TOO_BIG); + return null(); + } + + TokenKind tt; + if (!tokenStream.getToken(&tt)) { + return null(); + } + + if (tt == TokenKind::RightBracket) { + anyChars.ungetToken(); + break; + } + + if (tt == TokenKind::Comma) { + if (!handler_.addElision(literal, pos())) { + return null(); + } + } else if (tt == TokenKind::TripleDot) { + uint32_t begin = pos().begin; + + TokenKind tt; + if (!tokenStream.getToken(&tt)) { + return null(); + } + + Node inner = bindingIdentifierOrPattern(kind, yieldHandling, tt); + if (!inner) { + return null(); + } + + if (!handler_.addSpreadElement(literal, begin, inner)) { + return null(); + } + } else { + Node binding = bindingIdentifierOrPattern(kind, yieldHandling, tt); + if (!binding) { + return null(); + } + + bool hasInitializer; + if (!tokenStream.matchToken(&hasInitializer, TokenKind::Assign, + TokenStream::SlashIsRegExp)) { + return null(); + } + + Node element = hasInitializer + ? bindingInitializer(binding, kind, yieldHandling) + : binding; + if (!element) { + return null(); + } + + handler_.addArrayElement(literal, element); + } + + if (tt != TokenKind::Comma) { + // If we didn't already match TokenKind::Comma in above case. + bool matched; + if (!tokenStream.matchToken(&matched, TokenKind::Comma, + TokenStream::SlashIsRegExp)) { + return null(); + } + if (!matched) { + break; + } + + if (tt == TokenKind::TripleDot) { + error(JSMSG_REST_WITH_COMMA); + return null(); + } + } + } + + if (!mustMatchToken(TokenKind::RightBracket, [this, begin](TokenKind actual) { + this->reportMissingClosing(JSMSG_BRACKET_AFTER_LIST, + JSMSG_BRACKET_OPENED, begin); + })) { + return null(); + } + + handler_.setEndPosition(literal, pos().end); + return literal; +} + +template +typename ParseHandler::Node +GeneralParser::destructuringDeclaration( + DeclarationKind kind, YieldHandling yieldHandling, TokenKind tt) { + MOZ_ASSERT(anyChars.isCurrentTokenType(tt)); + MOZ_ASSERT(tt == TokenKind::LeftBracket || tt == TokenKind::LeftCurly); + + return tt == TokenKind::LeftBracket + ? arrayBindingPattern(kind, yieldHandling) + : objectBindingPattern(kind, yieldHandling); +} + +template +typename ParseHandler::Node +GeneralParser::destructuringDeclarationWithoutYieldOrAwait( + DeclarationKind kind, YieldHandling yieldHandling, TokenKind tt) { + uint32_t startYieldOffset = pc_->lastYieldOffset; + uint32_t startAwaitOffset = pc_->lastAwaitOffset; + Node res = destructuringDeclaration(kind, yieldHandling, tt); + if (res) { + if (pc_->lastYieldOffset != startYieldOffset) { + errorAt(pc_->lastYieldOffset, JSMSG_YIELD_IN_PARAMETER); + return null(); + } + if (pc_->lastAwaitOffset != startAwaitOffset) { + errorAt(pc_->lastAwaitOffset, JSMSG_AWAIT_IN_PARAMETER); + return null(); + } + } + return res; +} + +template +typename ParseHandler::LexicalScopeNodeType +GeneralParser::blockStatement(YieldHandling yieldHandling, + unsigned errorNumber) { + MOZ_ASSERT(anyChars.isCurrentTokenType(TokenKind::LeftCurly)); + uint32_t openedPos = pos().begin; + + ParseContext::Statement stmt(pc_, StatementKind::Block); + ParseContext::Scope scope(this); + if (!scope.init(pc_)) { + return null(); + } + + ListNodeType list = statementList(yieldHandling); + if (!list) { + return null(); + } + + if (!mustMatchToken(TokenKind::RightCurly, [this, errorNumber, + openedPos](TokenKind actual) { + this->reportMissingClosing(errorNumber, JSMSG_CURLY_OPENED, openedPos); + })) { + return null(); + } + + return finishLexicalScope(scope, list); +} + +template +typename ParseHandler::Node +GeneralParser::expressionAfterForInOrOf( + ParseNodeKind forHeadKind, YieldHandling yieldHandling) { + MOZ_ASSERT(forHeadKind == ParseNodeKind::ForIn || + forHeadKind == ParseNodeKind::ForOf); + Node pn = forHeadKind == ParseNodeKind::ForOf + ? assignExpr(InAllowed, yieldHandling, TripledotProhibited) + : expr(InAllowed, yieldHandling, TripledotProhibited); + return pn; +} + +template +typename ParseHandler::Node +GeneralParser::declarationPattern( + DeclarationKind declKind, TokenKind tt, bool initialDeclaration, + YieldHandling yieldHandling, ParseNodeKind* forHeadKind, + Node* forInOrOfExpression) { + MOZ_ASSERT(anyChars.isCurrentTokenType(TokenKind::LeftBracket) || + anyChars.isCurrentTokenType(TokenKind::LeftCurly)); + + Node pattern = destructuringDeclaration(declKind, yieldHandling, tt); + if (!pattern) { + return null(); + } + + if (initialDeclaration && forHeadKind) { + bool isForIn, isForOf; + if (!matchInOrOf(&isForIn, &isForOf)) { + return null(); + } + + if (isForIn) { + *forHeadKind = ParseNodeKind::ForIn; + } else if (isForOf) { + *forHeadKind = ParseNodeKind::ForOf; + } else { + *forHeadKind = ParseNodeKind::ForHead; + } + + if (*forHeadKind != ParseNodeKind::ForHead) { + *forInOrOfExpression = + expressionAfterForInOrOf(*forHeadKind, yieldHandling); + if (!*forInOrOfExpression) { + return null(); + } + + return pattern; + } + } + + if (!mustMatchToken(TokenKind::Assign, JSMSG_BAD_DESTRUCT_DECL)) { + return null(); + } + + Node init = assignExpr(forHeadKind ? InProhibited : InAllowed, yieldHandling, + TripledotProhibited); + if (!init) { + return null(); + } + + return handler_.newAssignment(ParseNodeKind::AssignExpr, pattern, init); +} + +template +typename ParseHandler::AssignmentNodeType +GeneralParser::initializerInNameDeclaration( + NameNodeType binding, DeclarationKind declKind, bool initialDeclaration, + YieldHandling yieldHandling, ParseNodeKind* forHeadKind, + Node* forInOrOfExpression) { + MOZ_ASSERT(anyChars.isCurrentTokenType(TokenKind::Assign)); + + uint32_t initializerOffset; + if (!tokenStream.peekOffset(&initializerOffset, TokenStream::SlashIsRegExp)) { + return null(); + } + + Node initializer = assignExpr(forHeadKind ? InProhibited : InAllowed, + yieldHandling, TripledotProhibited); + if (!initializer) { + return null(); + } + + if (forHeadKind && initialDeclaration) { + bool isForIn, isForOf; + if (!matchInOrOf(&isForIn, &isForOf)) { + return null(); + } + + // An initialized declaration can't appear in a for-of: + // + // for (var/let/const x = ... of ...); // BAD + if (isForOf) { + errorAt(initializerOffset, JSMSG_OF_AFTER_FOR_LOOP_DECL); + return null(); + } + + if (isForIn) { + // Lexical declarations in for-in loops can't be initialized: + // + // for (let/const x = ... in ...); // BAD + if (DeclarationKindIsLexical(declKind)) { + errorAt(initializerOffset, JSMSG_IN_AFTER_LEXICAL_FOR_DECL); + return null(); + } + + // This leaves only initialized for-in |var| declarations. ES6 + // forbids these; later ES un-forbids in non-strict mode code. + *forHeadKind = ParseNodeKind::ForIn; + if (!strictModeErrorAt(initializerOffset, + JSMSG_INVALID_FOR_IN_DECL_WITH_INIT)) { + return null(); + } + + *forInOrOfExpression = + expressionAfterForInOrOf(ParseNodeKind::ForIn, yieldHandling); + if (!*forInOrOfExpression) { + return null(); + } + } else { + *forHeadKind = ParseNodeKind::ForHead; + } + } + + return handler_.finishInitializerAssignment(binding, initializer); +} + +template +typename ParseHandler::Node GeneralParser::declarationName( + DeclarationKind declKind, TokenKind tt, bool initialDeclaration, + YieldHandling yieldHandling, ParseNodeKind* forHeadKind, + Node* forInOrOfExpression) { + // Anything other than possible identifier is an error. + if (!TokenKindIsPossibleIdentifier(tt)) { + error(JSMSG_NO_VARIABLE_NAME); + return null(); + } + + TaggedParserAtomIndex name = bindingIdentifier(yieldHandling); + if (!name) { + return null(); + } + + NameNodeType binding = newName(name); + if (!binding) { + return null(); + } + + TokenPos namePos = pos(); + + // The '=' context after a variable name in a declaration is an opportunity + // for ASI, and thus for the next token to start an ExpressionStatement: + // + // var foo // VariableDeclaration + // /bar/g; // ExpressionStatement + // + // Therefore get the token here with SlashIsRegExp. + bool matched; + if (!tokenStream.matchToken(&matched, TokenKind::Assign, + TokenStream::SlashIsRegExp)) { + return null(); + } + + Node declaration; + if (matched) { + declaration = initializerInNameDeclaration( + binding, declKind, initialDeclaration, yieldHandling, forHeadKind, + forInOrOfExpression); + if (!declaration) { + return null(); + } + } else { + declaration = binding; + + if (initialDeclaration && forHeadKind) { + bool isForIn, isForOf; + if (!matchInOrOf(&isForIn, &isForOf)) { + return null(); + } + + if (isForIn) { + *forHeadKind = ParseNodeKind::ForIn; + } else if (isForOf) { + *forHeadKind = ParseNodeKind::ForOf; + } else { + *forHeadKind = ParseNodeKind::ForHead; + } + } + + if (forHeadKind && *forHeadKind != ParseNodeKind::ForHead) { + *forInOrOfExpression = + expressionAfterForInOrOf(*forHeadKind, yieldHandling); + if (!*forInOrOfExpression) { + return null(); + } + } else { + // Normal const declarations, and const declarations in for(;;) + // heads, must be initialized. + if (declKind == DeclarationKind::Const) { + errorAt(namePos.begin, JSMSG_BAD_CONST_DECL); + return null(); + } + } + } + + // Note the declared name after knowing whether or not we are in a for-of + // loop, due to special early error semantics in Annex B.3.5. + if (!noteDeclaredName(name, declKind, namePos)) { + return null(); + } + + return declaration; +} + +template +typename ParseHandler::DeclarationListNodeType +GeneralParser::declarationList( + YieldHandling yieldHandling, ParseNodeKind kind, + ParseNodeKind* forHeadKind /* = nullptr */, + Node* forInOrOfExpression /* = nullptr */) { + MOZ_ASSERT(kind == ParseNodeKind::VarStmt || kind == ParseNodeKind::LetDecl || + kind == ParseNodeKind::ConstDecl); + + DeclarationKind declKind; + switch (kind) { + case ParseNodeKind::VarStmt: + declKind = DeclarationKind::Var; + break; + case ParseNodeKind::ConstDecl: + declKind = DeclarationKind::Const; + break; + case ParseNodeKind::LetDecl: + declKind = DeclarationKind::Let; + break; + default: + MOZ_CRASH("Unknown declaration kind"); + } + + DeclarationListNodeType decl = handler_.newDeclarationList(kind, pos()); + if (!decl) { + return null(); + } + + bool moreDeclarations; + bool initialDeclaration = true; + do { + MOZ_ASSERT_IF(!initialDeclaration && forHeadKind, + *forHeadKind == ParseNodeKind::ForHead); + + TokenKind tt; + if (!tokenStream.getToken(&tt)) { + return null(); + } + + Node binding = + (tt == TokenKind::LeftBracket || tt == TokenKind::LeftCurly) + ? declarationPattern(declKind, tt, initialDeclaration, + yieldHandling, forHeadKind, + forInOrOfExpression) + : declarationName(declKind, tt, initialDeclaration, yieldHandling, + forHeadKind, forInOrOfExpression); + if (!binding) { + return null(); + } + + handler_.addList(decl, binding); + + // If we have a for-in/of loop, the above call matches the entirety + // of the loop head (up to the closing parenthesis). + if (forHeadKind && *forHeadKind != ParseNodeKind::ForHead) { + break; + } + + initialDeclaration = false; + + if (!tokenStream.matchToken(&moreDeclarations, TokenKind::Comma, + TokenStream::SlashIsRegExp)) { + return null(); + } + } while (moreDeclarations); + + return decl; +} + +template +typename ParseHandler::DeclarationListNodeType +GeneralParser::lexicalDeclaration( + YieldHandling yieldHandling, DeclarationKind kind) { + MOZ_ASSERT(kind == DeclarationKind::Const || kind == DeclarationKind::Let); + + /* + * Parse body-level lets without a new block object. ES6 specs + * that an execution environment's initial lexical environment + * is the VariableEnvironment, i.e., body-level lets are in + * the same environment record as vars. + * + * However, they cannot be parsed exactly as vars, as ES6 + * requires that uninitialized lets throw ReferenceError on use. + * + * See 8.1.1.1.6 and the note in 13.2.1. + */ + DeclarationListNodeType decl = declarationList( + yieldHandling, kind == DeclarationKind::Const ? ParseNodeKind::ConstDecl + : ParseNodeKind::LetDecl); + if (!decl || !matchOrInsertSemicolon()) { + return null(); + } + + return decl; +} + +template +typename ParseHandler::NameNodeType +GeneralParser::moduleExportName() { + MOZ_ASSERT(anyChars.currentToken().type == TokenKind::String); + TaggedParserAtomIndex name = anyChars.currentToken().atom(); + if (!this->parserAtoms().isModuleExportName(name)) { + error(JSMSG_UNPAIRED_SURROGATE_EXPORT); + return null(); + } + return handler_.newStringLiteral(name, pos()); +} + +template +bool GeneralParser::assertClause( + ListNodeType assertionsSet) { + MOZ_ASSERT(anyChars.isCurrentTokenType(TokenKind::Assert)); + + if (!options().importAssertions) { + error(JSMSG_IMPORT_ASSERTIONS_NOT_SUPPORTED); + return false; + } + + if (!abortIfSyntaxParser()) { + return false; + } + + if (!mustMatchToken(TokenKind::LeftCurly, JSMSG_CURLY_AFTER_ASSERT)) { + return false; + } + + // Handle the form |... assert {}| + TokenKind token; + if (!tokenStream.getToken(&token)) { + return false; + } + if (token == TokenKind::RightCurly) { + return true; + } + + js::HashSet + usedAssertionKeys; + + for (;;) { + TaggedParserAtomIndex keyName; + if (TokenKindIsPossibleIdentifierName(token)) { + keyName = anyChars.currentName(); + } else if (token == TokenKind::String) { + keyName = anyChars.currentToken().atom(); + } else { + error(JSMSG_ASSERT_KEY_EXPECTED); + return false; + } + + auto p = usedAssertionKeys.lookupForAdd(keyName); + if (p) { + UniqueChars str = this->parserAtoms().toPrintableString(keyName); + if (!str) { + ReportOutOfMemory(this->fc_); + return false; + } + error(JSMSG_DUPLICATE_ASSERT_KEY, str.get()); + return false; + } + if (!usedAssertionKeys.add(p, keyName)) { + ReportOutOfMemory(this->fc_); + return false; + } + + NameNodeType keyNode = newName(keyName); + if (!keyNode) { + return false; + } + + if (!mustMatchToken(TokenKind::Colon, JSMSG_COLON_AFTER_ASSERT_KEY)) { + return false; + } + if (!mustMatchToken(TokenKind::String, JSMSG_ASSERT_STRING_LITERAL)) { + return false; + } + + NameNodeType valueNode = stringLiteral(); + if (!valueNode) { + return false; + } + + BinaryNodeType importAssertionNode = + handler_.newImportAssertion(keyNode, valueNode); + if (!importAssertionNode) { + return false; + } + + handler_.addList(assertionsSet, importAssertionNode); + + if (!tokenStream.getToken(&token)) { + return false; + } + if (token == TokenKind::Comma) { + if (!tokenStream.getToken(&token)) { + return false; + } + } + if (token == TokenKind::RightCurly) { + break; + } + } + + return true; +} + +template +bool GeneralParser::namedImports( + ListNodeType importSpecSet) { + if (!abortIfSyntaxParser()) { + return false; + } + + while (true) { + // Handle the forms |import {} from 'a'| and + // |import { ..., } from 'a'| (where ... is non empty), by + // escaping the loop early if the next token is }. + TokenKind tt; + if (!tokenStream.getToken(&tt)) { + return false; + } + + if (tt == TokenKind::RightCurly) { + break; + } + + TaggedParserAtomIndex importName; + NameNodeType importNameNode = null(); + if (TokenKindIsPossibleIdentifierName(tt)) { + importName = anyChars.currentName(); + importNameNode = newName(importName); + } else if (tt == TokenKind::String) { + importNameNode = moduleExportName(); + } else { + error(JSMSG_NO_IMPORT_NAME); + } + if (!importNameNode) { + return false; + } + + bool matched; + if (!tokenStream.matchToken(&matched, TokenKind::As)) { + return false; + } + + if (matched) { + TokenKind afterAs; + if (!tokenStream.getToken(&afterAs)) { + return false; + } + + if (!TokenKindIsPossibleIdentifierName(afterAs)) { + error(JSMSG_NO_BINDING_NAME); + return false; + } + } else { + // String export names can't refer to local bindings. + if (tt == TokenKind::String) { + error(JSMSG_AS_AFTER_STRING); + return false; + } + + // Keywords cannot be bound to themselves, so an import name + // that is a keyword is a syntax error if it is not followed + // by the keyword 'as'. + // See the ImportSpecifier production in ES6 section 15.2.2. + MOZ_ASSERT(importName); + if (IsKeyword(importName)) { + error(JSMSG_AS_AFTER_RESERVED_WORD, ReservedWordToCharZ(importName)); + return false; + } + } + + TaggedParserAtomIndex bindingAtom = importedBinding(); + if (!bindingAtom) { + return false; + } + + NameNodeType bindingName = newName(bindingAtom); + if (!bindingName) { + return false; + } + if (!noteDeclaredName(bindingAtom, DeclarationKind::Import, pos())) { + return false; + } + + BinaryNodeType importSpec = + handler_.newImportSpec(importNameNode, bindingName); + if (!importSpec) { + return false; + } + + handler_.addList(importSpecSet, importSpec); + + TokenKind next; + if (!tokenStream.getToken(&next)) { + return false; + } + + if (next == TokenKind::RightCurly) { + break; + } + + if (next != TokenKind::Comma) { + error(JSMSG_RC_AFTER_IMPORT_SPEC_LIST); + return false; + } + } + + return true; +} + +template +bool GeneralParser::namespaceImport( + ListNodeType importSpecSet) { + if (!abortIfSyntaxParser()) { + return false; + } + + if (!mustMatchToken(TokenKind::As, JSMSG_AS_AFTER_IMPORT_STAR)) { + return false; + } + uint32_t begin = pos().begin; + + if (!mustMatchToken(TokenKindIsPossibleIdentifierName, + JSMSG_NO_BINDING_NAME)) { + return false; + } + + // Namespace imports are not indirect bindings but lexical + // definitions that hold a module namespace object. They are treated + // as const variables which are initialized during the + // ModuleInstantiate step. + TaggedParserAtomIndex bindingName = importedBinding(); + if (!bindingName) { + return false; + } + NameNodeType bindingNameNode = newName(bindingName); + if (!bindingNameNode) { + return false; + } + if (!noteDeclaredName(bindingName, DeclarationKind::Const, pos())) { + return false; + } + + // The namespace import name is currently required to live on the + // environment. + pc_->varScope().lookupDeclaredName(bindingName)->value()->setClosedOver(); + + UnaryNodeType importSpec = + handler_.newImportNamespaceSpec(begin, bindingNameNode); + if (!importSpec) { + return false; + } + + handler_.addList(importSpecSet, importSpec); + + return true; +} + +template +typename ParseHandler::BinaryNodeType +GeneralParser::importDeclaration() { + if (!abortIfSyntaxParser()) { + return null(); + } + + MOZ_ASSERT(anyChars.isCurrentTokenType(TokenKind::Import)); + + if (!pc_->atModuleLevel()) { + error(JSMSG_IMPORT_DECL_AT_TOP_LEVEL); + return null(); + } + + uint32_t begin = pos().begin; + TokenKind tt; + if (!tokenStream.getToken(&tt)) { + return null(); + } + + ListNodeType importSpecSet = + handler_.newList(ParseNodeKind::ImportSpecList, pos()); + if (!importSpecSet) { + return null(); + } + + if (tt == TokenKind::String) { + // Handle the form |import 'a'| by leaving the list empty. This is + // equivalent to |import {} from 'a'|. + handler_.setEndPosition(importSpecSet, pos().begin); + } else { + if (tt == TokenKind::LeftCurly) { + if (!namedImports(importSpecSet)) { + return null(); + } + } else if (tt == TokenKind::Mul) { + if (!namespaceImport(importSpecSet)) { + return null(); + } + } else if (TokenKindIsPossibleIdentifierName(tt)) { + // Handle the form |import a from 'b'|, by adding a single import + // specifier to the list, with 'default' as the import name and + // 'a' as the binding name. This is equivalent to + // |import { default as a } from 'b'|. + NameNodeType importName = + newName(TaggedParserAtomIndex::WellKnown::default_()); + if (!importName) { + return null(); + } + + TaggedParserAtomIndex bindingAtom = importedBinding(); + if (!bindingAtom) { + return null(); + } + + NameNodeType bindingName = newName(bindingAtom); + if (!bindingName) { + return null(); + } + + if (!noteDeclaredName(bindingAtom, DeclarationKind::Import, pos())) { + return null(); + } + + BinaryNodeType importSpec = + handler_.newImportSpec(importName, bindingName); + if (!importSpec) { + return null(); + } + + handler_.addList(importSpecSet, importSpec); + + if (!tokenStream.peekToken(&tt)) { + return null(); + } + + if (tt == TokenKind::Comma) { + tokenStream.consumeKnownToken(tt); + if (!tokenStream.getToken(&tt)) { + return null(); + } + + if (tt == TokenKind::LeftCurly) { + if (!namedImports(importSpecSet)) { + return null(); + } + } else if (tt == TokenKind::Mul) { + if (!namespaceImport(importSpecSet)) { + return null(); + } + } else { + error(JSMSG_NAMED_IMPORTS_OR_NAMESPACE_IMPORT); + return null(); + } + } + } else { + error(JSMSG_DECLARATION_AFTER_IMPORT); + return null(); + } + + if (!mustMatchToken(TokenKind::From, JSMSG_FROM_AFTER_IMPORT_CLAUSE)) { + return null(); + } + + if (!mustMatchToken(TokenKind::String, JSMSG_MODULE_SPEC_AFTER_FROM)) { + return null(); + } + } + + NameNodeType moduleSpec = stringLiteral(); + if (!moduleSpec) { + return null(); + } + + if (!tokenStream.peekTokenSameLine(&tt, TokenStream::SlashIsRegExp)) { + return null(); + } + + ListNodeType importAssertionList = + handler_.newList(ParseNodeKind::ImportAssertionList, pos()); + if (!importAssertionList) { + return null(); + } + + if (tt == TokenKind::Assert) { + tokenStream.consumeKnownToken(TokenKind::Assert, + TokenStream::SlashIsRegExp); + + if (!assertClause(importAssertionList)) { + return null(); + } + } + + if (!matchOrInsertSemicolon(TokenStream::SlashIsRegExp)) { + return null(); + } + + BinaryNodeType moduleRequest = handler_.newModuleRequest( + moduleSpec, importAssertionList, TokenPos(begin, pos().end)); + if (!moduleRequest) { + return null(); + } + + BinaryNodeType node = handler_.newImportDeclaration( + importSpecSet, moduleRequest, TokenPos(begin, pos().end)); + if (!node || !processImport(node)) { + return null(); + } + + return node; +} + +template +inline typename ParseHandler::Node +GeneralParser::importDeclarationOrImportExpr( + YieldHandling yieldHandling) { + MOZ_ASSERT(anyChars.isCurrentTokenType(TokenKind::Import)); + + TokenKind tt; + if (!tokenStream.peekToken(&tt)) { + return null(); + } + + if (tt == TokenKind::Dot || tt == TokenKind::LeftParen) { + return expressionStatement(yieldHandling); + } + + return importDeclaration(); +} + +template +bool Parser::checkExportedName( + TaggedParserAtomIndex exportName) { + if (!pc_->sc()->asModuleContext()->builder.hasExportedName(exportName)) { + return true; + } + + UniqueChars str = this->parserAtoms().toPrintableString(exportName); + if (!str) { + ReportOutOfMemory(this->fc_); + return false; + } + + error(JSMSG_DUPLICATE_EXPORT_NAME, str.get()); + return false; +} + +template +inline bool Parser::checkExportedName( + TaggedParserAtomIndex exportName) { + MOZ_ALWAYS_FALSE(abortIfSyntaxParser()); + return false; +} + +template +inline bool GeneralParser::checkExportedName( + TaggedParserAtomIndex exportName) { + return asFinalParser()->checkExportedName(exportName); +} + +template +bool Parser::checkExportedNamesForArrayBinding( + ListNode* array) { + MOZ_ASSERT(array->isKind(ParseNodeKind::ArrayExpr)); + + for (ParseNode* node : array->contents()) { + if (node->isKind(ParseNodeKind::Elision)) { + continue; + } + + ParseNode* binding; + if (node->isKind(ParseNodeKind::Spread)) { + binding = node->as().kid(); + } else if (node->isKind(ParseNodeKind::AssignExpr)) { + binding = node->as().left(); + } else { + binding = node; + } + + if (!checkExportedNamesForDeclaration(binding)) { + return false; + } + } + + return true; +} + +template +inline bool Parser::checkExportedNamesForArrayBinding( + ListNodeType array) { + MOZ_ALWAYS_FALSE(abortIfSyntaxParser()); + return false; +} + +template +inline bool +GeneralParser::checkExportedNamesForArrayBinding( + ListNodeType array) { + return asFinalParser()->checkExportedNamesForArrayBinding(array); +} + +template +bool Parser::checkExportedNamesForObjectBinding( + ListNode* obj) { + MOZ_ASSERT(obj->isKind(ParseNodeKind::ObjectExpr)); + + for (ParseNode* node : obj->contents()) { + MOZ_ASSERT(node->isKind(ParseNodeKind::MutateProto) || + node->isKind(ParseNodeKind::PropertyDefinition) || + node->isKind(ParseNodeKind::Shorthand) || + node->isKind(ParseNodeKind::Spread)); + + ParseNode* target; + if (node->isKind(ParseNodeKind::Spread)) { + target = node->as().kid(); + } else { + if (node->isKind(ParseNodeKind::MutateProto)) { + target = node->as().kid(); + } else { + target = node->as().right(); + } + + if (target->isKind(ParseNodeKind::AssignExpr)) { + target = target->as().left(); + } + } + + if (!checkExportedNamesForDeclaration(target)) { + return false; + } + } + + return true; +} + +template +inline bool Parser::checkExportedNamesForObjectBinding(ListNodeType obj) { + MOZ_ALWAYS_FALSE(abortIfSyntaxParser()); + return false; +} + +template +inline bool +GeneralParser::checkExportedNamesForObjectBinding( + ListNodeType obj) { + return asFinalParser()->checkExportedNamesForObjectBinding(obj); +} + +template +bool Parser::checkExportedNamesForDeclaration( + ParseNode* node) { + if (node->isKind(ParseNodeKind::Name)) { + if (!checkExportedName(node->as().atom())) { + return false; + } + } else if (node->isKind(ParseNodeKind::ArrayExpr)) { + if (!checkExportedNamesForArrayBinding(&node->as())) { + return false; + } + } else { + MOZ_ASSERT(node->isKind(ParseNodeKind::ObjectExpr)); + if (!checkExportedNamesForObjectBinding(&node->as())) { + return false; + } + } + + return true; +} + +template +inline bool Parser::checkExportedNamesForDeclaration( + Node node) { + MOZ_ALWAYS_FALSE(abortIfSyntaxParser()); + return false; +} + +template +inline bool GeneralParser::checkExportedNamesForDeclaration( + Node node) { + return asFinalParser()->checkExportedNamesForDeclaration(node); +} + +template +bool Parser::checkExportedNamesForDeclarationList( + DeclarationListNodeType node) { + for (ParseNode* binding : node->contents()) { + if (binding->isKind(ParseNodeKind::AssignExpr)) { + binding = binding->as().left(); + } else { + MOZ_ASSERT(binding->isKind(ParseNodeKind::Name)); + } + + if (!checkExportedNamesForDeclaration(binding)) { + return false; + } + } + + return true; +} + +template +inline bool +Parser::checkExportedNamesForDeclarationList( + DeclarationListNodeType node) { + MOZ_ALWAYS_FALSE(abortIfSyntaxParser()); + return false; +} + +template +inline bool +GeneralParser::checkExportedNamesForDeclarationList( + DeclarationListNodeType node) { + return asFinalParser()->checkExportedNamesForDeclarationList(node); +} + +template +inline bool Parser::checkExportedNameForClause( + NameNode* nameNode) { + return checkExportedName(nameNode->atom()); +} + +template +inline bool Parser::checkExportedNameForClause( + NameNodeType nameNode) { + MOZ_ALWAYS_FALSE(abortIfSyntaxParser()); + return false; +} + +template +inline bool GeneralParser::checkExportedNameForClause( + NameNodeType nameNode) { + return asFinalParser()->checkExportedNameForClause(nameNode); +} + +template +bool Parser::checkExportedNameForFunction( + FunctionNode* funNode) { + return checkExportedName(funNode->funbox()->explicitName()); +} + +template +inline bool Parser::checkExportedNameForFunction( + FunctionNodeType funNode) { + MOZ_ALWAYS_FALSE(abortIfSyntaxParser()); + return false; +} + +template +inline bool GeneralParser::checkExportedNameForFunction( + FunctionNodeType funNode) { + return asFinalParser()->checkExportedNameForFunction(funNode); +} + +template +bool Parser::checkExportedNameForClass( + ClassNode* classNode) { + MOZ_ASSERT(classNode->names()); + return checkExportedName(classNode->names()->innerBinding()->atom()); +} + +template +inline bool Parser::checkExportedNameForClass( + ClassNodeType classNode) { + MOZ_ALWAYS_FALSE(abortIfSyntaxParser()); + return false; +} + +template +inline bool GeneralParser::checkExportedNameForClass( + ClassNodeType classNode) { + return asFinalParser()->checkExportedNameForClass(classNode); +} + +template <> +inline bool PerHandlerParser::processExport(ParseNode* node) { + return pc_->sc()->asModuleContext()->builder.processExport(node); +} + +template <> +inline bool PerHandlerParser::processExport(Node node) { + MOZ_ALWAYS_FALSE(abortIfSyntaxParser()); + return false; +} + +template <> +inline bool PerHandlerParser::processExportFrom( + BinaryNodeType node) { + return pc_->sc()->asModuleContext()->builder.processExportFrom(node); +} + +template <> +inline bool PerHandlerParser::processExportFrom( + BinaryNodeType node) { + MOZ_ALWAYS_FALSE(abortIfSyntaxParser()); + return false; +} + +template <> +inline bool PerHandlerParser::processImport( + BinaryNodeType node) { + return pc_->sc()->asModuleContext()->builder.processImport(node); +} + +template <> +inline bool PerHandlerParser::processImport( + BinaryNodeType node) { + MOZ_ALWAYS_FALSE(abortIfSyntaxParser()); + return false; +} + +template +typename ParseHandler::BinaryNodeType +GeneralParser::exportFrom(uint32_t begin, Node specList) { + if (!abortIfSyntaxParser()) { + return null(); + } + + MOZ_ASSERT(anyChars.isCurrentTokenType(TokenKind::From)); + + if (!mustMatchToken(TokenKind::String, JSMSG_MODULE_SPEC_AFTER_FROM)) { + return null(); + } + + NameNodeType moduleSpec = stringLiteral(); + if (!moduleSpec) { + return null(); + } + + TokenKind tt; + if (!tokenStream.peekTokenSameLine(&tt, TokenStream::SlashIsRegExp)) { + return null(); + } + uint32_t moduleSpecPos = pos().begin; + + ListNodeType importAssertionList = + handler_.newList(ParseNodeKind::ImportAssertionList, pos()); + if (!importAssertionList) { + return null(); + } + if (tt == TokenKind::Assert) { + tokenStream.consumeKnownToken(TokenKind::Assert, + TokenStream::SlashIsRegExp); + + if (!assertClause(importAssertionList)) { + return null(); + } + } + + if (!matchOrInsertSemicolon(TokenStream::SlashIsRegExp)) { + return null(); + } + + BinaryNodeType moduleRequest = handler_.newModuleRequest( + moduleSpec, importAssertionList, TokenPos(moduleSpecPos, pos().end)); + if (!moduleRequest) { + return null(); + } + + BinaryNodeType node = + handler_.newExportFromDeclaration(begin, specList, moduleRequest); + if (!node) { + return null(); + } + + if (!processExportFrom(node)) { + return null(); + } + + return node; +} + +template +typename ParseHandler::BinaryNodeType +GeneralParser::exportBatch(uint32_t begin) { + if (!abortIfSyntaxParser()) { + return null(); + } + + MOZ_ASSERT(anyChars.isCurrentTokenType(TokenKind::Mul)); + uint32_t beginExportSpec = pos().begin; + + ListNodeType kid = handler_.newList(ParseNodeKind::ExportSpecList, pos()); + if (!kid) { + return null(); + } + + bool foundAs; + if (!tokenStream.matchToken(&foundAs, TokenKind::As)) { + return null(); + } + + if (foundAs) { + TokenKind tt; + if (!tokenStream.getToken(&tt)) { + return null(); + } + + NameNodeType exportName = null(); + if (TokenKindIsPossibleIdentifierName(tt)) { + exportName = newName(anyChars.currentName()); + } else if (tt == TokenKind::String) { + exportName = moduleExportName(); + } else { + error(JSMSG_NO_EXPORT_NAME); + } + if (!exportName) { + return null(); + } + + if (!checkExportedNameForClause(exportName)) { + return null(); + } + + UnaryNodeType exportSpec = + handler_.newExportNamespaceSpec(beginExportSpec, exportName); + if (!exportSpec) { + return null(); + } + + handler_.addList(kid, exportSpec); + } else { + // Handle the form |export *| by adding a special export batch + // specifier to the list. + NullaryNodeType exportSpec = handler_.newExportBatchSpec(pos()); + if (!exportSpec) { + return null(); + } + + handler_.addList(kid, exportSpec); + } + + if (!mustMatchToken(TokenKind::From, JSMSG_FROM_AFTER_EXPORT_STAR)) { + return null(); + } + + return exportFrom(begin, kid); +} + +template +bool Parser::checkLocalExportNames(ListNode* node) { + // ES 2017 draft 15.2.3.1. + for (ParseNode* next : node->contents()) { + ParseNode* name = next->as().left(); + + if (name->isKind(ParseNodeKind::StringExpr)) { + errorAt(name->pn_pos.begin, JSMSG_BAD_LOCAL_STRING_EXPORT); + return false; + } + + MOZ_ASSERT(name->isKind(ParseNodeKind::Name)); + + TaggedParserAtomIndex ident = name->as().atom(); + if (!checkLocalExportName(ident, name->pn_pos.begin)) { + return false; + } + } + + return true; +} + +template +bool Parser::checkLocalExportNames( + ListNodeType node) { + MOZ_ALWAYS_FALSE(abortIfSyntaxParser()); + return false; +} + +template +inline bool GeneralParser::checkLocalExportNames( + ListNodeType node) { + return asFinalParser()->checkLocalExportNames(node); +} + +template +typename ParseHandler::Node GeneralParser::exportClause( + uint32_t begin) { + if (!abortIfSyntaxParser()) { + return null(); + } + + MOZ_ASSERT(anyChars.isCurrentTokenType(TokenKind::LeftCurly)); + + ListNodeType kid = handler_.newList(ParseNodeKind::ExportSpecList, pos()); + if (!kid) { + return null(); + } + + TokenKind tt; + while (true) { + // Handle the forms |export {}| and |export { ..., }| (where ... is non + // empty), by escaping the loop early if the next token is }. + if (!tokenStream.getToken(&tt)) { + return null(); + } + + if (tt == TokenKind::RightCurly) { + break; + } + + NameNodeType bindingName = null(); + if (TokenKindIsPossibleIdentifierName(tt)) { + bindingName = newName(anyChars.currentName()); + } else if (tt == TokenKind::String) { + bindingName = moduleExportName(); + } else { + error(JSMSG_NO_BINDING_NAME); + } + if (!bindingName) { + return null(); + } + + bool foundAs; + if (!tokenStream.matchToken(&foundAs, TokenKind::As)) { + return null(); + } + + NameNodeType exportName = null(); + if (foundAs) { + TokenKind tt; + if (!tokenStream.getToken(&tt)) { + return null(); + } + + if (TokenKindIsPossibleIdentifierName(tt)) { + exportName = newName(anyChars.currentName()); + } else if (tt == TokenKind::String) { + exportName = moduleExportName(); + } else { + error(JSMSG_NO_EXPORT_NAME); + } + } else { + if (tt != TokenKind::String) { + exportName = newName(anyChars.currentName()); + } else { + exportName = moduleExportName(); + } + } + if (!exportName) { + return null(); + } + + if (!checkExportedNameForClause(exportName)) { + return null(); + } + + BinaryNodeType exportSpec = handler_.newExportSpec(bindingName, exportName); + if (!exportSpec) { + return null(); + } + + handler_.addList(kid, exportSpec); + + TokenKind next; + if (!tokenStream.getToken(&next)) { + return null(); + } + + if (next == TokenKind::RightCurly) { + break; + } + + if (next != TokenKind::Comma) { + error(JSMSG_RC_AFTER_EXPORT_SPEC_LIST); + return null(); + } + } + + // Careful! If |from| follows, even on a new line, it must start a + // FromClause: + // + // export { x } + // from "foo"; // a single ExportDeclaration + // + // But if it doesn't, we might have an ASI opportunity in SlashIsRegExp + // context: + // + // export { x } // ExportDeclaration, terminated by ASI + // fro\u006D // ExpressionStatement, the name "from" + // + // In that case let matchOrInsertSemicolon sort out ASI or any necessary + // error. + bool matched; + if (!tokenStream.matchToken(&matched, TokenKind::From, + TokenStream::SlashIsRegExp)) { + return null(); + } + + if (matched) { + return exportFrom(begin, kid); + } + + if (!matchOrInsertSemicolon()) { + return null(); + } + + if (!checkLocalExportNames(kid)) { + return null(); + } + + UnaryNodeType node = + handler_.newExportDeclaration(kid, TokenPos(begin, pos().end)); + if (!node) { + return null(); + } + + if (!processExport(node)) { + return null(); + } + + return node; +} + +template +typename ParseHandler::UnaryNodeType +GeneralParser::exportVariableStatement(uint32_t begin) { + if (!abortIfSyntaxParser()) { + return null(); + } + + MOZ_ASSERT(anyChars.isCurrentTokenType(TokenKind::Var)); + + DeclarationListNodeType kid = + declarationList(YieldIsName, ParseNodeKind::VarStmt); + if (!kid) { + return null(); + } + if (!matchOrInsertSemicolon()) { + return null(); + } + if (!checkExportedNamesForDeclarationList(kid)) { + return null(); + } + + UnaryNodeType node = + handler_.newExportDeclaration(kid, TokenPos(begin, pos().end)); + if (!node) { + return null(); + } + + if (!processExport(node)) { + return null(); + } + + return node; +} + +template +typename ParseHandler::UnaryNodeType +GeneralParser::exportFunctionDeclaration( + uint32_t begin, uint32_t toStringStart, + FunctionAsyncKind asyncKind /* = SyncFunction */) { + if (!abortIfSyntaxParser()) { + return null(); + } + + MOZ_ASSERT(anyChars.isCurrentTokenType(TokenKind::Function)); + + Node kid = functionStmt(toStringStart, YieldIsName, NameRequired, asyncKind); + if (!kid) { + return null(); + } + + if (!checkExportedNameForFunction(handler_.asFunction(kid))) { + return null(); + } + + UnaryNodeType node = + handler_.newExportDeclaration(kid, TokenPos(begin, pos().end)); + if (!node) { + return null(); + } + + if (!processExport(node)) { + return null(); + } + + return node; +} + +template +typename ParseHandler::UnaryNodeType +GeneralParser::exportClassDeclaration(uint32_t begin) { + if (!abortIfSyntaxParser()) { + return null(); + } + + MOZ_ASSERT(anyChars.isCurrentTokenType(TokenKind::Class)); + + ClassNodeType kid = + classDefinition(YieldIsName, ClassStatement, NameRequired); + if (!kid) { + return null(); + } + + if (!checkExportedNameForClass(kid)) { + return null(); + } + + UnaryNodeType node = + handler_.newExportDeclaration(kid, TokenPos(begin, pos().end)); + if (!node) { + return null(); + } + + if (!processExport(node)) { + return null(); + } + + return node; +} + +template +typename ParseHandler::UnaryNodeType +GeneralParser::exportLexicalDeclaration( + uint32_t begin, DeclarationKind kind) { + if (!abortIfSyntaxParser()) { + return null(); + } + + MOZ_ASSERT(kind == DeclarationKind::Const || kind == DeclarationKind::Let); + MOZ_ASSERT_IF(kind == DeclarationKind::Const, + anyChars.isCurrentTokenType(TokenKind::Const)); + MOZ_ASSERT_IF(kind == DeclarationKind::Let, + anyChars.isCurrentTokenType(TokenKind::Let)); + + DeclarationListNodeType kid = lexicalDeclaration(YieldIsName, kind); + if (!kid) { + return null(); + } + if (!checkExportedNamesForDeclarationList(kid)) { + return null(); + } + + UnaryNodeType node = + handler_.newExportDeclaration(kid, TokenPos(begin, pos().end)); + if (!node) { + return null(); + } + + if (!processExport(node)) { + return null(); + } + + return node; +} + +template +typename ParseHandler::BinaryNodeType +GeneralParser::exportDefaultFunctionDeclaration( + uint32_t begin, uint32_t toStringStart, + FunctionAsyncKind asyncKind /* = SyncFunction */) { + if (!abortIfSyntaxParser()) { + return null(); + } + + MOZ_ASSERT(anyChars.isCurrentTokenType(TokenKind::Function)); + + Node kid = + functionStmt(toStringStart, YieldIsName, AllowDefaultName, asyncKind); + if (!kid) { + return null(); + } + + BinaryNodeType node = handler_.newExportDefaultDeclaration( + kid, null(), TokenPos(begin, pos().end)); + if (!node) { + return null(); + } + + if (!processExport(node)) { + return null(); + } + + return node; +} + +template +typename ParseHandler::BinaryNodeType +GeneralParser::exportDefaultClassDeclaration( + uint32_t begin) { + if (!abortIfSyntaxParser()) { + return null(); + } + + MOZ_ASSERT(anyChars.isCurrentTokenType(TokenKind::Class)); + + ClassNodeType kid = + classDefinition(YieldIsName, ClassStatement, AllowDefaultName); + if (!kid) { + return null(); + } + + BinaryNodeType node = handler_.newExportDefaultDeclaration( + kid, null(), TokenPos(begin, pos().end)); + if (!node) { + return null(); + } + + if (!processExport(node)) { + return null(); + } + + return node; +} + +template +typename ParseHandler::BinaryNodeType +GeneralParser::exportDefaultAssignExpr(uint32_t begin) { + if (!abortIfSyntaxParser()) { + return null(); + } + + TaggedParserAtomIndex name = TaggedParserAtomIndex::WellKnown::default_(); + NameNodeType nameNode = newName(name); + if (!nameNode) { + return null(); + } + if (!noteDeclaredName(name, DeclarationKind::Const, pos())) { + return null(); + } + + Node kid = assignExpr(InAllowed, YieldIsName, TripledotProhibited); + if (!kid) { + return null(); + } + + if (!matchOrInsertSemicolon()) { + return null(); + } + + BinaryNodeType node = handler_.newExportDefaultDeclaration( + kid, nameNode, TokenPos(begin, pos().end)); + if (!node) { + return null(); + } + + if (!processExport(node)) { + return null(); + } + + return node; +} + +template +typename ParseHandler::BinaryNodeType +GeneralParser::exportDefault(uint32_t begin) { + if (!abortIfSyntaxParser()) { + return null(); + } + + MOZ_ASSERT(anyChars.isCurrentTokenType(TokenKind::Default)); + + TokenKind tt; + if (!tokenStream.getToken(&tt, TokenStream::SlashIsRegExp)) { + return null(); + } + + if (!checkExportedName(TaggedParserAtomIndex::WellKnown::default_())) { + return null(); + } + + switch (tt) { + case TokenKind::Function: + return exportDefaultFunctionDeclaration(begin, pos().begin); + + case TokenKind::Async: { + TokenKind nextSameLine = TokenKind::Eof; + if (!tokenStream.peekTokenSameLine(&nextSameLine)) { + return null(); + } + + if (nextSameLine == TokenKind::Function) { + uint32_t toStringStart = pos().begin; + tokenStream.consumeKnownToken(TokenKind::Function); + return exportDefaultFunctionDeclaration( + begin, toStringStart, FunctionAsyncKind::AsyncFunction); + } + + anyChars.ungetToken(); + return exportDefaultAssignExpr(begin); + } + + case TokenKind::Class: + return exportDefaultClassDeclaration(begin); + + default: + anyChars.ungetToken(); + return exportDefaultAssignExpr(begin); + } +} + +template +typename ParseHandler::Node +GeneralParser::exportDeclaration() { + if (!abortIfSyntaxParser()) { + return null(); + } + + MOZ_ASSERT(anyChars.isCurrentTokenType(TokenKind::Export)); + + if (!pc_->atModuleLevel()) { + error(JSMSG_EXPORT_DECL_AT_TOP_LEVEL); + return null(); + } + + uint32_t begin = pos().begin; + + TokenKind tt; + if (!tokenStream.getToken(&tt)) { + return null(); + } + switch (tt) { + case TokenKind::Mul: + return exportBatch(begin); + + case TokenKind::LeftCurly: + return exportClause(begin); + + case TokenKind::Var: + return exportVariableStatement(begin); + + case TokenKind::Function: + return exportFunctionDeclaration(begin, pos().begin); + + case TokenKind::Async: { + TokenKind nextSameLine = TokenKind::Eof; + if (!tokenStream.peekTokenSameLine(&nextSameLine)) { + return null(); + } + + if (nextSameLine == TokenKind::Function) { + uint32_t toStringStart = pos().begin; + tokenStream.consumeKnownToken(TokenKind::Function); + return exportFunctionDeclaration(begin, toStringStart, + FunctionAsyncKind::AsyncFunction); + } + + error(JSMSG_DECLARATION_AFTER_EXPORT); + return null(); + } + + case TokenKind::Class: + return exportClassDeclaration(begin); + + case TokenKind::Const: + return exportLexicalDeclaration(begin, DeclarationKind::Const); + + case TokenKind::Let: + return exportLexicalDeclaration(begin, DeclarationKind::Let); + + case TokenKind::Default: + return exportDefault(begin); + + default: + error(JSMSG_DECLARATION_AFTER_EXPORT); + return null(); + } +} + +template +typename ParseHandler::UnaryNodeType +GeneralParser::expressionStatement( + YieldHandling yieldHandling, InvokedPrediction invoked) { + anyChars.ungetToken(); + Node pnexpr = expr(InAllowed, yieldHandling, TripledotProhibited, + /* possibleError = */ nullptr, invoked); + if (!pnexpr) { + return null(); + } + if (!matchOrInsertSemicolon()) { + return null(); + } + return handler_.newExprStatement(pnexpr, pos().end); +} + +template +typename ParseHandler::Node +GeneralParser::consequentOrAlternative( + YieldHandling yieldHandling) { + TokenKind next; + if (!tokenStream.peekToken(&next, TokenStream::SlashIsRegExp)) { + return null(); + } + + // Annex B.3.4 says that unbraced FunctionDeclarations under if/else in + // non-strict code act as if they were braced: |if (x) function f() {}| + // parses as |if (x) { function f() {} }|. + // + // Careful! FunctionDeclaration doesn't include generators or async + // functions. + if (next == TokenKind::Function) { + tokenStream.consumeKnownToken(next, TokenStream::SlashIsRegExp); + + // Parser::statement would handle this, but as this function handles + // every other error case, it seems best to handle this. + if (pc_->sc()->strict()) { + error(JSMSG_FORBIDDEN_AS_STATEMENT, "function declarations"); + return null(); + } + + TokenKind maybeStar; + if (!tokenStream.peekToken(&maybeStar)) { + return null(); + } + + if (maybeStar == TokenKind::Mul) { + error(JSMSG_FORBIDDEN_AS_STATEMENT, "generator declarations"); + return null(); + } + + ParseContext::Statement stmt(pc_, StatementKind::Block); + ParseContext::Scope scope(this); + if (!scope.init(pc_)) { + return null(); + } + + TokenPos funcPos = pos(); + Node fun = functionStmt(pos().begin, yieldHandling, NameRequired); + if (!fun) { + return null(); + } + + ListNodeType block = handler_.newStatementList(funcPos); + if (!block) { + return null(); + } + + handler_.addStatementToList(block, fun); + return finishLexicalScope(scope, block); + } + + return statement(yieldHandling); +} + +template +typename ParseHandler::TernaryNodeType +GeneralParser::ifStatement(YieldHandling yieldHandling) { + Vector condList(fc_), thenList(fc_); + Vector posList(fc_); + Node elseBranch; + + ParseContext::Statement stmt(pc_, StatementKind::If); + + while (true) { + uint32_t begin = pos().begin; + + /* An IF node has three kids: condition, then, and optional else. */ + Node cond = condition(InAllowed, yieldHandling); + if (!cond) { + return null(); + } + + TokenKind tt; + if (!tokenStream.peekToken(&tt, TokenStream::SlashIsRegExp)) { + return null(); + } + + Node thenBranch = consequentOrAlternative(yieldHandling); + if (!thenBranch) { + return null(); + } + + if (!condList.append(cond) || !thenList.append(thenBranch) || + !posList.append(begin)) { + return null(); + } + + bool matched; + if (!tokenStream.matchToken(&matched, TokenKind::Else, + TokenStream::SlashIsRegExp)) { + return null(); + } + if (matched) { + if (!tokenStream.matchToken(&matched, TokenKind::If, + TokenStream::SlashIsRegExp)) { + return null(); + } + if (matched) { + continue; + } + elseBranch = consequentOrAlternative(yieldHandling); + if (!elseBranch) { + return null(); + } + } else { + elseBranch = null(); + } + break; + } + + TernaryNodeType ifNode; + for (int i = condList.length() - 1; i >= 0; i--) { + ifNode = handler_.newIfStatement(posList[i], condList[i], thenList[i], + elseBranch); + if (!ifNode) { + return null(); + } + elseBranch = ifNode; + } + + return ifNode; +} + +template +typename ParseHandler::BinaryNodeType +GeneralParser::doWhileStatement( + YieldHandling yieldHandling) { + uint32_t begin = pos().begin; + ParseContext::Statement stmt(pc_, StatementKind::DoLoop); + Node body = statement(yieldHandling); + if (!body) { + return null(); + } + if (!mustMatchToken(TokenKind::While, JSMSG_WHILE_AFTER_DO)) { + return null(); + } + Node cond = condition(InAllowed, yieldHandling); + if (!cond) { + return null(); + } + + // The semicolon after do-while is even more optional than most + // semicolons in JS. Web compat required this by 2004: + // http://bugzilla.mozilla.org/show_bug.cgi?id=238945 + // ES3 and ES5 disagreed, but ES6 conforms to Web reality: + // https://bugs.ecmascript.org/show_bug.cgi?id=157 + // To parse |do {} while (true) false| correctly, use SlashIsRegExp. + bool ignored; + if (!tokenStream.matchToken(&ignored, TokenKind::Semi, + TokenStream::SlashIsRegExp)) { + return null(); + } + return handler_.newDoWhileStatement(body, cond, TokenPos(begin, pos().end)); +} + +template +typename ParseHandler::BinaryNodeType +GeneralParser::whileStatement(YieldHandling yieldHandling) { + uint32_t begin = pos().begin; + ParseContext::Statement stmt(pc_, StatementKind::WhileLoop); + Node cond = condition(InAllowed, yieldHandling); + if (!cond) { + return null(); + } + Node body = statement(yieldHandling); + if (!body) { + return null(); + } + return handler_.newWhileStatement(begin, cond, body); +} + +template +bool GeneralParser::matchInOrOf(bool* isForInp, + bool* isForOfp) { + TokenKind tt; + if (!tokenStream.getToken(&tt, TokenStream::SlashIsRegExp)) { + return false; + } + + *isForInp = tt == TokenKind::In; + *isForOfp = tt == TokenKind::Of; + if (!*isForInp && !*isForOfp) { + anyChars.ungetToken(); + } + + MOZ_ASSERT_IF(*isForInp || *isForOfp, *isForInp != *isForOfp); + return true; +} + +template +bool GeneralParser::forHeadStart( + YieldHandling yieldHandling, IteratorKind iterKind, + ParseNodeKind* forHeadKind, Node* forInitialPart, + Maybe& forLoopLexicalScope, + Node* forInOrOfExpression) { + MOZ_ASSERT(anyChars.isCurrentTokenType(TokenKind::LeftParen)); + + TokenKind tt; + if (!tokenStream.peekToken(&tt, TokenStream::SlashIsRegExp)) { + return false; + } + + // Super-duper easy case: |for (;| is a C-style for-loop with no init + // component. + if (tt == TokenKind::Semi) { + *forInitialPart = null(); + *forHeadKind = ParseNodeKind::ForHead; + return true; + } + + // Parsing after |for (var| is also relatively simple (from this method's + // point of view). No block-related work complicates matters, so delegate + // to Parser::declaration. + if (tt == TokenKind::Var) { + tokenStream.consumeKnownToken(tt, TokenStream::SlashIsRegExp); + + // Pass null for block object because |var| declarations don't use one. + *forInitialPart = declarationList(yieldHandling, ParseNodeKind::VarStmt, + forHeadKind, forInOrOfExpression); + return *forInitialPart != null(); + } + + // Otherwise we have a lexical declaration or an expression. + + // For-in loop backwards compatibility requires that |let| starting a + // for-loop that's not a (new to ES6) for-of loop, in non-strict mode code, + // parse as an identifier. (|let| in for-of is always a declaration.) + // + // For-of loops can't start with the token sequence "async of", because that + // leads to a shift-reduce conflict when parsing |for (async of => {};;)| or + // |for (async of [])|. + bool parsingLexicalDeclaration = false; + bool letIsIdentifier = false; + bool startsWithForOf = false; + if (tt == TokenKind::Const) { + parsingLexicalDeclaration = true; + tokenStream.consumeKnownToken(tt, TokenStream::SlashIsRegExp); + } else if (tt == TokenKind::Let) { + // We could have a {For,Lexical}Declaration, or we could have a + // LeftHandSideExpression with lookahead restrictions so it's not + // ambiguous with the former. Check for a continuation of the former + // to decide which we have. + tokenStream.consumeKnownToken(TokenKind::Let, TokenStream::SlashIsRegExp); + + TokenKind next; + if (!tokenStream.peekToken(&next)) { + return false; + } + + parsingLexicalDeclaration = nextTokenContinuesLetDeclaration(next); + if (!parsingLexicalDeclaration) { + anyChars.ungetToken(); + letIsIdentifier = true; + } + } else if (tt == TokenKind::Async && iterKind == IteratorKind::Sync) { + tokenStream.consumeKnownToken(TokenKind::Async, TokenStream::SlashIsRegExp); + + TokenKind next; + if (!tokenStream.peekToken(&next)) { + return false; + } + + if (next == TokenKind::Of) { + startsWithForOf = true; + } + anyChars.ungetToken(); + } + + if (parsingLexicalDeclaration) { + forLoopLexicalScope.emplace(this); + if (!forLoopLexicalScope->init(pc_)) { + return false; + } + + // Push a temporary ForLoopLexicalHead Statement that allows for + // lexical declarations, as they are usually allowed only in braced + // statements. + ParseContext::Statement forHeadStmt(pc_, StatementKind::ForLoopLexicalHead); + + *forInitialPart = + declarationList(yieldHandling, + tt == TokenKind::Const ? ParseNodeKind::ConstDecl + : ParseNodeKind::LetDecl, + forHeadKind, forInOrOfExpression); + return *forInitialPart != null(); + } + + uint32_t exprOffset; + if (!tokenStream.peekOffset(&exprOffset, TokenStream::SlashIsRegExp)) { + return false; + } + + // Finally, handle for-loops that start with expressions. Pass + // |InProhibited| so that |in| isn't parsed in a RelationalExpression as a + // binary operator. |in| makes it a for-in loop, *not* an |in| expression. + PossibleError possibleError(*this); + *forInitialPart = + expr(InProhibited, yieldHandling, TripledotProhibited, &possibleError); + if (!*forInitialPart) { + return false; + } + + bool isForIn, isForOf; + if (!matchInOrOf(&isForIn, &isForOf)) { + return false; + } + + // If we don't encounter 'in'/'of', we have a for(;;) loop. We've handled + // the init expression; the caller handles the rest. + if (!isForIn && !isForOf) { + if (!possibleError.checkForExpressionError()) { + return false; + } + + *forHeadKind = ParseNodeKind::ForHead; + return true; + } + + MOZ_ASSERT(isForIn != isForOf); + + // In a for-of loop, 'let' that starts the loop head is a |let| keyword, + // per the [lookahead ≠ let] restriction on the LeftHandSideExpression + // variant of such loops. Expressions that start with |let| can't be used + // here. + // + // var let = {}; + // for (let.prop of [1]) // BAD + // break; + // + // See ES6 13.7. + if (isForOf && letIsIdentifier) { + errorAt(exprOffset, JSMSG_BAD_STARTING_FOROF_LHS, "let"); + return false; + } + + // In a for-of loop, the LeftHandSideExpression isn't allowed to be an + // identifier named "async" per the [lookahead ≠ async of] restriction. + if (isForOf && startsWithForOf) { + errorAt(exprOffset, JSMSG_BAD_STARTING_FOROF_LHS, "async of"); + return false; + } + + *forHeadKind = isForIn ? ParseNodeKind::ForIn : ParseNodeKind::ForOf; + + // Verify the left-hand side expression doesn't have a forbidden form. + if (handler_.isUnparenthesizedDestructuringPattern(*forInitialPart)) { + if (!possibleError.checkForDestructuringErrorOrWarning()) { + return false; + } + } else if (handler_.isName(*forInitialPart)) { + if (const char* chars = nameIsArgumentsOrEval(*forInitialPart)) { + // |chars| is "arguments" or "eval" here. + if (!strictModeErrorAt(exprOffset, JSMSG_BAD_STRICT_ASSIGN, chars)) { + return false; + } + } + } else if (handler_.isPropertyOrPrivateMemberAccess(*forInitialPart)) { + // Permitted: no additional testing/fixup needed. + } else if (handler_.isFunctionCall(*forInitialPart)) { + if (!strictModeErrorAt(exprOffset, JSMSG_BAD_FOR_LEFTSIDE)) { + return false; + } + } else { + errorAt(exprOffset, JSMSG_BAD_FOR_LEFTSIDE); + return false; + } + + if (!possibleError.checkForExpressionError()) { + return false; + } + + // Finally, parse the iterated expression, making the for-loop's closing + // ')' the next token. + *forInOrOfExpression = expressionAfterForInOrOf(*forHeadKind, yieldHandling); + return *forInOrOfExpression != null(); +} + +template +typename ParseHandler::Node GeneralParser::forStatement( + YieldHandling yieldHandling) { + MOZ_ASSERT(anyChars.isCurrentTokenType(TokenKind::For)); + + uint32_t begin = pos().begin; + + ParseContext::Statement stmt(pc_, StatementKind::ForLoop); + + IteratorKind iterKind = IteratorKind::Sync; + unsigned iflags = 0; + + if (pc_->isAsync() || pc_->sc()->isModuleContext()) { + bool matched; + if (!tokenStream.matchToken(&matched, TokenKind::Await)) { + return null(); + } + + // If we come across a top level await here, mark the module as async. + if (matched && pc_->sc()->isModuleContext() && !pc_->isAsync()) { + if (!options().topLevelAwait) { + error(JSMSG_TOP_LEVEL_AWAIT_NOT_SUPPORTED); + return null(); + } + pc_->sc()->asModuleContext()->setIsAsync(); + MOZ_ASSERT(pc_->isAsync()); + } + + if (matched) { + iflags |= JSITER_FORAWAITOF; + iterKind = IteratorKind::Async; + } + } + + if (!mustMatchToken(TokenKind::LeftParen, [this](TokenKind actual) { + this->error((actual == TokenKind::Await && !this->pc_->isAsync()) + ? JSMSG_FOR_AWAIT_OUTSIDE_ASYNC + : JSMSG_PAREN_AFTER_FOR); + })) { + return null(); + } + + // ParseNodeKind::ForHead, ParseNodeKind::ForIn, or + // ParseNodeKind::ForOf depending on the loop type. + ParseNodeKind headKind; + + // |x| in either |for (x; ...; ...)| or |for (x in/of ...)|. + Node startNode; + + // The next two variables are used to implement `for (let/const ...)`. + // + // We generate an implicit block, wrapping the whole loop, to store loop + // variables declared this way. Note that if the loop uses `for (var...)` + // instead, those variables go on some existing enclosing scope, so no + // implicit block scope is created. + // + // Both variables remain null/none if the loop is any other form. + + // The static block scope for the implicit block scope. + Maybe forLoopLexicalScope; + + // The expression being iterated over, for for-in/of loops only. Unused + // for for(;;) loops. + Node iteratedExpr; + + // Parse the entirety of the loop-head for a for-in/of loop (so the next + // token is the closing ')'): + // + // for (... in/of ...) ... + // ^next token + // + // ...OR, parse up to the first ';' in a C-style for-loop: + // + // for (...; ...; ...) ... + // ^next token + // + // In either case the subsequent token can be consistently accessed using + // TokenStream::SlashIsDiv semantics. + if (!forHeadStart(yieldHandling, iterKind, &headKind, &startNode, + forLoopLexicalScope, &iteratedExpr)) { + return null(); + } + + MOZ_ASSERT(headKind == ParseNodeKind::ForIn || + headKind == ParseNodeKind::ForOf || + headKind == ParseNodeKind::ForHead); + + if (iterKind == IteratorKind::Async && headKind != ParseNodeKind::ForOf) { + errorAt(begin, JSMSG_FOR_AWAIT_NOT_OF); + return null(); + } + + TernaryNodeType forHead; + if (headKind == ParseNodeKind::ForHead) { + Node init = startNode; + + // Look for an operand: |for (;| means we might have already examined + // this semicolon with that modifier. + if (!mustMatchToken(TokenKind::Semi, JSMSG_SEMI_AFTER_FOR_INIT)) { + return null(); + } + + TokenKind tt; + if (!tokenStream.peekToken(&tt, TokenStream::SlashIsRegExp)) { + return null(); + } + + Node test; + if (tt == TokenKind::Semi) { + test = null(); + } else { + test = expr(InAllowed, yieldHandling, TripledotProhibited); + if (!test) { + return null(); + } + } + + if (!mustMatchToken(TokenKind::Semi, JSMSG_SEMI_AFTER_FOR_COND)) { + return null(); + } + + if (!tokenStream.peekToken(&tt, TokenStream::SlashIsRegExp)) { + return null(); + } + + Node update; + if (tt == TokenKind::RightParen) { + update = null(); + } else { + update = expr(InAllowed, yieldHandling, TripledotProhibited); + if (!update) { + return null(); + } + } + + if (!mustMatchToken(TokenKind::RightParen, JSMSG_PAREN_AFTER_FOR_CTRL)) { + return null(); + } + + TokenPos headPos(begin, pos().end); + forHead = handler_.newForHead(init, test, update, headPos); + if (!forHead) { + return null(); + } + } else { + MOZ_ASSERT(headKind == ParseNodeKind::ForIn || + headKind == ParseNodeKind::ForOf); + + // |target| is the LeftHandSideExpression or declaration to which the + // per-iteration value (an arbitrary value exposed by the iteration + // protocol, or a string naming a property) is assigned. + Node target = startNode; + + // Parse the rest of the for-in/of head. + if (headKind == ParseNodeKind::ForIn) { + stmt.refineForKind(StatementKind::ForInLoop); + } else { + stmt.refineForKind(StatementKind::ForOfLoop); + } + + // Parser::declaration consumed everything up to the closing ')'. That + // token follows an {Assignment,}Expression and so must be interpreted + // as an operand to be consistent with normal expression tokenizing. + if (!mustMatchToken(TokenKind::RightParen, JSMSG_PAREN_AFTER_FOR_CTRL)) { + return null(); + } + + TokenPos headPos(begin, pos().end); + forHead = + handler_.newForInOrOfHead(headKind, target, iteratedExpr, headPos); + if (!forHead) { + return null(); + } + } + + Node body = statement(yieldHandling); + if (!body) { + return null(); + } + + ForNodeType forLoop = handler_.newForStatement(begin, forHead, body, iflags); + if (!forLoop) { + return null(); + } + + if (forLoopLexicalScope) { + return finishLexicalScope(*forLoopLexicalScope, forLoop); + } + + return forLoop; +} + +template +typename ParseHandler::SwitchStatementType +GeneralParser::switchStatement( + YieldHandling yieldHandling) { + MOZ_ASSERT(anyChars.isCurrentTokenType(TokenKind::Switch)); + uint32_t begin = pos().begin; + + if (!mustMatchToken(TokenKind::LeftParen, JSMSG_PAREN_BEFORE_SWITCH)) { + return null(); + } + + Node discriminant = + exprInParens(InAllowed, yieldHandling, TripledotProhibited); + if (!discriminant) { + return null(); + } + + if (!mustMatchToken(TokenKind::RightParen, JSMSG_PAREN_AFTER_SWITCH)) { + return null(); + } + if (!mustMatchToken(TokenKind::LeftCurly, JSMSG_CURLY_BEFORE_SWITCH)) { + return null(); + } + + ParseContext::Statement stmt(pc_, StatementKind::Switch); + ParseContext::Scope scope(this); + if (!scope.init(pc_)) { + return null(); + } + + ListNodeType caseList = handler_.newStatementList(pos()); + if (!caseList) { + return null(); + } + + bool seenDefault = false; + TokenKind tt; + while (true) { + if (!tokenStream.getToken(&tt, TokenStream::SlashIsRegExp)) { + return null(); + } + if (tt == TokenKind::RightCurly) { + break; + } + uint32_t caseBegin = pos().begin; + + Node caseExpr; + switch (tt) { + case TokenKind::Default: + if (seenDefault) { + error(JSMSG_TOO_MANY_DEFAULTS); + return null(); + } + seenDefault = true; + caseExpr = null(); // The default case has pn_left == nullptr. + break; + + case TokenKind::Case: + caseExpr = expr(InAllowed, yieldHandling, TripledotProhibited); + if (!caseExpr) { + return null(); + } + break; + + default: + error(JSMSG_BAD_SWITCH); + return null(); + } + + if (!mustMatchToken(TokenKind::Colon, JSMSG_COLON_AFTER_CASE)) { + return null(); + } + + ListNodeType body = handler_.newStatementList(pos()); + if (!body) { + return null(); + } + + bool afterReturn = false; + bool warnedAboutStatementsAfterReturn = false; + uint32_t statementBegin = 0; + while (true) { + if (!tokenStream.peekToken(&tt, TokenStream::SlashIsRegExp)) { + return null(); + } + if (tt == TokenKind::RightCurly || tt == TokenKind::Case || + tt == TokenKind::Default) { + break; + } + if (afterReturn) { + if (!tokenStream.peekOffset(&statementBegin, + TokenStream::SlashIsRegExp)) { + return null(); + } + } + Node stmt = statementListItem(yieldHandling); + if (!stmt) { + return null(); + } + if (!warnedAboutStatementsAfterReturn) { + if (afterReturn) { + if (!handler_.isStatementPermittedAfterReturnStatement(stmt)) { + if (!warningAt(statementBegin, JSMSG_STMT_AFTER_RETURN)) { + return null(); + } + + warnedAboutStatementsAfterReturn = true; + } + } else if (handler_.isReturnStatement(stmt)) { + afterReturn = true; + } + } + handler_.addStatementToList(body, stmt); + } + + CaseClauseType caseClause = + handler_.newCaseOrDefault(caseBegin, caseExpr, body); + if (!caseClause) { + return null(); + } + handler_.addCaseStatementToList(caseList, caseClause); + } + + LexicalScopeNodeType lexicalForCaseList = finishLexicalScope(scope, caseList); + if (!lexicalForCaseList) { + return null(); + } + + handler_.setEndPosition(lexicalForCaseList, pos().end); + + return handler_.newSwitchStatement(begin, discriminant, lexicalForCaseList, + seenDefault); +} + +template +typename ParseHandler::ContinueStatementType +GeneralParser::continueStatement( + YieldHandling yieldHandling) { + MOZ_ASSERT(anyChars.isCurrentTokenType(TokenKind::Continue)); + uint32_t begin = pos().begin; + + TaggedParserAtomIndex label; + if (!matchLabel(yieldHandling, &label)) { + return null(); + } + + 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)); +} + +template +typename ParseHandler::BreakStatementType +GeneralParser::breakStatement(YieldHandling yieldHandling) { + MOZ_ASSERT(anyChars.isCurrentTokenType(TokenKind::Break)); + uint32_t begin = pos().begin; + + TaggedParserAtomIndex label; + if (!matchLabel(yieldHandling, &label)) { + return null(); + } + + auto validity = pc_->checkBreakStatement(label); + if (validity.isErr()) { + switch (validity.unwrapErr()) { + case ParseContext::BreakStatementError::ToughBreak: + errorAt(begin, JSMSG_TOUGH_BREAK); + return null(); + case ParseContext::BreakStatementError::LabelNotFound: + error(JSMSG_LABEL_NOT_FOUND); + return null(); + } + } + + if (!matchOrInsertSemicolon()) { + return null(); + } + + return handler_.newBreakStatement(label, TokenPos(begin, pos().end)); +} + +template +typename ParseHandler::UnaryNodeType +GeneralParser::returnStatement( + YieldHandling yieldHandling) { + MOZ_ASSERT(anyChars.isCurrentTokenType(TokenKind::Return)); + uint32_t begin = pos().begin; + + MOZ_ASSERT(pc_->isFunctionBox()); + + // Parse an optional operand. + // + // This is ugly, but we don't want to require a semicolon. + Node exprNode; + TokenKind tt = TokenKind::Eof; + if (!tokenStream.peekTokenSameLine(&tt, TokenStream::SlashIsRegExp)) { + return null(); + } + switch (tt) { + case TokenKind::Eol: + case TokenKind::Eof: + case TokenKind::Semi: + case TokenKind::RightCurly: + exprNode = null(); + break; + default: { + exprNode = expr(InAllowed, yieldHandling, TripledotProhibited); + if (!exprNode) { + return null(); + } + } + } + + if (!matchOrInsertSemicolon()) { + return null(); + } + + return handler_.newReturnStatement(exprNode, TokenPos(begin, pos().end)); +} + +template +typename ParseHandler::UnaryNodeType +GeneralParser::yieldExpression(InHandling inHandling) { + MOZ_ASSERT(anyChars.isCurrentTokenType(TokenKind::Yield)); + uint32_t begin = pos().begin; + + MOZ_ASSERT(pc_->isGenerator()); + MOZ_ASSERT(pc_->isFunctionBox()); + + pc_->lastYieldOffset = begin; + + Node exprNode; + ParseNodeKind kind = ParseNodeKind::YieldExpr; + TokenKind tt = TokenKind::Eof; + if (!tokenStream.peekTokenSameLine(&tt, TokenStream::SlashIsRegExp)) { + return null(); + } + switch (tt) { + // TokenKind::Eol is special; it implements the [no LineTerminator here] + // quirk in the grammar. + case TokenKind::Eol: + // The rest of these make up the complete set of tokens that can + // appear after any of the places where AssignmentExpression is used + // throughout the grammar. Conveniently, none of them can also be the + // start an expression. + case TokenKind::Eof: + case TokenKind::Semi: + case TokenKind::RightCurly: + case TokenKind::RightBracket: + case TokenKind::RightParen: + case TokenKind::Colon: + case TokenKind::Comma: + case TokenKind::In: // Annex B.3.6 `for (x = yield in y) ;` + // No value. + exprNode = null(); + break; + case TokenKind::Mul: + kind = ParseNodeKind::YieldStarExpr; + tokenStream.consumeKnownToken(TokenKind::Mul, TokenStream::SlashIsRegExp); + [[fallthrough]]; + default: + exprNode = assignExpr(inHandling, YieldIsKeyword, TripledotProhibited); + if (!exprNode) { + return null(); + } + } + if (kind == ParseNodeKind::YieldStarExpr) { + return handler_.newYieldStarExpression(begin, exprNode); + } + return handler_.newYieldExpression(begin, exprNode); +} + +template +typename ParseHandler::BinaryNodeType +GeneralParser::withStatement(YieldHandling yieldHandling) { + MOZ_ASSERT(anyChars.isCurrentTokenType(TokenKind::With)); + uint32_t begin = pos().begin; + + if (pc_->sc()->strict()) { + if (!strictModeError(JSMSG_STRICT_CODE_WITH)) { + return null(); + } + } + + if (!mustMatchToken(TokenKind::LeftParen, JSMSG_PAREN_BEFORE_WITH)) { + return null(); + } + + Node objectExpr = exprInParens(InAllowed, yieldHandling, TripledotProhibited); + if (!objectExpr) { + return null(); + } + + if (!mustMatchToken(TokenKind::RightParen, JSMSG_PAREN_AFTER_WITH)) { + return null(); + } + + Node innerBlock; + { + ParseContext::Statement stmt(pc_, StatementKind::With); + innerBlock = statement(yieldHandling); + if (!innerBlock) { + return null(); + } + } + + pc_->sc()->setBindingsAccessedDynamically(); + + return handler_.newWithStatement(begin, objectExpr, innerBlock); +} + +template +typename ParseHandler::Node GeneralParser::labeledItem( + YieldHandling yieldHandling) { + TokenKind tt; + if (!tokenStream.getToken(&tt, TokenStream::SlashIsRegExp)) { + return null(); + } + + if (tt == TokenKind::Function) { + TokenKind next; + if (!tokenStream.peekToken(&next)) { + return null(); + } + + // GeneratorDeclaration is only matched by HoistableDeclaration in + // StatementListItem, so generators can't be inside labels. + if (next == TokenKind::Mul) { + error(JSMSG_GENERATOR_LABEL); + return null(); + } + + // Per 13.13.1 it's a syntax error if LabelledItem: FunctionDeclaration + // is ever matched. Per Annex B.3.2 that modifies this text, this + // applies only to strict mode code. + if (pc_->sc()->strict()) { + error(JSMSG_FUNCTION_LABEL); + return null(); + } + + return functionStmt(pos().begin, yieldHandling, NameRequired); + } + + anyChars.ungetToken(); + return statement(yieldHandling); +} + +template +typename ParseHandler::LabeledStatementType +GeneralParser::labeledStatement( + YieldHandling yieldHandling) { + TaggedParserAtomIndex label = labelIdentifier(yieldHandling); + if (!label) { + return null(); + } + + auto hasSameLabel = [&label](ParseContext::LabelStatement* stmt) { + return stmt->label() == label; + }; + + uint32_t begin = pos().begin; + + if (pc_->template findInnermostStatement( + hasSameLabel)) { + errorAt(begin, JSMSG_DUPLICATE_LABEL); + return null(); + } + + tokenStream.consumeKnownToken(TokenKind::Colon); + + /* Push a label struct and parse the statement. */ + ParseContext::LabelStatement stmt(pc_, label); + Node pn = labeledItem(yieldHandling); + if (!pn) { + return null(); + } + + return handler_.newLabeledStatement(label, pn, begin); +} + +template +typename ParseHandler::UnaryNodeType +GeneralParser::throwStatement(YieldHandling yieldHandling) { + MOZ_ASSERT(anyChars.isCurrentTokenType(TokenKind::Throw)); + uint32_t begin = pos().begin; + + /* ECMA-262 Edition 3 says 'throw [no LineTerminator here] Expr'. */ + TokenKind tt = TokenKind::Eof; + if (!tokenStream.peekTokenSameLine(&tt, TokenStream::SlashIsRegExp)) { + return null(); + } + if (tt == TokenKind::Eof || tt == TokenKind::Semi || + tt == TokenKind::RightCurly) { + error(JSMSG_MISSING_EXPR_AFTER_THROW); + return null(); + } + if (tt == TokenKind::Eol) { + error(JSMSG_LINE_BREAK_AFTER_THROW); + return null(); + } + + Node throwExpr = expr(InAllowed, yieldHandling, TripledotProhibited); + if (!throwExpr) { + return null(); + } + + if (!matchOrInsertSemicolon()) { + return null(); + } + + return handler_.newThrowStatement(throwExpr, TokenPos(begin, pos().end)); +} + +template +typename ParseHandler::TernaryNodeType +GeneralParser::tryStatement(YieldHandling yieldHandling) { + MOZ_ASSERT(anyChars.isCurrentTokenType(TokenKind::Try)); + uint32_t begin = pos().begin; + + /* + * try nodes are ternary. + * kid1 is the try statement + * kid2 is the catch node list or null + * kid3 is the finally statement + * + * catch nodes are binary. + * left is the catch-name/pattern or null + * right is the catch block + * + * catch lvalue nodes are either: + * a single identifier + * TokenKind::RightBracket for a destructuring left-hand side + * TokenKind::RightCurly for a destructuring left-hand side + * + * finally nodes are TokenKind::LeftCurly statement lists. + */ + + Node innerBlock; + { + if (!mustMatchToken(TokenKind::LeftCurly, JSMSG_CURLY_BEFORE_TRY)) { + return null(); + } + + uint32_t openedPos = pos().begin; + + ParseContext::Statement stmt(pc_, StatementKind::Try); + ParseContext::Scope scope(this); + if (!scope.init(pc_)) { + return null(); + } + + innerBlock = statementList(yieldHandling); + if (!innerBlock) { + return null(); + } + + innerBlock = finishLexicalScope(scope, innerBlock); + if (!innerBlock) { + return null(); + } + + if (!mustMatchToken( + TokenKind::RightCurly, [this, openedPos](TokenKind actual) { + this->reportMissingClosing(JSMSG_CURLY_AFTER_TRY, + JSMSG_CURLY_OPENED, openedPos); + })) { + return null(); + } + } + + LexicalScopeNodeType catchScope = null(); + TokenKind tt; + if (!tokenStream.getToken(&tt)) { + return null(); + } + if (tt == TokenKind::Catch) { + /* + * Create a lexical scope node around the whole catch clause, + * including the head. + */ + ParseContext::Statement stmt(pc_, StatementKind::Catch); + ParseContext::Scope scope(this); + if (!scope.init(pc_)) { + return null(); + } + + /* + * Legal catch forms are: + * catch (lhs) { + * catch { + * where lhs is a name or a destructuring left-hand side. + */ + bool omittedBinding; + if (!tokenStream.matchToken(&omittedBinding, TokenKind::LeftCurly)) { + return null(); + } + + Node catchName; + if (omittedBinding) { + catchName = null(); + } else { + if (!mustMatchToken(TokenKind::LeftParen, JSMSG_PAREN_BEFORE_CATCH)) { + return null(); + } + + if (!tokenStream.getToken(&tt)) { + return null(); + } + switch (tt) { + case TokenKind::LeftBracket: + case TokenKind::LeftCurly: + catchName = destructuringDeclaration(DeclarationKind::CatchParameter, + yieldHandling, tt); + if (!catchName) { + return null(); + } + break; + + default: { + if (!TokenKindIsPossibleIdentifierName(tt)) { + error(JSMSG_CATCH_IDENTIFIER); + return null(); + } + + catchName = bindingIdentifier(DeclarationKind::SimpleCatchParameter, + yieldHandling); + if (!catchName) { + return null(); + } + break; + } + } + + if (!mustMatchToken(TokenKind::RightParen, JSMSG_PAREN_AFTER_CATCH)) { + return null(); + } + + if (!mustMatchToken(TokenKind::LeftCurly, JSMSG_CURLY_BEFORE_CATCH)) { + return null(); + } + } + + LexicalScopeNodeType catchBody = catchBlockStatement(yieldHandling, scope); + if (!catchBody) { + return null(); + } + + catchScope = finishLexicalScope(scope, catchBody); + if (!catchScope) { + return null(); + } + + if (!handler_.setupCatchScope(catchScope, catchName, catchBody)) { + return null(); + } + handler_.setEndPosition(catchScope, pos().end); + + if (!tokenStream.getToken(&tt, TokenStream::SlashIsRegExp)) { + return null(); + } + } + + Node finallyBlock = null(); + + if (tt == TokenKind::Finally) { + if (!mustMatchToken(TokenKind::LeftCurly, JSMSG_CURLY_BEFORE_FINALLY)) { + return null(); + } + + uint32_t openedPos = pos().begin; + + ParseContext::Statement stmt(pc_, StatementKind::Finally); + ParseContext::Scope scope(this); + if (!scope.init(pc_)) { + return null(); + } + + finallyBlock = statementList(yieldHandling); + if (!finallyBlock) { + return null(); + } + + finallyBlock = finishLexicalScope(scope, finallyBlock); + if (!finallyBlock) { + return null(); + } + + if (!mustMatchToken( + TokenKind::RightCurly, [this, openedPos](TokenKind actual) { + this->reportMissingClosing(JSMSG_CURLY_AFTER_FINALLY, + JSMSG_CURLY_OPENED, openedPos); + })) { + return null(); + } + } else { + anyChars.ungetToken(); + } + if (!catchScope && !finallyBlock) { + error(JSMSG_CATCH_OR_FINALLY); + return null(); + } + + return handler_.newTryStatement(begin, innerBlock, catchScope, finallyBlock); +} + +template +typename ParseHandler::LexicalScopeNodeType +GeneralParser::catchBlockStatement( + YieldHandling yieldHandling, ParseContext::Scope& catchParamScope) { + uint32_t openedPos = pos().begin; + + ParseContext::Statement stmt(pc_, StatementKind::Block); + + // ES 13.15.7 CatchClauseEvaluation + // + // Step 8 means that the body of a catch block always has an additional + // lexical scope. + ParseContext::Scope scope(this); + if (!scope.init(pc_)) { + return null(); + } + + // The catch parameter names cannot be redeclared inside the catch + // block, so declare the name in the inner scope. + if (!scope.addCatchParameters(pc_, catchParamScope)) { + return null(); + } + + ListNodeType list = statementList(yieldHandling); + if (!list) { + return null(); + } + + if (!mustMatchToken( + TokenKind::RightCurly, [this, openedPos](TokenKind actual) { + this->reportMissingClosing(JSMSG_CURLY_AFTER_CATCH, + JSMSG_CURLY_OPENED, openedPos); + })) { + return null(); + } + + // The catch parameter names are not bound in the body scope, so remove + // them before generating bindings. + scope.removeCatchParameters(pc_, catchParamScope); + return finishLexicalScope(scope, list); +} + +template +typename ParseHandler::DebuggerStatementType +GeneralParser::debuggerStatement() { + TokenPos p; + p.begin = pos().begin; + if (!matchOrInsertSemicolon()) { + return null(); + } + p.end = pos().end; + + return handler_.newDebuggerStatement(p); +} + +static AccessorType ToAccessorType(PropertyType propType) { + switch (propType) { + case PropertyType::Getter: + return AccessorType::Getter; + case PropertyType::Setter: + return AccessorType::Setter; + case PropertyType::Normal: + case PropertyType::Method: + case PropertyType::GeneratorMethod: + case PropertyType::AsyncMethod: + case PropertyType::AsyncGeneratorMethod: + case PropertyType::Constructor: + case PropertyType::DerivedConstructor: + return AccessorType::None; + default: + MOZ_CRASH("unexpected property type"); + } +} + +#ifdef ENABLE_DECORATORS +template +typename ParseHandler::ListNodeType +GeneralParser::decoratorList(YieldHandling yieldHandling) { + ListNodeType decorators = + handler_.newList(ParseNodeKind::DecoratorList, pos()); + + // Build a decorator list element. At each entry point to this loop we have + // already consumed the |@| token + TokenKind tt; + for (;;) { + if (!tokenStream.getToken(&tt, TokenStream::SlashIsInvalid)) { + return null(); + } + + Node decorator = null(); + if (tt == TokenKind::LeftParen) { + // Handle DecoratorParenthesizedExpression + decorator = exprInParens(InAllowed, yieldHandling, TripledotProhibited); + if (!decorator) { + return null(); + } + + if (!mustMatchToken(TokenKind::RightParen, JSMSG_PAREN_AFTER_DECORATOR)) { + return null(); + } + + if (!tokenStream.getToken(&tt)) { + return null(); + } + } else { + // Get decorator identifier + if (!(tt == TokenKind::Name || TokenKindIsContextualKeyword(tt))) { + error(JSMSG_DECORATOR_NAME_EXPECTED); + return null(); + } + TaggedParserAtomIndex name = anyChars.currentName(); + if (!tokenStream.getToken(&tt)) { + return null(); + } + + // Handle DecoratorMemberExpression + decorator = handler_.newName(name, pos()); + + // Ok, this DecoratorMemberExpression is actually a list of + // Identifiers separated by `.` + if (tt == TokenKind::Dot) { + ListNodeType ids = + handler_.newList(ParseNodeKind::DecoratorList, pos()); + handler_.addList(ids, decorator); + for (;;) { + if (!tokenStream.getToken(&tt)) { + return null(); + } + + // Reject invalid or missing identifiers, like dec1.( + if (!(tt == TokenKind::Name || TokenKindIsContextualKeyword(tt))) { + error(JSMSG_DECORATOR_NAME_EXPECTED); + return null(); + } + TaggedParserAtomIndex name = anyChars.currentName(); + Node id = handler_.newName(name, pos()); + handler_.addList(ids, id); + + if (!tokenStream.getToken(&tt)) { + return null(); + } + + if (tt != TokenKind::Dot) { + break; + } + } + decorator = ids; + } + + // We've hit a `(`, so it's actually a DecoratorCallExpression + if (tt == TokenKind::LeftParen) { + bool isSpread = false; + Node args = argumentList(yieldHandling, &isSpread); + if (!args) { + return null(); + } + decorator = handler_.newCall(decorator, args, + isSpread ? JSOp::SpreadCall : JSOp::Call); + + if (!tokenStream.getToken(&tt)) { + return null(); + } + } + } + MOZ_ASSERT(decorator, "Decorator should exist"); + handler_.addList(decorators, decorator); + + if (tt != TokenKind::At) { + anyChars.ungetToken(); + break; + } + } + return decorators; +} +#endif + +template +bool GeneralParser::classMember( + YieldHandling yieldHandling, const ParseContext::ClassStatement& classStmt, + TaggedParserAtomIndex className, uint32_t classStartOffset, + HasHeritage hasHeritage, ClassInitializedMembers& classInitializedMembers, + ListNodeType& classMembers, bool* done) { + *done = false; + + TokenKind tt; + if (!tokenStream.getToken(&tt, TokenStream::SlashIsInvalid)) { + return false; + } + if (tt == TokenKind::RightCurly) { + *done = true; + return true; + } + + if (tt == TokenKind::Semi) { + return true; + } + +#ifdef ENABLE_DECORATORS + ListNodeType decorators = null(); + if (tt == TokenKind::At) { + decorators = decoratorList(yieldHandling); + if (!decorators) { + return false; + } + + if (!tokenStream.getToken(&tt, TokenStream::SlashIsInvalid)) { + return false; + } + } +#endif + + bool isStatic = false; + if (tt == TokenKind::Static) { + if (!tokenStream.peekToken(&tt)) { + return false; + } + + if (tt == TokenKind::LeftCurly) { + /* Parsing static class block: static { ... } */ + FunctionNodeType staticBlockBody = + staticClassBlock(classInitializedMembers); + if (!staticBlockBody) { + return false; + } + + StaticClassBlockType classBlock = + handler_.newStaticClassBlock(staticBlockBody); + if (!classBlock) { + return false; + } + + return handler_.addClassMemberDefinition(classMembers, classBlock); + } + + if (tt != TokenKind::LeftParen && tt != TokenKind::Assign && + tt != TokenKind::Semi && tt != TokenKind::RightCurly) { + isStatic = true; + } else { + anyChars.ungetToken(); + } + } else { + anyChars.ungetToken(); + } + + uint32_t propNameOffset; + if (!tokenStream.peekOffset(&propNameOffset, TokenStream::SlashIsInvalid)) { + return false; + } + + TaggedParserAtomIndex propAtom; + PropertyType propType; + Node propName = propertyOrMethodName(yieldHandling, PropertyNameInClass, + /* maybeDecl = */ Nothing(), + classMembers, &propType, &propAtom); + if (!propName) { + return false; + } + + if (propType == PropertyType::Field || + propType == PropertyType::FieldWithAccessor) { + if (isStatic) { + if (propAtom == TaggedParserAtomIndex::WellKnown::prototype()) { + errorAt(propNameOffset, JSMSG_BAD_METHOD_DEF); + return false; + } + } + + if (propAtom == TaggedParserAtomIndex::WellKnown::constructor()) { + errorAt(propNameOffset, JSMSG_BAD_METHOD_DEF); + return false; + } + + if (handler_.isPrivateName(propName)) { + if (propAtom == TaggedParserAtomIndex::WellKnown::hashConstructor()) { + errorAt(propNameOffset, JSMSG_BAD_METHOD_DEF); + return false; + } + + auto privateName = propAtom; + if (!noteDeclaredPrivateName( + propName, privateName, propType, + isStatic ? FieldPlacement::Static : FieldPlacement::Instance, + pos())) { + return false; + } + } + +#ifdef ENABLE_DECORATORS + if (propType == PropertyType::FieldWithAccessor) { + // Decorators Proposal + // https://arai-a.github.io/ecma262-compare/?pr=2417&id=sec-runtime-semantics-classfielddefinitionevaluation + // + // FieldDefinition : accessor ClassElementName Initializeropt + // + // Step 1. Let name be the result of evaluating ClassElementName. + // ... + // Step 3. Let privateStateDesc be the string-concatenation of name + // and " accessor storage". + StringBuffer privateStateDesc(fc_); + if (!privateStateDesc.append(this->parserAtoms(), propAtom)) { + return false; + } + if (!privateStateDesc.append(" accessor storage")) { + return false; + } + // Step 4. Let privateStateName be a new Private Name whose + // [[Description]] value is privateStateDesc. + TokenPos propNamePos(propNameOffset, pos().end); + auto privateStateName = + privateStateDesc.finishParserAtom(this->parserAtoms(), fc_); + if (!noteDeclaredPrivateName( + propName, privateStateName, propType, + isStatic ? FieldPlacement::Static : FieldPlacement::Instance, + propNamePos)) { + return false; + } + + // Step 5. Let getter be MakeAutoAccessorGetter(homeObject, name, + // privateStateName). + Node method = synthesizeAccessor( + propName, propNamePos, propAtom, privateStateName, isStatic, + FunctionSyntaxKind::Getter, decorators, classInitializedMembers); + if (!method) { + return false; + } + if (!handler_.addClassMemberDefinition(classMembers, method)) { + return false; + } + + // Step 6. Let setter be MakeAutoAccessorSetter(homeObject, name, + // privateStateName). + method = synthesizeAccessor( + propName, propNamePos, propAtom, privateStateName, isStatic, + FunctionSyntaxKind::Setter, decorators, classInitializedMembers); + if (!method) { + return false; + } + if (!handler_.addClassMemberDefinition(classMembers, method)) { + return false; + } + + // Step 10. Return ClassElementDefinition Record { [[Key]]: name, + // [[Kind]]: accessor, [[Get]]: getter, [[Set]]: setter, + // [[BackingStorageKey]]: privateStateName, [[Initializers]]: + // initializers, [[Decorators]]: empty }. + propName = handler_.newPrivateName(privateStateName, pos()); + propAtom = privateStateName; + decorators = handler_.newList(ParseNodeKind::DecoratorList, pos()); + } +#endif + if (isStatic) { + classInitializedMembers.staticFields++; + } else { + classInitializedMembers.instanceFields++; + } + + TokenPos propNamePos(propNameOffset, pos().end); + FunctionNodeType initializer = + fieldInitializerOpt(propNamePos, propName, propAtom, + classInitializedMembers, isStatic, hasHeritage); + if (!initializer) { + return false; + } + + if (!matchOrInsertSemicolon(TokenStream::SlashIsInvalid)) { + return false; + } + + ClassFieldType field = handler_.newClassFieldDefinition( + propName, initializer, isStatic +#ifdef ENABLE_DECORATORS + , + decorators, propType == PropertyType::FieldWithAccessor +#endif + ); + if (!field) { + return false; + } + + return handler_.addClassMemberDefinition(classMembers, field); + } + + if (propType != PropertyType::Getter && propType != PropertyType::Setter && + propType != PropertyType::Method && + propType != PropertyType::GeneratorMethod && + propType != PropertyType::AsyncMethod && + propType != PropertyType::AsyncGeneratorMethod) { + errorAt(propNameOffset, JSMSG_BAD_METHOD_DEF); + return false; + } + + bool isConstructor = + !isStatic && propAtom == TaggedParserAtomIndex::WellKnown::constructor(); + if (isConstructor) { + if (propType != PropertyType::Method) { + errorAt(propNameOffset, JSMSG_BAD_METHOD_DEF); + return false; + } + if (classStmt.constructorBox) { + errorAt(propNameOffset, JSMSG_DUPLICATE_PROPERTY, "constructor"); + return false; + } + propType = hasHeritage == HasHeritage::Yes + ? PropertyType::DerivedConstructor + : PropertyType::Constructor; + } else if (isStatic && + propAtom == TaggedParserAtomIndex::WellKnown::prototype()) { + errorAt(propNameOffset, JSMSG_BAD_METHOD_DEF); + return false; + } + + TaggedParserAtomIndex funName; + switch (propType) { + case PropertyType::Getter: + case PropertyType::Setter: { + bool hasStaticName = + !anyChars.isCurrentTokenType(TokenKind::RightBracket) && propAtom; + if (hasStaticName) { + funName = prefixAccessorName(propType, propAtom); + if (!funName) { + return false; + } + } + break; + } + case PropertyType::Constructor: + case PropertyType::DerivedConstructor: + funName = className; + break; + default: + if (!anyChars.isCurrentTokenType(TokenKind::RightBracket)) { + funName = propAtom; + } + } + + // When |super()| is invoked, we search for the nearest scope containing + // |.initializers| to initialize the class fields. This set-up precludes + // declaring |.initializers| in the class scope, because in some syntactic + // contexts |super()| can appear nested in a class, while actually belonging + // to an outer class definition. + // + // Example: + // class Outer extends Base { + // field = 1; + // constructor() { + // class Inner { + // field = 2; + // + // // The super() call in the computed property name mustn't access + // // Inner's |.initializers| array, but instead Outer's. + // [super()]() {} + // } + // } + // } + Maybe dotInitializersScope; + if (isConstructor && !options().selfHostingMode) { + dotInitializersScope.emplace(this); + if (!dotInitializersScope->init(pc_)) { + return false; + } + + if (!noteDeclaredName(TaggedParserAtomIndex::WellKnown::dotInitializers(), + DeclarationKind::Let, pos())) { + return false; + } + } + + // Calling toString on constructors need to return the source text for + // the entire class. The end offset is unknown at this point in + // parsing and will be amended when class parsing finishes below. + FunctionNodeType funNode = methodDefinition( + isConstructor ? classStartOffset : propNameOffset, propType, funName); + if (!funNode) { + return false; + } + + AccessorType atype = ToAccessorType(propType); + + Maybe initializerIfPrivate = Nothing(); + if (handler_.isPrivateName(propName)) { + if (propAtom == TaggedParserAtomIndex::WellKnown::hashConstructor()) { + // #constructor is an invalid private name. + errorAt(propNameOffset, JSMSG_BAD_METHOD_DEF); + return false; + } + + TaggedParserAtomIndex privateName = propAtom; + if (!noteDeclaredPrivateName( + propName, privateName, propType, + isStatic ? FieldPlacement::Static : FieldPlacement::Instance, + pos())) { + return false; + } + + // Private non-static methods are stored in the class body environment. + // Private non-static accessors are stamped onto every instance using + // initializers. Private static methods are stamped onto the constructor + // during class evaluation; see BytecodeEmitter::emitPropertyList. + if (!isStatic) { + if (atype == AccessorType::Getter || atype == AccessorType::Setter) { + classInitializedMembers.privateAccessors++; + TokenPos propNamePos(propNameOffset, pos().end); + auto initializerNode = + synthesizePrivateMethodInitializer(propAtom, atype, propNamePos); + if (!initializerNode) { + return false; + } + initializerIfPrivate = Some(initializerNode); + } else { + MOZ_ASSERT(atype == AccessorType::None); + classInitializedMembers.privateMethods++; + } + } + } + + Node method = handler_.newClassMethodDefinition(propName, funNode, atype, + isStatic, initializerIfPrivate +#ifdef ENABLE_DECORATORS + , + decorators +#endif + ); + if (!method) { + return false; + } + + if (dotInitializersScope.isSome()) { + method = finishLexicalScope(*dotInitializersScope, method); + if (!method) { + return false; + } + dotInitializersScope.reset(); + } + + return handler_.addClassMemberDefinition(classMembers, method); +} + +template +bool GeneralParser::finishClassConstructor( + const ParseContext::ClassStatement& classStmt, + TaggedParserAtomIndex className, HasHeritage hasHeritage, + uint32_t classStartOffset, uint32_t classEndOffset, + const ClassInitializedMembers& classInitializedMembers, + ListNodeType& classMembers) { + if (classStmt.constructorBox == nullptr) { + MOZ_ASSERT(!options().selfHostingMode); + // Unconditionally create the scope here, because it's always the + // constructor. + ParseContext::Scope dotInitializersScope(this); + if (!dotInitializersScope.init(pc_)) { + return false; + } + + if (!noteDeclaredName(TaggedParserAtomIndex::WellKnown::dotInitializers(), + DeclarationKind::Let, pos())) { + return false; + } + + // synthesizeConstructor assigns to classStmt.constructorBox + TokenPos synthesizedBodyPos(classStartOffset, classEndOffset); + FunctionNodeType synthesizedCtor = + synthesizeConstructor(className, synthesizedBodyPos, hasHeritage); + if (!synthesizedCtor) { + return false; + } + + // Note: the *function* has the name of the class, but the *property* + // containing the function has the name "constructor" + Node constructorNameNode = handler_.newObjectLiteralPropertyName( + TaggedParserAtomIndex::WellKnown::constructor(), pos()); + if (!constructorNameNode) { + return false; + } + ClassMethodType method = handler_.newDefaultClassConstructor( + constructorNameNode, synthesizedCtor); + if (!method) { + return false; + } + LexicalScopeNodeType scope = + finishLexicalScope(dotInitializersScope, method); + if (!scope) { + return false; + } + if (!handler_.addClassMemberDefinition(classMembers, scope)) { + return false; + } + } + + MOZ_ASSERT(classStmt.constructorBox); + FunctionBox* ctorbox = classStmt.constructorBox; + + // Amend the toStringEnd offset for the constructor now that we've + // finished parsing the class. + ctorbox->setCtorToStringEnd(classEndOffset); + + size_t numMemberInitializers = classInitializedMembers.privateAccessors + + classInitializedMembers.instanceFields; + bool hasPrivateBrand = classInitializedMembers.hasPrivateBrand(); + if (hasPrivateBrand || numMemberInitializers > 0) { + // Now that we have full set of initializers, update the constructor. + MemberInitializers initializers(hasPrivateBrand, numMemberInitializers); + ctorbox->setMemberInitializers(initializers); + + // Field initialization need access to `this`. + ctorbox->setCtorFunctionHasThisBinding(); + } + + return true; +} + +template +typename ParseHandler::ClassNodeType +GeneralParser::classDefinition( + YieldHandling yieldHandling, ClassContext classContext, + DefaultHandling defaultHandling) { +#ifdef ENABLE_DECORATORS + MOZ_ASSERT(anyChars.isCurrentTokenType(TokenKind::At) || + anyChars.isCurrentTokenType(TokenKind::Class)); + + ListNodeType decorators = null(); + if (anyChars.isCurrentTokenType(TokenKind::At)) { + decorators = decoratorList(yieldHandling); + if (!decorators) { + return null(); + } + TokenKind next; + if (!tokenStream.getToken(&next)) { + return null(); + } + if (next != TokenKind::Class) { + error(JSMSG_CLASS_EXPECTED); + return null(); + } + } +#else + MOZ_ASSERT(anyChars.isCurrentTokenType(TokenKind::Class)); +#endif + + uint32_t classStartOffset = pos().begin; + bool savedStrictness = setLocalStrictMode(true); + + TokenKind tt; + if (!tokenStream.getToken(&tt)) { + return null(); + } + + TaggedParserAtomIndex className; + if (TokenKindIsPossibleIdentifier(tt)) { + className = bindingIdentifier(yieldHandling); + if (!className) { + return null(); + } + } else if (classContext == ClassStatement) { + if (defaultHandling == AllowDefaultName) { + className = TaggedParserAtomIndex::WellKnown::default_(); + anyChars.ungetToken(); + } else { + // Class statements must have a bound name + error(JSMSG_UNNAMED_CLASS_STMT); + return null(); + } + } else { + // Make sure to put it back, whatever it was + anyChars.ungetToken(); + } + + // Because the binding definitions keep track of their blockId, we need to + // create at least the inner binding later. Keep track of the name's + // position in order to provide it for the nodes created later. + TokenPos namePos = pos(); + + bool isInClass = pc_->sc()->inClass(); + + // Push a ParseContext::ClassStatement to keep track of the constructor + // funbox. + ParseContext::ClassStatement classStmt(pc_); + + NameNodeType innerName; + Node nameNode = null(); + Node classHeritage = null(); + LexicalScopeNodeType classBlock = null(); + ClassBodyScopeNodeType classBodyBlock = null(); + uint32_t classEndOffset; + { + // A named class creates a new lexical scope with a const binding of the + // class name for the "inner name". + ParseContext::Statement innerScopeStmt(pc_, StatementKind::Block); + ParseContext::Scope innerScope(this); + if (!innerScope.init(pc_)) { + return null(); + } + + bool hasHeritageBool; + if (!tokenStream.matchToken(&hasHeritageBool, TokenKind::Extends)) { + return null(); + } + HasHeritage hasHeritage = + hasHeritageBool ? HasHeritage::Yes : HasHeritage::No; + if (hasHeritage == HasHeritage::Yes) { + if (!tokenStream.getToken(&tt)) { + return null(); + } + classHeritage = optionalExpr(yieldHandling, TripledotProhibited, tt); + if (!classHeritage) { + return null(); + } + } + + if (!mustMatchToken(TokenKind::LeftCurly, JSMSG_CURLY_BEFORE_CLASS)) { + return null(); + } + + { + ParseContext::Statement bodyScopeStmt(pc_, StatementKind::Block); + ParseContext::Scope bodyScope(this); + if (!bodyScope.init(pc_)) { + return null(); + } + + ListNodeType classMembers = handler_.newClassMemberList(pos().begin); + if (!classMembers) { + return null(); + } + + ClassInitializedMembers classInitializedMembers{}; + for (;;) { + bool done; + if (!classMember(yieldHandling, classStmt, className, classStartOffset, + hasHeritage, classInitializedMembers, classMembers, + &done)) { + return null(); + } + if (done) { + break; + } + } + + if (classInitializedMembers.privateMethods + + classInitializedMembers.privateAccessors > + 0) { + // We declare `.privateBrand` as ClosedOver because the constructor + // always uses it, even a default constructor. We could equivalently + // `noteNameUsed` when parsing the constructor, except that at that + // time, we don't necessarily know if the class has a private brand. + if (!noteDeclaredName( + TaggedParserAtomIndex::WellKnown::dotPrivateBrand(), + DeclarationKind::Synthetic, namePos, ClosedOver::Yes)) { + return null(); + } + } + + if (classInitializedMembers.instanceFieldKeys > 0) { + if (!noteDeclaredName(TaggedParserAtomIndex::WellKnown::dotFieldKeys(), + DeclarationKind::Synthetic, namePos)) { + return null(); + } + } + + if (classInitializedMembers.staticFields > 0) { + if (!noteDeclaredName( + TaggedParserAtomIndex::WellKnown::dotStaticInitializers(), + DeclarationKind::Synthetic, namePos)) { + return null(); + } + } + + if (classInitializedMembers.staticFieldKeys > 0) { + if (!noteDeclaredName( + TaggedParserAtomIndex::WellKnown::dotStaticFieldKeys(), + DeclarationKind::Synthetic, namePos)) { + return null(); + } + } + + classEndOffset = pos().end; + if (!finishClassConstructor(classStmt, className, hasHeritage, + classStartOffset, classEndOffset, + classInitializedMembers, classMembers)) { + return null(); + } + + classBodyBlock = finishClassBodyScope(bodyScope, classMembers); + if (!classBodyBlock) { + return null(); + } + + // Pop the class body scope + } + + if (className) { + // The inner name is immutable. + if (!noteDeclaredName(className, DeclarationKind::Const, namePos)) { + return null(); + } + + innerName = newName(className, namePos); + if (!innerName) { + return null(); + } + } + + classBlock = finishLexicalScope(innerScope, classBodyBlock); + if (!classBlock) { + return null(); + } + + // Pop the inner scope. + } + + if (className) { + NameNodeType outerName = null(); + if (classContext == ClassStatement) { + // The outer name is mutable. + if (!noteDeclaredName(className, DeclarationKind::Class, namePos)) { + return null(); + } + + outerName = newName(className, namePos); + if (!outerName) { + return null(); + } + } + + nameNode = handler_.newClassNames(outerName, innerName, namePos); + if (!nameNode) { + return null(); + } + } + MOZ_ALWAYS_TRUE(setLocalStrictMode(savedStrictness)); + // We're leaving a class definition that was not itself nested within a class + if (!isInClass) { + mozilla::Maybe maybeUnboundName; + if (!usedNames_.hasUnboundPrivateNames(fc_, maybeUnboundName)) { + return null(); + } + if (maybeUnboundName) { + UniqueChars str = + this->parserAtoms().toPrintableString(maybeUnboundName->atom); + if (!str) { + ReportOutOfMemory(this->fc_); + return null(); + } + + errorAt(maybeUnboundName->position.begin, JSMSG_MISSING_PRIVATE_DECL, + str.get()); + return null(); + } + } + + return handler_.newClass(nameNode, classHeritage, classBlock, +#ifdef ENABLE_DECORATORS + decorators, +#endif + TokenPos(classStartOffset, classEndOffset)); +} + +template +typename ParseHandler::FunctionNodeType +GeneralParser::synthesizeConstructor( + TaggedParserAtomIndex className, TokenPos synthesizedBodyPos, + HasHeritage hasHeritage) { + FunctionSyntaxKind functionSyntaxKind = + hasHeritage == HasHeritage::Yes + ? FunctionSyntaxKind::DerivedClassConstructor + : FunctionSyntaxKind::ClassConstructor; + + bool isSelfHosting = options().selfHostingMode; + FunctionFlags flags = + InitialFunctionFlags(functionSyntaxKind, GeneratorKind::NotGenerator, + FunctionAsyncKind::SyncFunction, isSelfHosting); + + // Create the top-level field initializer node. + FunctionNodeType funNode = + handler_.newFunction(functionSyntaxKind, synthesizedBodyPos); + if (!funNode) { + return null(); + } + + // If we see any inner function, note it on our current context. The bytecode + // emitter may eliminate the function later, but we use a conservative + // definition for consistency between lazy and full parsing. + pc_->sc()->setHasInnerFunctions(); + + // When fully parsing a lazy script, we do not fully reparse its inner + // functions, which are also lazy. Instead, their free variables and source + // extents are recorded and may be skipped. + if (handler_.reuseLazyInnerFunctions()) { + if (!skipLazyInnerFunction(funNode, synthesizedBodyPos.begin, + /* tryAnnexB = */ false)) { + return null(); + } + + return funNode; + } + + // Create the FunctionBox and link it to the function object. + Directives directives(true); + FunctionBox* funbox = newFunctionBox( + funNode, className, flags, synthesizedBodyPos.begin, directives, + GeneratorKind::NotGenerator, FunctionAsyncKind::SyncFunction); + if (!funbox) { + return null(); + } + funbox->initWithEnclosingParseContext(pc_, functionSyntaxKind); + setFunctionEndFromCurrentToken(funbox); + + // Mark this function as being synthesized by the parser. This means special + // handling in delazification will be used since we don't have typical + // function syntax. + funbox->setSyntheticFunction(); + + // Push a SourceParseContext on to the stack. + ParseContext* outerpc = pc_; + SourceParseContext funpc(this, funbox, /* newDirectives = */ nullptr); + if (!funpc.init()) { + return null(); + } + + if (!synthesizeConstructorBody(synthesizedBodyPos, hasHeritage, funNode, + funbox)) { + return null(); + } + + if (!leaveInnerFunction(outerpc)) { + return null(); + } + + return funNode; +} + +template +typename ParseHandler::FunctionNodeType +GeneralParser::synthesizeConstructorBody( + TokenPos synthesizedBodyPos, HasHeritage hasHeritage, + FunctionNodeType funNode, FunctionBox* funbox) { + MOZ_ASSERT(funbox->isClassConstructor()); + + // Create a ParamsBodyNode for the parameters + body (there are no + // parameters). + ParamsBodyNodeType argsbody = handler_.newParamsBody(synthesizedBodyPos); + if (!argsbody) { + return null(); + } + handler_.setFunctionFormalParametersAndBody(funNode, argsbody); + setFunctionStartAtPosition(funbox, synthesizedBodyPos); + + if (hasHeritage == HasHeritage::Yes) { + // Synthesize the equivalent to `function f(...args)` + funbox->setHasRest(); + if (!notePositionalFormalParameter( + funNode, TaggedParserAtomIndex::WellKnown::dotArgs(), + synthesizedBodyPos.begin, + /* disallowDuplicateParams = */ false, + /* duplicatedParam = */ nullptr)) { + return null(); + } + funbox->setArgCount(1); + } else { + funbox->setArgCount(0); + } + + pc_->functionScope().useAsVarScope(pc_); + + auto stmtList = handler_.newStatementList(synthesizedBodyPos); + if (!stmtList) { + return null(); + } + + if (!noteUsedName(TaggedParserAtomIndex::WellKnown::dotThis())) { + return null(); + } + + if (!noteUsedName(TaggedParserAtomIndex::WellKnown::dotInitializers())) { + return null(); + } + + if (hasHeritage == HasHeritage::Yes) { + // |super()| implicitly reads |new.target|. + if (!noteUsedName(TaggedParserAtomIndex::WellKnown::dotNewTarget())) { + return null(); + } + + NameNodeType thisName = newThisName(); + if (!thisName) { + return null(); + } + + UnaryNodeType superBase = + handler_.newSuperBase(thisName, synthesizedBodyPos); + if (!superBase) { + return null(); + } + + ListNodeType arguments = handler_.newArguments(synthesizedBodyPos); + if (!arguments) { + return null(); + } + + NameNodeType argsNameNode = newName( + TaggedParserAtomIndex::WellKnown::dotArgs(), synthesizedBodyPos); + if (!argsNameNode) { + return null(); + } + if (!noteUsedName(TaggedParserAtomIndex::WellKnown::dotArgs())) { + return null(); + } + + UnaryNodeType spreadArgs = + handler_.newSpread(synthesizedBodyPos.begin, argsNameNode); + if (!spreadArgs) { + return null(); + } + handler_.addList(arguments, spreadArgs); + + CallNodeType superCall = + handler_.newSuperCall(superBase, arguments, /* isSpread = */ true); + if (!superCall) { + return null(); + } + + BinaryNodeType setThis = handler_.newSetThis(thisName, superCall); + if (!setThis) { + return null(); + } + + UnaryNodeType exprStatement = + handler_.newExprStatement(setThis, synthesizedBodyPos.end); + if (!exprStatement) { + return null(); + } + + handler_.addStatementToList(stmtList, exprStatement); + } + + bool canSkipLazyClosedOverBindings = handler_.reuseClosedOverBindings(); + if (!pc_->declareFunctionThis(usedNames_, canSkipLazyClosedOverBindings)) { + return null(); + } + if (!pc_->declareNewTarget(usedNames_, canSkipLazyClosedOverBindings)) { + return null(); + } + + auto initializerBody = + finishLexicalScope(pc_->varScope(), stmtList, ScopeKind::FunctionLexical); + if (!initializerBody) { + return null(); + } + handler_.setBeginPosition(initializerBody, stmtList); + handler_.setEndPosition(initializerBody, stmtList); + + handler_.setFunctionBody(funNode, initializerBody); + + if (!finishFunction()) { + return null(); + } + + return funNode; +} + +template +typename ParseHandler::FunctionNodeType +GeneralParser::privateMethodInitializer( + TokenPos propNamePos, TaggedParserAtomIndex propAtom, + TaggedParserAtomIndex storedMethodAtom) { + if (!abortIfSyntaxParser()) { + return null(); + } + + // Synthesize an initializer function that the constructor can use to stamp a + // private method onto an instance object. + FunctionSyntaxKind syntaxKind = FunctionSyntaxKind::FieldInitializer; + FunctionAsyncKind asyncKind = FunctionAsyncKind::SyncFunction; + GeneratorKind generatorKind = GeneratorKind::NotGenerator; + bool isSelfHosting = options().selfHostingMode; + FunctionFlags flags = + InitialFunctionFlags(syntaxKind, generatorKind, asyncKind, isSelfHosting); + + FunctionNodeType funNode = handler_.newFunction(syntaxKind, propNamePos); + if (!funNode) { + return null(); + } + + Directives directives(true); + FunctionBox* funbox = + newFunctionBox(funNode, TaggedParserAtomIndex::null(), flags, + propNamePos.begin, directives, generatorKind, asyncKind); + if (!funbox) { + return null(); + } + funbox->initWithEnclosingParseContext(pc_, syntaxKind); + + // Push a SourceParseContext on to the stack. + ParseContext* outerpc = pc_; + SourceParseContext funpc(this, funbox, /* newDirectives = */ nullptr); + if (!funpc.init()) { + return null(); + } + pc_->functionScope().useAsVarScope(pc_); + + // Add empty parameter list. + ParamsBodyNodeType argsbody = handler_.newParamsBody(propNamePos); + if (!argsbody) { + return null(); + } + handler_.setFunctionFormalParametersAndBody(funNode, argsbody); + setFunctionStartAtCurrentToken(funbox); + funbox->setArgCount(0); + + // Note both the stored private method body and it's private name as being + // used in the initializer. They will be emitted into the method body in the + // BCE. + if (!noteUsedName(storedMethodAtom)) { + return null(); + } + NameNodeType privateNameNode = privateNameReference(propAtom); + if (!privateNameNode) { + return null(); + } + + // Unlike field initializers, private method initializers are not created with + // a body of synthesized AST nodes. Instead, the body is left empty and the + // initializer is synthesized at the bytecode level. + // See BytecodeEmitter::emitPrivateMethodInitializer. + ListNodeType stmtList = handler_.newStatementList(propNamePos); + if (!stmtList) { + return null(); + } + + bool canSkipLazyClosedOverBindings = handler_.reuseClosedOverBindings(); + if (!pc_->declareFunctionThis(usedNames_, canSkipLazyClosedOverBindings)) { + return null(); + } + if (!pc_->declareNewTarget(usedNames_, canSkipLazyClosedOverBindings)) { + return null(); + } + + LexicalScopeNodeType initializerBody = + finishLexicalScope(pc_->varScope(), stmtList, ScopeKind::FunctionLexical); + if (!initializerBody) { + return null(); + } + handler_.setBeginPosition(initializerBody, stmtList); + handler_.setEndPosition(initializerBody, stmtList); + handler_.setFunctionBody(funNode, initializerBody); + + // Set field-initializer lambda boundary to start at property name and end + // after method body. + setFunctionStartAtPosition(funbox, propNamePos); + setFunctionEndFromCurrentToken(funbox); + + if (!finishFunction()) { + return null(); + } + + if (!leaveInnerFunction(outerpc)) { + return null(); + } + + return funNode; +} + +template +typename ParseHandler::FunctionNodeType +GeneralParser::staticClassBlock( + ClassInitializedMembers& classInitializedMembers) { + // Both for getting-this-done, and because this will invariably be executed, + // syntax parsing should be aborted. + if (!abortIfSyntaxParser()) { + return null(); + } + + FunctionSyntaxKind syntaxKind = FunctionSyntaxKind::StaticClassBlock; + FunctionAsyncKind asyncKind = FunctionAsyncKind::SyncFunction; + GeneratorKind generatorKind = GeneratorKind::NotGenerator; + bool isSelfHosting = options().selfHostingMode; + FunctionFlags flags = + InitialFunctionFlags(syntaxKind, generatorKind, asyncKind, isSelfHosting); + + AutoAwaitIsKeyword awaitIsKeyword(this, AwaitHandling::AwaitIsDisallowed); + + // Create the function node for the static class body. + FunctionNodeType funNode = handler_.newFunction(syntaxKind, pos()); + if (!funNode) { + return null(); + } + + // Create the FunctionBox and link it to the function object. + Directives directives(true); + FunctionBox* funbox = + newFunctionBox(funNode, TaggedParserAtomIndex::null(), flags, pos().begin, + directives, generatorKind, asyncKind); + if (!funbox) { + return null(); + } + funbox->initWithEnclosingParseContext(pc_, syntaxKind); + MOZ_ASSERT(funbox->isSyntheticFunction()); + MOZ_ASSERT(!funbox->allowSuperCall()); + MOZ_ASSERT(!funbox->allowArguments()); + MOZ_ASSERT(!funbox->allowReturn()); + + // Set start at `static` token. + MOZ_ASSERT(anyChars.isCurrentTokenType(TokenKind::Static)); + setFunctionStartAtCurrentToken(funbox); + + // Push a SourceParseContext on to the stack. + ParseContext* outerpc = pc_; + SourceParseContext funpc(this, funbox, /* newDirectives = */ nullptr); + if (!funpc.init()) { + return null(); + } + + pc_->functionScope().useAsVarScope(pc_); + + uint32_t start = pos().begin; + + tokenStream.consumeKnownToken(TokenKind::LeftCurly); + + // Static class blocks are code-generated as if they were static field + // initializers, so we bump the staticFields count here, which ensures + // .staticInitializers is noted as used. + classInitializedMembers.staticFields++; + + LexicalScopeNodeType body = + functionBody(InHandling::InAllowed, YieldHandling::YieldIsKeyword, + syntaxKind, FunctionBodyType::StatementListBody); + if (!body) { + return null(); + } + + if (anyChars.isEOF()) { + error(JSMSG_UNTERMINATED_STATIC_CLASS_BLOCK); + return null(); + } + + tokenStream.consumeKnownToken(TokenKind::RightCurly, + TokenStream::Modifier::SlashIsRegExp); + + TokenPos wholeBodyPos(start, pos().end); + + handler_.setEndPosition(funNode, wholeBodyPos.end); + setFunctionEndFromCurrentToken(funbox); + + // Create a ParamsBodyNode for the parameters + body (there are no + // parameters). + ParamsBodyNodeType argsbody = handler_.newParamsBody(wholeBodyPos); + if (!argsbody) { + return null(); + } + + handler_.setFunctionFormalParametersAndBody(funNode, argsbody); + funbox->setArgCount(0); + + if (pc_->superScopeNeedsHomeObject()) { + funbox->setNeedsHomeObject(); + } + + handler_.setEndPosition(body, pos().begin); + handler_.setEndPosition(funNode, pos().end); + handler_.setFunctionBody(funNode, body); + + if (!finishFunction()) { + return null(); + } + + if (!leaveInnerFunction(outerpc)) { + return null(); + } + + return funNode; +} + +template +typename ParseHandler::FunctionNodeType +GeneralParser::fieldInitializerOpt( + TokenPos propNamePos, Node propName, TaggedParserAtomIndex propAtom, + ClassInitializedMembers& classInitializedMembers, bool isStatic, + HasHeritage hasHeritage) { + if (!abortIfSyntaxParser()) { + return null(); + } + + bool hasInitializer = false; + if (!tokenStream.matchToken(&hasInitializer, TokenKind::Assign, + TokenStream::SlashIsDiv)) { + return null(); + } + + FunctionSyntaxKind syntaxKind = FunctionSyntaxKind::FieldInitializer; + FunctionAsyncKind asyncKind = FunctionAsyncKind::SyncFunction; + GeneratorKind generatorKind = GeneratorKind::NotGenerator; + bool isSelfHosting = options().selfHostingMode; + FunctionFlags flags = + InitialFunctionFlags(syntaxKind, generatorKind, asyncKind, isSelfHosting); + + // Create the top-level field initializer node. + FunctionNodeType funNode = handler_.newFunction(syntaxKind, propNamePos); + if (!funNode) { + return null(); + } + + // Create the FunctionBox and link it to the function object. + Directives directives(true); + FunctionBox* funbox = + newFunctionBox(funNode, TaggedParserAtomIndex::null(), flags, + propNamePos.begin, directives, generatorKind, asyncKind); + if (!funbox) { + return null(); + } + funbox->initWithEnclosingParseContext(pc_, syntaxKind); + MOZ_ASSERT(funbox->isSyntheticFunction()); + + // We can't use setFunctionStartAtCurrentToken because that uses pos().begin, + // which is incorrect for fields without initializers (pos() points to the + // field identifier) + setFunctionStartAtPosition(funbox, propNamePos); + + // Push a SourceParseContext on to the stack. + ParseContext* outerpc = pc_; + SourceParseContext funpc(this, funbox, /* newDirectives = */ nullptr); + if (!funpc.init()) { + return null(); + } + + pc_->functionScope().useAsVarScope(pc_); + + Node initializerExpr; + if (hasInitializer) { + // Parse the expression for the field initializer. + { + AutoAwaitIsKeyword awaitHandling(this, AwaitIsName); + initializerExpr = assignExpr(InAllowed, YieldIsName, TripledotProhibited); + if (!initializerExpr) { + return null(); + } + } + + handler_.checkAndSetIsDirectRHSAnonFunction(initializerExpr); + } else { + initializerExpr = handler_.newRawUndefinedLiteral(propNamePos); + if (!initializerExpr) { + return null(); + } + } + + TokenPos wholeInitializerPos(propNamePos.begin, pos().end); + + // Update the end position of the parse node. + handler_.setEndPosition(funNode, wholeInitializerPos.end); + setFunctionEndFromCurrentToken(funbox); + + // Create a ParamsBodyNode for the parameters + body (there are no + // parameters). + ParamsBodyNodeType argsbody = handler_.newParamsBody(wholeInitializerPos); + if (!argsbody) { + return null(); + } + handler_.setFunctionFormalParametersAndBody(funNode, argsbody); + funbox->setArgCount(0); + + NameNodeType thisName = newThisName(); + if (!thisName) { + return null(); + } + + // Build `this.field` expression. + ThisLiteralType propAssignThis = + handler_.newThisLiteral(wholeInitializerPos, thisName); + if (!propAssignThis) { + return null(); + } + + Node propAssignFieldAccess; + uint32_t indexValue; + if (!propAtom) { + // See BytecodeEmitter::emitCreateFieldKeys for an explanation of what + // .fieldKeys means and its purpose. + NameNodeType fieldKeysName; + if (isStatic) { + fieldKeysName = newInternalDotName( + TaggedParserAtomIndex::WellKnown::dotStaticFieldKeys()); + } else { + fieldKeysName = + newInternalDotName(TaggedParserAtomIndex::WellKnown::dotFieldKeys()); + } + if (!fieldKeysName) { + return null(); + } + + double fieldKeyIndex; + if (isStatic) { + fieldKeyIndex = classInitializedMembers.staticFieldKeys++; + } else { + fieldKeyIndex = classInitializedMembers.instanceFieldKeys++; + } + Node fieldKeyIndexNode = handler_.newNumber( + fieldKeyIndex, DecimalPoint::NoDecimal, wholeInitializerPos); + if (!fieldKeyIndexNode) { + return null(); + } + + Node fieldKeyValue = handler_.newPropertyByValue( + fieldKeysName, fieldKeyIndexNode, wholeInitializerPos.end); + if (!fieldKeyValue) { + return null(); + } + + propAssignFieldAccess = handler_.newPropertyByValue( + propAssignThis, fieldKeyValue, wholeInitializerPos.end); + if (!propAssignFieldAccess) { + return null(); + } + } else if (handler_.isPrivateName(propName)) { + // It would be nice if we could tweak this here such that only if + // HasHeritage::Yes we end up emitting CheckPrivateField, but otherwise we + // emit InitElem -- this is an optimization to minimize HasOwn checks + // in InitElem for classes without heritage. + // + // Further tweaking would be to ultimately only do CheckPrivateField for the + // -first- field in a derived class, which would suffice to match the + // semantic check. + + NameNodeType privateNameNode = privateNameReference(propAtom); + if (!privateNameNode) { + return null(); + } + + propAssignFieldAccess = handler_.newPrivateMemberAccess( + propAssignThis, privateNameNode, wholeInitializerPos.end); + if (!propAssignFieldAccess) { + return null(); + } + } else if (this->parserAtoms().isIndex(propAtom, &indexValue)) { + propAssignFieldAccess = handler_.newPropertyByValue( + propAssignThis, propName, wholeInitializerPos.end); + if (!propAssignFieldAccess) { + return null(); + } + } else { + NameNodeType propAssignName = + handler_.newPropertyName(propAtom, wholeInitializerPos); + if (!propAssignName) { + return null(); + } + + propAssignFieldAccess = + handler_.newPropertyAccess(propAssignThis, propAssignName); + if (!propAssignFieldAccess) { + return null(); + } + } + + // Synthesize an property init. + BinaryNodeType initializerPropInit = + handler_.newInitExpr(propAssignFieldAccess, initializerExpr); + if (!initializerPropInit) { + return null(); + } + + UnaryNodeType exprStatement = + handler_.newExprStatement(initializerPropInit, wholeInitializerPos.end); + if (!exprStatement) { + return null(); + } + + ListNodeType statementList = handler_.newStatementList(wholeInitializerPos); + if (!statementList) { + return null(); + } + handler_.addStatementToList(statementList, exprStatement); + + bool canSkipLazyClosedOverBindings = handler_.reuseClosedOverBindings(); + if (!pc_->declareFunctionThis(usedNames_, canSkipLazyClosedOverBindings)) { + return null(); + } + if (!pc_->declareNewTarget(usedNames_, canSkipLazyClosedOverBindings)) { + return null(); + } + + // Set the function's body to the field assignment. + LexicalScopeNodeType initializerBody = finishLexicalScope( + pc_->varScope(), statementList, ScopeKind::FunctionLexical); + if (!initializerBody) { + return null(); + } + + handler_.setFunctionBody(funNode, initializerBody); + + if (pc_->superScopeNeedsHomeObject()) { + funbox->setNeedsHomeObject(); + } + + if (!finishFunction()) { + return null(); + } + + if (!leaveInnerFunction(outerpc)) { + return null(); + } + + return funNode; +} + +template +typename ParseHandler::FunctionNodeType +GeneralParser::synthesizePrivateMethodInitializer( + TaggedParserAtomIndex propAtom, AccessorType accessorType, + TokenPos propNamePos) { + if (!abortIfSyntaxParser()) { + return null(); + } + + // Synthesize a name for the lexical variable that will store the + // accessor body. + StringBuffer storedMethodName(fc_); + if (!storedMethodName.append(this->parserAtoms(), propAtom)) { + return null(); + } + if (!storedMethodName.append( + accessorType == AccessorType::Getter ? ".getter" : ".setter")) { + return null(); + } + auto storedMethodProp = + storedMethodName.finishParserAtom(this->parserAtoms(), fc_); + if (!storedMethodProp) { + return null(); + } + if (!noteDeclaredName(storedMethodProp, DeclarationKind::Synthetic, pos())) { + return null(); + } + + return privateMethodInitializer(propNamePos, propAtom, storedMethodProp); +} + +#ifdef ENABLE_DECORATORS + +template +typename ParseHandler::Node +GeneralParser::synthesizeAccessor( + Node propName, TokenPos propNamePos, TaggedParserAtomIndex propAtom, + TaggedParserAtomIndex privateStateNameAtom, bool isStatic, + FunctionSyntaxKind syntaxKind, ListNodeType decorators, + ClassInitializedMembers& classInitializedMembers) { + // Decorators Proposal + // https://arai-a.github.io/ecma262-compare/?pr=2417&id=sec-makeautoaccessorgetter + // The abstract operation MakeAutoAccessorGetter takes arguments homeObject + // (an Object), name (a property key or Private Name), and privateStateName (a + // Private Name) and returns a function object. + // + // https://arai-a.github.io/ecma262-compare/?pr=2417&id=sec-makeautoaccessorsetter + // The abstract operation MakeAutoAccessorSetter takes arguments homeObject + // (an Object), name (a property key or Private Name), and privateStateName (a + // Private Name) and returns a function object. + if (!abortIfSyntaxParser()) { + return null(); + } + + AccessorType accessorType = syntaxKind == FunctionSyntaxKind::Getter + ? AccessorType::Getter + : AccessorType::Setter; + + mozilla::Maybe initializerIfPrivate = Nothing(); + if (handler_.isPrivateName(propName)) { + classInitializedMembers.privateAccessors++; + auto initializerNode = + synthesizePrivateMethodInitializer(propAtom, accessorType, propNamePos); + if (!initializerNode) { + return null(); + } + initializerIfPrivate = Some(initializerNode); + handler_.setPrivateNameKind(propName, PrivateNameKind::GetterSetter); + } + + // https://arai-a.github.io/ecma262-compare/?pr=2417&id=sec-makeautoaccessorgetter + // 2. Let getter be CreateBuiltinFunction(getterClosure, 0, "get", « »). + // + // https://arai-a.github.io/ecma262-compare/?pr=2417&id=sec-makeautoaccessorsetter + // 2. Let setter be CreateBuiltinFunction(setterClosure, 1, "set", « »). + FunctionNodeType funNode = + synthesizeAccessorBody(propNamePos, privateStateNameAtom, syntaxKind); + if (!funNode) { + return null(); + } + + // https://arai-a.github.io/ecma262-compare/?pr=2417&id=sec-makeautoaccessorgetter + // 3. Perform MakeMethod(getter, homeObject). + // 4. Return getter. + // + // https://arai-a.github.io/ecma262-compare/?pr=2417&id=sec-makeautoaccessorsetter + // 3. Perform MakeMethod(setter, homeObject). + // 4. Return setter. + return handler_.newClassMethodDefinition(propName, funNode, accessorType, + isStatic, initializerIfPrivate, + decorators); +} + +template +typename ParseHandler::FunctionNodeType +GeneralParser::synthesizeAccessorBody( + TokenPos propNamePos, TaggedParserAtomIndex propAtom, + FunctionSyntaxKind syntaxKind) { + if (!abortIfSyntaxParser()) { + return null(); + } + + FunctionAsyncKind asyncKind = FunctionAsyncKind::SyncFunction; + GeneratorKind generatorKind = GeneratorKind::NotGenerator; + bool isSelfHosting = options().selfHostingMode; + FunctionFlags flags = + InitialFunctionFlags(syntaxKind, generatorKind, asyncKind, isSelfHosting); + + // Create the top-level function node. + FunctionNodeType funNode = handler_.newFunction(syntaxKind, propNamePos); + if (!funNode) { + return null(); + } + + // Create the FunctionBox and link it to the function object. + Directives directives(true); + FunctionBox* funbox = + newFunctionBox(funNode, TaggedParserAtomIndex::null(), flags, + propNamePos.begin, directives, generatorKind, asyncKind); + if (!funbox) { + return null(); + } + funbox->initWithEnclosingParseContext(pc_, syntaxKind); + funbox->setSyntheticFunction(); + + // Push a SourceParseContext on to the stack. + ParseContext* outerpc = pc_; + SourceParseContext funpc(this, funbox, /* newDirectives = */ nullptr); + if (!funpc.init()) { + return null(); + } + + pc_->functionScope().useAsVarScope(pc_); + + // The function we synthesize is located at the field with the + // accessor. + setFunctionStartAtCurrentToken(funbox); + setFunctionEndFromCurrentToken(funbox); + + // Create a ListNode for the parameters + body + ParamsBodyNodeType paramsbody = handler_.newParamsBody(propNamePos); + if (!paramsbody) { + return null(); + } + handler_.setFunctionFormalParametersAndBody(funNode, paramsbody); + + if (syntaxKind == FunctionSyntaxKind::Getter) { + funbox->setArgCount(0); + } else { + funbox->setArgCount(1); + } + + // Build `this` expression to access the privateStateName for use in the + // operations to create the getter and setter below. + NameNodeType thisName = newThisName(); + if (!thisName) { + return null(); + } + + ThisLiteralType propThis = handler_.newThisLiteral(propNamePos, thisName); + if (!propThis) { + return null(); + } + + NameNodeType privateNameNode = privateNameReference(propAtom); + if (!privateNameNode) { + return null(); + } + + Node propFieldAccess = handler_.newPrivateMemberAccess( + propThis, privateNameNode, propNamePos.end); + if (!propFieldAccess) { + return null(); + } + + Node accessorBody; + if (syntaxKind == FunctionSyntaxKind::Getter) { + // Decorators Proposal + // https://arai-a.github.io/ecma262-compare/?pr=2417&id=sec-makeautoaccessorgetter + // 1. Let getterClosure be a new Abstract Closure with no parameters that + // captures privateStateName and performs the following steps when called: + // 1.a. Let o be the this value. + // 1.b. Return ? PrivateGet(privateStateName, o). + accessorBody = handler_.newReturnStatement(propFieldAccess, propNamePos); + } else { + // Decorators Proposal + // https://arai-a.github.io/ecma262-compare/?pr=2417&id=sec-makeautoaccessorsetter + // The abstract operation MakeAutoAccessorSetter takes arguments homeObject + // (an Object), name (a property key or Private Name), and privateStateName + // (a Private Name) and returns a function object. + // 1. Let setterClosure be a new Abstract Closure with parameters (value) + // that captures privateStateName and performs the following steps when + // called: + // 1.a. Let o be the this value. + notePositionalFormalParameter(funNode, + TaggedParserAtomIndex::WellKnown::value(), + /* pos = */ 0, false, + /* duplicatedParam = */ nullptr); + + Node initializerExpr = handler_.newName( + TaggedParserAtomIndex::WellKnown::value(), propNamePos); + if (!initializerExpr) { + return null(); + } + + // 1.b. Perform ? PrivateSet(privateStateName, o, value). + Node assignment = handler_.newAssignment(ParseNodeKind::AssignExpr, + propFieldAccess, initializerExpr); + if (!assignment) { + return null(); + } + + accessorBody = handler_.newExprStatement(assignment, propNamePos.end); + if (!accessorBody) { + return null(); + } + + // 1.c. Return undefined. + } + + ListNodeType statementList = handler_.newStatementList(propNamePos); + if (!statementList) { + return null(); + } + handler_.addStatementToList(statementList, accessorBody); + + bool canSkipLazyClosedOverBindings = handler_.reuseClosedOverBindings(); + if (!pc_->declareFunctionThis(usedNames_, canSkipLazyClosedOverBindings)) { + return null(); + } + if (!pc_->declareNewTarget(usedNames_, canSkipLazyClosedOverBindings)) { + return null(); + } + + LexicalScopeNodeType initializerBody = finishLexicalScope( + pc_->varScope(), statementList, ScopeKind::FunctionLexical); + if (!initializerBody) { + return null(); + } + + handler_.setFunctionBody(funNode, initializerBody); + + if (pc_->superScopeNeedsHomeObject()) { + funbox->setNeedsHomeObject(); + } + + if (!finishFunction()) { + return null(); + } + + if (!leaveInnerFunction(outerpc)) { + return null(); + } + + return funNode; +} + +#endif + +bool ParserBase::nextTokenContinuesLetDeclaration(TokenKind next) { + MOZ_ASSERT(anyChars.isCurrentTokenType(TokenKind::Let)); + MOZ_ASSERT(anyChars.nextToken().type == next); + + TokenStreamShared::verifyConsistentModifier(TokenStreamShared::SlashIsDiv, + anyChars.nextToken()); + + // Destructuring continues a let declaration. + if (next == TokenKind::LeftBracket || next == TokenKind::LeftCurly) { + return true; + } + + // A "let" edge case deserves special comment. Consider this: + // + // let // not an ASI opportunity + // let; + // + // Static semantics in §13.3.1.1 turn a LexicalDeclaration that binds + // "let" into an early error. Does this retroactively permit ASI so + // that we should parse this as two ExpressionStatements? No. ASI + // resolves during parsing. Static semantics only apply to the full + // parse tree with ASI applied. No backsies! + + // Otherwise a let declaration must have a name. + return TokenKindIsPossibleIdentifier(next); +} + +template +typename ParseHandler::DeclarationListNodeType +GeneralParser::variableStatement( + YieldHandling yieldHandling) { + DeclarationListNodeType vars = + declarationList(yieldHandling, ParseNodeKind::VarStmt); + if (!vars) { + return null(); + } + if (!matchOrInsertSemicolon()) { + return null(); + } + return vars; +} + +template +typename ParseHandler::Node GeneralParser::statement( + YieldHandling yieldHandling) { + MOZ_ASSERT(checkOptionsCalled_); + + AutoCheckRecursionLimit recursion(this->fc_); + if (!recursion.check(this->fc_)) { + return null(); + } + + TokenKind tt; + if (!tokenStream.getToken(&tt, TokenStream::SlashIsRegExp)) { + return null(); + } + + switch (tt) { + // BlockStatement[?Yield, ?Return] + case TokenKind::LeftCurly: + return blockStatement(yieldHandling); + + // VariableStatement[?Yield] + case TokenKind::Var: + return variableStatement(yieldHandling); + + // EmptyStatement + case TokenKind::Semi: + return handler_.newEmptyStatement(pos()); + + // ExpressionStatement[?Yield]. + + case TokenKind::Yield: { + // Don't use a ternary operator here due to obscure linker issues + // around using static consts in the arms of a ternary. + Modifier modifier; + if (yieldExpressionsSupported()) { + modifier = TokenStream::SlashIsRegExp; + } else { + modifier = TokenStream::SlashIsDiv; + } + + TokenKind next; + if (!tokenStream.peekToken(&next, modifier)) { + return null(); + } + + if (next == TokenKind::Colon) { + return labeledStatement(yieldHandling); + } + + return expressionStatement(yieldHandling); + } + + default: { + // If we encounter an await in a module, and the module is not marked + // as async, mark the module as async. + if (tt == TokenKind::Await && !pc_->isAsync()) { + if (pc_->atModuleTopLevel()) { + if (!options().topLevelAwait) { + error(JSMSG_TOP_LEVEL_AWAIT_NOT_SUPPORTED); + return null(); + } + pc_->sc()->asModuleContext()->setIsAsync(); + MOZ_ASSERT(pc_->isAsync()); + } + } + + // Avoid getting next token with SlashIsDiv. + if (tt == TokenKind::Await && pc_->isAsync()) { + return expressionStatement(yieldHandling); + } + + if (!TokenKindIsPossibleIdentifier(tt)) { + return expressionStatement(yieldHandling); + } + + TokenKind next; + if (!tokenStream.peekToken(&next)) { + return null(); + } + + // |let| here can only be an Identifier, not a declaration. Give nicer + // errors for declaration-looking typos. + if (tt == TokenKind::Let) { + bool forbiddenLetDeclaration = false; + + if (next == TokenKind::LeftBracket) { + // Enforce ExpressionStatement's 'let [' lookahead restriction. + forbiddenLetDeclaration = true; + } else if (next == TokenKind::LeftCurly || + TokenKindIsPossibleIdentifier(next)) { + // 'let {' and 'let foo' aren't completely forbidden, if ASI + // causes 'let' to be the entire Statement. But if they're + // same-line, we can aggressively give a better error message. + // + // Note that this ignores 'yield' as TokenKind::Yield: we'll handle it + // correctly but with a worse error message. + TokenKind nextSameLine; + if (!tokenStream.peekTokenSameLine(&nextSameLine)) { + return null(); + } + + MOZ_ASSERT(TokenKindIsPossibleIdentifier(nextSameLine) || + nextSameLine == TokenKind::LeftCurly || + nextSameLine == TokenKind::Eol); + + forbiddenLetDeclaration = nextSameLine != TokenKind::Eol; + } + + if (forbiddenLetDeclaration) { + error(JSMSG_FORBIDDEN_AS_STATEMENT, "lexical declarations"); + return null(); + } + } else if (tt == TokenKind::Async) { + // Peek only on the same line: ExpressionStatement's lookahead + // restriction is phrased as + // + // [lookahead ∉ { '{', + // function, + // async [no LineTerminator here] function, + // class, + // let '[' }] + // + // meaning that code like this is valid: + // + // if (true) + // async // ASI opportunity + // function clownshoes() {} + TokenKind maybeFunction; + if (!tokenStream.peekTokenSameLine(&maybeFunction)) { + return null(); + } + + if (maybeFunction == TokenKind::Function) { + error(JSMSG_FORBIDDEN_AS_STATEMENT, "async function declarations"); + return null(); + } + + // Otherwise this |async| begins an ExpressionStatement or is a + // label name. + } + + // NOTE: It's unfortunately allowed to have a label named 'let' in + // non-strict code. 💯 + if (next == TokenKind::Colon) { + return labeledStatement(yieldHandling); + } + + return expressionStatement(yieldHandling); + } + + case TokenKind::New: + return expressionStatement(yieldHandling, PredictInvoked); + + // IfStatement[?Yield, ?Return] + case TokenKind::If: + return ifStatement(yieldHandling); + + // BreakableStatement[?Yield, ?Return] + // + // BreakableStatement[Yield, Return]: + // IterationStatement[?Yield, ?Return] + // SwitchStatement[?Yield, ?Return] + case TokenKind::Do: + return doWhileStatement(yieldHandling); + + case TokenKind::While: + return whileStatement(yieldHandling); + + case TokenKind::For: + return forStatement(yieldHandling); + + case TokenKind::Switch: + return switchStatement(yieldHandling); + + // ContinueStatement[?Yield] + case TokenKind::Continue: + return continueStatement(yieldHandling); + + // BreakStatement[?Yield] + case TokenKind::Break: + return breakStatement(yieldHandling); + + // [+Return] ReturnStatement[?Yield] + case TokenKind::Return: + // The Return parameter is only used here, and the effect is easily + // detected this way, so don't bother passing around an extra parameter + // everywhere. + if (!pc_->allowReturn()) { + error(JSMSG_BAD_RETURN_OR_YIELD, js_return_str); + return null(); + } + return returnStatement(yieldHandling); + + // WithStatement[?Yield, ?Return] + case TokenKind::With: + return withStatement(yieldHandling); + + // LabelledStatement[?Yield, ?Return] + // This is really handled by default and TokenKind::Yield cases above. + + // ThrowStatement[?Yield] + case TokenKind::Throw: + return throwStatement(yieldHandling); + + // TryStatement[?Yield, ?Return] + case TokenKind::Try: + return tryStatement(yieldHandling); + + // DebuggerStatement + case TokenKind::Debugger: + return debuggerStatement(); + + // |function| is forbidden by lookahead restriction (unless as child + // statement of |if| or |else|, but Parser::consequentOrAlternative + // handles that). + case TokenKind::Function: + error(JSMSG_FORBIDDEN_AS_STATEMENT, "function declarations"); + return null(); + + // |class| is also forbidden by lookahead restriction. + case TokenKind::Class: + error(JSMSG_FORBIDDEN_AS_STATEMENT, "classes"); + return null(); + + // ImportDeclaration (only inside modules) + case TokenKind::Import: + return importDeclarationOrImportExpr(yieldHandling); + + // ExportDeclaration (only inside modules) + case TokenKind::Export: + return exportDeclaration(); + + // Miscellaneous error cases arguably better caught here than elsewhere. + + case TokenKind::Catch: + error(JSMSG_CATCH_WITHOUT_TRY); + return null(); + + case TokenKind::Finally: + error(JSMSG_FINALLY_WITHOUT_TRY); + return null(); + + // NOTE: default case handled in the ExpressionStatement section. + } +} + +template +typename ParseHandler::Node +GeneralParser::statementListItem( + YieldHandling yieldHandling, bool canHaveDirectives /* = false */) { + MOZ_ASSERT(checkOptionsCalled_); + + AutoCheckRecursionLimit recursion(this->fc_); + if (!recursion.check(this->fc_)) { + return null(); + } + + TokenKind tt; + if (!tokenStream.getToken(&tt, TokenStream::SlashIsRegExp)) { + return null(); + } + + switch (tt) { + // BlockStatement[?Yield, ?Return] + case TokenKind::LeftCurly: + return blockStatement(yieldHandling); + + // VariableStatement[?Yield] + case TokenKind::Var: + return variableStatement(yieldHandling); + + // EmptyStatement + case TokenKind::Semi: + return handler_.newEmptyStatement(pos()); + + // ExpressionStatement[?Yield]. + // + // These should probably be handled by a single ExpressionStatement + // function in a default, not split up this way. + case TokenKind::String: + if (!canHaveDirectives && + anyChars.currentToken().atom() == + TaggedParserAtomIndex::WellKnown::useAsm()) { + if (!warning(JSMSG_USE_ASM_DIRECTIVE_FAIL)) { + return null(); + } + } + return expressionStatement(yieldHandling); + + case TokenKind::Yield: { + // Don't use a ternary operator here due to obscure linker issues + // around using static consts in the arms of a ternary. + Modifier modifier; + if (yieldExpressionsSupported()) { + modifier = TokenStream::SlashIsRegExp; + } else { + modifier = TokenStream::SlashIsDiv; + } + + TokenKind next; + if (!tokenStream.peekToken(&next, modifier)) { + return null(); + } + + if (next == TokenKind::Colon) { + return labeledStatement(yieldHandling); + } + + return expressionStatement(yieldHandling); + } + + default: { + // If we encounter an await in a module, and the module is not marked + // as async, mark the module as async. + if (tt == TokenKind::Await && !pc_->isAsync()) { + if (pc_->atModuleTopLevel()) { + if (!options().topLevelAwait) { + error(JSMSG_TOP_LEVEL_AWAIT_NOT_SUPPORTED); + return null(); + } + pc_->sc()->asModuleContext()->setIsAsync(); + MOZ_ASSERT(pc_->isAsync()); + } + } + + // Avoid getting next token with SlashIsDiv. + if (tt == TokenKind::Await && pc_->isAsync()) { + return expressionStatement(yieldHandling); + } + + if (!TokenKindIsPossibleIdentifier(tt)) { + return expressionStatement(yieldHandling); + } + + TokenKind next; + if (!tokenStream.peekToken(&next)) { + return null(); + } + + if (tt == TokenKind::Let && nextTokenContinuesLetDeclaration(next)) { + return lexicalDeclaration(yieldHandling, DeclarationKind::Let); + } + + if (tt == TokenKind::Async) { + TokenKind nextSameLine = TokenKind::Eof; + if (!tokenStream.peekTokenSameLine(&nextSameLine)) { + return null(); + } + if (nextSameLine == TokenKind::Function) { + uint32_t toStringStart = pos().begin; + tokenStream.consumeKnownToken(TokenKind::Function); + return functionStmt(toStringStart, yieldHandling, NameRequired, + FunctionAsyncKind::AsyncFunction); + } + } + + if (next == TokenKind::Colon) { + return labeledStatement(yieldHandling); + } + + return expressionStatement(yieldHandling); + } + + case TokenKind::New: + return expressionStatement(yieldHandling, PredictInvoked); + + // IfStatement[?Yield, ?Return] + case TokenKind::If: + return ifStatement(yieldHandling); + + // BreakableStatement[?Yield, ?Return] + // + // BreakableStatement[Yield, Return]: + // IterationStatement[?Yield, ?Return] + // SwitchStatement[?Yield, ?Return] + case TokenKind::Do: + return doWhileStatement(yieldHandling); + + case TokenKind::While: + return whileStatement(yieldHandling); + + case TokenKind::For: + return forStatement(yieldHandling); + + case TokenKind::Switch: + return switchStatement(yieldHandling); + + // ContinueStatement[?Yield] + case TokenKind::Continue: + return continueStatement(yieldHandling); + + // BreakStatement[?Yield] + case TokenKind::Break: + return breakStatement(yieldHandling); + + // [+Return] ReturnStatement[?Yield] + case TokenKind::Return: + // The Return parameter is only used here, and the effect is easily + // detected this way, so don't bother passing around an extra parameter + // everywhere. + if (!pc_->allowReturn()) { + error(JSMSG_BAD_RETURN_OR_YIELD, js_return_str); + return null(); + } + return returnStatement(yieldHandling); + + // WithStatement[?Yield, ?Return] + case TokenKind::With: + return withStatement(yieldHandling); + + // LabelledStatement[?Yield, ?Return] + // This is really handled by default and TokenKind::Yield cases above. + + // ThrowStatement[?Yield] + case TokenKind::Throw: + return throwStatement(yieldHandling); + + // TryStatement[?Yield, ?Return] + case TokenKind::Try: + return tryStatement(yieldHandling); + + // DebuggerStatement + case TokenKind::Debugger: + return debuggerStatement(); + + // Declaration[Yield]: + + // HoistableDeclaration[?Yield, ~Default] + case TokenKind::Function: + return functionStmt(pos().begin, yieldHandling, NameRequired); + + // DecoratorList[?Yield, ?Await] opt ClassDeclaration[?Yield, ~Default] +#ifdef ENABLE_DECORATORS + case TokenKind::At: + return classDefinition(yieldHandling, ClassExpression, NameRequired); +#endif + + case TokenKind::Class: + return classDefinition(yieldHandling, ClassStatement, NameRequired); + + // LexicalDeclaration[In, ?Yield] + // LetOrConst BindingList[?In, ?Yield] + case TokenKind::Const: + // [In] is the default behavior, because for-loops specially parse + // their heads to handle |in| in this situation. + return lexicalDeclaration(yieldHandling, DeclarationKind::Const); + + // ImportDeclaration (only inside modules) + case TokenKind::Import: + return importDeclarationOrImportExpr(yieldHandling); + + // ExportDeclaration (only inside modules) + case TokenKind::Export: + return exportDeclaration(); + + // Miscellaneous error cases arguably better caught here than elsewhere. + + case TokenKind::Catch: + error(JSMSG_CATCH_WITHOUT_TRY); + return null(); + + case TokenKind::Finally: + error(JSMSG_FINALLY_WITHOUT_TRY); + return null(); + + // NOTE: default case handled in the ExpressionStatement section. + } +} + +template +typename ParseHandler::Node GeneralParser::expr( + InHandling inHandling, YieldHandling yieldHandling, + TripledotHandling tripledotHandling, + PossibleError* possibleError /* = nullptr */, + InvokedPrediction invoked /* = PredictUninvoked */) { + Node pn = assignExpr(inHandling, yieldHandling, tripledotHandling, + possibleError, invoked); + if (!pn) { + return null(); + } + + bool matched; + if (!tokenStream.matchToken(&matched, TokenKind::Comma, + TokenStream::SlashIsRegExp)) { + return null(); + } + if (!matched) { + return pn; + } + + ListNodeType seq = handler_.newCommaExpressionList(pn); + if (!seq) { + return null(); + } + while (true) { + // Trailing comma before the closing parenthesis is valid in an arrow + // function parameters list: `(a, b, ) => body`. Check if we are + // directly under CoverParenthesizedExpressionAndArrowParameterList, + // and the next two tokens are closing parenthesis and arrow. If all + // are present allow the trailing comma. + if (tripledotHandling == TripledotAllowed) { + TokenKind tt; + if (!tokenStream.peekToken(&tt, TokenStream::SlashIsRegExp)) { + return null(); + } + + if (tt == TokenKind::RightParen) { + tokenStream.consumeKnownToken(TokenKind::RightParen, + TokenStream::SlashIsRegExp); + + if (!tokenStream.peekToken(&tt)) { + return null(); + } + if (tt != TokenKind::Arrow) { + error(JSMSG_UNEXPECTED_TOKEN, "expression", + TokenKindToDesc(TokenKind::RightParen)); + return null(); + } + + anyChars.ungetToken(); // put back right paren + break; + } + } + + // Additional calls to assignExpr should not reuse the possibleError + // which had been passed into the function. Otherwise we would lose + // information needed to determine whether or not we're dealing with + // a non-recoverable situation. + PossibleError possibleErrorInner(*this); + pn = assignExpr(inHandling, yieldHandling, tripledotHandling, + &possibleErrorInner); + if (!pn) { + return null(); + } + + if (!possibleError) { + // Report any pending expression error. + if (!possibleErrorInner.checkForExpressionError()) { + return null(); + } + } else { + possibleErrorInner.transferErrorsTo(possibleError); + } + + handler_.addList(seq, pn); + + if (!tokenStream.matchToken(&matched, TokenKind::Comma, + TokenStream::SlashIsRegExp)) { + return null(); + } + if (!matched) { + break; + } + } + return seq; +} + +static ParseNodeKind BinaryOpTokenKindToParseNodeKind(TokenKind tok) { + MOZ_ASSERT(TokenKindIsBinaryOp(tok)); + return ParseNodeKind(size_t(ParseNodeKind::BinOpFirst) + + (size_t(tok) - size_t(TokenKind::BinOpFirst))); +} + +// This list must be kept in the same order in several places: +// - The binary operators in ParseNode.h , +// - the binary operators in TokenKind.h +// - the JSOp code list in BytecodeEmitter.cpp +static const int PrecedenceTable[] = { + 1, /* ParseNodeKind::Coalesce */ + 2, /* ParseNodeKind::Or */ + 3, /* ParseNodeKind::And */ + 4, /* ParseNodeKind::BitOr */ + 5, /* ParseNodeKind::BitXor */ + 6, /* ParseNodeKind::BitAnd */ + 7, /* ParseNodeKind::StrictEq */ + 7, /* ParseNodeKind::Eq */ + 7, /* ParseNodeKind::StrictNe */ + 7, /* ParseNodeKind::Ne */ + 8, /* ParseNodeKind::Lt */ + 8, /* ParseNodeKind::Le */ + 8, /* ParseNodeKind::Gt */ + 8, /* ParseNodeKind::Ge */ + 8, /* ParseNodeKind::InstanceOf */ + 8, /* ParseNodeKind::In */ + 8, /* ParseNodeKind::PrivateIn */ + 9, /* ParseNodeKind::Lsh */ + 9, /* ParseNodeKind::Rsh */ + 9, /* ParseNodeKind::Ursh */ + 10, /* ParseNodeKind::Add */ + 10, /* ParseNodeKind::Sub */ + 11, /* ParseNodeKind::Star */ + 11, /* ParseNodeKind::Div */ + 11, /* ParseNodeKind::Mod */ + 12 /* ParseNodeKind::Pow */ +}; + +static const int PRECEDENCE_CLASSES = 12; + +static int Precedence(ParseNodeKind pnk) { + // Everything binds tighter than ParseNodeKind::Limit, because we want + // to reduce all nodes to a single node when we reach a token that is not + // another binary operator. + if (pnk == ParseNodeKind::Limit) { + return 0; + } + + MOZ_ASSERT(pnk >= ParseNodeKind::BinOpFirst); + MOZ_ASSERT(pnk <= ParseNodeKind::BinOpLast); + return PrecedenceTable[size_t(pnk) - size_t(ParseNodeKind::BinOpFirst)]; +} + +enum class EnforcedParentheses : uint8_t { CoalesceExpr, AndOrExpr, None }; + +template +MOZ_ALWAYS_INLINE typename ParseHandler::Node +GeneralParser::orExpr(InHandling inHandling, + YieldHandling yieldHandling, + TripledotHandling tripledotHandling, + PossibleError* possibleError, + InvokedPrediction invoked) { + // Shift-reduce parser for the binary operator part of the JS expression + // syntax. + + // Conceptually there's just one stack, a stack of pairs (lhs, op). + // It's implemented using two separate arrays, though. + Node nodeStack[PRECEDENCE_CLASSES]; + ParseNodeKind kindStack[PRECEDENCE_CLASSES]; + int depth = 0; + Node pn; + EnforcedParentheses unparenthesizedExpression = EnforcedParentheses::None; + for (;;) { + pn = unaryExpr(yieldHandling, tripledotHandling, possibleError, invoked, + PrivateNameHandling::PrivateNameAllowed); + if (!pn) { + return null(); + } + + // If a binary operator follows, consume it and compute the + // corresponding operator. + TokenKind tok; + if (!tokenStream.getToken(&tok)) { + return null(); + } + + // Ensure that if we have a private name lhs we are legally constructing a + // `#x in obj` expessions: + if (handler_.isPrivateName(pn)) { + if (tok != TokenKind::In || inHandling != InAllowed) { + error(JSMSG_ILLEGAL_PRIVATE_NAME); + return null(); + } + } + + ParseNodeKind pnk; + if (tok == TokenKind::In ? inHandling == InAllowed + : TokenKindIsBinaryOp(tok)) { + // We're definitely not in a destructuring context, so report any + // pending expression error now. + if (possibleError && !possibleError->checkForExpressionError()) { + return null(); + } + + bool isErgonomicBrandCheck = false; + switch (tok) { + // Report an error for unary expressions on the LHS of **. + case TokenKind::Pow: + if (handler_.isUnparenthesizedUnaryExpression(pn)) { + error(JSMSG_BAD_POW_LEFTSIDE); + return null(); + } + break; + + case TokenKind::Or: + case TokenKind::And: + // In the case that the `??` is on the left hand side of the + // expression: Disallow Mixing of ?? and other logical operators (|| + // and &&) unless one expression is parenthesized + if (unparenthesizedExpression == EnforcedParentheses::CoalesceExpr) { + error(JSMSG_BAD_COALESCE_MIXING); + return null(); + } + // If we have not detected a mixing error at this point, record that + // we have an unparenthesized expression, in case we have one later. + unparenthesizedExpression = EnforcedParentheses::AndOrExpr; + break; + + case TokenKind::Coalesce: + if (unparenthesizedExpression == EnforcedParentheses::AndOrExpr) { + error(JSMSG_BAD_COALESCE_MIXING); + return null(); + } + // If we have not detected a mixing error at this point, record that + // we have an unparenthesized expression, in case we have one later. + unparenthesizedExpression = EnforcedParentheses::CoalesceExpr; + break; + + case TokenKind::In: + // if the LHS is a private name, and the operator is In, + // ensure we're construcing an ergonomic brand check of + // '#x in y', rather than having a higher precedence operator + // like + cause a different reduction, such as + // 1 + #x in y. + if (handler_.isPrivateName(pn)) { + if (depth > 0 && Precedence(kindStack[depth - 1]) >= + Precedence(ParseNodeKind::InExpr)) { + error(JSMSG_INVALID_PRIVATE_NAME_PRECEDENCE); + return null(); + } + + isErgonomicBrandCheck = true; + } + break; + + default: + // do nothing in other cases + break; + } + + if (isErgonomicBrandCheck) { + pnk = ParseNodeKind::PrivateInExpr; + } else { + pnk = BinaryOpTokenKindToParseNodeKind(tok); + } + + } else { + tok = TokenKind::Eof; + pnk = ParseNodeKind::Limit; + } + + // From this point on, destructuring defaults are definitely an error. + possibleError = nullptr; + + // If pnk has precedence less than or equal to another operator on the + // stack, reduce. This combines nodes on the stack until we form the + // actual lhs of pnk. + // + // The >= in this condition works because it is appendOrCreateList's + // job to decide if the operator in question is left- or + // right-associative, and build the corresponding tree. + while (depth > 0 && Precedence(kindStack[depth - 1]) >= Precedence(pnk)) { + depth--; + ParseNodeKind combiningPnk = kindStack[depth]; + pn = handler_.appendOrCreateList(combiningPnk, nodeStack[depth], pn, pc_); + + if (!pn) { + return null(); + } + } + + if (pnk == ParseNodeKind::Limit) { + break; + } + + nodeStack[depth] = pn; + kindStack[depth] = pnk; + depth++; + MOZ_ASSERT(depth <= PRECEDENCE_CLASSES); + } + + anyChars.ungetToken(); + + // Had the next token been a Div, we would have consumed it. So there's no + // ambiguity if we later (after ASI) re-get this token with SlashIsRegExp. + anyChars.allowGettingNextTokenWithSlashIsRegExp(); + + MOZ_ASSERT(depth == 0); + return pn; +} + +template +MOZ_ALWAYS_INLINE typename ParseHandler::Node +GeneralParser::condExpr(InHandling inHandling, + YieldHandling yieldHandling, + TripledotHandling tripledotHandling, + PossibleError* possibleError, + InvokedPrediction invoked) { + Node condition = orExpr(inHandling, yieldHandling, tripledotHandling, + possibleError, invoked); + if (!condition) { + return null(); + } + + bool matched; + if (!tokenStream.matchToken(&matched, TokenKind::Hook, + TokenStream::SlashIsInvalid)) { + return null(); + } + if (!matched) { + return condition; + } + + Node thenExpr = assignExpr(InAllowed, yieldHandling, TripledotProhibited); + if (!thenExpr) { + return null(); + } + + if (!mustMatchToken(TokenKind::Colon, JSMSG_COLON_IN_COND)) { + return null(); + } + + Node elseExpr = assignExpr(inHandling, yieldHandling, TripledotProhibited); + if (!elseExpr) { + return null(); + } + + return handler_.newConditional(condition, thenExpr, elseExpr); +} + +template +typename ParseHandler::Node GeneralParser::assignExpr( + InHandling inHandling, YieldHandling yieldHandling, + TripledotHandling tripledotHandling, + PossibleError* possibleError /* = nullptr */, + InvokedPrediction invoked /* = PredictUninvoked */) { + AutoCheckRecursionLimit recursion(this->fc_); + if (!recursion.check(this->fc_)) { + return null(); + } + + // It's very common at this point to have a "detectably simple" expression, + // i.e. a name/number/string token followed by one of the following tokens + // that obviously isn't part of an expression: , ; : ) ] } + // + // (In Parsemark this happens 81.4% of the time; in code with large + // numeric arrays, such as some Kraken benchmarks, it happens more often.) + // + // In such cases, we can avoid the full expression parsing route through + // assignExpr(), condExpr(), orExpr(), unaryExpr(), memberExpr(), and + // primaryExpr(). + + TokenKind firstToken; + if (!tokenStream.getToken(&firstToken, TokenStream::SlashIsRegExp)) { + return null(); + } + + TokenPos exprPos = pos(); + + bool endsExpr; + + // This only handles identifiers that *never* have special meaning anywhere + // in the language. Contextual keywords, reserved words in strict mode, + // and other hard cases are handled outside this fast path. + if (firstToken == TokenKind::Name) { + if (!tokenStream.nextTokenEndsExpr(&endsExpr)) { + return null(); + } + if (endsExpr) { + TaggedParserAtomIndex name = identifierReference(yieldHandling); + if (!name) { + return null(); + } + + return identifierReference(name); + } + } + + if (firstToken == TokenKind::Number) { + if (!tokenStream.nextTokenEndsExpr(&endsExpr)) { + return null(); + } + if (endsExpr) { + return newNumber(anyChars.currentToken()); + } + } + + if (firstToken == TokenKind::String) { + if (!tokenStream.nextTokenEndsExpr(&endsExpr)) { + return null(); + } + if (endsExpr) { + return stringLiteral(); + } + } + + if (firstToken == TokenKind::Yield && yieldExpressionsSupported()) { + return yieldExpression(inHandling); + } + + bool maybeAsyncArrow = false; + if (firstToken == TokenKind::Async) { + TokenKind nextSameLine = TokenKind::Eof; + if (!tokenStream.peekTokenSameLine(&nextSameLine)) { + return null(); + } + + if (TokenKindIsPossibleIdentifier(nextSameLine)) { + maybeAsyncArrow = true; + } + } + + anyChars.ungetToken(); + + // Save the tokenizer state in case we find an arrow function and have to + // rewind. + Position start(tokenStream); + auto ghostToken = this->compilationState_.getPosition(); + + PossibleError possibleErrorInner(*this); + Node lhs; + TokenKind tokenAfterLHS; + bool isArrow; + if (maybeAsyncArrow) { + tokenStream.consumeKnownToken(TokenKind::Async, TokenStream::SlashIsRegExp); + + TokenKind tokenAfterAsync; + if (!tokenStream.getToken(&tokenAfterAsync)) { + return null(); + } + MOZ_ASSERT(TokenKindIsPossibleIdentifier(tokenAfterAsync)); + + // Check yield validity here. + TaggedParserAtomIndex name = bindingIdentifier(yieldHandling); + if (!name) { + return null(); + } + + if (!tokenStream.peekToken(&tokenAfterLHS, TokenStream::SlashIsRegExp)) { + return null(); + } + + isArrow = tokenAfterLHS == TokenKind::Arrow; + + // |async [no LineTerminator] of| without being followed by => is only + // possible in for-await-of loops, e.g. |for await (async of [])|. Pretend + // the |async| token was parsed an identifier reference and then proceed + // with the rest of this function. + if (!isArrow) { + anyChars.ungetToken(); // unget the binding identifier + + // The next token is guaranteed to never be a Div (, because it's an + // identifier), so it's okay to re-get the token with SlashIsRegExp. + anyChars.allowGettingNextTokenWithSlashIsRegExp(); + + TaggedParserAtomIndex asyncName = identifierReference(yieldHandling); + if (!asyncName) { + return null(); + } + + lhs = identifierReference(asyncName); + if (!lhs) { + return null(); + } + } + } else { + lhs = condExpr(inHandling, yieldHandling, tripledotHandling, + &possibleErrorInner, invoked); + if (!lhs) { + return null(); + } + + // Use SlashIsRegExp here because the ConditionalExpression parsed above + // could be the entirety of this AssignmentExpression, and then ASI + // permits this token to be a regular expression. + if (!tokenStream.peekToken(&tokenAfterLHS, TokenStream::SlashIsRegExp)) { + return null(); + } + + isArrow = tokenAfterLHS == TokenKind::Arrow; + } + + if (isArrow) { + // Rewind to reparse as an arrow function. + // + // Note: We do not call CompilationState::rewind here because parsing + // during delazification will see the same rewind and need the same sequence + // of inner functions to skip over. + // Instead, we mark inner functions as "ghost". + // + // See GHOST_FUNCTION in FunctionFlags.h for more details. + tokenStream.rewind(start); + this->compilationState_.markGhost(ghostToken); + + TokenKind next; + if (!tokenStream.getToken(&next, TokenStream::SlashIsRegExp)) { + return null(); + } + TokenPos startPos = pos(); + uint32_t toStringStart = startPos.begin; + anyChars.ungetToken(); + + FunctionAsyncKind asyncKind = FunctionAsyncKind::SyncFunction; + + if (next == TokenKind::Async) { + tokenStream.consumeKnownToken(next, TokenStream::SlashIsRegExp); + + TokenKind nextSameLine = TokenKind::Eof; + if (!tokenStream.peekTokenSameLine(&nextSameLine)) { + return null(); + } + + // The AsyncArrowFunction production are + // async [no LineTerminator here] AsyncArrowBindingIdentifier ... + // async [no LineTerminator here] ArrowFormalParameters ... + if (TokenKindIsPossibleIdentifier(nextSameLine) || + nextSameLine == TokenKind::LeftParen) { + asyncKind = FunctionAsyncKind::AsyncFunction; + } else { + anyChars.ungetToken(); + } + } + + FunctionSyntaxKind syntaxKind = FunctionSyntaxKind::Arrow; + FunctionNodeType funNode = handler_.newFunction(syntaxKind, startPos); + if (!funNode) { + return null(); + } + + return functionDefinition(funNode, toStringStart, inHandling, yieldHandling, + TaggedParserAtomIndex::null(), syntaxKind, + GeneratorKind::NotGenerator, asyncKind); + } + + MOZ_ALWAYS_TRUE( + tokenStream.getToken(&tokenAfterLHS, TokenStream::SlashIsRegExp)); + + ParseNodeKind kind; + switch (tokenAfterLHS) { + case TokenKind::Assign: + kind = ParseNodeKind::AssignExpr; + break; + case TokenKind::AddAssign: + kind = ParseNodeKind::AddAssignExpr; + break; + case TokenKind::SubAssign: + kind = ParseNodeKind::SubAssignExpr; + break; + case TokenKind::CoalesceAssign: + kind = ParseNodeKind::CoalesceAssignExpr; + break; + case TokenKind::OrAssign: + kind = ParseNodeKind::OrAssignExpr; + break; + case TokenKind::AndAssign: + kind = ParseNodeKind::AndAssignExpr; + break; + case TokenKind::BitOrAssign: + kind = ParseNodeKind::BitOrAssignExpr; + break; + case TokenKind::BitXorAssign: + kind = ParseNodeKind::BitXorAssignExpr; + break; + case TokenKind::BitAndAssign: + kind = ParseNodeKind::BitAndAssignExpr; + break; + case TokenKind::LshAssign: + kind = ParseNodeKind::LshAssignExpr; + break; + case TokenKind::RshAssign: + kind = ParseNodeKind::RshAssignExpr; + break; + case TokenKind::UrshAssign: + kind = ParseNodeKind::UrshAssignExpr; + break; + case TokenKind::MulAssign: + kind = ParseNodeKind::MulAssignExpr; + break; + case TokenKind::DivAssign: + kind = ParseNodeKind::DivAssignExpr; + break; + case TokenKind::ModAssign: + kind = ParseNodeKind::ModAssignExpr; + break; + case TokenKind::PowAssign: + kind = ParseNodeKind::PowAssignExpr; + break; + + default: + MOZ_ASSERT(!anyChars.isCurrentTokenAssignment()); + if (!possibleError) { + if (!possibleErrorInner.checkForExpressionError()) { + return null(); + } + } else { + possibleErrorInner.transferErrorsTo(possibleError); + } + + anyChars.ungetToken(); + return lhs; + } + + // Verify the left-hand side expression doesn't have a forbidden form. + if (handler_.isUnparenthesizedDestructuringPattern(lhs)) { + if (kind != ParseNodeKind::AssignExpr) { + error(JSMSG_BAD_DESTRUCT_ASS); + return null(); + } + + if (!possibleErrorInner.checkForDestructuringErrorOrWarning()) { + return null(); + } + } else if (handler_.isName(lhs)) { + if (const char* chars = nameIsArgumentsOrEval(lhs)) { + // |chars| is "arguments" or "eval" here. + if (!strictModeErrorAt(exprPos.begin, JSMSG_BAD_STRICT_ASSIGN, chars)) { + return null(); + } + } + } else if (handler_.isPropertyOrPrivateMemberAccess(lhs)) { + // Permitted: no additional testing/fixup needed. + } else if (handler_.isFunctionCall(lhs)) { + // We don't have to worry about backward compatibility issues with the new + // compound assignment operators, so we always throw here. Also that way we + // don't have to worry if |f() &&= expr| should always throw an error or + // only if |f()| returns true. + if (kind == ParseNodeKind::CoalesceAssignExpr || + kind == ParseNodeKind::OrAssignExpr || + kind == ParseNodeKind::AndAssignExpr) { + errorAt(exprPos.begin, JSMSG_BAD_LEFTSIDE_OF_ASS); + return null(); + } + + if (!strictModeErrorAt(exprPos.begin, JSMSG_BAD_LEFTSIDE_OF_ASS)) { + return null(); + } + + if (possibleError) { + possibleError->setPendingDestructuringErrorAt(exprPos, + JSMSG_BAD_DESTRUCT_TARGET); + } + } else { + errorAt(exprPos.begin, JSMSG_BAD_LEFTSIDE_OF_ASS); + return null(); + } + + if (!possibleErrorInner.checkForExpressionError()) { + return null(); + } + + Node rhs = assignExpr(inHandling, yieldHandling, TripledotProhibited); + if (!rhs) { + return null(); + } + + return handler_.newAssignment(kind, lhs, rhs); +} + +template +const char* PerHandlerParser::nameIsArgumentsOrEval(Node node) { + MOZ_ASSERT(handler_.isName(node), + "must only call this function on known names"); + + if (handler_.isEvalName(node)) { + return js_eval_str; + } + if (handler_.isArgumentsName(node)) { + return js_arguments_str; + } + return nullptr; +} + +template +bool GeneralParser::checkIncDecOperand( + Node operand, uint32_t operandOffset) { + if (handler_.isName(operand)) { + if (const char* chars = nameIsArgumentsOrEval(operand)) { + if (!strictModeErrorAt(operandOffset, JSMSG_BAD_STRICT_ASSIGN, chars)) { + return false; + } + } + } else if (handler_.isPropertyOrPrivateMemberAccess(operand)) { + // Permitted: no additional testing/fixup needed. + } else if (handler_.isFunctionCall(operand)) { + // Assignment to function calls is forbidden in ES6. We're still + // somewhat concerned about sites using this in dead code, so forbid it + // only in strict mode code. + if (!strictModeErrorAt(operandOffset, JSMSG_BAD_INCOP_OPERAND)) { + return false; + } + } else { + errorAt(operandOffset, JSMSG_BAD_INCOP_OPERAND); + return false; + } + return true; +} + +template +typename ParseHandler::UnaryNodeType +GeneralParser::unaryOpExpr(YieldHandling yieldHandling, + ParseNodeKind kind, + uint32_t begin) { + Node kid = unaryExpr(yieldHandling, TripledotProhibited); + if (!kid) { + return null(); + } + return handler_.newUnary(kind, begin, kid); +} + +template +typename ParseHandler::Node GeneralParser::optionalExpr( + YieldHandling yieldHandling, TripledotHandling tripledotHandling, + TokenKind tt, PossibleError* possibleError /* = nullptr */, + InvokedPrediction invoked /* = PredictUninvoked */) { + AutoCheckRecursionLimit recursion(this->fc_); + if (!recursion.check(this->fc_)) { + return null(); + } + + uint32_t begin = pos().begin; + + Node lhs = memberExpr(yieldHandling, tripledotHandling, tt, + /* allowCallSyntax = */ true, possibleError, invoked); + if (!lhs) { + return null(); + } + + if (!tokenStream.peekToken(&tt, TokenStream::SlashIsDiv)) { + return null(); + } + + if (tt != TokenKind::OptionalChain) { + return lhs; + } + + while (true) { + if (!tokenStream.getToken(&tt)) { + return null(); + } + + if (tt == TokenKind::Eof) { + anyChars.ungetToken(); + break; + } + + Node nextMember; + if (tt == TokenKind::OptionalChain) { + if (!tokenStream.getToken(&tt)) { + return null(); + } + if (TokenKindIsPossibleIdentifierName(tt)) { + nextMember = memberPropertyAccess(lhs, OptionalKind::Optional); + if (!nextMember) { + return null(); + } + } else if (tt == TokenKind::PrivateName) { + nextMember = memberPrivateAccess(lhs, OptionalKind::Optional); + if (!nextMember) { + return null(); + } + } else if (tt == TokenKind::LeftBracket) { + nextMember = + memberElemAccess(lhs, yieldHandling, OptionalKind::Optional); + if (!nextMember) { + return null(); + } + } else if (tt == TokenKind::LeftParen) { + nextMember = memberCall(tt, lhs, yieldHandling, possibleError, + OptionalKind::Optional); + if (!nextMember) { + return null(); + } + } else { + error(JSMSG_NAME_AFTER_DOT); + return null(); + } + } else if (tt == TokenKind::Dot) { + if (!tokenStream.getToken(&tt)) { + return null(); + } + if (TokenKindIsPossibleIdentifierName(tt)) { + nextMember = memberPropertyAccess(lhs); + if (!nextMember) { + return null(); + } + } else if (tt == TokenKind::PrivateName) { + nextMember = memberPrivateAccess(lhs); + if (!nextMember) { + return null(); + } + } else { + error(JSMSG_NAME_AFTER_DOT); + return null(); + } + } else if (tt == TokenKind::LeftBracket) { + nextMember = memberElemAccess(lhs, yieldHandling); + if (!nextMember) { + return null(); + } + } else if (tt == TokenKind::LeftParen) { + nextMember = memberCall(tt, lhs, yieldHandling, possibleError); + if (!nextMember) { + return null(); + } + } else if (tt == TokenKind::TemplateHead || + tt == TokenKind::NoSubsTemplate) { + error(JSMSG_BAD_OPTIONAL_TEMPLATE); + return null(); + } else { + anyChars.ungetToken(); + break; + } + + MOZ_ASSERT(nextMember); + lhs = nextMember; + } + + return handler_.newOptionalChain(begin, lhs); +} + +template +typename ParseHandler::Node GeneralParser::unaryExpr( + YieldHandling yieldHandling, TripledotHandling tripledotHandling, + PossibleError* possibleError /* = nullptr */, + InvokedPrediction invoked /* = PredictUninvoked */, + PrivateNameHandling privateNameHandling /* = PrivateNameProhibited */) { + AutoCheckRecursionLimit recursion(this->fc_); + if (!recursion.check(this->fc_)) { + return null(); + } + + TokenKind tt; + if (!tokenStream.getToken(&tt, TokenStream::SlashIsRegExp)) { + return null(); + } + uint32_t begin = pos().begin; + switch (tt) { + case TokenKind::Void: + return unaryOpExpr(yieldHandling, ParseNodeKind::VoidExpr, begin); + case TokenKind::Not: + return unaryOpExpr(yieldHandling, ParseNodeKind::NotExpr, begin); + case TokenKind::BitNot: + return unaryOpExpr(yieldHandling, ParseNodeKind::BitNotExpr, begin); + case TokenKind::Add: + return unaryOpExpr(yieldHandling, ParseNodeKind::PosExpr, begin); + case TokenKind::Sub: + return unaryOpExpr(yieldHandling, ParseNodeKind::NegExpr, begin); + + case TokenKind::TypeOf: { + // The |typeof| operator is specially parsed to distinguish its + // application to a name, from its application to a non-name + // expression: + // + // // Looks up the name, doesn't find it and so evaluates to + // // "undefined". + // assertEq(typeof nonExistentName, "undefined"); + // + // // Evaluates expression, triggering a runtime ReferenceError for + // // the undefined name. + // typeof (1, nonExistentName); + Node kid = unaryExpr(yieldHandling, TripledotProhibited); + if (!kid) { + return null(); + } + + return handler_.newTypeof(begin, kid); + } + + case TokenKind::Inc: + case TokenKind::Dec: { + TokenKind tt2; + if (!tokenStream.getToken(&tt2, TokenStream::SlashIsRegExp)) { + return null(); + } + + uint32_t operandOffset = pos().begin; + Node operand = optionalExpr(yieldHandling, TripledotProhibited, tt2); + if (!operand || !checkIncDecOperand(operand, operandOffset)) { + return null(); + } + ParseNodeKind pnk = (tt == TokenKind::Inc) + ? ParseNodeKind::PreIncrementExpr + : ParseNodeKind::PreDecrementExpr; + return handler_.newUpdate(pnk, begin, operand); + } + case TokenKind::PrivateName: { + if (privateNameHandling == PrivateNameHandling::PrivateNameAllowed) { + TaggedParserAtomIndex field = anyChars.currentName(); + return privateNameReference(field); + } + error(JSMSG_INVALID_PRIVATE_NAME_IN_UNARY_EXPR); + return null(); + } + + case TokenKind::Delete: { + uint32_t exprOffset; + if (!tokenStream.peekOffset(&exprOffset, TokenStream::SlashIsRegExp)) { + return null(); + } + + Node expr = unaryExpr(yieldHandling, TripledotProhibited); + if (!expr) { + return null(); + } + + // Per spec, deleting most unary expressions is valid -- it simply + // returns true -- except for two cases: + // 1. `var x; ...; delete x` is a syntax error in strict mode. + // 2. Private fields cannot be deleted. + if (handler_.isName(expr)) { + if (!strictModeErrorAt(exprOffset, JSMSG_DEPRECATED_DELETE_OPERAND)) { + return null(); + } + + pc_->sc()->setBindingsAccessedDynamically(); + } + + if (handler_.isPrivateMemberAccess(expr)) { + errorAt(exprOffset, JSMSG_PRIVATE_DELETE); + return null(); + } + + return handler_.newDelete(begin, expr); + } + case TokenKind::Await: { + // If we encounter an await in a module, mark it as async. + if (!pc_->isAsync() && pc_->sc()->isModule()) { + if (!options().topLevelAwait) { + error(JSMSG_TOP_LEVEL_AWAIT_NOT_SUPPORTED); + return null(); + } + pc_->sc()->asModuleContext()->setIsAsync(); + MOZ_ASSERT(pc_->isAsync()); + } + + if (pc_->isAsync()) { + if (inParametersOfAsyncFunction()) { + error(JSMSG_AWAIT_IN_PARAMETER); + return null(); + } + Node kid = + unaryExpr(yieldHandling, tripledotHandling, possibleError, invoked); + if (!kid) { + return null(); + } + pc_->lastAwaitOffset = begin; + return handler_.newAwaitExpression(begin, kid); + } + } + + [[fallthrough]]; + + default: { + Node expr = optionalExpr(yieldHandling, tripledotHandling, tt, + possibleError, invoked); + if (!expr) { + return null(); + } + + /* Don't look across a newline boundary for a postfix incop. */ + if (!tokenStream.peekTokenSameLine(&tt)) { + return null(); + } + + if (tt != TokenKind::Inc && tt != TokenKind::Dec) { + return expr; + } + + tokenStream.consumeKnownToken(tt); + if (!checkIncDecOperand(expr, begin)) { + return null(); + } + + ParseNodeKind pnk = (tt == TokenKind::Inc) + ? ParseNodeKind::PostIncrementExpr + : ParseNodeKind::PostDecrementExpr; + return handler_.newUpdate(pnk, begin, expr); + } + } +} + +template +typename ParseHandler::Node +GeneralParser::assignExprWithoutYieldOrAwait( + YieldHandling yieldHandling) { + uint32_t startYieldOffset = pc_->lastYieldOffset; + uint32_t startAwaitOffset = pc_->lastAwaitOffset; + Node res = assignExpr(InAllowed, yieldHandling, TripledotProhibited); + if (res) { + if (pc_->lastYieldOffset != startYieldOffset) { + errorAt(pc_->lastYieldOffset, JSMSG_YIELD_IN_PARAMETER); + return null(); + } + if (pc_->lastAwaitOffset != startAwaitOffset) { + errorAt(pc_->lastAwaitOffset, JSMSG_AWAIT_IN_PARAMETER); + return null(); + } + } + return res; +} + +template +typename ParseHandler::ListNodeType +GeneralParser::argumentList( + YieldHandling yieldHandling, bool* isSpread, + PossibleError* possibleError /* = nullptr */) { + ListNodeType argsList = handler_.newArguments(pos()); + if (!argsList) { + return null(); + } + + bool matched; + if (!tokenStream.matchToken(&matched, TokenKind::RightParen, + TokenStream::SlashIsRegExp)) { + return null(); + } + if (matched) { + handler_.setEndPosition(argsList, pos().end); + return argsList; + } + + while (true) { + bool spread = false; + uint32_t begin = 0; + if (!tokenStream.matchToken(&matched, TokenKind::TripleDot, + TokenStream::SlashIsRegExp)) { + return null(); + } + if (matched) { + spread = true; + begin = pos().begin; + *isSpread = true; + } + + Node argNode = assignExpr(InAllowed, yieldHandling, TripledotProhibited, + possibleError); + if (!argNode) { + return null(); + } + if (spread) { + argNode = handler_.newSpread(begin, argNode); + if (!argNode) { + return null(); + } + } + + handler_.addList(argsList, argNode); + + bool matched; + if (!tokenStream.matchToken(&matched, TokenKind::Comma, + TokenStream::SlashIsRegExp)) { + return null(); + } + if (!matched) { + break; + } + + TokenKind tt; + if (!tokenStream.peekToken(&tt, TokenStream::SlashIsRegExp)) { + return null(); + } + if (tt == TokenKind::RightParen) { + break; + } + } + + if (!mustMatchToken(TokenKind::RightParen, JSMSG_PAREN_AFTER_ARGS)) { + return null(); + } + + handler_.setEndPosition(argsList, pos().end); + return argsList; +} + +bool ParserBase::checkAndMarkSuperScope() { + if (!pc_->sc()->allowSuperProperty()) { + return false; + } + + pc_->setSuperScopeNeedsHomeObject(); + return true; +} + +template +bool GeneralParser::computeErrorMetadata( + ErrorMetadata* err, const ErrorReportMixin::ErrorOffset& offset) const { + if (offset.is()) { + return tokenStream.computeErrorMetadata(err, AsVariant(pos().begin)); + } + return tokenStream.computeErrorMetadata(err, offset); +} + +template +typename ParseHandler::Node GeneralParser::memberExpr( + YieldHandling yieldHandling, TripledotHandling tripledotHandling, + TokenKind tt, bool allowCallSyntax, PossibleError* possibleError, + InvokedPrediction invoked) { + MOZ_ASSERT(anyChars.isCurrentTokenType(tt)); + + Node lhs; + + AutoCheckRecursionLimit recursion(this->fc_); + if (!recursion.check(this->fc_)) { + return null(); + } + + /* Check for new expression first. */ + if (tt == TokenKind::New) { + uint32_t newBegin = pos().begin; + // Make sure this wasn't a |new.target| in disguise. + NewTargetNodeType newTarget; + if (!tryNewTarget(&newTarget)) { + return null(); + } + if (newTarget) { + lhs = newTarget; + } else { + // Gotten by tryNewTarget + tt = anyChars.currentToken().type; + Node ctorExpr = memberExpr(yieldHandling, TripledotProhibited, tt, + /* allowCallSyntax = */ false, + /* possibleError = */ nullptr, PredictInvoked); + if (!ctorExpr) { + return null(); + } + + // If we have encountered an optional chain, in the form of `new + // ClassName?.()` then we need to throw, as this is disallowed by the + // spec. + bool optionalToken; + if (!tokenStream.matchToken(&optionalToken, TokenKind::OptionalChain)) { + return null(); + } + if (optionalToken) { + errorAt(newBegin, JSMSG_BAD_NEW_OPTIONAL); + return null(); + } + + bool matched; + if (!tokenStream.matchToken(&matched, TokenKind::LeftParen)) { + return null(); + } + + bool isSpread = false; + Node args; + if (matched) { + args = argumentList(yieldHandling, &isSpread); + } else { + args = handler_.newArguments(pos()); + } + + if (!args) { + return null(); + } + + lhs = handler_.newNewExpression(newBegin, ctorExpr, args, isSpread); + if (!lhs) { + return null(); + } + } + } else if (tt == TokenKind::Super) { + NameNodeType thisName = newThisName(); + if (!thisName) { + return null(); + } + lhs = handler_.newSuperBase(thisName, pos()); + if (!lhs) { + return null(); + } + } else if (tt == TokenKind::Import) { + lhs = importExpr(yieldHandling, allowCallSyntax); + if (!lhs) { + return null(); + } + } else { + lhs = primaryExpr(yieldHandling, tripledotHandling, tt, possibleError, + invoked); + if (!lhs) { + return null(); + } + } + + MOZ_ASSERT_IF(handler_.isSuperBase(lhs), + anyChars.isCurrentTokenType(TokenKind::Super)); + + while (true) { + if (!tokenStream.getToken(&tt)) { + return null(); + } + if (tt == TokenKind::Eof) { + anyChars.ungetToken(); + break; + } + + Node nextMember; + if (tt == TokenKind::Dot) { + if (!tokenStream.getToken(&tt)) { + return null(); + } + + if (TokenKindIsPossibleIdentifierName(tt)) { + nextMember = memberPropertyAccess(lhs); + if (!nextMember) { + return null(); + } + } else if (tt == TokenKind::PrivateName) { + nextMember = memberPrivateAccess(lhs); + if (!nextMember) { + return null(); + } + } else { + error(JSMSG_NAME_AFTER_DOT); + return null(); + } + } else if (tt == TokenKind::LeftBracket) { + nextMember = memberElemAccess(lhs, yieldHandling); + if (!nextMember) { + return null(); + } + } else if ((allowCallSyntax && tt == TokenKind::LeftParen) || + tt == TokenKind::TemplateHead || + tt == TokenKind::NoSubsTemplate) { + if (handler_.isSuperBase(lhs)) { + if (!pc_->sc()->allowSuperCall()) { + error(JSMSG_BAD_SUPERCALL); + return null(); + } + + if (tt != TokenKind::LeftParen) { + error(JSMSG_BAD_SUPER); + return null(); + } + + nextMember = memberSuperCall(lhs, yieldHandling); + if (!nextMember) { + return null(); + } + + if (!noteUsedName( + TaggedParserAtomIndex::WellKnown::dotInitializers())) { + return null(); + } + } else { + nextMember = memberCall(tt, lhs, yieldHandling, possibleError); + if (!nextMember) { + return null(); + } + } + } else { + anyChars.ungetToken(); + if (handler_.isSuperBase(lhs)) { + break; + } + return lhs; + } + + lhs = nextMember; + } + + if (handler_.isSuperBase(lhs)) { + error(JSMSG_BAD_SUPER); + return null(); + } + + return lhs; +} + +template +inline typename ParseHandler::NameNodeType +PerHandlerParser::newName(TaggedParserAtomIndex name) { + return newName(name, pos()); +} + +template +inline typename ParseHandler::NameNodeType +PerHandlerParser::newName(TaggedParserAtomIndex name, + TokenPos pos) { + return handler_.newName(name, pos); +} + +template +inline typename ParseHandler::NameNodeType +PerHandlerParser::newPrivateName(TaggedParserAtomIndex name) { + return handler_.newPrivateName(name, pos()); +} + +template +typename ParseHandler::Node +GeneralParser::memberPropertyAccess( + Node lhs, OptionalKind optionalKind /* = OptionalKind::NonOptional */) { + MOZ_ASSERT(TokenKindIsPossibleIdentifierName(anyChars.currentToken().type) || + anyChars.currentToken().type == TokenKind::PrivateName); + TaggedParserAtomIndex field = anyChars.currentName(); + if (handler_.isSuperBase(lhs) && !checkAndMarkSuperScope()) { + error(JSMSG_BAD_SUPERPROP, "property"); + return null(); + } + + NameNodeType name = handler_.newPropertyName(field, pos()); + if (!name) { + return null(); + } + + if (optionalKind == OptionalKind::Optional) { + MOZ_ASSERT(!handler_.isSuperBase(lhs)); + return handler_.newOptionalPropertyAccess(lhs, name); + } + return handler_.newPropertyAccess(lhs, name); +} + +template +typename ParseHandler::Node +GeneralParser::memberPrivateAccess( + Node lhs, OptionalKind optionalKind /* = OptionalKind::NonOptional */) { + MOZ_ASSERT(anyChars.currentToken().type == TokenKind::PrivateName); + + TaggedParserAtomIndex field = anyChars.currentName(); + // Cannot access private fields on super. + if (handler_.isSuperBase(lhs)) { + error(JSMSG_BAD_SUPERPRIVATE); + return null(); + } + + NameNodeType privateName = privateNameReference(field); + if (!privateName) { + return null(); + } + + if (optionalKind == OptionalKind::Optional) { + MOZ_ASSERT(!handler_.isSuperBase(lhs)); + return handler_.newOptionalPrivateMemberAccess(lhs, privateName, pos().end); + } + return handler_.newPrivateMemberAccess(lhs, privateName, pos().end); +} + +template +typename ParseHandler::Node GeneralParser::memberElemAccess( + Node lhs, YieldHandling yieldHandling, + OptionalKind optionalKind /* = OptionalKind::NonOptional */) { + MOZ_ASSERT(anyChars.currentToken().type == TokenKind::LeftBracket); + Node propExpr = expr(InAllowed, yieldHandling, TripledotProhibited); + if (!propExpr) { + return null(); + } + + if (!mustMatchToken(TokenKind::RightBracket, JSMSG_BRACKET_IN_INDEX)) { + return null(); + } + + if (handler_.isSuperBase(lhs) && !checkAndMarkSuperScope()) { + error(JSMSG_BAD_SUPERPROP, "member"); + return null(); + } + if (optionalKind == OptionalKind::Optional) { + MOZ_ASSERT(!handler_.isSuperBase(lhs)); + return handler_.newOptionalPropertyByValue(lhs, propExpr, pos().end); + } + return handler_.newPropertyByValue(lhs, propExpr, pos().end); +} + +template +typename ParseHandler::Node GeneralParser::memberSuperCall( + Node lhs, YieldHandling yieldHandling) { + MOZ_ASSERT(anyChars.currentToken().type == TokenKind::LeftParen); + // Despite the fact that it's impossible to have |super()| in a + // generator, we still inherit the yieldHandling of the + // memberExpression, per spec. Curious. + bool isSpread = false; + Node args = argumentList(yieldHandling, &isSpread); + if (!args) { + return null(); + } + + CallNodeType superCall = handler_.newSuperCall(lhs, args, isSpread); + if (!superCall) { + return null(); + } + + // |super()| implicitly reads |new.target|. + if (!noteUsedName(TaggedParserAtomIndex::WellKnown::dotNewTarget())) { + return null(); + } + + NameNodeType thisName = newThisName(); + if (!thisName) { + return null(); + } + + return handler_.newSetThis(thisName, superCall); +} + +template +typename ParseHandler::Node GeneralParser::memberCall( + TokenKind tt, Node lhs, YieldHandling yieldHandling, + PossibleError* possibleError /* = nullptr */, + OptionalKind optionalKind /* = OptionalKind::NonOptional */) { + if (options().selfHostingMode && + (handler_.isPropertyOrPrivateMemberAccess(lhs) || + handler_.isOptionalPropertyOrPrivateMemberAccess(lhs))) { + error(JSMSG_SELFHOSTED_METHOD_CALL); + return null(); + } + + MOZ_ASSERT(tt == TokenKind::LeftParen || tt == TokenKind::TemplateHead || + tt == TokenKind::NoSubsTemplate, + "Unexpected token kind for member call"); + + JSOp op = JSOp::Call; + bool maybeAsyncArrow = false; + if (tt == TokenKind::LeftParen && optionalKind == OptionalKind::NonOptional) { + if (handler_.isAsyncKeyword(lhs)) { + // |async (| can be the start of an async arrow + // function, so we need to defer reporting possible + // errors from destructuring syntax. To give better + // error messages, we only allow the AsyncArrowHead + // part of the CoverCallExpressionAndAsyncArrowHead + // syntax when the initial name is "async". + maybeAsyncArrow = true; + } else if (handler_.isEvalName(lhs)) { + // Select the right Eval op and flag pc_ as having a + // direct eval. + op = pc_->sc()->strict() ? JSOp::StrictEval : JSOp::Eval; + pc_->sc()->setBindingsAccessedDynamically(); + pc_->sc()->setHasDirectEval(); + + // In non-strict mode code, direct calls to eval can + // add variables to the call object. + if (pc_->isFunctionBox() && !pc_->sc()->strict()) { + pc_->functionBox()->setFunHasExtensibleScope(); + } + + // If we're in a method, mark the method as requiring + // support for 'super', since direct eval code can use + // it. (If we're not in a method, that's fine, so + // ignore the return value.) + checkAndMarkSuperScope(); + } + } + + if (tt == TokenKind::LeftParen) { + bool isSpread = false; + PossibleError* asyncPossibleError = + maybeAsyncArrow ? possibleError : nullptr; + Node args = argumentList(yieldHandling, &isSpread, asyncPossibleError); + if (!args) { + return null(); + } + if (isSpread) { + if (op == JSOp::Eval) { + op = JSOp::SpreadEval; + } else if (op == JSOp::StrictEval) { + op = JSOp::StrictSpreadEval; + } else { + op = JSOp::SpreadCall; + } + } + + if (optionalKind == OptionalKind::Optional) { + return handler_.newOptionalCall(lhs, args, op); + } + return handler_.newCall(lhs, args, op); + } + + ListNodeType args = handler_.newArguments(pos()); + if (!args) { + return null(); + } + + if (!taggedTemplate(yieldHandling, args, tt)) { + return null(); + } + + if (optionalKind == OptionalKind::Optional) { + error(JSMSG_BAD_OPTIONAL_TEMPLATE); + return null(); + } + + return handler_.newTaggedTemplate(lhs, args, op); +} + +template +bool GeneralParser::checkLabelOrIdentifierReference( + TaggedParserAtomIndex ident, uint32_t offset, YieldHandling yieldHandling, + TokenKind hint /* = TokenKind::Limit */) { + TokenKind tt; + if (hint == TokenKind::Limit) { + tt = ReservedWordTokenKind(ident); + } else { + // All non-reserved word kinds are folded into TokenKind::Limit in + // ReservedWordTokenKind and the following code. + if (hint == TokenKind::Name || hint == TokenKind::PrivateName) { + hint = TokenKind::Limit; + } + MOZ_ASSERT(hint == ReservedWordTokenKind(ident), + "hint doesn't match actual token kind"); + tt = hint; + } + + if (!pc_->sc()->allowArguments() && + ident == TaggedParserAtomIndex::WellKnown::arguments()) { + error(JSMSG_BAD_ARGUMENTS); + return false; + } + + if (tt == TokenKind::Limit) { + // Either TokenKind::Name or TokenKind::PrivateName + return true; + } + if (TokenKindIsContextualKeyword(tt)) { + if (tt == TokenKind::Yield) { + if (yieldHandling == YieldIsKeyword) { + errorAt(offset, JSMSG_RESERVED_ID, "yield"); + return false; + } + if (pc_->sc()->strict()) { + if (!strictModeErrorAt(offset, JSMSG_RESERVED_ID, "yield")) { + return false; + } + } + return true; + } + if (tt == TokenKind::Await) { + if (awaitIsKeyword() || awaitIsDisallowed()) { + errorAt(offset, JSMSG_RESERVED_ID, "await"); + return false; + } + return true; + } + if (pc_->sc()->strict()) { + if (tt == TokenKind::Let) { + if (!strictModeErrorAt(offset, JSMSG_RESERVED_ID, "let")) { + return false; + } + return true; + } + if (tt == TokenKind::Static) { + if (!strictModeErrorAt(offset, JSMSG_RESERVED_ID, "static")) { + return false; + } + return true; + } + } + return true; + } + if (TokenKindIsStrictReservedWord(tt)) { + if (pc_->sc()->strict()) { + if (!strictModeErrorAt(offset, JSMSG_RESERVED_ID, + ReservedWordToCharZ(tt))) { + return false; + } + } + return true; + } + if (TokenKindIsKeyword(tt) || TokenKindIsReservedWordLiteral(tt)) { + errorAt(offset, JSMSG_INVALID_ID, ReservedWordToCharZ(tt)); + return false; + } + if (TokenKindIsFutureReservedWord(tt)) { + errorAt(offset, JSMSG_RESERVED_ID, ReservedWordToCharZ(tt)); + return false; + } + MOZ_ASSERT_UNREACHABLE("Unexpected reserved word kind."); + return false; +} + +template +bool GeneralParser::checkBindingIdentifier( + TaggedParserAtomIndex ident, uint32_t offset, YieldHandling yieldHandling, + TokenKind hint /* = TokenKind::Limit */) { + if (pc_->sc()->strict()) { + if (ident == TaggedParserAtomIndex::WellKnown::arguments()) { + if (!strictModeErrorAt(offset, JSMSG_BAD_STRICT_ASSIGN, "arguments")) { + return false; + } + return true; + } + + if (ident == TaggedParserAtomIndex::WellKnown::eval()) { + if (!strictModeErrorAt(offset, JSMSG_BAD_STRICT_ASSIGN, "eval")) { + return false; + } + return true; + } + } + + return checkLabelOrIdentifierReference(ident, offset, yieldHandling, hint); +} + +template +TaggedParserAtomIndex +GeneralParser::labelOrIdentifierReference( + YieldHandling yieldHandling) { + // ES 2017 draft 12.1.1. + // StringValue of IdentifierName normalizes any Unicode escape sequences + // in IdentifierName hence such escapes cannot be used to write an + // Identifier whose code point sequence is the same as a ReservedWord. + // + // Use const ParserName* instead of TokenKind to reflect the normalization. + + // Unless the name contains escapes, we can reuse the current TokenKind + // to determine if the name is a restricted identifier. + TokenKind hint = !anyChars.currentNameHasEscapes(this->parserAtoms()) + ? anyChars.currentToken().type + : TokenKind::Limit; + TaggedParserAtomIndex ident = anyChars.currentName(); + if (!checkLabelOrIdentifierReference(ident, pos().begin, yieldHandling, + hint)) { + return TaggedParserAtomIndex::null(); + } + return ident; +} + +template +TaggedParserAtomIndex GeneralParser::bindingIdentifier( + YieldHandling yieldHandling) { + TokenKind hint = !anyChars.currentNameHasEscapes(this->parserAtoms()) + ? anyChars.currentToken().type + : TokenKind::Limit; + TaggedParserAtomIndex ident = anyChars.currentName(); + if (!checkBindingIdentifier(ident, pos().begin, yieldHandling, hint)) { + return TaggedParserAtomIndex::null(); + } + return ident; +} + +template +typename ParseHandler::NameNodeType +PerHandlerParser::identifierReference( + TaggedParserAtomIndex name) { + NameNodeType id = newName(name); + if (!id) { + return null(); + } + + if (!noteUsedName(name)) { + return null(); + } + + return id; +} + +template +typename ParseHandler::NameNodeType +PerHandlerParser::privateNameReference( + TaggedParserAtomIndex name) { + NameNodeType id = newPrivateName(name); + if (!id) { + return null(); + } + + if (!noteUsedName(name, NameVisibility::Private, Some(pos()))) { + return null(); + } + + return id; +} + +template +typename ParseHandler::NameNodeType +PerHandlerParser::stringLiteral() { + return handler_.newStringLiteral(anyChars.currentToken().atom(), pos()); +} + +template +typename ParseHandler::Node +PerHandlerParser::noSubstitutionTaggedTemplate() { + if (anyChars.hasInvalidTemplateEscape()) { + anyChars.clearInvalidTemplateEscape(); + return handler_.newRawUndefinedLiteral(pos()); + } + + return handler_.newTemplateStringLiteral(anyChars.currentToken().atom(), + pos()); +} + +template +typename ParseHandler::NameNodeType +GeneralParser::noSubstitutionUntaggedTemplate() { + if (!tokenStream.checkForInvalidTemplateEscapeError()) { + return null(); + } + + return handler_.newTemplateStringLiteral(anyChars.currentToken().atom(), + pos()); +} + +template +RegExpLiteral* Parser::newRegExp() { + MOZ_ASSERT(!options().selfHostingMode); + + // Create the regexp and check its syntax. + const auto& chars = tokenStream.getCharBuffer(); + mozilla::Range range(chars.begin(), chars.length()); + RegExpFlags flags = anyChars.currentToken().regExpFlags(); + + uint32_t offset = anyChars.currentToken().pos.begin; + uint32_t line, column; + tokenStream.computeLineAndColumn(offset, &line, &column); + + if (!handler_.reuseRegexpSyntaxParse()) { + // Verify that the Regexp will syntax parse when the time comes to + // instantiate it. If we have already done a syntax parse, we can + // skip this. + if (!irregexp::CheckPatternSyntax(this->alloc_, this->fc_->stackLimit(), + anyChars, range, flags, Some(line), + Some(column))) { + return nullptr; + } + } + + auto atom = + this->parserAtoms().internChar16(fc_, chars.begin(), chars.length()); + if (!atom) { + return nullptr; + } + // RegExp patterm must be atomized. + this->parserAtoms().markUsedByStencil(atom, ParserAtom::Atomize::Yes); + + RegExpIndex index(this->compilationState_.regExpData.length()); + if (uint32_t(index) >= TaggedScriptThingIndex::IndexLimit) { + ReportAllocationOverflow(fc_); + return nullptr; + } + if (!this->compilationState_.regExpData.emplaceBack(atom, flags)) { + js::ReportOutOfMemory(this->fc_); + return nullptr; + } + + return handler_.newRegExp(index, pos()); +} + +template +SyntaxParseHandler::RegExpLiteralType +Parser::newRegExp() { + MOZ_ASSERT(!options().selfHostingMode); + + // Only check the regexp's syntax, but don't create a regexp object. + const auto& chars = tokenStream.getCharBuffer(); + RegExpFlags flags = anyChars.currentToken().regExpFlags(); + + uint32_t offset = anyChars.currentToken().pos.begin; + uint32_t line, column; + tokenStream.computeLineAndColumn(offset, &line, &column); + + mozilla::Range source(chars.begin(), chars.length()); + if (!irregexp::CheckPatternSyntax(this->alloc_, this->fc_->stackLimit(), + anyChars, source, flags, Some(line), + Some(column))) { + return null(); + } + + return handler_.newRegExp(SyntaxParseHandler::NodeGeneric, pos()); +} + +template +typename ParseHandler::RegExpLiteralType +GeneralParser::newRegExp() { + return asFinalParser()->newRegExp(); +} + +template +BigIntLiteral* Parser::newBigInt() { + // The token's charBuffer contains the DecimalIntegerLiteral or + // NonDecimalIntegerLiteral production, and as such does not include the + // BigIntLiteralSuffix (the trailing "n"). Note that NonDecimalIntegerLiteral + // productions start with 0[bBoOxX], indicating binary/octal/hex. + const auto& chars = tokenStream.getCharBuffer(); + if (chars.length() > UINT32_MAX) { + ReportAllocationOverflow(fc_); + return null(); + } + + BigIntIndex index(this->compilationState_.bigIntData.length()); + if (uint32_t(index) >= TaggedScriptThingIndex::IndexLimit) { + ReportAllocationOverflow(fc_); + return null(); + } + if (!this->compilationState_.bigIntData.emplaceBack()) { + js::ReportOutOfMemory(this->fc_); + return null(); + } + + if (!this->compilationState_.bigIntData[index].init( + this->fc_, this->stencilAlloc(), chars)) { + return null(); + } + + bool isZero = this->compilationState_.bigIntData[index].isZero(); + + // Should the operations below fail, the buffer held by data will + // be cleaned up by the CompilationState destructor. + return handler_.newBigInt(index, isZero, pos()); +} + +template +SyntaxParseHandler::BigIntLiteralType +Parser::newBigInt() { + // The tokenizer has already checked the syntax of the bigint. + + return handler_.newBigInt(); +} + +template +typename ParseHandler::BigIntLiteralType +GeneralParser::newBigInt() { + return asFinalParser()->newBigInt(); +} + +// |exprPossibleError| is the PossibleError state within |expr|, +// |possibleError| is the surrounding PossibleError state. +template +bool GeneralParser::checkDestructuringAssignmentTarget( + Node expr, TokenPos exprPos, PossibleError* exprPossibleError, + PossibleError* possibleError, TargetBehavior behavior) { + // Report any pending expression error if we're definitely not in a + // destructuring context or the possible destructuring target is a + // property accessor. + if (!possibleError || handler_.isPropertyOrPrivateMemberAccess(expr)) { + return exprPossibleError->checkForExpressionError(); + } + + // |expr| may end up as a destructuring assignment target, so we need to + // validate it's either a name or can be parsed as a nested destructuring + // pattern. Property accessors are also valid assignment targets, but + // those are already handled above. + + exprPossibleError->transferErrorsTo(possibleError); + + // Return early if a pending destructuring error is already present. + if (possibleError->hasPendingDestructuringError()) { + return true; + } + + if (handler_.isName(expr)) { + checkDestructuringAssignmentName(handler_.asName(expr), exprPos, + possibleError); + return true; + } + + if (handler_.isUnparenthesizedDestructuringPattern(expr)) { + if (behavior == TargetBehavior::ForbidAssignmentPattern) { + possibleError->setPendingDestructuringErrorAt(exprPos, + JSMSG_BAD_DESTRUCT_TARGET); + } + return true; + } + + // Parentheses are forbidden around destructuring *patterns* (but allowed + // around names). Use our nicer error message for parenthesized, nested + // patterns if nested destructuring patterns are allowed. + if (handler_.isParenthesizedDestructuringPattern(expr) && + behavior != TargetBehavior::ForbidAssignmentPattern) { + possibleError->setPendingDestructuringErrorAt(exprPos, + JSMSG_BAD_DESTRUCT_PARENS); + } else { + possibleError->setPendingDestructuringErrorAt(exprPos, + JSMSG_BAD_DESTRUCT_TARGET); + } + + return true; +} + +template +void GeneralParser::checkDestructuringAssignmentName( + NameNodeType name, TokenPos namePos, PossibleError* possibleError) { +#ifdef DEBUG + // GCC 8.0.1 crashes if this is a one-liner. + bool isName = handler_.isName(name); + MOZ_ASSERT(isName); +#endif + + // Return early if a pending destructuring error is already present. + if (possibleError->hasPendingDestructuringError()) { + return; + } + + if (pc_->sc()->strict()) { + if (handler_.isArgumentsName(name)) { + if (pc_->sc()->strict()) { + possibleError->setPendingDestructuringErrorAt( + namePos, JSMSG_BAD_STRICT_ASSIGN_ARGUMENTS); + } else { + possibleError->setPendingDestructuringWarningAt( + namePos, JSMSG_BAD_STRICT_ASSIGN_ARGUMENTS); + } + return; + } + + if (handler_.isEvalName(name)) { + if (pc_->sc()->strict()) { + possibleError->setPendingDestructuringErrorAt( + namePos, JSMSG_BAD_STRICT_ASSIGN_EVAL); + } else { + possibleError->setPendingDestructuringWarningAt( + namePos, JSMSG_BAD_STRICT_ASSIGN_EVAL); + } + return; + } + } +} + +template +bool GeneralParser::checkDestructuringAssignmentElement( + Node expr, TokenPos exprPos, PossibleError* exprPossibleError, + PossibleError* possibleError) { + // ES2018 draft rev 0719f44aab93215ed9a626b2f45bd34f36916834 + // 12.15.5 Destructuring Assignment + // + // AssignmentElement[Yield, Await]: + // DestructuringAssignmentTarget[?Yield, ?Await] + // DestructuringAssignmentTarget[?Yield, ?Await] Initializer[+In, + // ?Yield, + // ?Await] + + // If |expr| is an assignment element with an initializer expression, its + // destructuring assignment target was already validated in assignExpr(). + // Otherwise we need to check that |expr| is a valid destructuring target. + if (handler_.isUnparenthesizedAssignment(expr)) { + // Report any pending expression error if we're definitely not in a + // destructuring context. + if (!possibleError) { + return exprPossibleError->checkForExpressionError(); + } + + exprPossibleError->transferErrorsTo(possibleError); + return true; + } + return checkDestructuringAssignmentTarget(expr, exprPos, exprPossibleError, + possibleError); +} + +template +typename ParseHandler::ListNodeType +GeneralParser::arrayInitializer( + YieldHandling yieldHandling, PossibleError* possibleError) { + MOZ_ASSERT(anyChars.isCurrentTokenType(TokenKind::LeftBracket)); + + uint32_t begin = pos().begin; + ListNodeType literal = handler_.newArrayLiteral(begin); + if (!literal) { + return null(); + } + + TokenKind tt; + if (!tokenStream.getToken(&tt, TokenStream::SlashIsRegExp)) { + return null(); + } + + if (tt == TokenKind::RightBracket) { + /* + * Mark empty arrays as non-constant, since we cannot easily + * determine their type. + */ + handler_.setListHasNonConstInitializer(literal); + } else { + anyChars.ungetToken(); + + for (uint32_t index = 0;; index++) { + if (index >= NativeObject::MAX_DENSE_ELEMENTS_COUNT) { + error(JSMSG_ARRAY_INIT_TOO_BIG); + return null(); + } + + TokenKind tt; + if (!tokenStream.peekToken(&tt, TokenStream::SlashIsRegExp)) { + return null(); + } + if (tt == TokenKind::RightBracket) { + break; + } + + if (tt == TokenKind::Comma) { + tokenStream.consumeKnownToken(TokenKind::Comma, + TokenStream::SlashIsRegExp); + if (!handler_.addElision(literal, pos())) { + return null(); + } + continue; + } + + if (tt == TokenKind::TripleDot) { + tokenStream.consumeKnownToken(TokenKind::TripleDot, + TokenStream::SlashIsRegExp); + uint32_t begin = pos().begin; + + TokenPos innerPos; + if (!tokenStream.peekTokenPos(&innerPos, TokenStream::SlashIsRegExp)) { + return null(); + } + + PossibleError possibleErrorInner(*this); + Node inner = assignExpr(InAllowed, yieldHandling, TripledotProhibited, + &possibleErrorInner); + if (!inner) { + return null(); + } + if (!checkDestructuringAssignmentTarget( + inner, innerPos, &possibleErrorInner, possibleError)) { + return null(); + } + + if (!handler_.addSpreadElement(literal, begin, inner)) { + return null(); + } + } else { + TokenPos elementPos; + if (!tokenStream.peekTokenPos(&elementPos, + TokenStream::SlashIsRegExp)) { + return null(); + } + + PossibleError possibleErrorInner(*this); + Node element = assignExpr(InAllowed, yieldHandling, TripledotProhibited, + &possibleErrorInner); + if (!element) { + return null(); + } + if (!checkDestructuringAssignmentElement( + element, elementPos, &possibleErrorInner, possibleError)) { + return null(); + } + handler_.addArrayElement(literal, element); + } + + bool matched; + if (!tokenStream.matchToken(&matched, TokenKind::Comma, + TokenStream::SlashIsRegExp)) { + return null(); + } + if (!matched) { + break; + } + + if (tt == TokenKind::TripleDot && possibleError) { + possibleError->setPendingDestructuringErrorAt(pos(), + JSMSG_REST_WITH_COMMA); + } + } + + if (!mustMatchToken( + TokenKind::RightBracket, [this, begin](TokenKind actual) { + this->reportMissingClosing(JSMSG_BRACKET_AFTER_LIST, + JSMSG_BRACKET_OPENED, begin); + })) { + return null(); + } + } + + handler_.setEndPosition(literal, pos().end); + return literal; +} + +template +typename ParseHandler::Node GeneralParser::propertyName( + YieldHandling yieldHandling, PropertyNameContext propertyNameContext, + const Maybe& maybeDecl, ListNodeType propList, + TaggedParserAtomIndex* propAtomOut) { + // PropertyName[Yield, Await]: + // LiteralPropertyName + // ComputedPropertyName[?Yield, ?Await] + // + // LiteralPropertyName: + // IdentifierName + // StringLiteral + // NumericLiteral + TokenKind ltok = anyChars.currentToken().type; + + *propAtomOut = TaggedParserAtomIndex::null(); + switch (ltok) { + case TokenKind::Number: { + auto numAtom = NumberToParserAtom(fc_, this->parserAtoms(), + anyChars.currentToken().number()); + if (!numAtom) { + return null(); + } + *propAtomOut = numAtom; + return newNumber(anyChars.currentToken()); + } + + case TokenKind::BigInt: { + Node biNode = newBigInt(); + if (!biNode) { + return null(); + } + return handler_.newSyntheticComputedName(biNode, pos().begin, pos().end); + } + case TokenKind::String: { + auto str = anyChars.currentToken().atom(); + *propAtomOut = str; + uint32_t index; + if (this->parserAtoms().isIndex(str, &index)) { + return handler_.newNumber(index, NoDecimal, pos()); + } + return stringLiteral(); + } + + case TokenKind::LeftBracket: + return computedPropertyName(yieldHandling, maybeDecl, propertyNameContext, + propList); + + case TokenKind::PrivateName: { + if (propertyNameContext != PropertyNameContext::PropertyNameInClass) { + error(JSMSG_ILLEGAL_PRIVATE_FIELD); + return null(); + } + + TaggedParserAtomIndex propName = anyChars.currentName(); + *propAtomOut = propName; + return privateNameReference(propName); + } + + default: { + if (!TokenKindIsPossibleIdentifierName(ltok)) { + error(JSMSG_UNEXPECTED_TOKEN, "property name", TokenKindToDesc(ltok)); + return null(); + } + + TaggedParserAtomIndex name = anyChars.currentName(); + *propAtomOut = name; + return handler_.newObjectLiteralPropertyName(name, pos()); + } + } +} + +// True if `kind` can be the first token of a PropertyName. +static bool TokenKindCanStartPropertyName(TokenKind tt) { + return TokenKindIsPossibleIdentifierName(tt) || tt == TokenKind::String || + tt == TokenKind::Number || tt == TokenKind::LeftBracket || + tt == TokenKind::Mul || tt == TokenKind::BigInt || + tt == TokenKind::PrivateName; +} + +template +typename ParseHandler::Node +GeneralParser::propertyOrMethodName( + YieldHandling yieldHandling, PropertyNameContext propertyNameContext, + const Maybe& maybeDecl, ListNodeType propList, + PropertyType* propType, TaggedParserAtomIndex* propAtomOut) { + // We're parsing an object literal, class, or destructuring pattern; + // propertyNameContext tells which one. This method parses any of the + // following, storing the corresponding PropertyType in `*propType` to tell + // the caller what we parsed: + // + // async [no LineTerminator here] PropertyName + // ==> PropertyType::AsyncMethod + // async [no LineTerminator here] * PropertyName + // ==> PropertyType::AsyncGeneratorMethod + // * PropertyName ==> PropertyType::GeneratorMethod + // get PropertyName ==> PropertyType::Getter + // set PropertyName ==> PropertyType::Setter + // accessor PropertyName ==> PropertyType::FieldWithAccessor + // PropertyName : ==> PropertyType::Normal + // PropertyName ==> see below + // + // In the last case, where there's not a `:` token to consume, we peek at + // (but don't consume) the next token to decide how to set `*propType`. + // + // `,` or `}` ==> PropertyType::Shorthand + // `(` ==> PropertyType::Method + // `=`, not in a class ==> PropertyType::CoverInitializedName + // '=', in a class ==> PropertyType::Field + // any token, in a class ==> PropertyType::Field (ASI) + // + // The caller must check `*propType` and throw if whatever we parsed isn't + // allowed here (for example, a getter in a destructuring pattern). + // + // This method does *not* match `static` (allowed in classes) or `...` + // (allowed in object literals and patterns). The caller must take care of + // those before calling this method. + + TokenKind ltok; + if (!tokenStream.getToken(<ok, TokenStream::SlashIsInvalid)) { + return null(); + } + + MOZ_ASSERT(ltok != TokenKind::RightCurly, + "caller should have handled TokenKind::RightCurly"); + + // Accept `async` and/or `*`, indicating an async or generator method; + // or `get` or `set` or `accessor`, indicating an accessor. + bool isGenerator = false; + bool isAsync = false; + bool isGetter = false; + bool isSetter = false; +#ifdef ENABLE_DECORATORS + bool hasAccessor = false; +#endif + + if (ltok == TokenKind::Async) { + // `async` is also a PropertyName by itself (it's a conditional keyword), + // so peek at the next token to see if we're really looking at a method. + TokenKind tt = TokenKind::Eof; + if (!tokenStream.peekTokenSameLine(&tt)) { + return null(); + } + if (TokenKindCanStartPropertyName(tt)) { + isAsync = true; + tokenStream.consumeKnownToken(tt); + ltok = tt; + } + } + + if (ltok == TokenKind::Mul) { + isGenerator = true; + if (!tokenStream.getToken(<ok)) { + return null(); + } + } + + if (!isAsync && !isGenerator && + (ltok == TokenKind::Get || ltok == TokenKind::Set)) { + // We have parsed |get| or |set|. Look for an accessor property + // name next. + TokenKind tt; + if (!tokenStream.peekToken(&tt)) { + return null(); + } + if (TokenKindCanStartPropertyName(tt)) { + tokenStream.consumeKnownToken(tt); + isGetter = (ltok == TokenKind::Get); + isSetter = (ltok == TokenKind::Set); + } + } + +#ifdef ENABLE_DECORATORS + if (!isGenerator && !isAsync && propertyNameContext == PropertyNameInClass && + ltok == TokenKind::Accessor) { + MOZ_ASSERT(!isGetter && !isSetter); + TokenKind tt; + if (!tokenStream.peekTokenSameLine(&tt)) { + return null(); + } + + // The target rule is `accessor [no LineTerminator here] + // ClassElementName[?Yield, ?Await] Initializer[+In, ?Yield, ?Await]opt` + if (TokenKindCanStartPropertyName(tt)) { + tokenStream.consumeKnownToken(tt); + hasAccessor = true; + } + } +#endif + + Node propName = propertyName(yieldHandling, propertyNameContext, maybeDecl, + propList, propAtomOut); + if (!propName) { + return null(); + } + + // Grab the next token following the property/method name. + // (If this isn't a colon, we're going to either put it back or throw.) + TokenKind tt; + if (!tokenStream.getToken(&tt)) { + return null(); + } + + if (tt == TokenKind::Colon) { + if (isGenerator || isAsync || isGetter || isSetter +#ifdef ENABLE_DECORATORS + || hasAccessor +#endif + ) { + error(JSMSG_BAD_PROP_ID); + return null(); + } + *propType = PropertyType::Normal; + return propName; + } + + if (propertyNameContext != PropertyNameInClass && + TokenKindIsPossibleIdentifierName(ltok) && + (tt == TokenKind::Comma || tt == TokenKind::RightCurly || + tt == TokenKind::Assign)) { +#ifdef ENABLE_DECORATORS + MOZ_ASSERT(!hasAccessor); +#endif + if (isGenerator || isAsync || isGetter || isSetter) { + error(JSMSG_BAD_PROP_ID); + return null(); + } + + anyChars.ungetToken(); + *propType = tt == TokenKind::Assign ? PropertyType::CoverInitializedName + : PropertyType::Shorthand; + return propName; + } + + if (tt == TokenKind::LeftParen) { + anyChars.ungetToken(); + +#ifdef ENABLE_RECORD_TUPLE + if (propertyNameContext == PropertyNameInRecord) { + // Record & Tuple proposal, section 7.1.1: + // RecordPropertyDefinition doesn't cover methods + error(JSMSG_BAD_PROP_ID); + return null(); + } +#endif + +#ifdef ENABLE_DECORATORS + if (hasAccessor) { + error(JSMSG_BAD_PROP_ID); + return null(); + } +#endif + + if (isGenerator && isAsync) { + *propType = PropertyType::AsyncGeneratorMethod; + } else if (isGenerator) { + *propType = PropertyType::GeneratorMethod; + } else if (isAsync) { + *propType = PropertyType::AsyncMethod; + } else if (isGetter) { + *propType = PropertyType::Getter; + } else if (isSetter) { + *propType = PropertyType::Setter; + } else { + *propType = PropertyType::Method; + } + return propName; + } + + if (propertyNameContext == PropertyNameInClass) { + if (isGenerator || isAsync || isGetter || isSetter) { + error(JSMSG_BAD_PROP_ID); + return null(); + } + anyChars.ungetToken(); +#ifdef ENABLE_DECORATORS + if (!hasAccessor) { + *propType = PropertyType::Field; + } else { + *propType = PropertyType::FieldWithAccessor; + } +#else + *propType = PropertyType::Field; +#endif + return propName; + } + + error(JSMSG_COLON_AFTER_ID); + return null(); +} + +template +typename ParseHandler::UnaryNodeType +GeneralParser::computedPropertyName( + YieldHandling yieldHandling, const Maybe& maybeDecl, + PropertyNameContext propertyNameContext, ListNodeType literal) { + MOZ_ASSERT(anyChars.isCurrentTokenType(TokenKind::LeftBracket)); + + uint32_t begin = pos().begin; + + if (maybeDecl) { + if (*maybeDecl == DeclarationKind::FormalParameter) { + pc_->functionBox()->hasParameterExprs = true; + } + } else if (propertyNameContext == + PropertyNameContext::PropertyNameInLiteral) { + handler_.setListHasNonConstInitializer(literal); + } + + Node assignNode = assignExpr(InAllowed, yieldHandling, TripledotProhibited); + if (!assignNode) { + return null(); + } + + if (!mustMatchToken(TokenKind::RightBracket, JSMSG_COMP_PROP_UNTERM_EXPR)) { + return null(); + } + return handler_.newComputedName(assignNode, begin, pos().end); +} + +template +typename ParseHandler::ListNodeType +GeneralParser::objectLiteral(YieldHandling yieldHandling, + PossibleError* possibleError) { + MOZ_ASSERT(anyChars.isCurrentTokenType(TokenKind::LeftCurly)); + + uint32_t openedPos = pos().begin; + + ListNodeType literal = handler_.newObjectLiteral(pos().begin); + if (!literal) { + return null(); + } + + bool seenPrototypeMutation = false; + bool seenCoverInitializedName = false; + Maybe declKind = Nothing(); + TaggedParserAtomIndex propAtom; + for (;;) { + TokenKind tt; + if (!tokenStream.peekToken(&tt)) { + return null(); + } + if (tt == TokenKind::RightCurly) { + break; + } + + if (tt == TokenKind::TripleDot) { + tokenStream.consumeKnownToken(TokenKind::TripleDot); + uint32_t begin = pos().begin; + + TokenPos innerPos; + if (!tokenStream.peekTokenPos(&innerPos, TokenStream::SlashIsRegExp)) { + return null(); + } + + PossibleError possibleErrorInner(*this); + Node inner = assignExpr(InAllowed, yieldHandling, TripledotProhibited, + &possibleErrorInner); + if (!inner) { + return null(); + } + if (!checkDestructuringAssignmentTarget( + inner, innerPos, &possibleErrorInner, possibleError, + TargetBehavior::ForbidAssignmentPattern)) { + return null(); + } + if (!handler_.addSpreadProperty(literal, begin, inner)) { + return null(); + } + } else { + TokenPos namePos = anyChars.nextToken().pos; + + PropertyType propType; + Node propName = + propertyOrMethodName(yieldHandling, PropertyNameInLiteral, declKind, + literal, &propType, &propAtom); + if (!propName) { + return null(); + } + + if (propType == PropertyType::Normal) { + TokenPos exprPos; + if (!tokenStream.peekTokenPos(&exprPos, TokenStream::SlashIsRegExp)) { + return null(); + } + + PossibleError possibleErrorInner(*this); + Node propExpr = assignExpr(InAllowed, yieldHandling, + TripledotProhibited, &possibleErrorInner); + if (!propExpr) { + return null(); + } + + if (!checkDestructuringAssignmentElement( + propExpr, exprPos, &possibleErrorInner, possibleError)) { + return null(); + } + + if (propAtom == TaggedParserAtomIndex::WellKnown::proto()) { + if (seenPrototypeMutation) { + // Directly report the error when we're definitely not + // in a destructuring context. + if (!possibleError) { + errorAt(namePos.begin, JSMSG_DUPLICATE_PROTO_PROPERTY); + return null(); + } + + // Otherwise delay error reporting until we've + // determined whether or not we're destructuring. + possibleError->setPendingExpressionErrorAt( + namePos, JSMSG_DUPLICATE_PROTO_PROPERTY); + } + seenPrototypeMutation = true; + + // This occurs *only* if we observe PropertyType::Normal! + // Only |__proto__: v| mutates [[Prototype]]. Getters, + // setters, method/generator definitions, computed + // property name versions of all of these, and shorthands + // do not. + if (!handler_.addPrototypeMutation(literal, namePos.begin, + propExpr)) { + return null(); + } + } else { + BinaryNodeType propDef = + handler_.newPropertyDefinition(propName, propExpr); + if (!propDef) { + return null(); + } + + handler_.addPropertyDefinition(literal, propDef); + } + } else if (propType == PropertyType::Shorthand) { + /* + * Support, e.g., |({x, y} = o)| as destructuring shorthand + * for |({x: x, y: y} = o)|, and |var o = {x, y}| as + * initializer shorthand for |var o = {x: x, y: y}|. + */ + TaggedParserAtomIndex name = identifierReference(yieldHandling); + if (!name) { + return null(); + } + + NameNodeType nameExpr = identifierReference(name); + if (!nameExpr) { + return null(); + } + + if (possibleError) { + checkDestructuringAssignmentName(nameExpr, namePos, possibleError); + } + + if (!handler_.addShorthand(literal, handler_.asName(propName), + nameExpr)) { + return null(); + } + } else if (propType == PropertyType::CoverInitializedName) { + /* + * Support, e.g., |({x=1, y=2} = o)| as destructuring + * shorthand with default values, as per ES6 12.14.5 + */ + TaggedParserAtomIndex name = identifierReference(yieldHandling); + if (!name) { + return null(); + } + + Node lhs = identifierReference(name); + if (!lhs) { + return null(); + } + + tokenStream.consumeKnownToken(TokenKind::Assign); + + if (!seenCoverInitializedName) { + // "shorthand default" or "CoverInitializedName" syntax is + // only valid in the case of destructuring. + seenCoverInitializedName = true; + + if (!possibleError) { + // Destructuring defaults are definitely not allowed + // in this object literal, because of something the + // caller knows about the preceding code. For example, + // maybe the preceding token is an operator: + // |x + {y=z}|. + error(JSMSG_COLON_AFTER_ID); + return null(); + } + + // Here we set a pending error so that later in the parse, + // once we've determined whether or not we're + // destructuring, the error can be reported or ignored + // appropriately. + possibleError->setPendingExpressionErrorAt(pos(), + JSMSG_COLON_AFTER_ID); + } + + if (const char* chars = nameIsArgumentsOrEval(lhs)) { + // |chars| is "arguments" or "eval" here. + if (!strictModeErrorAt(namePos.begin, JSMSG_BAD_STRICT_ASSIGN, + chars)) { + return null(); + } + } + + Node rhs = assignExpr(InAllowed, yieldHandling, TripledotProhibited); + if (!rhs) { + return null(); + } + + BinaryNodeType propExpr = + handler_.newAssignment(ParseNodeKind::AssignExpr, lhs, rhs); + if (!propExpr) { + return null(); + } + + if (!handler_.addPropertyDefinition(literal, propName, propExpr)) { + return null(); + } + } else { + TaggedParserAtomIndex funName; + bool hasStaticName = + !anyChars.isCurrentTokenType(TokenKind::RightBracket) && propAtom; + if (hasStaticName) { + funName = propAtom; + + if (propType == PropertyType::Getter || + propType == PropertyType::Setter) { + funName = prefixAccessorName(propType, propAtom); + if (!funName) { + return null(); + } + } + } + + FunctionNodeType funNode = + methodDefinition(namePos.begin, propType, funName); + if (!funNode) { + return null(); + } + + AccessorType atype = ToAccessorType(propType); + if (!handler_.addObjectMethodDefinition(literal, propName, funNode, + atype)) { + return null(); + } + + if (possibleError) { + possibleError->setPendingDestructuringErrorAt( + namePos, JSMSG_BAD_DESTRUCT_TARGET); + } + } + } + + bool matched; + if (!tokenStream.matchToken(&matched, TokenKind::Comma, + TokenStream::SlashIsInvalid)) { + return null(); + } + if (!matched) { + break; + } + if (tt == TokenKind::TripleDot && possibleError) { + possibleError->setPendingDestructuringErrorAt(pos(), + JSMSG_REST_WITH_COMMA); + } + } + + if (!mustMatchToken( + TokenKind::RightCurly, [this, openedPos](TokenKind actual) { + this->reportMissingClosing(JSMSG_CURLY_AFTER_LIST, + JSMSG_CURLY_OPENED, openedPos); + })) { + return null(); + } + + handler_.setEndPosition(literal, pos().end); + return literal; +} + +#ifdef ENABLE_RECORD_TUPLE +template +typename ParseHandler::ListNodeType +GeneralParser::recordLiteral(YieldHandling yieldHandling) { + MOZ_ASSERT(anyChars.isCurrentTokenType(TokenKind::HashCurly)); + + uint32_t openedPos = pos().begin; + + ListNodeType literal = handler_.newRecordLiteral(pos().begin); + if (!literal) { + return null(); + } + + TaggedParserAtomIndex propAtom; + for (;;) { + TokenKind tt; + if (!tokenStream.peekToken(&tt)) { + return null(); + } + if (tt == TokenKind::RightCurly) { + break; + } + + if (tt == TokenKind::TripleDot) { + tokenStream.consumeKnownToken(TokenKind::TripleDot); + uint32_t begin = pos().begin; + + TokenPos innerPos; + if (!tokenStream.peekTokenPos(&innerPos, TokenStream::SlashIsRegExp)) { + return null(); + } + + Node inner = assignExpr(InAllowed, yieldHandling, TripledotProhibited); + if (!inner) { + return null(); + } + + if (!handler_.addSpreadProperty(literal, begin, inner)) { + return null(); + } + } else { + TokenPos namePos = anyChars.nextToken().pos; + + PropertyType propType; + Node propName = propertyOrMethodName(yieldHandling, PropertyNameInRecord, + /* maybeDecl */ Nothing(), literal, + &propType, &propAtom); + if (!propName) { + return null(); + } + + if (propType == PropertyType::Normal) { + TokenPos exprPos; + if (!tokenStream.peekTokenPos(&exprPos, TokenStream::SlashIsRegExp)) { + return null(); + } + + Node propExpr = + assignExpr(InAllowed, yieldHandling, TripledotProhibited); + if (!propExpr) { + return null(); + } + + if (propAtom == TaggedParserAtomIndex::WellKnown::proto()) { + errorAt(namePos.begin, JSMSG_RECORD_NO_PROTO); + return null(); + } + + BinaryNodeType propDef = + handler_.newPropertyDefinition(propName, propExpr); + if (!propDef) { + return null(); + } + + handler_.addPropertyDefinition(literal, propDef); + } else if (propType == PropertyType::Shorthand) { + /* + * Support |var o = #{x, y}| as initializer shorthand for + * |var o = #{x: x, y: y}|. + */ + TaggedParserAtomIndex name = identifierReference(yieldHandling); + if (!name) { + return null(); + } + + NameNodeType nameExpr = identifierReference(name); + if (!nameExpr) { + return null(); + } + + if (!handler_.addShorthand(literal, handler_.asName(propName), + nameExpr)) { + return null(); + } + } else { + error(JSMSG_BAD_PROP_ID); + return null(); + } + } + + bool matched; + if (!tokenStream.matchToken(&matched, TokenKind::Comma, + TokenStream::SlashIsInvalid)) { + return null(); + } + if (!matched) { + break; + } + } + + if (!mustMatchToken( + TokenKind::RightCurly, [this, openedPos](TokenKind actual) { + this->reportMissingClosing(JSMSG_CURLY_AFTER_LIST, + JSMSG_CURLY_OPENED, openedPos); + })) { + return null(); + } + + handler_.setEndPosition(literal, pos().end); + return literal; +} + +template +typename ParseHandler::ListNodeType +GeneralParser::tupleLiteral(YieldHandling yieldHandling) { + MOZ_ASSERT(anyChars.isCurrentTokenType(TokenKind::HashBracket)); + + uint32_t begin = pos().begin; + ListNodeType literal = handler_.newTupleLiteral(begin); + if (!literal) { + return null(); + } + + for (uint32_t index = 0;; index++) { + if (index >= NativeObject::MAX_DENSE_ELEMENTS_COUNT) { + error(JSMSG_ARRAY_INIT_TOO_BIG); + return null(); + } + + TokenKind tt; + if (!tokenStream.peekToken(&tt, TokenStream::SlashIsRegExp)) { + return null(); + } + if (tt == TokenKind::RightBracket) { + break; + } + + if (tt == TokenKind::TripleDot) { + tokenStream.consumeKnownToken(TokenKind::TripleDot, + TokenStream::SlashIsRegExp); + uint32_t begin = pos().begin; + + TokenPos innerPos; + if (!tokenStream.peekTokenPos(&innerPos, TokenStream::SlashIsRegExp)) { + return null(); + } + + Node inner = assignExpr(InAllowed, yieldHandling, TripledotProhibited); + if (!inner) { + return null(); + } + + if (!handler_.addSpreadElement(literal, begin, inner)) { + return null(); + } + } else { + TokenPos elementPos; + if (!tokenStream.peekTokenPos(&elementPos, TokenStream::SlashIsRegExp)) { + return null(); + } + + Node element = assignExpr(InAllowed, yieldHandling, TripledotProhibited); + if (!element) { + return null(); + } + handler_.addArrayElement(literal, element); + } + + bool matched; + if (!tokenStream.matchToken(&matched, TokenKind::Comma, + TokenStream::SlashIsRegExp)) { + return null(); + } + if (!matched) { + break; + } + } + + if (!mustMatchToken(TokenKind::RightBracket, [this, begin](TokenKind actual) { + this->reportMissingClosing(JSMSG_BRACKET_AFTER_LIST, + JSMSG_BRACKET_OPENED, begin); + })) { + return null(); + } + + handler_.setEndPosition(literal, pos().end); + return literal; +} +#endif + +template +typename ParseHandler::FunctionNodeType +GeneralParser::methodDefinition( + uint32_t toStringStart, PropertyType propType, + TaggedParserAtomIndex funName) { + FunctionSyntaxKind syntaxKind; + switch (propType) { + case PropertyType::Getter: + syntaxKind = FunctionSyntaxKind::Getter; + break; + + case PropertyType::Setter: + syntaxKind = FunctionSyntaxKind::Setter; + break; + + case PropertyType::Method: + case PropertyType::GeneratorMethod: + case PropertyType::AsyncMethod: + case PropertyType::AsyncGeneratorMethod: + syntaxKind = FunctionSyntaxKind::Method; + break; + + case PropertyType::Constructor: + syntaxKind = FunctionSyntaxKind::ClassConstructor; + break; + + case PropertyType::DerivedConstructor: + syntaxKind = FunctionSyntaxKind::DerivedClassConstructor; + break; + + default: + MOZ_CRASH("unexpected property type"); + } + + GeneratorKind generatorKind = (propType == PropertyType::GeneratorMethod || + propType == PropertyType::AsyncGeneratorMethod) + ? GeneratorKind::Generator + : GeneratorKind::NotGenerator; + + FunctionAsyncKind asyncKind = (propType == PropertyType::AsyncMethod || + propType == PropertyType::AsyncGeneratorMethod) + ? FunctionAsyncKind::AsyncFunction + : FunctionAsyncKind::SyncFunction; + + YieldHandling yieldHandling = GetYieldHandling(generatorKind); + + FunctionNodeType funNode = handler_.newFunction(syntaxKind, pos()); + if (!funNode) { + return null(); + } + + return functionDefinition(funNode, toStringStart, InAllowed, yieldHandling, + funName, syntaxKind, generatorKind, asyncKind); +} + +template +bool GeneralParser::tryNewTarget( + NewTargetNodeType* newTarget) { + MOZ_ASSERT(anyChars.isCurrentTokenType(TokenKind::New)); + + *newTarget = null(); + + NullaryNodeType newHolder = handler_.newPosHolder(pos()); + if (!newHolder) { + return false; + } + + uint32_t begin = pos().begin; + + // |new| expects to look for an operand, so we will honor that. + TokenKind next; + if (!tokenStream.getToken(&next, TokenStream::SlashIsRegExp)) { + return false; + } + + // Don't unget the token, since lookahead cannot handle someone calling + // getToken() with a different modifier. Callers should inspect + // currentToken(). + if (next != TokenKind::Dot) { + return true; + } + + if (!tokenStream.getToken(&next)) { + return false; + } + if (next != TokenKind::Target) { + error(JSMSG_UNEXPECTED_TOKEN, "target", TokenKindToDesc(next)); + return false; + } + + if (!pc_->sc()->allowNewTarget()) { + errorAt(begin, JSMSG_BAD_NEWTARGET); + return false; + } + + NullaryNodeType targetHolder = handler_.newPosHolder(pos()); + if (!targetHolder) { + return false; + } + + NameNodeType newTargetName = newNewTargetName(); + if (!newTargetName) { + return false; + } + + *newTarget = handler_.newNewTarget(newHolder, targetHolder, newTargetName); + return !!*newTarget; +} + +template +typename ParseHandler::BinaryNodeType +GeneralParser::importExpr(YieldHandling yieldHandling, + bool allowCallSyntax) { + MOZ_ASSERT(anyChars.isCurrentTokenType(TokenKind::Import)); + + NullaryNodeType importHolder = handler_.newPosHolder(pos()); + if (!importHolder) { + return null(); + } + + TokenKind next; + if (!tokenStream.getToken(&next)) { + return null(); + } + + if (next == TokenKind::Dot) { + if (!tokenStream.getToken(&next)) { + return null(); + } + if (next != TokenKind::Meta) { + error(JSMSG_UNEXPECTED_TOKEN, "meta", TokenKindToDesc(next)); + return null(); + } + + if (parseGoal() != ParseGoal::Module) { + errorAt(pos().begin, JSMSG_IMPORT_META_OUTSIDE_MODULE); + return null(); + } + + NullaryNodeType metaHolder = handler_.newPosHolder(pos()); + if (!metaHolder) { + return null(); + } + + return handler_.newImportMeta(importHolder, metaHolder); + } else if (next == TokenKind::LeftParen && allowCallSyntax) { + Node arg = assignExpr(InAllowed, yieldHandling, TripledotProhibited); + if (!arg) { + return null(); + } + + if (!tokenStream.peekToken(&next, TokenStream::SlashIsRegExp)) { + return null(); + } + + Node optionalArg; + if (options().importAssertions) { + if (next == TokenKind::Comma) { + tokenStream.consumeKnownToken(TokenKind::Comma, + TokenStream::SlashIsRegExp); + + if (!tokenStream.peekToken(&next, TokenStream::SlashIsRegExp)) { + return null(); + } + + if (next != TokenKind::RightParen) { + optionalArg = + assignExpr(InAllowed, yieldHandling, TripledotProhibited); + if (!optionalArg) { + return null(); + } + + if (!tokenStream.peekToken(&next, TokenStream::SlashIsRegExp)) { + return null(); + } + + if (next == TokenKind::Comma) { + tokenStream.consumeKnownToken(TokenKind::Comma, + TokenStream::SlashIsRegExp); + } + } else { + optionalArg = handler_.newPosHolder(TokenPos(pos().end, pos().end)); + if (!optionalArg) { + return null(); + } + } + } else { + optionalArg = handler_.newPosHolder(TokenPos(pos().end, pos().end)); + if (!optionalArg) { + return null(); + } + } + } else { + optionalArg = handler_.newPosHolder(TokenPos(pos().end, pos().end)); + if (!optionalArg) { + return null(); + } + } + + if (!mustMatchToken(TokenKind::RightParen, JSMSG_PAREN_AFTER_ARGS)) { + return null(); + } + + Node spec = handler_.newCallImportSpec(arg, optionalArg); + if (!spec) { + return null(); + } + + return handler_.newCallImport(importHolder, spec); + } else { + error(JSMSG_UNEXPECTED_TOKEN_NO_EXPECT, TokenKindToDesc(next)); + return null(); + } +} + +template +typename ParseHandler::Node GeneralParser::primaryExpr( + YieldHandling yieldHandling, TripledotHandling tripledotHandling, + TokenKind tt, PossibleError* possibleError, InvokedPrediction invoked) { + MOZ_ASSERT(anyChars.isCurrentTokenType(tt)); + AutoCheckRecursionLimit recursion(this->fc_); + if (!recursion.check(this->fc_)) { + return null(); + } + + switch (tt) { + case TokenKind::Function: + return functionExpr(pos().begin, invoked, + FunctionAsyncKind::SyncFunction); + + case TokenKind::Class: + return classDefinition(yieldHandling, ClassExpression, NameRequired); + + case TokenKind::LeftBracket: + return arrayInitializer(yieldHandling, possibleError); + + case TokenKind::LeftCurly: + return objectLiteral(yieldHandling, possibleError); + +#ifdef ENABLE_RECORD_TUPLE + case TokenKind::HashCurly: + return recordLiteral(yieldHandling); + + case TokenKind::HashBracket: + return tupleLiteral(yieldHandling); +#endif + +#ifdef ENABLE_DECORATORS + case TokenKind::At: + return classDefinition(yieldHandling, ClassExpression, NameRequired); +#endif + + case TokenKind::LeftParen: { + TokenKind next; + if (!tokenStream.peekToken(&next, TokenStream::SlashIsRegExp)) { + return null(); + } + + if (next == TokenKind::RightParen) { + // Not valid expression syntax, but this is valid in an arrow function + // with no params: `() => body`. + tokenStream.consumeKnownToken(TokenKind::RightParen, + TokenStream::SlashIsRegExp); + + if (!tokenStream.peekToken(&next)) { + return null(); + } + if (next != TokenKind::Arrow) { + error(JSMSG_UNEXPECTED_TOKEN, "expression", + TokenKindToDesc(TokenKind::RightParen)); + return null(); + } + + // Now just return something that will allow parsing to continue. + // It doesn't matter what; when we reach the =>, we will rewind and + // reparse the whole arrow function. See Parser::assignExpr. + return handler_.newNullLiteral(pos()); + } + + // Pass |possibleError| to support destructuring in arrow parameters. + Node expr = exprInParens(InAllowed, yieldHandling, TripledotAllowed, + possibleError); + if (!expr) { + return null(); + } + if (!mustMatchToken(TokenKind::RightParen, JSMSG_PAREN_IN_PAREN)) { + return null(); + } + return handler_.parenthesize(expr); + } + + case TokenKind::TemplateHead: + return templateLiteral(yieldHandling); + + case TokenKind::NoSubsTemplate: + return noSubstitutionUntaggedTemplate(); + + case TokenKind::String: + return stringLiteral(); + + default: { + if (!TokenKindIsPossibleIdentifier(tt)) { + error(JSMSG_UNEXPECTED_TOKEN, "expression", TokenKindToDesc(tt)); + return null(); + } + + if (tt == TokenKind::Async) { + TokenKind nextSameLine = TokenKind::Eof; + if (!tokenStream.peekTokenSameLine(&nextSameLine)) { + return null(); + } + + if (nextSameLine == TokenKind::Function) { + uint32_t toStringStart = pos().begin; + tokenStream.consumeKnownToken(TokenKind::Function); + return functionExpr(toStringStart, PredictUninvoked, + FunctionAsyncKind::AsyncFunction); + } + } + + TaggedParserAtomIndex name = identifierReference(yieldHandling); + if (!name) { + return null(); + } + + return identifierReference(name); + } + + case TokenKind::RegExp: + return newRegExp(); + + case TokenKind::Number: + return newNumber(anyChars.currentToken()); + + case TokenKind::BigInt: + return newBigInt(); + + case TokenKind::True: + return handler_.newBooleanLiteral(true, pos()); + case TokenKind::False: + return handler_.newBooleanLiteral(false, pos()); + case TokenKind::This: { + NameNodeType thisName = null(); + if (pc_->sc()->hasFunctionThisBinding()) { + thisName = newThisName(); + if (!thisName) { + return null(); + } + } + return handler_.newThisLiteral(pos(), thisName); + } + case TokenKind::Null: + return handler_.newNullLiteral(pos()); + + case TokenKind::TripleDot: { + // This isn't valid expression syntax, but it's valid in an arrow + // function as a trailing rest param: `(a, b, ...rest) => body`. Check + // if it's directly under + // CoverParenthesizedExpressionAndArrowParameterList, and check for a + // name, closing parenthesis, and arrow, and allow it only if all are + // present. + if (tripledotHandling != TripledotAllowed) { + error(JSMSG_UNEXPECTED_TOKEN, "expression", TokenKindToDesc(tt)); + return null(); + } + + TokenKind next; + if (!tokenStream.getToken(&next)) { + return null(); + } + + if (next == TokenKind::LeftBracket || next == TokenKind::LeftCurly) { + // Validate, but don't store the pattern right now. The whole arrow + // function is reparsed in functionFormalParametersAndBody(). + if (!destructuringDeclaration(DeclarationKind::CoverArrowParameter, + yieldHandling, next)) { + return null(); + } + } else { + // This doesn't check that the provided name is allowed, e.g. if + // the enclosing code is strict mode code, any of "let", "yield", + // or "arguments" should be prohibited. Argument-parsing code + // handles that. + if (!TokenKindIsPossibleIdentifier(next)) { + error(JSMSG_UNEXPECTED_TOKEN, "rest argument name", + TokenKindToDesc(next)); + return null(); + } + } + + if (!tokenStream.getToken(&next)) { + return null(); + } + if (next != TokenKind::RightParen) { + error(JSMSG_UNEXPECTED_TOKEN, "closing parenthesis", + TokenKindToDesc(next)); + return null(); + } + + if (!tokenStream.peekToken(&next)) { + return null(); + } + if (next != TokenKind::Arrow) { + // Advance the scanner for proper error location reporting. + tokenStream.consumeKnownToken(next); + error(JSMSG_UNEXPECTED_TOKEN, "'=>' after argument list", + TokenKindToDesc(next)); + return null(); + } + + anyChars.ungetToken(); // put back right paren + + // Return an arbitrary expression node. See case TokenKind::RightParen + // above. + return handler_.newNullLiteral(pos()); + } + } +} + +template +typename ParseHandler::Node GeneralParser::exprInParens( + InHandling inHandling, YieldHandling yieldHandling, + TripledotHandling tripledotHandling, + PossibleError* possibleError /* = nullptr */) { + MOZ_ASSERT(anyChars.isCurrentTokenType(TokenKind::LeftParen)); + return expr(inHandling, yieldHandling, tripledotHandling, possibleError, + PredictInvoked); +} + +template class PerHandlerParser; +template class PerHandlerParser; +template class GeneralParser; +template class GeneralParser; +template class GeneralParser; +template class GeneralParser; +template class Parser; +template class Parser; +template class Parser; +template class Parser; + +} // namespace js::frontend diff --git a/js/src/frontend/Parser.h b/js/src/frontend/Parser.h new file mode 100644 index 0000000000..5f14a76227 --- /dev/null +++ b/js/src/frontend/Parser.h @@ -0,0 +1,1955 @@ +/* -*- 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. */ + +#ifndef frontend_Parser_h +#define frontend_Parser_h + +/* + * [SMDOC] JS Parser + * + * JS parsers capable of generating ASTs from source text. + * + * A parser embeds token stream information, then gets and matches tokens to + * generate a syntax tree that, if desired, BytecodeEmitter will use to compile + * bytecode. + * + * Like token streams (see the comment near the top of TokenStream.h), parser + * classes are heavily templatized -- along the token stream's character-type + * axis, and also along a full-parse/syntax-parse axis. Certain limitations of + * C++ (primarily the inability to partially specialize function templates), + * plus the desire to minimize compiled code size in duplicate function + * template instantiations wherever possible, mean that Parser exhibits much of + * the same unholy template/inheritance complexity as token streams. + * + * == ParserSharedBase == + * + * ParserSharedBase is the base class for both regular JS and BinAST parsing. + * This class contains common fields and methods between both parsers. There is + * currently no BinAST parser here so this can potentially be merged into the + * ParserBase type below. + * + * == ParserBase → ParserSharedBase, ErrorReportMixin == + * + * ParserBase is the base class for regular JS parser, shared by all regular JS + * parsers of all character types and parse-handling behavior. It stores + * everything character- and handler-agnostic. + * + * ParserBase's most important field is the parser's token stream's + * |TokenStreamAnyChars| component, for all tokenizing aspects that are + * character-type-agnostic. The character-type-sensitive components residing + * in |TokenStreamSpecific| (see the comment near the top of TokenStream.h) + * live elsewhere in this hierarchy. These separate locations are the reason + * for the |AnyCharsAccess| template parameter to |TokenStreamChars| and + * |TokenStreamSpecific|. + * + * == PerHandlerParser → ParserBase == + * + * Certain parsing behavior varies between full parsing and syntax-only parsing + * but does not vary across source-text character types. For example, the work + * to "create an arguments object for a function" obviously varies between + * syntax and full parsing but (because no source characters are examined) does + * not vary by source text character type. Such functionality is implemented + * through functions in PerHandlerParser. + * + * Functionality only used by syntax parsing or full parsing doesn't live here: + * it should be implemented in the appropriate Parser (described + * further below). + * + * == GeneralParser → PerHandlerParser == + * + * Most parsing behavior varies across the character-type axis (and possibly + * along the full/syntax axis). For example: + * + * * Parsing ECMAScript's Expression production, implemented by + * GeneralParser::expr, varies in this manner: different types are used to + * represent nodes in full and syntax parsing (ParseNode* versus an enum), + * and reading the tokens comprising the expression requires inspecting + * individual characters (necessarily dependent upon character type). + * * Reporting an error or warning does not depend on the full/syntax parsing + * distinction. But error reports and warnings include a line of context + * (or a slice of one), for pointing out where a mistake was made. + * Computing such line of context requires inspecting the source text to + * make that line/slice of context, which requires knowing the source text + * character type. + * + * Such functionality, implemented using identical function code across these + * axes, should live in GeneralParser. + * + * GeneralParser's most important field is the parser's token stream's + * |TokenStreamSpecific| component, for all aspects of tokenizing that (contra + * |TokenStreamAnyChars| in ParserBase above) are character-type-sensitive. As + * noted above, this field's existence separate from that in ParserBase + * motivates the |AnyCharsAccess| template parameters on various token stream + * classes. + * + * Everything in PerHandlerParser *could* be folded into GeneralParser (below) + * if desired. We don't fold in this manner because all such functions would + * be instantiated once per Unit -- but if exactly equivalent code would be + * generated (because PerHandlerParser functions have no awareness of Unit), + * it's risky to *depend* upon the compiler coalescing the instantiations into + * one in the final binary. PerHandlerParser guarantees no duplication. + * + * == Parser final → GeneralParser == + * + * The final (pun intended) axis of complexity lies in Parser. + * + * Some functionality depends on character type, yet also is defined in + * significantly different form in full and syntax parsing. For example, + * attempting to parse the source text of a module will do so in full parsing + * but immediately fail in syntax parsing -- so the former is a mess'o'code + * while the latter is effectively |return null();|. Such functionality is + * defined in Parser as + * appropriate. + * + * There's a crucial distinction between GeneralParser and Parser, that + * explains why both must exist (despite taking exactly the same template + * parameters, and despite GeneralParser and Parser existing in a one-to-one + * relationship). GeneralParser is one unspecialized template class: + * + * template + * class GeneralParser : ... + * { + * ...parsing functions... + * }; + * + * but Parser is one undefined template class with two separate + * specializations: + * + * // Declare, but do not define. + * template class Parser; + * + * // Define a syntax-parsing specialization. + * template + * class Parser final + * : public GeneralParser + * { + * ...parsing functions... + * }; + * + * // Define a full-parsing specialization. + * template + * class Parser final + * : public GeneralParser + * { + * ...parsing functions... + * }; + * + * This odd distinction is necessary because C++ unfortunately doesn't allow + * partial function specialization: + * + * // BAD: You can only specialize a template function if you specify *every* + * // template parameter, i.e. ParseHandler *and* Unit. + * template + * void + * GeneralParser::foo() {} + * + * But if you specialize Parser *as a class*, then this is allowed: + * + * template + * void + * Parser::foo() {} + * + * template + * void + * Parser::foo() {} + * + * because the only template parameter on the function is Unit -- and so all + * template parameters *are* varying, not a strict subset of them. + * + * So -- any parsing functionality that is differently defined for different + * ParseHandlers, *but* is defined textually identically for different Unit + * (even if different code ends up generated for them by the compiler), should + * reside in Parser. + */ + +#include "mozilla/Maybe.h" + +#include +#include + +#include "frontend/CompilationStencil.h" // CompilationState +#include "frontend/ErrorReporter.h" +#include "frontend/FullParseHandler.h" +#include "frontend/FunctionSyntaxKind.h" // FunctionSyntaxKind +#include "frontend/IteratorKind.h" +#include "frontend/NameAnalysisTypes.h" +#include "frontend/ParseContext.h" +#include "frontend/ParserAtom.h" // ParserAtomsTable, TaggedParserAtomIndex +#include "frontend/SharedContext.h" +#include "frontend/SyntaxParseHandler.h" +#include "frontend/TokenStream.h" +#include "js/friend/ErrorMessages.h" // JSErrNum, JSMSG_* +#include "vm/GeneratorAndAsyncKind.h" // js::GeneratorKind, js::FunctionAsyncKind + +namespace js { + +class FrontendContext; +struct ErrorMetadata; + +namespace frontend { + +template +class GeneralParser; + +class SourceParseContext : public ParseContext { + public: + template + SourceParseContext(GeneralParser* prs, SharedContext* sc, + Directives* newDirectives) + : ParseContext(prs->fc_, prs->pc_, sc, prs->tokenStream, + prs->compilationState_, newDirectives, + std::is_same_v) {} +}; + +enum VarContext { HoistVars, DontHoistVars }; +enum PropListType { ObjectLiteral, ClassBody, DerivedClassBody }; +enum class PropertyType { + Normal, + Shorthand, + CoverInitializedName, + Getter, + Setter, + Method, + GeneratorMethod, + AsyncMethod, + AsyncGeneratorMethod, + Constructor, + DerivedConstructor, + Field, + FieldWithAccessor, +}; + +enum AwaitHandling : uint8_t { + AwaitIsName, + AwaitIsKeyword, + AwaitIsModuleKeyword, + AwaitIsDisallowed +}; + +template +class AutoAwaitIsKeyword; + +template +class AutoInParametersOfAsyncFunction; + +class MOZ_STACK_CLASS ParserSharedBase { + public: + enum class Kind { Parser }; + + ParserSharedBase(FrontendContext* fc, CompilationState& compilationState, + Kind kind); + ~ParserSharedBase(); + + public: + FrontendContext* fc_; + + LifoAlloc& alloc_; + + CompilationState& compilationState_; + + // innermost parse context (stack-allocated) + ParseContext* pc_; + + // For tracking used names in this parsing session. + UsedNameTracker& usedNames_; + + public: + CompilationState& getCompilationState() { return compilationState_; } + + ParserAtomsTable& parserAtoms() { return compilationState_.parserAtoms; } + const ParserAtomsTable& parserAtoms() const { + return compilationState_.parserAtoms; + } + + LifoAlloc& stencilAlloc() { return compilationState_.alloc; } + +#if defined(DEBUG) || defined(JS_JITSPEW) + void dumpAtom(TaggedParserAtomIndex index) const; +#endif +}; + +class MOZ_STACK_CLASS ParserBase : public ParserSharedBase, + public ErrorReportMixin { + using Base = ErrorReportMixin; + + public: + TokenStreamAnyChars anyChars; + + ScriptSource* ss; + + // Perform constant-folding; must be true when interfacing with the emitter. + const bool foldConstants_ : 1; + + protected: +#if DEBUG + /* Our fallible 'checkOptions' member function has been called. */ + bool checkOptionsCalled_ : 1; +#endif + + /* Unexpected end of input, i.e. Eof not at top-level. */ + bool isUnexpectedEOF_ : 1; + + /* AwaitHandling */ uint8_t awaitHandling_ : 2; + + bool inParametersOfAsyncFunction_ : 1; + + public: + JSAtom* liftParserAtomToJSAtom(TaggedParserAtomIndex index); + + bool awaitIsKeyword() const { + return awaitHandling_ == AwaitIsKeyword || + awaitHandling_ == AwaitIsModuleKeyword; + } + bool awaitIsDisallowed() const { return awaitHandling_ == AwaitIsDisallowed; } + + bool inParametersOfAsyncFunction() const { + return inParametersOfAsyncFunction_; + } + + ParseGoal parseGoal() const { + return pc_->sc()->hasModuleGoal() ? ParseGoal::Module : ParseGoal::Script; + } + + template + friend class AutoAwaitIsKeyword; + template + friend class AutoInParametersOfAsyncFunction; + + ParserBase(FrontendContext* fc, const JS::ReadOnlyCompileOptions& options, + bool foldConstants, CompilationState& compilationState); + ~ParserBase(); + + bool checkOptions(); + + const char* getFilename() const { return anyChars.getFilename(); } + TokenPos pos() const { return anyChars.currentToken().pos; } + + // Determine whether |yield| is a valid name in the current context. + bool yieldExpressionsSupported() const { return pc_->isGenerator(); } + + bool setLocalStrictMode(bool strict) { + MOZ_ASSERT(anyChars.debugHasNoLookahead()); + return pc_->sc()->setLocalStrictMode(strict); + } + + public: + // Implement ErrorReportMixin. + + FrontendContext* getContext() const override { return fc_; } + + bool strictMode() const override { return pc_->sc()->strict(); } + + const JS::ReadOnlyCompileOptions& options() const override { + return anyChars.options(); + } + + using Base::error; + using Base::errorAt; + using Base::errorNoOffset; + using Base::errorWithNotes; + using Base::errorWithNotesAt; + using Base::errorWithNotesNoOffset; + using Base::strictModeError; + using Base::strictModeErrorAt; + using Base::strictModeErrorNoOffset; + using Base::strictModeErrorWithNotes; + using Base::strictModeErrorWithNotesAt; + using Base::strictModeErrorWithNotesNoOffset; + using Base::warning; + using Base::warningAt; + using Base::warningNoOffset; + + public: + bool isUnexpectedEOF() const { return isUnexpectedEOF_; } + + bool isValidStrictBinding(TaggedParserAtomIndex name); + + bool hasValidSimpleStrictParameterNames(); + + // A Parser::Mark is the extension of the LifoAlloc::Mark to the entire + // Parser's state. Note: clients must still take care that any ParseContext + // that points into released ParseNodes is destroyed. + class Mark { + friend class ParserBase; + LifoAlloc::Mark mark; + CompilationState::CompilationStatePosition pos; + }; + Mark mark() const { + Mark m; + m.mark = alloc_.mark(); + m.pos = compilationState_.getPosition(); + return m; + } + void release(Mark m) { + alloc_.release(m.mark); + compilationState_.rewind(m.pos); + } + + public: + mozilla::Maybe newGlobalScopeData( + ParseContext::Scope& scope); + mozilla::Maybe newModuleScopeData( + ParseContext::Scope& scope); + mozilla::Maybe newEvalScopeData( + ParseContext::Scope& scope); + mozilla::Maybe newFunctionScopeData( + ParseContext::Scope& scope, bool hasParameterExprs); + mozilla::Maybe newVarScopeData( + ParseContext::Scope& scope); + mozilla::Maybe newLexicalScopeData( + ParseContext::Scope& scope); + mozilla::Maybe newClassBodyScopeData( + ParseContext::Scope& scope); + + protected: + enum InvokedPrediction { PredictUninvoked = false, PredictInvoked = true }; + enum ForInitLocation { InForInit, NotInForInit }; + + // While on a |let| Name token, examine |next| (which must already be + // gotten). Indicate whether |next|, the next token already gotten with + // modifier TokenStream::SlashIsDiv, continues a LexicalDeclaration. + bool nextTokenContinuesLetDeclaration(TokenKind next); + + bool noteUsedNameInternal(TaggedParserAtomIndex name, + NameVisibility visibility, + mozilla::Maybe tokenPosition); + + bool checkAndMarkSuperScope(); + + bool leaveInnerFunction(ParseContext* outerpc); + + TaggedParserAtomIndex prefixAccessorName(PropertyType propType, + TaggedParserAtomIndex propAtom); + + [[nodiscard]] bool setSourceMapInfo(); + + void setFunctionEndFromCurrentToken(FunctionBox* funbox) const; +}; + +template +class MOZ_STACK_CLASS PerHandlerParser : public ParserBase { + using Base = ParserBase; + + private: + using Node = typename ParseHandler::Node; + +#define DECLARE_TYPE(typeName, longTypeName, asMethodName) \ + using longTypeName = typename ParseHandler::longTypeName; + FOR_EACH_PARSENODE_SUBCLASS(DECLARE_TYPE) +#undef DECLARE_TYPE + + protected: + /* State specific to the kind of parse being performed. */ + ParseHandler handler_; + + // When ParseHandler is FullParseHandler: + // + // If non-null, this field holds the syntax parser used to attempt lazy + // parsing of inner functions. If null, then lazy parsing is disabled. + // + // When ParseHandler is SyntaxParseHandler: + // + // If non-null, this field must be a sentinel value signaling that the + // syntax parse was aborted. If null, then lazy parsing was aborted due + // to encountering unsupported language constructs. + // + // |internalSyntaxParser_| is really a |Parser*| + // where |Unit| varies per |Parser|. But this + // template class doesn't know |Unit|, so we store a |void*| here and make + // |GeneralParser::getSyntaxParser| impose the real type. + void* internalSyntaxParser_; + + private: + // NOTE: The argument ordering here is deliberately different from the + // public constructor so that typos calling the public constructor + // are less likely to select this overload. + PerHandlerParser(FrontendContext* fc, + const JS::ReadOnlyCompileOptions& options, + bool foldConstants, CompilationState& compilationState, + void* internalSyntaxParser); + + protected: + template + PerHandlerParser(FrontendContext* fc, + const JS::ReadOnlyCompileOptions& options, + bool foldConstants, CompilationState& compilationState, + GeneralParser* syntaxParser) + : PerHandlerParser(fc, options, foldConstants, compilationState, + static_cast(syntaxParser)) {} + + static typename ParseHandler::NullNode null() { return ParseHandler::null(); } + + NameNodeType stringLiteral(); + + const char* nameIsArgumentsOrEval(Node node); + + bool noteDestructuredPositionalFormalParameter(FunctionNodeType funNode, + Node destruct); + + bool noteUsedName( + TaggedParserAtomIndex name, + NameVisibility visibility = NameVisibility::Public, + mozilla::Maybe tokenPosition = mozilla::Nothing()) { + // If the we are delazifying, the BaseScript already has all the closed-over + // info for bindings and there's no need to track used names. + if (handler_.reuseClosedOverBindings()) { + return true; + } + + return ParserBase::noteUsedNameInternal(name, visibility, tokenPosition); + } + + // Required on Scope exit. + bool propagateFreeNamesAndMarkClosedOverBindings(ParseContext::Scope& scope); + + bool checkForUndefinedPrivateFields(EvalSharedContext* evalSc = nullptr); + + bool finishFunctionScopes(bool isStandaloneFunction); + LexicalScopeNodeType finishLexicalScope(ParseContext::Scope& scope, Node body, + ScopeKind kind = ScopeKind::Lexical); + ClassBodyScopeNodeType finishClassBodyScope(ParseContext::Scope& scope, + ListNodeType body); + bool finishFunction(bool isStandaloneFunction = false); + + inline NameNodeType newName(TaggedParserAtomIndex name); + inline NameNodeType newName(TaggedParserAtomIndex name, TokenPos pos); + + inline NameNodeType newPrivateName(TaggedParserAtomIndex name); + + NameNodeType newInternalDotName(TaggedParserAtomIndex name); + NameNodeType newThisName(); + NameNodeType newNewTargetName(); + NameNodeType newDotGeneratorName(); + + NameNodeType identifierReference(TaggedParserAtomIndex name); + NameNodeType privateNameReference(TaggedParserAtomIndex name); + + Node noSubstitutionTaggedTemplate(); + + inline bool processExport(Node node); + inline bool processExportFrom(BinaryNodeType node); + inline bool processImport(BinaryNodeType node); + + // If ParseHandler is SyntaxParseHandler: + // Do nothing. + // If ParseHandler is FullParseHandler: + // Disable syntax parsing of all future inner functions during this + // full-parse. + inline void disableSyntaxParser(); + + // If ParseHandler is SyntaxParseHandler: + // Flag the current syntax parse as aborted due to unsupported language + // constructs and return false. Aborting the current syntax parse does + // not disable attempts to syntax-parse future inner functions. + // If ParseHandler is FullParseHandler: + // Disable syntax parsing of all future inner functions and return true. + inline bool abortIfSyntaxParser(); + + // If ParseHandler is SyntaxParseHandler: + // Return whether the last syntax parse was aborted due to unsupported + // language constructs. + // If ParseHandler is FullParseHandler: + // Return false. + inline bool hadAbortedSyntaxParse(); + + // If ParseHandler is SyntaxParseHandler: + // Clear whether the last syntax parse was aborted. + // If ParseHandler is FullParseHandler: + // Do nothing. + inline void clearAbortedSyntaxParse(); + + public: + NameNodeType newPropertyName(TaggedParserAtomIndex key, const TokenPos& pos) { + return handler_.newPropertyName(key, pos); + } + + PropertyAccessType newPropertyAccess(Node expr, NameNodeType key) { + return handler_.newPropertyAccess(expr, key); + } + + FunctionBox* newFunctionBox(FunctionNodeType funNode, + TaggedParserAtomIndex explicitName, + FunctionFlags flags, uint32_t toStringStart, + Directives directives, + GeneratorKind generatorKind, + FunctionAsyncKind asyncKind); + + FunctionBox* newFunctionBox(FunctionNodeType funNode, + const ScriptStencil& cachedScriptData, + const ScriptStencilExtra& cachedScriptExtra); + + public: + // ErrorReportMixin. + + using Base::error; + using Base::errorAt; + using Base::errorNoOffset; + using Base::errorWithNotes; + using Base::errorWithNotesAt; + using Base::errorWithNotesNoOffset; + using Base::strictModeError; + using Base::strictModeErrorAt; + using Base::strictModeErrorNoOffset; + using Base::strictModeErrorWithNotes; + using Base::strictModeErrorWithNotesAt; + using Base::strictModeErrorWithNotesNoOffset; + using Base::warning; + using Base::warningAt; + using Base::warningNoOffset; +}; + +#define ABORTED_SYNTAX_PARSE_SENTINEL reinterpret_cast(0x1) + +template <> +inline void PerHandlerParser::disableSyntaxParser() {} + +template <> +inline bool PerHandlerParser::abortIfSyntaxParser() { + internalSyntaxParser_ = ABORTED_SYNTAX_PARSE_SENTINEL; + return false; +} + +template <> +inline bool PerHandlerParser::hadAbortedSyntaxParse() { + return internalSyntaxParser_ == ABORTED_SYNTAX_PARSE_SENTINEL; +} + +template <> +inline void PerHandlerParser::clearAbortedSyntaxParse() { + internalSyntaxParser_ = nullptr; +} + +#undef ABORTED_SYNTAX_PARSE_SENTINEL + +// Disable syntax parsing of all future inner functions during this +// full-parse. +template <> +inline void PerHandlerParser::disableSyntaxParser() { + internalSyntaxParser_ = nullptr; +} + +template <> +inline bool PerHandlerParser::abortIfSyntaxParser() { + disableSyntaxParser(); + return true; +} + +template <> +inline bool PerHandlerParser::hadAbortedSyntaxParse() { + return false; +} + +template <> +inline void PerHandlerParser::clearAbortedSyntaxParse() {} + +template +class ParserAnyCharsAccess { + public: + using TokenStreamSpecific = typename Parser::TokenStream; + using GeneralTokenStreamChars = + typename TokenStreamSpecific::GeneralCharsBase; + + static inline TokenStreamAnyChars& anyChars(GeneralTokenStreamChars* ts); + static inline const TokenStreamAnyChars& anyChars( + const GeneralTokenStreamChars* ts); +}; + +// Specify a value for an ES6 grammar parametrization. We have no enum for +// [Return] because its behavior is almost exactly equivalent to checking +// whether we're in a function box -- easier and simpler than passing an extra +// parameter everywhere. +enum YieldHandling { YieldIsName, YieldIsKeyword }; +enum InHandling { InAllowed, InProhibited }; +enum DefaultHandling { NameRequired, AllowDefaultName }; +enum TripledotHandling { TripledotAllowed, TripledotProhibited }; + +// For Ergonomic brand checks. +enum PrivateNameHandling { PrivateNameProhibited, PrivateNameAllowed }; + +template +class Parser; + +template +class MOZ_STACK_CLASS GeneralParser : public PerHandlerParser { + public: + using TokenStream = + TokenStreamSpecific>; + + private: + using Base = PerHandlerParser; + using FinalParser = Parser; + using Node = typename ParseHandler::Node; + +#define DECLARE_TYPE(typeName, longTypeName, asMethodName) \ + using longTypeName = typename ParseHandler::longTypeName; + FOR_EACH_PARSENODE_SUBCLASS(DECLARE_TYPE) +#undef DECLARE_TYPE + + using typename Base::InvokedPrediction; + using SyntaxParser = Parser; + + protected: + using Modifier = TokenStreamShared::Modifier; + using Position = typename TokenStream::Position; + + using Base::PredictInvoked; + using Base::PredictUninvoked; + + using Base::alloc_; + using Base::awaitIsDisallowed; + using Base::awaitIsKeyword; + using Base::inParametersOfAsyncFunction; + using Base::parseGoal; +#if DEBUG + using Base::checkOptionsCalled_; +#endif + using Base::checkForUndefinedPrivateFields; + using Base::finishClassBodyScope; + using Base::finishFunctionScopes; + using Base::finishLexicalScope; + using Base::foldConstants_; + using Base::getFilename; + using Base::hasValidSimpleStrictParameterNames; + using Base::isUnexpectedEOF_; + using Base::nameIsArgumentsOrEval; + using Base::newDotGeneratorName; + using Base::newFunctionBox; + using Base::newName; + using Base::null; + using Base::options; + using Base::pos; + using Base::propagateFreeNamesAndMarkClosedOverBindings; + using Base::setLocalStrictMode; + using Base::stringLiteral; + using Base::yieldExpressionsSupported; + + using Base::abortIfSyntaxParser; + using Base::clearAbortedSyntaxParse; + using Base::disableSyntaxParser; + using Base::hadAbortedSyntaxParse; + + public: + // Implement ErrorReportMixin. + + [[nodiscard]] bool computeErrorMetadata( + ErrorMetadata* err, + const ErrorReportMixin::ErrorOffset& offset) const override; + + using Base::error; + using Base::errorAt; + using Base::errorNoOffset; + using Base::errorWithNotes; + using Base::errorWithNotesAt; + using Base::errorWithNotesNoOffset; + using Base::strictModeError; + using Base::strictModeErrorAt; + using Base::strictModeErrorNoOffset; + using Base::strictModeErrorWithNotes; + using Base::strictModeErrorWithNotesAt; + using Base::strictModeErrorWithNotesNoOffset; + using Base::warning; + using Base::warningAt; + using Base::warningNoOffset; + + public: + using Base::anyChars; + using Base::fc_; + using Base::handler_; + using Base::noteUsedName; + using Base::pc_; + using Base::usedNames_; + + private: + using Base::checkAndMarkSuperScope; + using Base::finishFunction; + using Base::identifierReference; + using Base::leaveInnerFunction; + using Base::newInternalDotName; + using Base::newNewTargetName; + using Base::newThisName; + using Base::nextTokenContinuesLetDeclaration; + using Base::noSubstitutionTaggedTemplate; + using Base::noteDestructuredPositionalFormalParameter; + using Base::prefixAccessorName; + using Base::privateNameReference; + using Base::processExport; + using Base::processExportFrom; + using Base::processImport; + using Base::setFunctionEndFromCurrentToken; + + private: + inline FinalParser* asFinalParser(); + inline const FinalParser* asFinalParser() const; + + /* + * A class for temporarily stashing errors while parsing continues. + * + * The ability to stash an error is useful for handling situations where we + * aren't able to verify that an error has occurred until later in the parse. + * For instance | ({x=1}) | is always parsed as an object literal with + * a SyntaxError, however, in the case where it is followed by '=>' we rewind + * and reparse it as a valid arrow function. Here a PossibleError would be + * set to 'pending' when the initial SyntaxError was encountered then + * 'resolved' just before rewinding the parser. + * + * There are currently two kinds of PossibleErrors: Expression and + * Destructuring errors. Expression errors are used to mark a possible + * syntax error when a grammar production is used in an expression context. + * For example in |{x = 1}|, we mark the CoverInitializedName |x = 1| as a + * possible expression error, because CoverInitializedName productions + * are disallowed when an actual ObjectLiteral is expected. + * Destructuring errors are used to record possible syntax errors in + * destructuring contexts. For example in |[...rest, ] = []|, we initially + * mark the trailing comma after the spread expression as a possible + * destructuring error, because the ArrayAssignmentPattern grammar + * production doesn't allow a trailing comma after the rest element. + * + * When using PossibleError one should set a pending error at the location + * where an error occurs. From that point, the error may be resolved + * (invalidated) or left until the PossibleError is checked. + * + * Ex: + * PossibleError possibleError(*this); + * possibleError.setPendingExpressionErrorAt(pos, JSMSG_BAD_PROP_ID); + * // A JSMSG_BAD_PROP_ID ParseError is reported, returns false. + * if (!possibleError.checkForExpressionError()) { + * return false; // we reach this point with a pending exception + * } + * + * PossibleError possibleError(*this); + * possibleError.setPendingExpressionErrorAt(pos, JSMSG_BAD_PROP_ID); + * // Returns true, no error is reported. + * if (!possibleError.checkForDestructuringError()) { + * return false; // not reached, no pending exception + * } + * + * PossibleError possibleError(*this); + * // Returns true, no error is reported. + * if (!possibleError.checkForExpressionError()) { + * return false; // not reached, no pending exception + * } + */ + class MOZ_STACK_CLASS PossibleError { + private: + enum class ErrorKind { Expression, Destructuring, DestructuringWarning }; + + enum class ErrorState { None, Pending }; + + struct Error { + ErrorState state_ = ErrorState::None; + + // Error reporting fields. + uint32_t offset_; + unsigned errorNumber_; + }; + + GeneralParser& parser_; + Error exprError_; + Error destructuringError_; + Error destructuringWarning_; + + // Returns the error report. + Error& error(ErrorKind kind); + + // Return true if an error is pending without reporting. + bool hasError(ErrorKind kind); + + // Resolve any pending error. + void setResolved(ErrorKind kind); + + // Set a pending error. Only a single error may be set per instance and + // error kind. + void setPending(ErrorKind kind, const TokenPos& pos, unsigned errorNumber); + + // If there is a pending error, report it and return false, otherwise + // return true. + [[nodiscard]] bool checkForError(ErrorKind kind); + + // Transfer an existing error to another instance. + void transferErrorTo(ErrorKind kind, PossibleError* other); + + public: + explicit PossibleError(GeneralParser& parser); + + // Return true if a pending destructuring error is present. + bool hasPendingDestructuringError(); + + // Set a pending destructuring error. Only a single error may be set + // per instance, i.e. subsequent calls to this method are ignored and + // won't overwrite the existing pending error. + void setPendingDestructuringErrorAt(const TokenPos& pos, + unsigned errorNumber); + + // Set a pending destructuring warning. Only a single warning may be + // set per instance, i.e. subsequent calls to this method are ignored + // and won't overwrite the existing pending warning. + void setPendingDestructuringWarningAt(const TokenPos& pos, + unsigned errorNumber); + + // Set a pending expression error. Only a single error may be set per + // instance, i.e. subsequent calls to this method are ignored and won't + // overwrite the existing pending error. + void setPendingExpressionErrorAt(const TokenPos& pos, unsigned errorNumber); + + // If there is a pending destructuring error or warning, report it and + // return false, otherwise return true. Clears any pending expression + // error. + [[nodiscard]] bool checkForDestructuringErrorOrWarning(); + + // If there is a pending expression error, report it and return false, + // otherwise return true. Clears any pending destructuring error or + // warning. + [[nodiscard]] bool checkForExpressionError(); + + // Pass pending errors between possible error instances. This is useful + // for extending the lifetime of a pending error beyond the scope of + // the PossibleError where it was initially set (keeping in mind that + // PossibleError is a MOZ_STACK_CLASS). + void transferErrorsTo(PossibleError* other); + }; + + protected: + SyntaxParser* getSyntaxParser() const { + return reinterpret_cast(Base::internalSyntaxParser_); + } + + public: + TokenStream tokenStream; + + public: + GeneralParser(FrontendContext* fc, const JS::ReadOnlyCompileOptions& options, + const Unit* units, size_t length, bool foldConstants, + CompilationState& compilationState, SyntaxParser* syntaxParser); + + inline void setAwaitHandling(AwaitHandling awaitHandling); + inline void setInParametersOfAsyncFunction(bool inParameters); + + /* + * Parse a top-level JS script. + */ + ListNodeType parse(); + + private: + /* + * Gets the next token and checks if it matches to the given `condition`. + * If it matches, returns true. + * If it doesn't match, calls `errorReport` to report the error, and + * returns false. + * If other error happens, it returns false but `errorReport` may not be + * called and other error will be thrown in that case. + * + * In any case, the already gotten token is not ungotten. + * + * The signature of `condition` is [...](TokenKind actual) -> bool, and + * the signature of `errorReport` is [...](TokenKind actual). + */ + template + [[nodiscard]] bool mustMatchTokenInternal(ConditionT condition, + ErrorReportT errorReport); + + public: + /* + * The following mustMatchToken variants follow the behavior and parameter + * types of mustMatchTokenInternal above. + * + * If modifier is omitted, `SlashIsDiv` is used. + * If TokenKind is passed instead of `condition`, it checks if the next + * token is the passed token. + * If error number is passed instead of `errorReport`, it reports an + * error with the passed errorNumber. + */ + [[nodiscard]] bool mustMatchToken(TokenKind expected, JSErrNum errorNumber) { + return mustMatchTokenInternal( + [expected](TokenKind actual) { return actual == expected; }, + [this, errorNumber](TokenKind) { this->error(errorNumber); }); + } + + template + [[nodiscard]] bool mustMatchToken(ConditionT condition, + JSErrNum errorNumber) { + return mustMatchTokenInternal(condition, [this, errorNumber](TokenKind) { + this->error(errorNumber); + }); + } + + template + [[nodiscard]] bool mustMatchToken(TokenKind expected, + ErrorReportT errorReport) { + return mustMatchTokenInternal( + [expected](TokenKind actual) { return actual == expected; }, + errorReport); + } + + private: + NameNodeType noSubstitutionUntaggedTemplate(); + ListNodeType templateLiteral(YieldHandling yieldHandling); + bool taggedTemplate(YieldHandling yieldHandling, ListNodeType tagArgsList, + TokenKind tt); + bool appendToCallSiteObj(CallSiteNodeType callSiteObj); + bool addExprAndGetNextTemplStrToken(YieldHandling yieldHandling, + ListNodeType nodeList, TokenKind* ttp); + + inline bool trySyntaxParseInnerFunction( + FunctionNodeType* funNode, TaggedParserAtomIndex explicitName, + FunctionFlags flags, uint32_t toStringStart, InHandling inHandling, + YieldHandling yieldHandling, FunctionSyntaxKind kind, + GeneratorKind generatorKind, FunctionAsyncKind asyncKind, bool tryAnnexB, + Directives inheritedDirectives, Directives* newDirectives); + + inline bool skipLazyInnerFunction(FunctionNodeType funNode, + uint32_t toStringStart, bool tryAnnexB); + + void setFunctionStartAtPosition(FunctionBox* funbox, TokenPos pos) const; + void setFunctionStartAtCurrentToken(FunctionBox* funbox) const; + + public: + /* Public entry points for parsing. */ + Node statementListItem(YieldHandling yieldHandling, + bool canHaveDirectives = false); + + // Parse an inner function given an enclosing ParseContext and a + // FunctionBox for the inner function. + [[nodiscard]] FunctionNodeType innerFunctionForFunctionBox( + FunctionNodeType funNode, ParseContext* outerpc, FunctionBox* funbox, + InHandling inHandling, YieldHandling yieldHandling, + FunctionSyntaxKind kind, Directives* newDirectives); + + // Parse a function's formal parameters and its body assuming its function + // ParseContext is already on the stack. + bool functionFormalParametersAndBody( + InHandling inHandling, YieldHandling yieldHandling, + FunctionNodeType* funNode, FunctionSyntaxKind kind, + const mozilla::Maybe& parameterListEnd = mozilla::Nothing(), + bool isStandaloneFunction = false); + + private: + /* + * JS parsers, from lowest to highest precedence. + * + * Each parser must be called during the dynamic scope of a ParseContext + * object, pointed to by this->pc_. + * + * Each returns a parse node tree or null on error. + */ + FunctionNodeType functionStmt( + uint32_t toStringStart, YieldHandling yieldHandling, + DefaultHandling defaultHandling, + FunctionAsyncKind asyncKind = FunctionAsyncKind::SyncFunction); + FunctionNodeType functionExpr(uint32_t toStringStart, + InvokedPrediction invoked, + FunctionAsyncKind asyncKind); + + Node statement(YieldHandling yieldHandling); + bool maybeParseDirective(ListNodeType list, Node pn, bool* cont); + + LexicalScopeNodeType blockStatement( + YieldHandling yieldHandling, + unsigned errorNumber = JSMSG_CURLY_IN_COMPOUND); + BinaryNodeType doWhileStatement(YieldHandling yieldHandling); + BinaryNodeType whileStatement(YieldHandling yieldHandling); + + Node forStatement(YieldHandling yieldHandling); + bool forHeadStart(YieldHandling yieldHandling, IteratorKind iterKind, + ParseNodeKind* forHeadKind, Node* forInitialPart, + mozilla::Maybe& forLetImpliedScope, + Node* forInOrOfExpression); + Node expressionAfterForInOrOf(ParseNodeKind forHeadKind, + YieldHandling yieldHandling); + + SwitchStatementType switchStatement(YieldHandling yieldHandling); + ContinueStatementType continueStatement(YieldHandling yieldHandling); + BreakStatementType breakStatement(YieldHandling yieldHandling); + UnaryNodeType returnStatement(YieldHandling yieldHandling); + BinaryNodeType withStatement(YieldHandling yieldHandling); + UnaryNodeType throwStatement(YieldHandling yieldHandling); + TernaryNodeType tryStatement(YieldHandling yieldHandling); + LexicalScopeNodeType catchBlockStatement( + YieldHandling yieldHandling, ParseContext::Scope& catchParamScope); + DebuggerStatementType debuggerStatement(); + + DeclarationListNodeType variableStatement(YieldHandling yieldHandling); + + LabeledStatementType labeledStatement(YieldHandling yieldHandling); + Node labeledItem(YieldHandling yieldHandling); + + TernaryNodeType ifStatement(YieldHandling yieldHandling); + Node consequentOrAlternative(YieldHandling yieldHandling); + + DeclarationListNodeType lexicalDeclaration(YieldHandling yieldHandling, + DeclarationKind kind); + + NameNodeType moduleExportName(); + + bool assertClause(ListNodeType assertionsSet); + + BinaryNodeType importDeclaration(); + Node importDeclarationOrImportExpr(YieldHandling yieldHandling); + bool namedImports(ListNodeType importSpecSet); + bool namespaceImport(ListNodeType importSpecSet); + + TaggedParserAtomIndex importedBinding() { + return bindingIdentifier(YieldIsName); + } + + BinaryNodeType exportFrom(uint32_t begin, Node specList); + BinaryNodeType exportBatch(uint32_t begin); + inline bool checkLocalExportNames(ListNodeType node); + Node exportClause(uint32_t begin); + UnaryNodeType exportFunctionDeclaration( + uint32_t begin, uint32_t toStringStart, + FunctionAsyncKind asyncKind = FunctionAsyncKind::SyncFunction); + UnaryNodeType exportVariableStatement(uint32_t begin); + UnaryNodeType exportClassDeclaration(uint32_t begin); + UnaryNodeType exportLexicalDeclaration(uint32_t begin, DeclarationKind kind); + BinaryNodeType exportDefaultFunctionDeclaration( + uint32_t begin, uint32_t toStringStart, + FunctionAsyncKind asyncKind = FunctionAsyncKind::SyncFunction); + BinaryNodeType exportDefaultClassDeclaration(uint32_t begin); + BinaryNodeType exportDefaultAssignExpr(uint32_t begin); + BinaryNodeType exportDefault(uint32_t begin); + Node exportDeclaration(); + + UnaryNodeType expressionStatement( + YieldHandling yieldHandling, + InvokedPrediction invoked = PredictUninvoked); + + // Declaration parsing. The main entrypoint is Parser::declarationList, + // with sub-functionality split out into the remaining methods. + + // |blockScope| may be non-null only when |kind| corresponds to a lexical + // declaration (that is, PNK_LET or PNK_CONST). + // + // The for* parameters, for normal declarations, should be null/ignored. + // They should be non-null only when Parser::forHeadStart parses a + // declaration at the start of a for-loop head. + // + // In this case, on success |*forHeadKind| is PNK_FORHEAD, PNK_FORIN, or + // PNK_FOROF, corresponding to the three for-loop kinds. The precise value + // indicates what was parsed. + // + // If parsing recognized a for(;;) loop, the next token is the ';' within + // the loop-head that separates the init/test parts. + // + // Otherwise, for for-in/of loops, the next token is the ')' ending the + // loop-head. Additionally, the expression that the loop iterates over was + // parsed into |*forInOrOfExpression|. + DeclarationListNodeType declarationList(YieldHandling yieldHandling, + ParseNodeKind kind, + ParseNodeKind* forHeadKind = nullptr, + Node* forInOrOfExpression = nullptr); + + // The items in a declaration list are either patterns or names, with or + // without initializers. These two methods parse a single pattern/name and + // any associated initializer -- and if parsing an |initialDeclaration| + // will, if parsing in a for-loop head (as specified by |forHeadKind| being + // non-null), consume additional tokens up to the closing ')' in a + // for-in/of loop head, returning the iterated expression in + // |*forInOrOfExpression|. (An "initial declaration" is the first + // declaration in a declaration list: |a| but not |b| in |var a, b|, |{c}| + // but not |d| in |let {c} = 3, d|.) + Node declarationPattern(DeclarationKind declKind, TokenKind tt, + bool initialDeclaration, YieldHandling yieldHandling, + ParseNodeKind* forHeadKind, + Node* forInOrOfExpression); + Node declarationName(DeclarationKind declKind, TokenKind tt, + bool initialDeclaration, YieldHandling yieldHandling, + ParseNodeKind* forHeadKind, Node* forInOrOfExpression); + + // Having parsed a name (not found in a destructuring pattern) declared by + // a declaration, with the current token being the '=' separating the name + // from its initializer, parse and bind that initializer -- and possibly + // consume trailing in/of and subsequent expression, if so directed by + // |forHeadKind|. + AssignmentNodeType initializerInNameDeclaration(NameNodeType binding, + DeclarationKind declKind, + bool initialDeclaration, + YieldHandling yieldHandling, + ParseNodeKind* forHeadKind, + Node* forInOrOfExpression); + + Node expr(InHandling inHandling, YieldHandling yieldHandling, + TripledotHandling tripledotHandling, + PossibleError* possibleError = nullptr, + InvokedPrediction invoked = PredictUninvoked); + Node assignExpr(InHandling inHandling, YieldHandling yieldHandling, + TripledotHandling tripledotHandling, + PossibleError* possibleError = nullptr, + InvokedPrediction invoked = PredictUninvoked); + Node assignExprWithoutYieldOrAwait(YieldHandling yieldHandling); + UnaryNodeType yieldExpression(InHandling inHandling); + Node condExpr(InHandling inHandling, YieldHandling yieldHandling, + TripledotHandling tripledotHandling, + PossibleError* possibleError, InvokedPrediction invoked); + Node orExpr(InHandling inHandling, YieldHandling yieldHandling, + TripledotHandling tripledotHandling, PossibleError* possibleError, + InvokedPrediction invoked); + Node unaryExpr(YieldHandling yieldHandling, + TripledotHandling tripledotHandling, + PossibleError* possibleError = nullptr, + InvokedPrediction invoked = PredictUninvoked, + PrivateNameHandling privateNameHandling = + PrivateNameHandling::PrivateNameProhibited); + Node optionalExpr(YieldHandling yieldHandling, + TripledotHandling tripledotHandling, TokenKind tt, + PossibleError* possibleError = nullptr, + InvokedPrediction invoked = PredictUninvoked); + Node memberExpr(YieldHandling yieldHandling, + TripledotHandling tripledotHandling, TokenKind tt, + bool allowCallSyntax, PossibleError* possibleError, + InvokedPrediction invoked); + Node primaryExpr(YieldHandling yieldHandling, + TripledotHandling tripledotHandling, TokenKind tt, + PossibleError* possibleError, InvokedPrediction invoked); + Node exprInParens(InHandling inHandling, YieldHandling yieldHandling, + TripledotHandling tripledotHandling, + PossibleError* possibleError = nullptr); + + bool tryNewTarget(NewTargetNodeType* newTarget); + + BinaryNodeType importExpr(YieldHandling yieldHandling, bool allowCallSyntax); + + FunctionNodeType methodDefinition(uint32_t toStringStart, + PropertyType propType, + TaggedParserAtomIndex funName); + + /* + * Additional JS parsers. + */ + bool functionArguments(YieldHandling yieldHandling, FunctionSyntaxKind kind, + FunctionNodeType funNode); + + FunctionNodeType functionDefinition( + FunctionNodeType funNode, uint32_t toStringStart, InHandling inHandling, + YieldHandling yieldHandling, TaggedParserAtomIndex name, + FunctionSyntaxKind kind, GeneratorKind generatorKind, + FunctionAsyncKind asyncKind, bool tryAnnexB = false); + + // Parse a function body. Pass StatementListBody if the body is a list of + // statements; pass ExpressionBody if the body is a single expression. + // + // Don't include opening LeftCurly token when invoking. + enum FunctionBodyType { StatementListBody, ExpressionBody }; + LexicalScopeNodeType functionBody(InHandling inHandling, + YieldHandling yieldHandling, + FunctionSyntaxKind kind, + FunctionBodyType type); + + UnaryNodeType unaryOpExpr(YieldHandling yieldHandling, ParseNodeKind kind, + uint32_t begin); + + Node condition(InHandling inHandling, YieldHandling yieldHandling); + + ListNodeType argumentList(YieldHandling yieldHandling, bool* isSpread, + PossibleError* possibleError = nullptr); + Node destructuringDeclaration(DeclarationKind kind, + YieldHandling yieldHandling, TokenKind tt); + Node destructuringDeclarationWithoutYieldOrAwait(DeclarationKind kind, + YieldHandling yieldHandling, + TokenKind tt); + + inline bool checkExportedName(TaggedParserAtomIndex exportName); + inline bool checkExportedNamesForArrayBinding(ListNodeType array); + inline bool checkExportedNamesForObjectBinding(ListNodeType obj); + inline bool checkExportedNamesForDeclaration(Node node); + inline bool checkExportedNamesForDeclarationList( + DeclarationListNodeType node); + inline bool checkExportedNameForFunction(FunctionNodeType funNode); + inline bool checkExportedNameForClass(ClassNodeType classNode); + inline bool checkExportedNameForClause(NameNodeType nameNode); + + enum ClassContext { ClassStatement, ClassExpression }; + ClassNodeType classDefinition(YieldHandling yieldHandling, + ClassContext classContext, + DefaultHandling defaultHandling); + + struct ClassInitializedMembers { + // The number of instance class fields. + size_t instanceFields = 0; + + // The number of instance class fields with computed property names. + size_t instanceFieldKeys = 0; + + // The number of static class fields. + size_t staticFields = 0; + + // The number of static blocks + size_t staticBlocks = 0; + + // The number of static class fields with computed property names. + size_t staticFieldKeys = 0; + + // The number of instance class private methods. + size_t privateMethods = 0; + + // The number of instance class private accessors. + size_t privateAccessors = 0; + + bool hasPrivateBrand() const { + return privateMethods > 0 || privateAccessors > 0; + } + }; +#ifdef ENABLE_DECORATORS + ListNodeType decoratorList(YieldHandling yieldHandling); +#endif + [[nodiscard]] bool classMember( + YieldHandling yieldHandling, + const ParseContext::ClassStatement& classStmt, + TaggedParserAtomIndex className, uint32_t classStartOffset, + HasHeritage hasHeritage, ClassInitializedMembers& classInitializedMembers, + ListNodeType& classMembers, bool* done); + [[nodiscard]] bool finishClassConstructor( + const ParseContext::ClassStatement& classStmt, + TaggedParserAtomIndex className, HasHeritage hasHeritage, + uint32_t classStartOffset, uint32_t classEndOffset, + const ClassInitializedMembers& classInitializedMembers, + ListNodeType& classMembers); + + FunctionNodeType privateMethodInitializer( + TokenPos propNamePos, TaggedParserAtomIndex propAtom, + TaggedParserAtomIndex storedMethodAtom); + FunctionNodeType fieldInitializerOpt( + TokenPos propNamePos, Node name, TaggedParserAtomIndex atom, + ClassInitializedMembers& classInitializedMembers, bool isStatic, + HasHeritage hasHeritage); + + FunctionNodeType synthesizePrivateMethodInitializer( + TaggedParserAtomIndex propAtom, AccessorType accessorType, + TokenPos propNamePos); + +#ifdef ENABLE_DECORATORS + Node synthesizeAccessor(Node propName, TokenPos propNamePos, + TaggedParserAtomIndex propAtom, + TaggedParserAtomIndex privateStateNameAtom, + bool isStatic, FunctionSyntaxKind syntaxKind, + ListNodeType decorators, + ClassInitializedMembers& classInitializedMembers); + + FunctionNodeType synthesizeAccessorBody(TokenPos propNamePos, + TaggedParserAtomIndex atom, + FunctionSyntaxKind syntaxKind); +#endif + + FunctionNodeType staticClassBlock( + ClassInitializedMembers& classInitializedMembers); + + FunctionNodeType synthesizeConstructor(TaggedParserAtomIndex className, + TokenPos synthesizedBodyPos, + HasHeritage hasHeritage); + + protected: + FunctionNodeType synthesizeConstructorBody(TokenPos synthesizedBodyPos, + HasHeritage hasHeritage, + FunctionNodeType funNode, + FunctionBox* funbox); + + private: + bool checkBindingIdentifier(TaggedParserAtomIndex ident, uint32_t offset, + YieldHandling yieldHandling, + TokenKind hint = TokenKind::Limit); + + TaggedParserAtomIndex labelOrIdentifierReference(YieldHandling yieldHandling); + + TaggedParserAtomIndex labelIdentifier(YieldHandling yieldHandling) { + return labelOrIdentifierReference(yieldHandling); + } + + TaggedParserAtomIndex identifierReference(YieldHandling yieldHandling) { + return labelOrIdentifierReference(yieldHandling); + } + + bool matchLabel(YieldHandling yieldHandling, TaggedParserAtomIndex* labelOut); + + // Indicate if the next token (tokenized with SlashIsRegExp) is |in| or |of|. + // If so, consume it. + bool matchInOrOf(bool* isForInp, bool* isForOfp); + + private: + bool checkIncDecOperand(Node operand, uint32_t operandOffset); + bool checkStrictAssignment(Node lhs); + + void reportMissingClosing(unsigned errorNumber, unsigned noteNumber, + uint32_t openedPos); + + void reportRedeclaration(TaggedParserAtomIndex name, DeclarationKind prevKind, + TokenPos pos, uint32_t prevPos); + bool notePositionalFormalParameter(FunctionNodeType funNode, + TaggedParserAtomIndex name, + uint32_t beginPos, + bool disallowDuplicateParams, + bool* duplicatedParam); + + enum PropertyNameContext { + PropertyNameInLiteral, + PropertyNameInPattern, + PropertyNameInClass, +#ifdef ENABLE_RECORD_TUPLE + PropertyNameInRecord +#endif + }; + Node propertyName(YieldHandling yieldHandling, + PropertyNameContext propertyNameContext, + const mozilla::Maybe& maybeDecl, + ListNodeType propList, TaggedParserAtomIndex* propAtomOut); + Node propertyOrMethodName(YieldHandling yieldHandling, + PropertyNameContext propertyNameContext, + const mozilla::Maybe& maybeDecl, + ListNodeType propList, PropertyType* propType, + TaggedParserAtomIndex* propAtomOut); + UnaryNodeType computedPropertyName( + YieldHandling yieldHandling, + const mozilla::Maybe& maybeDecl, + PropertyNameContext propertyNameContext, ListNodeType literal); + ListNodeType arrayInitializer(YieldHandling yieldHandling, + PossibleError* possibleError); + inline RegExpLiteralType newRegExp(); + + ListNodeType objectLiteral(YieldHandling yieldHandling, + PossibleError* possibleError); + +#ifdef ENABLE_RECORD_TUPLE + ListNodeType recordLiteral(YieldHandling yieldHandling); + ListNodeType tupleLiteral(YieldHandling yieldHandling); +#endif + + BinaryNodeType bindingInitializer(Node lhs, DeclarationKind kind, + YieldHandling yieldHandling); + NameNodeType bindingIdentifier(DeclarationKind kind, + YieldHandling yieldHandling); + Node bindingIdentifierOrPattern(DeclarationKind kind, + YieldHandling yieldHandling, TokenKind tt); + ListNodeType objectBindingPattern(DeclarationKind kind, + YieldHandling yieldHandling); + ListNodeType arrayBindingPattern(DeclarationKind kind, + YieldHandling yieldHandling); + + enum class TargetBehavior { + PermitAssignmentPattern, + ForbidAssignmentPattern + }; + bool checkDestructuringAssignmentTarget( + Node expr, TokenPos exprPos, PossibleError* exprPossibleError, + PossibleError* possibleError, + TargetBehavior behavior = TargetBehavior::PermitAssignmentPattern); + void checkDestructuringAssignmentName(NameNodeType name, TokenPos namePos, + PossibleError* possibleError); + bool checkDestructuringAssignmentElement(Node expr, TokenPos exprPos, + PossibleError* exprPossibleError, + PossibleError* possibleError); + + NumericLiteralType newNumber(const Token& tok) { + return handler_.newNumber(tok.number(), tok.decimalPoint(), tok.pos); + } + + inline BigIntLiteralType newBigInt(); + + enum class OptionalKind { + NonOptional = 0, + Optional, + }; + Node memberPropertyAccess( + Node lhs, OptionalKind optionalKind = OptionalKind::NonOptional); + Node memberPrivateAccess( + Node lhs, OptionalKind optionalKind = OptionalKind::NonOptional); + Node memberElemAccess(Node lhs, YieldHandling yieldHandling, + OptionalKind optionalKind = OptionalKind::NonOptional); + Node memberSuperCall(Node lhs, YieldHandling yieldHandling); + Node memberCall(TokenKind tt, Node lhs, YieldHandling yieldHandling, + PossibleError* possibleError, + OptionalKind optionalKind = OptionalKind::NonOptional); + + protected: + // Match the current token against the BindingIdentifier production with + // the given Yield parameter. If there is no match, report a syntax + // error. + TaggedParserAtomIndex bindingIdentifier(YieldHandling yieldHandling); + + bool checkLabelOrIdentifierReference(TaggedParserAtomIndex ident, + uint32_t offset, + YieldHandling yieldHandling, + TokenKind hint = TokenKind::Limit); + + ListNodeType statementList(YieldHandling yieldHandling); + + [[nodiscard]] FunctionNodeType innerFunction( + FunctionNodeType funNode, ParseContext* outerpc, + TaggedParserAtomIndex explicitName, FunctionFlags flags, + uint32_t toStringStart, InHandling inHandling, + YieldHandling yieldHandling, FunctionSyntaxKind kind, + GeneratorKind generatorKind, FunctionAsyncKind asyncKind, bool tryAnnexB, + Directives inheritedDirectives, Directives* newDirectives); + + // Implements Automatic Semicolon Insertion. + // + // Use this to match `;` in contexts where ASI is allowed. Call this after + // ruling out all other possibilities except `;`, by peeking ahead if + // necessary. + // + // Unlike most optional Modifiers, this method's `modifier` argument defaults + // to SlashIsRegExp, since that's by far the most common case: usually an + // optional semicolon is at the end of a statement or declaration, and the + // next token could be a RegExp literal beginning a new ExpressionStatement. + bool matchOrInsertSemicolon(Modifier modifier = TokenStream::SlashIsRegExp); + + bool noteDeclaredName(TaggedParserAtomIndex name, DeclarationKind kind, + TokenPos pos, ClosedOver isClosedOver = ClosedOver::No); + + bool noteDeclaredPrivateName(Node nameNode, TaggedParserAtomIndex name, + PropertyType propType, FieldPlacement placement, + TokenPos pos); + + private: + inline bool asmJS(ListNodeType list); +}; + +template +class MOZ_STACK_CLASS Parser final + : public GeneralParser { + using Base = GeneralParser; + using Node = SyntaxParseHandler::Node; + +#define DECLARE_TYPE(typeName, longTypeName, asMethodName) \ + using longTypeName = SyntaxParseHandler::longTypeName; + FOR_EACH_PARSENODE_SUBCLASS(DECLARE_TYPE) +#undef DECLARE_TYPE + + using SyntaxParser = Parser; + + // Numerous Base::* functions have bodies like + // + // return asFinalParser()->func(...); + // + // and must be able to call functions here. Add a friendship relationship + // so functions here can be hidden when appropriate. + friend class GeneralParser; + + public: + using Base::Base; + + // Inherited types, listed here to have non-dependent names. + using typename Base::Modifier; + using typename Base::Position; + using typename Base::TokenStream; + + // Inherited functions, listed here to have non-dependent names. + + public: + using Base::anyChars; + using Base::clearAbortedSyntaxParse; + using Base::hadAbortedSyntaxParse; + using Base::innerFunctionForFunctionBox; + using Base::tokenStream; + + public: + // ErrorReportMixin. + + using Base::error; + using Base::errorAt; + using Base::errorNoOffset; + using Base::errorWithNotes; + using Base::errorWithNotesAt; + using Base::errorWithNotesNoOffset; + using Base::strictModeError; + using Base::strictModeErrorAt; + using Base::strictModeErrorNoOffset; + using Base::strictModeErrorWithNotes; + using Base::strictModeErrorWithNotesAt; + using Base::strictModeErrorWithNotesNoOffset; + using Base::warning; + using Base::warningAt; + using Base::warningNoOffset; + + private: + using Base::alloc_; +#if DEBUG + using Base::checkOptionsCalled_; +#endif + using Base::checkForUndefinedPrivateFields; + using Base::finishFunctionScopes; + using Base::functionFormalParametersAndBody; + using Base::handler_; + using Base::innerFunction; + using Base::matchOrInsertSemicolon; + using Base::mustMatchToken; + using Base::newFunctionBox; + using Base::newLexicalScopeData; + using Base::newModuleScopeData; + using Base::newName; + using Base::noteDeclaredName; + using Base::null; + using Base::options; + using Base::pc_; + using Base::pos; + using Base::propagateFreeNamesAndMarkClosedOverBindings; + using Base::ss; + using Base::statementList; + using Base::stringLiteral; + using Base::usedNames_; + + private: + using Base::abortIfSyntaxParser; + using Base::disableSyntaxParser; + + public: + // Functions with multiple overloads of different visibility. We can't + // |using| the whole thing into existence because of the visibility + // distinction, so we instead must manually delegate the required overload. + + TaggedParserAtomIndex bindingIdentifier(YieldHandling yieldHandling) { + return Base::bindingIdentifier(yieldHandling); + } + + // Functions present in both Parser specializations. + + inline void setAwaitHandling(AwaitHandling awaitHandling); + inline void setInParametersOfAsyncFunction(bool inParameters); + + RegExpLiteralType newRegExp(); + BigIntLiteralType newBigInt(); + + // Parse a module. + ModuleNodeType moduleBody(ModuleSharedContext* modulesc); + + inline bool checkLocalExportNames(ListNodeType node); + inline bool checkExportedName(TaggedParserAtomIndex exportName); + inline bool checkExportedNamesForArrayBinding(ListNodeType array); + inline bool checkExportedNamesForObjectBinding(ListNodeType obj); + inline bool checkExportedNamesForDeclaration(Node node); + inline bool checkExportedNamesForDeclarationList( + DeclarationListNodeType node); + inline bool checkExportedNameForFunction(FunctionNodeType funNode); + inline bool checkExportedNameForClass(ClassNodeType classNode); + inline bool checkExportedNameForClause(NameNodeType nameNode); + + bool trySyntaxParseInnerFunction( + FunctionNodeType* funNode, TaggedParserAtomIndex explicitName, + FunctionFlags flags, uint32_t toStringStart, InHandling inHandling, + YieldHandling yieldHandling, FunctionSyntaxKind kind, + GeneratorKind generatorKind, FunctionAsyncKind asyncKind, bool tryAnnexB, + Directives inheritedDirectives, Directives* newDirectives); + + bool skipLazyInnerFunction(FunctionNodeType funNode, uint32_t toStringStart, + bool tryAnnexB); + + bool asmJS(ListNodeType list); + + // Functions present only in Parser. +}; + +template +class MOZ_STACK_CLASS Parser final + : public GeneralParser { + using Base = GeneralParser; + using Node = FullParseHandler::Node; + +#define DECLARE_TYPE(typeName, longTypeName, asMethodName) \ + using longTypeName = FullParseHandler::longTypeName; + FOR_EACH_PARSENODE_SUBCLASS(DECLARE_TYPE) +#undef DECLARE_TYPE + + using SyntaxParser = Parser; + + // Numerous Base::* functions have bodies like + // + // return asFinalParser()->func(...); + // + // and must be able to call functions here. Add a friendship relationship + // so functions here can be hidden when appropriate. + friend class GeneralParser; + + public: + using Base::Base; + + // Inherited types, listed here to have non-dependent names. + using typename Base::Modifier; + using typename Base::Position; + using typename Base::TokenStream; + + // Inherited functions, listed here to have non-dependent names. + + public: + using Base::anyChars; + using Base::clearAbortedSyntaxParse; + using Base::functionFormalParametersAndBody; + using Base::hadAbortedSyntaxParse; + using Base::handler_; + using Base::newFunctionBox; + using Base::options; + using Base::pc_; + using Base::pos; + using Base::ss; + using Base::tokenStream; + + public: + // ErrorReportMixin. + + using Base::error; + using Base::errorAt; + using Base::errorNoOffset; + using Base::errorWithNotes; + using Base::errorWithNotesAt; + using Base::errorWithNotesNoOffset; + using Base::strictModeError; + using Base::strictModeErrorAt; + using Base::strictModeErrorNoOffset; + using Base::strictModeErrorWithNotes; + using Base::strictModeErrorWithNotesAt; + using Base::strictModeErrorWithNotesNoOffset; + using Base::warning; + using Base::warningAt; + using Base::warningNoOffset; + + private: + using Base::alloc_; + using Base::checkLabelOrIdentifierReference; +#if DEBUG + using Base::checkOptionsCalled_; +#endif + using Base::checkForUndefinedPrivateFields; + using Base::fc_; + using Base::finishClassBodyScope; + using Base::finishFunctionScopes; + using Base::finishLexicalScope; + using Base::innerFunction; + using Base::innerFunctionForFunctionBox; + using Base::matchOrInsertSemicolon; + using Base::mustMatchToken; + using Base::newEvalScopeData; + using Base::newFunctionScopeData; + using Base::newGlobalScopeData; + using Base::newLexicalScopeData; + using Base::newModuleScopeData; + using Base::newName; + using Base::newVarScopeData; + using Base::noteDeclaredName; + using Base::noteUsedName; + using Base::null; + using Base::propagateFreeNamesAndMarkClosedOverBindings; + using Base::statementList; + using Base::stringLiteral; + using Base::usedNames_; + + using Base::abortIfSyntaxParser; + using Base::disableSyntaxParser; + using Base::getSyntaxParser; + + public: + // Functions with multiple overloads of different visibility. We can't + // |using| the whole thing into existence because of the visibility + // distinction, so we instead must manually delegate the required overload. + + TaggedParserAtomIndex bindingIdentifier(YieldHandling yieldHandling) { + return Base::bindingIdentifier(yieldHandling); + } + + // Functions present in both Parser specializations. + + friend class AutoAwaitIsKeyword; + inline void setAwaitHandling(AwaitHandling awaitHandling); + + friend class AutoInParametersOfAsyncFunction; + inline void setInParametersOfAsyncFunction(bool inParameters); + + RegExpLiteralType newRegExp(); + BigIntLiteralType newBigInt(); + + // Parse a module. + ModuleNodeType moduleBody(ModuleSharedContext* modulesc); + + bool checkLocalExportNames(ListNodeType node); + bool checkExportedName(TaggedParserAtomIndex exportName); + bool checkExportedNamesForArrayBinding(ListNodeType array); + bool checkExportedNamesForObjectBinding(ListNodeType obj); + bool checkExportedNamesForDeclaration(Node node); + bool checkExportedNamesForDeclarationList(DeclarationListNodeType node); + bool checkExportedNameForFunction(FunctionNodeType funNode); + bool checkExportedNameForClass(ClassNodeType classNode); + inline bool checkExportedNameForClause(NameNodeType nameNode); + + bool trySyntaxParseInnerFunction( + FunctionNodeType* funNode, TaggedParserAtomIndex explicitName, + FunctionFlags flags, uint32_t toStringStart, InHandling inHandling, + YieldHandling yieldHandling, FunctionSyntaxKind kind, + GeneratorKind generatorKind, FunctionAsyncKind asyncKind, bool tryAnnexB, + Directives inheritedDirectives, Directives* newDirectives); + + [[nodiscard]] bool advancePastSyntaxParsedFunction( + SyntaxParser* syntaxParser); + + bool skipLazyInnerFunction(FunctionNodeType funNode, uint32_t toStringStart, + bool tryAnnexB); + + // Functions present only in Parser. + + // Parse the body of an eval. + // + // Eval scripts are distinguished from global scripts in that in ES6, per + // 18.2.1.1 steps 9 and 10, all eval scripts are executed under a fresh + // lexical scope. + LexicalScopeNodeType evalBody(EvalSharedContext* evalsc); + + // Parse a function, given only its arguments and body. Used for lazily + // parsed functions. + FunctionNodeType standaloneLazyFunction(CompilationInput& input, + uint32_t toStringStart, bool strict, + GeneratorKind generatorKind, + FunctionAsyncKind asyncKind); + + // Parse a function, used for the Function, GeneratorFunction, and + // AsyncFunction constructors. + FunctionNodeType standaloneFunction( + const mozilla::Maybe& parameterListEnd, + FunctionSyntaxKind syntaxKind, GeneratorKind generatorKind, + FunctionAsyncKind asyncKind, Directives inheritedDirectives, + Directives* newDirectives); + + bool checkStatementsEOF(); + + // Parse the body of a global script. + ListNodeType globalBody(GlobalSharedContext* globalsc); + + bool checkLocalExportName(TaggedParserAtomIndex ident, uint32_t offset) { + return checkLabelOrIdentifierReference(ident, offset, YieldIsName); + } + + bool asmJS(ListNodeType list); +}; + +template +/* static */ inline const TokenStreamAnyChars& +ParserAnyCharsAccess::anyChars(const GeneralTokenStreamChars* ts) { + // The structure we're walking through looks like this: + // + // struct ParserBase + // { + // ...; + // TokenStreamAnyChars anyChars; + // ...; + // }; + // struct Parser : + // { + // ...; + // TokenStreamSpecific tokenStream; + // ...; + // }; + // + // We're passed a GeneralTokenStreamChars* (this being a base class of + // Parser::tokenStream). We cast that pointer to a TokenStreamSpecific*, + // then translate that to the enclosing Parser*, then return the |anyChars| + // member within. + + static_assert(std::is_base_of_v, + "the static_cast<> below assumes a base-class relationship"); + const auto* tss = static_cast(ts); + + auto tssAddr = reinterpret_cast(tss); + + using ActualTokenStreamType = decltype(std::declval().tokenStream); + static_assert(std::is_same_v, + "Parser::tokenStream must have type TokenStreamSpecific"); + + uintptr_t parserAddr = tssAddr - offsetof(Parser, tokenStream); + + return reinterpret_cast(parserAddr)->anyChars; +} + +template +/* static */ inline TokenStreamAnyChars& ParserAnyCharsAccess::anyChars( + GeneralTokenStreamChars* ts) { + const TokenStreamAnyChars& anyCharsConst = + anyChars(const_cast(ts)); + + return const_cast(anyCharsConst); +} + +template +class MOZ_STACK_CLASS AutoAwaitIsKeyword { + using GeneralParser = frontend::GeneralParser; + + private: + GeneralParser* parser_; + AwaitHandling oldAwaitHandling_; + + public: + AutoAwaitIsKeyword(GeneralParser* parser, AwaitHandling awaitHandling) { + parser_ = parser; + oldAwaitHandling_ = static_cast(parser_->awaitHandling_); + + // 'await' is always a keyword in module contexts, so we don't modify + // the state when the original handling is AwaitIsModuleKeyword. + if (oldAwaitHandling_ != AwaitIsModuleKeyword) { + parser_->setAwaitHandling(awaitHandling); + } + } + + ~AutoAwaitIsKeyword() { parser_->setAwaitHandling(oldAwaitHandling_); } +}; + +template +class MOZ_STACK_CLASS AutoInParametersOfAsyncFunction { + using GeneralParser = frontend::GeneralParser; + + private: + GeneralParser* parser_; + bool oldInParametersOfAsyncFunction_; + + public: + AutoInParametersOfAsyncFunction(GeneralParser* parser, bool inParameters) { + parser_ = parser; + oldInParametersOfAsyncFunction_ = parser_->inParametersOfAsyncFunction_; + parser_->setInParametersOfAsyncFunction(inParameters); + } + + ~AutoInParametersOfAsyncFunction() { + parser_->setInParametersOfAsyncFunction(oldInParametersOfAsyncFunction_); + } +}; + +GlobalScope::ParserData* NewEmptyGlobalScopeData(FrontendContext* fc, + LifoAlloc& alloc, + uint32_t numBindings); + +VarScope::ParserData* NewEmptyVarScopeData(FrontendContext* fc, + LifoAlloc& alloc, + uint32_t numBindings); + +LexicalScope::ParserData* NewEmptyLexicalScopeData(FrontendContext* fc, + LifoAlloc& alloc, + uint32_t numBindings); + +FunctionScope::ParserData* NewEmptyFunctionScopeData(FrontendContext* fc, + LifoAlloc& alloc, + uint32_t numBindings); + +mozilla::Maybe NewGlobalScopeData( + JSContext* cx, FrontendContext* fc, ParseContext::Scope& scope, + LifoAlloc& alloc, ParseContext* pc); + +mozilla::Maybe NewEvalScopeData( + JSContext* cx, FrontendContext* fc, ParseContext::Scope& scope, + LifoAlloc& alloc, ParseContext* pc); + +mozilla::Maybe NewFunctionScopeData( + JSContext* cx, FrontendContext* fc, ParseContext::Scope& scope, + bool hasParameterExprs, LifoAlloc& alloc, ParseContext* pc); + +mozilla::Maybe NewVarScopeData( + JSContext* cx, FrontendContext* fc, ParseContext::Scope& scope, + LifoAlloc& alloc, ParseContext* pc); + +mozilla::Maybe NewLexicalScopeData( + JSContext* cx, FrontendContext* fc, ParseContext::Scope& scope, + LifoAlloc& alloc, ParseContext* pc); + +bool FunctionScopeHasClosedOverBindings(ParseContext* pc); +bool LexicalScopeHasClosedOverBindings(ParseContext* pc, + ParseContext::Scope& scope); + +} /* namespace frontend */ +} /* namespace js */ + +#endif /* frontend_Parser_h */ diff --git a/js/src/frontend/ParserAtom.cpp b/js/src/frontend/ParserAtom.cpp new file mode 100644 index 0000000000..2141a7189b --- /dev/null +++ b/js/src/frontend/ParserAtom.cpp @@ -0,0 +1,1316 @@ +/* -*- 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/ParserAtom.h" + +#include "mozilla/TextUtils.h" // mozilla::IsAscii + +#include // std::uninitialized_fill_n + +#include "jsnum.h" // CharsToNumber + +#include "frontend/BytecodeCompiler.h" // IsIdentifier +#include "frontend/CompilationStencil.h" +#include "js/GCAPI.h" // JS::AutoSuppressGCAnalysis +#include "js/Printer.h" // Sprinter, QuoteString +#include "util/StringBuffer.h" // StringBuffer +#include "util/Text.h" // AsciiDigitToNumber +#include "util/Unicode.h" +#include "vm/JSContext.h" +#include "vm/Runtime.h" +#include "vm/SelfHosting.h" // ExtendedUnclonedSelfHostedFunctionNamePrefix +#include "vm/StaticStrings.h" +#include "vm/StringType.h" + +using namespace js; +using namespace js::frontend; + +namespace js { +namespace frontend { + +JSAtom* GetWellKnownAtom(JSContext* cx, WellKnownAtomId atomId) { +#define ASSERT_OFFSET_(_, NAME, _2) \ + static_assert(offsetof(JSAtomState, NAME) == \ + int32_t(WellKnownAtomId::NAME) * \ + sizeof(js::ImmutableTenuredPtr)); + FOR_EACH_COMMON_PROPERTYNAME(ASSERT_OFFSET_); +#undef ASSERT_OFFSET_ + +#define ASSERT_OFFSET_(NAME, _) \ + static_assert(offsetof(JSAtomState, NAME) == \ + int32_t(WellKnownAtomId::NAME) * \ + sizeof(js::ImmutableTenuredPtr)); + JS_FOR_EACH_PROTOTYPE(ASSERT_OFFSET_); +#undef ASSERT_OFFSET_ + +#define ASSERT_OFFSET_(NAME) \ + static_assert(offsetof(JSAtomState, NAME) == \ + int32_t(WellKnownAtomId::NAME) * \ + sizeof(js::ImmutableTenuredPtr)); + JS_FOR_EACH_WELL_KNOWN_SYMBOL(ASSERT_OFFSET_); +#undef ASSERT_OFFSET_ + + static_assert(int32_t(WellKnownAtomId::abort) == 0, + "Unexpected order of WellKnownAtom"); + + return (&cx->names().abort)[int32_t(atomId)]; +} + +#ifdef DEBUG +void TaggedParserAtomIndex::validateRaw() { + if (isParserAtomIndex()) { + MOZ_ASSERT(toParserAtomIndex().index < IndexLimit); + } else if (isWellKnownAtomId()) { + MOZ_ASSERT(uint32_t(toWellKnownAtomId()) < + uint32_t(WellKnownAtomId::Limit)); + } else if (isLength1StaticParserString()) { + // always valid + } else if (isLength2StaticParserString()) { + MOZ_ASSERT(size_t(toLength2StaticParserString()) < Length2StaticLimit); + } else if (isLength3StaticParserString()) { + // always valid + } else { + MOZ_ASSERT(isNull()); + } +} +#endif + +HashNumber TaggedParserAtomIndex::staticOrWellKnownHash() const { + MOZ_ASSERT(!isParserAtomIndex()); + + if (isWellKnownAtomId()) { + const auto& info = GetWellKnownAtomInfo(toWellKnownAtomId()); + return info.hash; + } + + if (isLength1StaticParserString()) { + Latin1Char content[1]; + ParserAtomsTable::getLength1Content(toLength1StaticParserString(), content); + return mozilla::HashString(content, 1); + } + + if (isLength2StaticParserString()) { + char content[2]; + ParserAtomsTable::getLength2Content(toLength2StaticParserString(), content); + return mozilla::HashString(reinterpret_cast(content), 2); + } + + MOZ_ASSERT(isLength3StaticParserString()); + char content[3]; + ParserAtomsTable::getLength3Content(toLength3StaticParserString(), content); + return mozilla::HashString(reinterpret_cast(content), 3); +} + +template +/* static */ ParserAtom* ParserAtom::allocate( + FrontendContext* fc, LifoAlloc& alloc, InflatedChar16Sequence seq, + uint32_t length, HashNumber hash) { + constexpr size_t HeaderSize = sizeof(ParserAtom); + void* raw = alloc.alloc(HeaderSize + (sizeof(CharT) * length)); + if (!raw) { + js::ReportOutOfMemory(fc); + return nullptr; + } + + constexpr bool hasTwoByteChars = (sizeof(CharT) == 2); + static_assert(sizeof(CharT) == 1 || sizeof(CharT) == 2, + "CharT should be 1 or 2 byte type"); + ParserAtom* entry = new (raw) ParserAtom(length, hash, hasTwoByteChars); + CharT* entryBuf = entry->chars(); + drainChar16Seq(entryBuf, seq, length); + return entry; +} + +bool ParserAtom::isInstantiatedAsJSAtom() const { + if (isMarkedAtomize()) { + return true; + } + + // Always use JSAtom for short strings. + if (length() < MinimumLengthForNonAtom) { + return true; + } + + return false; +} + +JSString* ParserAtom::instantiateString(JSContext* cx, FrontendContext* fc, + ParserAtomIndex index, + CompilationAtomCache& atomCache) const { + MOZ_ASSERT(!isInstantiatedAsJSAtom()); + + JSString* str; + if (hasLatin1Chars()) { + str = NewStringCopyNDontDeflateNonStaticValidLength( + cx, latin1Chars(), length(), gc::Heap::Tenured); + } else { + str = NewStringCopyNDontDeflateNonStaticValidLength( + cx, twoByteChars(), length(), gc::Heap::Tenured); + } + if (!str) { + return nullptr; + } + if (!atomCache.setAtomAt(fc, index, str)) { + return nullptr; + } + + return str; +} + +JSAtom* ParserAtom::instantiateAtom(JSContext* cx, FrontendContext* fc, + ParserAtomIndex index, + CompilationAtomCache& atomCache) const { + MOZ_ASSERT(isInstantiatedAsJSAtom()); + + JSAtom* atom; + if (hasLatin1Chars()) { + atom = + AtomizeCharsNonStaticValidLength(cx, hash(), latin1Chars(), length()); + } else { + atom = + AtomizeCharsNonStaticValidLength(cx, hash(), twoByteChars(), length()); + } + if (!atom) { + return nullptr; + } + if (!atomCache.setAtomAt(fc, index, atom)) { + return nullptr; + } + return atom; +} + +JSAtom* ParserAtom::instantiatePermanentAtom( + JSContext* cx, FrontendContext* fc, AtomSet& atomSet, ParserAtomIndex index, + CompilationAtomCache& atomCache) const { + MOZ_ASSERT(!cx->zone()); + + MOZ_ASSERT(hasLatin1Chars()); + MOZ_ASSERT(length() <= JSString::MAX_LENGTH); + JSAtom* atom = PermanentlyAtomizeCharsNonStaticValidLength( + cx, atomSet, hash(), latin1Chars(), length()); + if (!atom) { + return nullptr; + } + if (!atomCache.setAtomAt(fc, index, atom)) { + return nullptr; + } + return atom; +} + +#if defined(DEBUG) || defined(JS_JITSPEW) +void ParserAtom::dump() const { + js::Fprinter out(stderr); + out.put("\""); + dumpCharsNoQuote(out); + out.put("\"\n"); +} + +void ParserAtom::dumpCharsNoQuote(js::GenericPrinter& out) const { + if (hasLatin1Chars()) { + JSString::dumpCharsNoQuote(latin1Chars(), length(), out); + } else { + JSString::dumpCharsNoQuote(twoByteChars(), length(), out); + } +} + +void ParserAtomsTable::dump(TaggedParserAtomIndex index) const { + if (index.isParserAtomIndex()) { + getParserAtom(index.toParserAtomIndex())->dump(); + return; + } + + if (index.isWellKnownAtomId()) { + const auto& info = GetWellKnownAtomInfo(index.toWellKnownAtomId()); + js::Fprinter out(stderr); + out.put("\""); + out.put(info.content, info.length); + out.put("\""); + return; + } + + if (index.isLength1StaticParserString()) { + js::Fprinter out(stderr); + out.put("\""); + dumpCharsNoQuote(out, index.toLength1StaticParserString()); + out.put("\"\n"); + return; + } + + if (index.isLength2StaticParserString()) { + js::Fprinter out(stderr); + out.put("\""); + dumpCharsNoQuote(out, index.toLength2StaticParserString()); + out.put("\"\n"); + return; + } + + if (index.isLength3StaticParserString()) { + js::Fprinter out(stderr); + out.put("\""); + dumpCharsNoQuote(out, index.toLength3StaticParserString()); + out.put("\"\n"); + return; + } + + MOZ_ASSERT(index.isNull()); + js::Fprinter out(stderr); + out.put("#"); +} + +void ParserAtomsTable::dumpCharsNoQuote(js::GenericPrinter& out, + TaggedParserAtomIndex index) const { + if (index.isParserAtomIndex()) { + getParserAtom(index.toParserAtomIndex())->dumpCharsNoQuote(out); + return; + } + + if (index.isWellKnownAtomId()) { + dumpCharsNoQuote(out, index.toWellKnownAtomId()); + return; + } + + if (index.isLength1StaticParserString()) { + dumpCharsNoQuote(out, index.toLength1StaticParserString()); + return; + } + + if (index.isLength2StaticParserString()) { + dumpCharsNoQuote(out, index.toLength2StaticParserString()); + return; + } + + if (index.isLength3StaticParserString()) { + dumpCharsNoQuote(out, index.toLength3StaticParserString()); + return; + } + + MOZ_ASSERT(index.isNull()); + out.put("#"); +} + +/* static */ +void ParserAtomsTable::dumpCharsNoQuote(js::GenericPrinter& out, + WellKnownAtomId id) { + const auto& info = GetWellKnownAtomInfo(id); + out.put(info.content, info.length); +} + +/* static */ +void ParserAtomsTable::dumpCharsNoQuote(js::GenericPrinter& out, + Length1StaticParserString index) { + Latin1Char content[1]; + getLength1Content(index, content); + out.putChar(content[0]); +} + +/* static */ +void ParserAtomsTable::dumpCharsNoQuote(js::GenericPrinter& out, + Length2StaticParserString index) { + char content[2]; + getLength2Content(index, content); + out.putChar(content[0]); + out.putChar(content[1]); +} + +/* static */ +void ParserAtomsTable::dumpCharsNoQuote(js::GenericPrinter& out, + Length3StaticParserString index) { + char content[3]; + getLength3Content(index, content); + out.putChar(content[0]); + out.putChar(content[1]); + out.putChar(content[2]); +} +#endif + +ParserAtomsTable::ParserAtomsTable(LifoAlloc& alloc) : alloc_(&alloc) {} + +TaggedParserAtomIndex ParserAtomsTable::addEntry(FrontendContext* fc, + EntryMap::AddPtr& addPtr, + ParserAtom* entry) { + MOZ_ASSERT(!addPtr); + ParserAtomIndex index = ParserAtomIndex(entries_.length()); + if (size_t(index) >= TaggedParserAtomIndex::IndexLimit) { + ReportAllocationOverflow(fc); + return TaggedParserAtomIndex::null(); + } + if (!entries_.append(entry)) { + js::ReportOutOfMemory(fc); + return TaggedParserAtomIndex::null(); + } + auto taggedIndex = TaggedParserAtomIndex(index); + if (!entryMap_.add(addPtr, entry, taggedIndex)) { + js::ReportOutOfMemory(fc); + return TaggedParserAtomIndex::null(); + } + return taggedIndex; +} + +template +TaggedParserAtomIndex ParserAtomsTable::internChar16Seq( + FrontendContext* fc, EntryMap::AddPtr& addPtr, HashNumber hash, + InflatedChar16Sequence seq, uint32_t length) { + MOZ_ASSERT(!addPtr); + + ParserAtom* entry = + ParserAtom::allocate(fc, *alloc_, seq, length, hash); + if (!entry) { + return TaggedParserAtomIndex::null(); + } + return addEntry(fc, addPtr, entry); +} + +static const uint16_t MAX_LATIN1_CHAR = 0xff; + +TaggedParserAtomIndex ParserAtomsTable::internAscii(FrontendContext* fc, + const char* asciiPtr, + uint32_t length) { + // ASCII strings are strict subsets of Latin1 strings. + const Latin1Char* latin1Ptr = reinterpret_cast(asciiPtr); + return internLatin1(fc, latin1Ptr, length); +} + +TaggedParserAtomIndex ParserAtomsTable::internLatin1( + FrontendContext* fc, const Latin1Char* latin1Ptr, uint32_t length) { + // Check for tiny strings which are abundant in minified code. + if (auto tiny = WellKnownParserAtoms::getSingleton().lookupTinyIndex( + latin1Ptr, length)) { + return tiny; + } + + // Check for well-known atom. + InflatedChar16Sequence seq(latin1Ptr, length); + SpecificParserAtomLookup lookup(seq); + if (auto wk = WellKnownParserAtoms::getSingleton().lookupChar16Seq(lookup)) { + return wk; + } + + // Check for existing atom. + auto addPtr = entryMap_.lookupForAdd(lookup); + if (addPtr) { + return addPtr->value(); + } + + return internChar16Seq(fc, addPtr, lookup.hash(), seq, length); +} + +bool IsWide(const InflatedChar16Sequence& seq) { + InflatedChar16Sequence seqCopy = seq; + while (seqCopy.hasMore()) { + char16_t ch = seqCopy.next(); + if (ch > MAX_LATIN1_CHAR) { + return true; + } + } + + return false; +} + +template +TaggedParserAtomIndex ParserAtomsTable::internExternalParserAtomImpl( + FrontendContext* fc, const ParserAtom* atom) { + InflatedChar16Sequence seq(atom->chars(), + atom->length()); + SpecificParserAtomLookup lookup(seq, atom->hash()); + + // Check for existing atom. + auto addPtr = entryMap_.lookupForAdd(lookup); + if (addPtr) { + auto index = addPtr->value(); + + // Copy UsedByStencilFlag and AtomizeFlag. + MOZ_ASSERT(entries_[index.toParserAtomIndex()]->hasTwoByteChars() == + atom->hasTwoByteChars()); + entries_[index.toParserAtomIndex()]->flags_ |= atom->flags_; + return index; + } + + auto index = + internChar16Seq(fc, addPtr, atom->hash(), seq, atom->length()); + if (!index) { + return TaggedParserAtomIndex::null(); + } + + // Copy UsedByStencilFlag and AtomizeFlag. + MOZ_ASSERT(entries_[index.toParserAtomIndex()]->hasTwoByteChars() == + atom->hasTwoByteChars()); + entries_[index.toParserAtomIndex()]->flags_ |= atom->flags_; + return index; +} + +TaggedParserAtomIndex ParserAtomsTable::internExternalParserAtom( + FrontendContext* fc, const ParserAtom* atom) { + if (atom->hasLatin1Chars()) { + return internExternalParserAtomImpl(fc, atom); + } + return internExternalParserAtomImpl(fc, atom); +} + +bool ParserAtomsTable::addPlaceholder(FrontendContext* fc) { + ParserAtomIndex index = ParserAtomIndex(entries_.length()); + if (size_t(index) >= TaggedParserAtomIndex::IndexLimit) { + ReportAllocationOverflow(fc); + return false; + } + if (!entries_.append(nullptr)) { + js::ReportOutOfMemory(fc); + return false; + } + return true; +} + +TaggedParserAtomIndex ParserAtomsTable::internExternalParserAtomIndex( + FrontendContext* fc, const CompilationStencil& context, + TaggedParserAtomIndex atom) { + // When the atom is not a parser atom index, the value represent the atom + // without the need for a ParserAtom, and thus we can skip interning it. + if (!atom.isParserAtomIndex()) { + return atom; + } + auto index = atom.toParserAtomIndex(); + return internExternalParserAtom(fc, context.parserAtomData[index]); +} + +bool ParserAtomsTable::isEqualToExternalParserAtomIndex( + TaggedParserAtomIndex internal, const CompilationStencil& context, + TaggedParserAtomIndex external) const { + // If one is null, well-known or static, then testing the equality of the bits + // of the TaggedParserAtomIndex is sufficient. + if (!internal.isParserAtomIndex() || !external.isParserAtomIndex()) { + return internal == external; + } + + // Otherwise we have to compare 2 atom-indexes from different ParserAtomTable. + ParserAtom* internalAtom = getParserAtom(internal.toParserAtomIndex()); + ParserAtom* externalAtom = + context.parserAtomData[external.toParserAtomIndex()]; + + if (internalAtom->hash() != externalAtom->hash()) { + return false; + } + + HashNumber hash = internalAtom->hash(); + size_t length = internalAtom->length(); + if (internalAtom->hasLatin1Chars()) { + const Latin1Char* chars = internalAtom->latin1Chars(); + InflatedChar16Sequence seq(chars, length); + return externalAtom->equalsSeq(hash, seq); + } + + const char16_t* chars = internalAtom->twoByteChars(); + InflatedChar16Sequence seq(chars, length); + return externalAtom->equalsSeq(hash, seq); +} + +bool ParserAtomSpanBuilder::allocate(FrontendContext* fc, LifoAlloc& alloc, + size_t count) { + if (count >= TaggedParserAtomIndex::IndexLimit) { + ReportAllocationOverflow(fc); + return false; + } + + auto* p = alloc.newArrayUninitialized(count); + if (!p) { + js::ReportOutOfMemory(fc); + return false; + } + std::uninitialized_fill_n(p, count, nullptr); + + entries_ = mozilla::Span(p, count); + return true; +} + +static inline bool IsLatin1(mozilla::Utf8Unit c1, mozilla::Utf8Unit c2) { + auto u1 = c1.toUint8(); + auto u2 = c2.toUint8(); + + // 0x80-0xBF + if (u1 == 0xC2 && 0x80 <= u2 && u2 <= 0xBF) { + return true; + } + + // 0xC0-0xFF + if (u1 == 0xC3 && 0x80 <= u2 && u2 <= 0xBF) { + return true; + } + + return false; +} + +TaggedParserAtomIndex ParserAtomsTable::internUtf8( + FrontendContext* fc, const mozilla::Utf8Unit* utf8Ptr, uint32_t nbyte) { + if (auto tiny = WellKnownParserAtoms::getSingleton().lookupTinyIndexUTF8( + utf8Ptr, nbyte)) { + return tiny; + } + + // If source text is ASCII, then the length of the target char buffer + // is the same as the length of the UTF8 input. Convert it to a Latin1 + // encoded string on the heap. + JS::UTF8Chars utf8(utf8Ptr, nbyte); + JS::SmallestEncoding minEncoding = FindSmallestEncoding(utf8); + if (minEncoding == JS::SmallestEncoding::ASCII) { + // As ascii strings are a subset of Latin1 strings, and each encoding + // unit is the same size, we can reliably cast this `Utf8Unit*` + // to a `Latin1Char*`. + const Latin1Char* latin1Ptr = reinterpret_cast(utf8Ptr); + return internLatin1(fc, latin1Ptr, nbyte); + } + + // Check for existing. + // NOTE: Well-known are all ASCII so have been handled above. + InflatedChar16Sequence seq(utf8Ptr, nbyte); + SpecificParserAtomLookup lookup(seq); + MOZ_ASSERT(!WellKnownParserAtoms::getSingleton().lookupChar16Seq(lookup)); + EntryMap::AddPtr addPtr = entryMap_.lookupForAdd(lookup); + if (addPtr) { + return addPtr->value(); + } + + // Compute length in code-points. + uint32_t length = 0; + InflatedChar16Sequence seqCopy = seq; + while (seqCopy.hasMore()) { + (void)seqCopy.next(); + length += 1; + } + + // Otherwise, add new entry. + bool wide = (minEncoding == JS::SmallestEncoding::UTF16); + return wide + ? internChar16Seq(fc, addPtr, lookup.hash(), seq, length) + : internChar16Seq(fc, addPtr, lookup.hash(), seq, + length); +} + +TaggedParserAtomIndex ParserAtomsTable::internChar16(FrontendContext* fc, + const char16_t* char16Ptr, + uint32_t length) { + // Check for tiny strings which are abundant in minified code. + if (auto tiny = WellKnownParserAtoms::getSingleton().lookupTinyIndex( + char16Ptr, length)) { + return tiny; + } + + // Check against well-known. + InflatedChar16Sequence seq(char16Ptr, length); + SpecificParserAtomLookup lookup(seq); + if (auto wk = WellKnownParserAtoms::getSingleton().lookupChar16Seq(lookup)) { + return wk; + } + + // Check for existing atom. + EntryMap::AddPtr addPtr = entryMap_.lookupForAdd(lookup); + if (addPtr) { + return addPtr->value(); + } + + // Otherwise, add new entry. + return IsWide(seq) + ? internChar16Seq(fc, addPtr, lookup.hash(), seq, length) + : internChar16Seq(fc, addPtr, lookup.hash(), seq, + length); +} + +TaggedParserAtomIndex ParserAtomsTable::internJSAtom( + FrontendContext* fc, CompilationAtomCache& atomCache, JSAtom* atom) { + TaggedParserAtomIndex parserAtom; + { + JS::AutoCheckCannotGC nogc; + + parserAtom = + atom->hasLatin1Chars() + ? internLatin1(fc, atom->latin1Chars(nogc), atom->length()) + : internChar16(fc, atom->twoByteChars(nogc), atom->length()); + if (!parserAtom) { + return TaggedParserAtomIndex::null(); + } + } + + if (parserAtom.isParserAtomIndex()) { + ParserAtomIndex index = parserAtom.toParserAtomIndex(); + if (!atomCache.hasAtomAt(index)) { + if (!atomCache.setAtomAt(fc, index, atom)) { + return TaggedParserAtomIndex::null(); + } + } + } + + // We should (infallibly) map back to the same JSAtom. +#ifdef DEBUG + if (JSContext* cx = fc->maybeCurrentJSContext()) { + JS::AutoSuppressGCAnalysis suppress(cx); + MOZ_ASSERT(toJSAtom(cx, fc, parserAtom, atomCache) == atom); + } +#endif + + return parserAtom; +} + +ParserAtom* ParserAtomsTable::getParserAtom(ParserAtomIndex index) const { + return entries_[index]; +} + +void ParserAtomsTable::markUsedByStencil(TaggedParserAtomIndex index, + ParserAtom::Atomize atomize) const { + if (!index.isParserAtomIndex()) { + return; + } + + getParserAtom(index.toParserAtomIndex())->markUsedByStencil(atomize); +} + +void ParserAtomsTable::markAtomize(TaggedParserAtomIndex index, + ParserAtom::Atomize atomize) const { + if (!index.isParserAtomIndex()) { + return; + } + + getParserAtom(index.toParserAtomIndex())->markAtomize(atomize); +} + +bool ParserAtomsTable::isIdentifier(TaggedParserAtomIndex index) const { + if (index.isParserAtomIndex()) { + const auto* atom = getParserAtom(index.toParserAtomIndex()); + return atom->hasLatin1Chars() + ? IsIdentifier(atom->latin1Chars(), atom->length()) + : IsIdentifier(atom->twoByteChars(), atom->length()); + } + + if (index.isWellKnownAtomId()) { + const auto& info = GetWellKnownAtomInfo(index.toWellKnownAtomId()); + return IsIdentifier(reinterpret_cast(info.content), + info.length); + } + + if (index.isLength1StaticParserString()) { + Latin1Char content[1]; + getLength1Content(index.toLength1StaticParserString(), content); + if (MOZ_UNLIKELY(content[0] > 127)) { + return IsIdentifier(content, 1); + } + return IsIdentifierASCII(char(content[0])); + } + + if (index.isLength2StaticParserString()) { + char content[2]; + getLength2Content(index.toLength2StaticParserString(), content); + return IsIdentifierASCII(content[0], content[1]); + } + + MOZ_ASSERT(index.isLength3StaticParserString()); +#ifdef DEBUG + char content[3]; + getLength3Content(index.toLength3StaticParserString(), content); + MOZ_ASSERT(!reinterpret_cast( + IsIdentifier(reinterpret_cast(content), 3))); +#endif + return false; +} + +bool ParserAtomsTable::isPrivateName(TaggedParserAtomIndex index) const { + if (!index.isParserAtomIndex()) { + return false; + } + + const auto* atom = getParserAtom(index.toParserAtomIndex()); + return atom->isPrivateName(); +} + +bool ParserAtomsTable::isExtendedUnclonedSelfHostedFunctionName( + TaggedParserAtomIndex index) const { + if (index.isParserAtomIndex()) { + const auto* atom = getParserAtom(index.toParserAtomIndex()); + if (atom->length() < 2) { + return false; + } + + return atom->charAt(0) == ExtendedUnclonedSelfHostedFunctionNamePrefix; + } + + if (index.isWellKnownAtomId()) { + switch (index.toWellKnownAtomId()) { + case WellKnownAtomId::ArrayBufferSpecies: + case WellKnownAtomId::ArraySpecies: + case WellKnownAtomId::ArrayValues: + case WellKnownAtomId::RegExpFlagsGetter: + case WellKnownAtomId::RegExpToString: { +#ifdef DEBUG + const auto& info = GetWellKnownAtomInfo(index.toWellKnownAtomId()); + MOZ_ASSERT(info.content[0] == + ExtendedUnclonedSelfHostedFunctionNamePrefix); +#endif + return true; + } + default: { +#ifdef DEBUG + const auto& info = GetWellKnownAtomInfo(index.toWellKnownAtomId()); + MOZ_ASSERT(info.length == 0 || + info.content[0] != + ExtendedUnclonedSelfHostedFunctionNamePrefix); +#endif + break; + } + } + return false; + } + + // Length-1/2/3 shouldn't be used for extented uncloned self-hosted + // function name, and this query shouldn't be used for them. +#ifdef DEBUG + if (index.isLength1StaticParserString()) { + Latin1Char content[1]; + getLength1Content(index.toLength1StaticParserString(), content); + MOZ_ASSERT(content[0] != ExtendedUnclonedSelfHostedFunctionNamePrefix); + } else if (index.isLength2StaticParserString()) { + char content[2]; + getLength2Content(index.toLength2StaticParserString(), content); + MOZ_ASSERT(content[0] != ExtendedUnclonedSelfHostedFunctionNamePrefix); + } else { + MOZ_ASSERT(index.isLength3StaticParserString()); + char content[3]; + getLength3Content(index.toLength3StaticParserString(), content); + MOZ_ASSERT(content[0] != ExtendedUnclonedSelfHostedFunctionNamePrefix); + } +#endif + return false; +} + +static bool HasUnpairedSurrogate(mozilla::Range chars) { + for (auto ptr = chars.begin(); ptr < chars.end();) { + char16_t ch = *ptr++; + if (unicode::IsLeadSurrogate(ch)) { + if (ptr == chars.end() || !unicode::IsTrailSurrogate(*ptr++)) { + return true; + } + } else if (unicode::IsTrailSurrogate(ch)) { + return true; + } + } + return false; +} + +bool ParserAtomsTable::isModuleExportName(TaggedParserAtomIndex index) const { + if (index.isParserAtomIndex()) { + const ParserAtom* name = getParserAtom(index.toParserAtomIndex()); + return name->hasLatin1Chars() || + !HasUnpairedSurrogate(name->twoByteRange()); + } + + // Well-known/length-2 are ASCII. + // length-1 are Latin1. + return true; +} + +bool ParserAtomsTable::isIndex(TaggedParserAtomIndex index, + uint32_t* indexp) const { + if (index.isParserAtomIndex()) { + const auto* atom = getParserAtom(index.toParserAtomIndex()); + size_t len = atom->length(); + if (len == 0 || len > UINT32_CHAR_BUFFER_LENGTH) { + return false; + } + if (atom->hasLatin1Chars()) { + return mozilla::IsAsciiDigit(*atom->latin1Chars()) && + js::CheckStringIsIndex(atom->latin1Chars(), len, indexp); + } + return mozilla::IsAsciiDigit(*atom->twoByteChars()) && + js::CheckStringIsIndex(atom->twoByteChars(), len, indexp); + } + + if (index.isWellKnownAtomId()) { +#ifdef DEBUG + // Well-known atom shouldn't start with digit. + const auto& info = GetWellKnownAtomInfo(index.toWellKnownAtomId()); + MOZ_ASSERT(info.length == 0 || !mozilla::IsAsciiDigit(info.content[0])); +#endif + return false; + } + + if (index.isLength1StaticParserString()) { + Latin1Char content[1]; + getLength1Content(index.toLength1StaticParserString(), content); + if (mozilla::IsAsciiDigit(content[0])) { + *indexp = AsciiDigitToNumber(content[0]); + return true; + } + return false; + } + + if (index.isLength2StaticParserString()) { + char content[2]; + getLength2Content(index.toLength2StaticParserString(), content); + // Leading '0' isn't allowed. + // See CheckStringIsIndex comment. + if (content[0] != '0' && mozilla::IsAsciiDigit(content[0]) && + mozilla::IsAsciiDigit(content[1])) { + *indexp = + AsciiDigitToNumber(content[0]) * 10 + AsciiDigitToNumber(content[1]); + return true; + } + return false; + } + + MOZ_ASSERT(index.isLength3StaticParserString()); + *indexp = uint32_t(index.toLength3StaticParserString()); +#ifdef DEBUG + char content[3]; + getLength3Content(index.toLength3StaticParserString(), content); + MOZ_ASSERT(uint32_t(AsciiDigitToNumber(content[0])) * 100 + + uint32_t(AsciiDigitToNumber(content[1])) * 10 + + uint32_t(AsciiDigitToNumber(content[2])) == + *indexp); + MOZ_ASSERT(100 <= *indexp); +#endif + return true; +} + +bool ParserAtomsTable::isInstantiatedAsJSAtom( + TaggedParserAtomIndex index) const { + if (index.isParserAtomIndex()) { + const auto* atom = getParserAtom(index.toParserAtomIndex()); + return atom->isInstantiatedAsJSAtom(); + } + + // Everything else are always JSAtom. + return true; +} + +uint32_t ParserAtomsTable::length(TaggedParserAtomIndex index) const { + if (index.isParserAtomIndex()) { + return getParserAtom(index.toParserAtomIndex())->length(); + } + + if (index.isWellKnownAtomId()) { + const auto& info = GetWellKnownAtomInfo(index.toWellKnownAtomId()); + return info.length; + } + + if (index.isLength1StaticParserString()) { + return 1; + } + + if (index.isLength2StaticParserString()) { + return 2; + } + + MOZ_ASSERT(index.isLength3StaticParserString()); + return 3; +} + +HashNumber ParserAtomsTable::hash(TaggedParserAtomIndex index) const { + if (index.isParserAtomIndex()) { + return getParserAtom(index.toParserAtomIndex())->hash(); + } + + return index.staticOrWellKnownHash(); +} + +double ParserAtomsTable::toNumber(TaggedParserAtomIndex index) const { + if (index.isParserAtomIndex()) { + const auto* atom = getParserAtom(index.toParserAtomIndex()); + size_t len = atom->length(); + return atom->hasLatin1Chars() ? CharsToNumber(atom->latin1Chars(), len) + : CharsToNumber(atom->twoByteChars(), len); + } + + if (index.isWellKnownAtomId()) { + const auto& info = GetWellKnownAtomInfo(index.toWellKnownAtomId()); + return CharsToNumber(reinterpret_cast(info.content), + info.length); + } + + if (index.isLength1StaticParserString()) { + Latin1Char content[1]; + getLength1Content(index.toLength1StaticParserString(), content); + return CharsToNumber(content, 1); + } + + if (index.isLength2StaticParserString()) { + char content[2]; + getLength2Content(index.toLength2StaticParserString(), content); + return CharsToNumber(reinterpret_cast(content), 2); + } + + MOZ_ASSERT(index.isLength3StaticParserString()); + double result = double(index.toLength3StaticParserString()); +#ifdef DEBUG + char content[3]; + getLength3Content(index.toLength3StaticParserString(), content); + double tmp = CharsToNumber(reinterpret_cast(content), 3); + MOZ_ASSERT(tmp == result); +#endif + return result; +} + +UniqueChars ParserAtomsTable::toNewUTF8CharsZ( + FrontendContext* fc, TaggedParserAtomIndex index) const { + auto* alloc = fc->getAllocator(); + + if (index.isParserAtomIndex()) { + const auto* atom = getParserAtom(index.toParserAtomIndex()); + return UniqueChars( + atom->hasLatin1Chars() + ? JS::CharsToNewUTF8CharsZ(alloc, atom->latin1Range()).c_str() + : JS::CharsToNewUTF8CharsZ(alloc, atom->twoByteRange()).c_str()); + } + + if (index.isWellKnownAtomId()) { + const auto& info = GetWellKnownAtomInfo(index.toWellKnownAtomId()); + return UniqueChars( + JS::CharsToNewUTF8CharsZ( + alloc, + mozilla::Range(reinterpret_cast(info.content), + info.length)) + .c_str()); + } + + if (index.isLength1StaticParserString()) { + Latin1Char content[1]; + getLength1Content(index.toLength1StaticParserString(), content); + return UniqueChars( + JS::CharsToNewUTF8CharsZ(alloc, mozilla::Range(content, 1)).c_str()); + } + + if (index.isLength2StaticParserString()) { + char content[2]; + getLength2Content(index.toLength2StaticParserString(), content); + return UniqueChars( + JS::CharsToNewUTF8CharsZ( + alloc, + mozilla::Range(reinterpret_cast(content), 2)) + .c_str()); + } + + MOZ_ASSERT(index.isLength3StaticParserString()); + char content[3]; + getLength3Content(index.toLength3StaticParserString(), content); + return UniqueChars( + JS::CharsToNewUTF8CharsZ( + alloc, + mozilla::Range(reinterpret_cast(content), 3)) + .c_str()); +} + +template +UniqueChars ToPrintableStringImpl(mozilla::Range str, + char quote = '\0') { + // Pass nullptr as JSContext, given we don't use JSString. + // OOM should be handled by caller. + Sprinter sprinter(nullptr); + if (!sprinter.init()) { + return nullptr; + } + if (!QuoteString(&sprinter, str, quote)) { + return nullptr; + } + return sprinter.release(); +} + +UniqueChars ParserAtomsTable::toPrintableString( + TaggedParserAtomIndex index) const { + if (index.isParserAtomIndex()) { + const auto* atom = getParserAtom(index.toParserAtomIndex()); + return atom->hasLatin1Chars() ? ToPrintableStringImpl(atom->latin1Range()) + : ToPrintableStringImpl(atom->twoByteRange()); + } + + if (index.isWellKnownAtomId()) { + const auto& info = GetWellKnownAtomInfo(index.toWellKnownAtomId()); + return ToPrintableStringImpl(mozilla::Range( + reinterpret_cast(info.content), info.length)); + } + + if (index.isLength1StaticParserString()) { + Latin1Char content[1]; + getLength1Content(index.toLength1StaticParserString(), content); + return ToPrintableStringImpl(mozilla::Range(content, 1)); + } + + if (index.isLength2StaticParserString()) { + char content[2]; + getLength2Content(index.toLength2StaticParserString(), content); + return ToPrintableStringImpl( + mozilla::Range(reinterpret_cast(content), 2)); + } + + MOZ_ASSERT(index.isLength3StaticParserString()); + char content[3]; + getLength3Content(index.toLength3StaticParserString(), content); + return ToPrintableStringImpl( + mozilla::Range(reinterpret_cast(content), 3)); +} + +UniqueChars ParserAtomsTable::toQuotedString( + TaggedParserAtomIndex index) const { + if (index.isParserAtomIndex()) { + const auto* atom = getParserAtom(index.toParserAtomIndex()); + return atom->hasLatin1Chars() + ? ToPrintableStringImpl(atom->latin1Range(), '\"') + : ToPrintableStringImpl(atom->twoByteRange(), '\"'); + } + + if (index.isWellKnownAtomId()) { + const auto& info = GetWellKnownAtomInfo(index.toWellKnownAtomId()); + return ToPrintableStringImpl( + mozilla::Range(reinterpret_cast(info.content), + info.length), + '\"'); + } + + if (index.isLength1StaticParserString()) { + Latin1Char content[1]; + getLength1Content(index.toLength1StaticParserString(), content); + return ToPrintableStringImpl(mozilla::Range(content, 1), + '\"'); + } + + if (index.isLength2StaticParserString()) { + char content[2]; + getLength2Content(index.toLength2StaticParserString(), content); + return ToPrintableStringImpl( + mozilla::Range(reinterpret_cast(content), 2), '\"'); + } + + MOZ_ASSERT(index.isLength3StaticParserString()); + char content[3]; + getLength3Content(index.toLength3StaticParserString(), content); + return ToPrintableStringImpl( + mozilla::Range(reinterpret_cast(content), 3), '\"'); +} + +JSAtom* ParserAtomsTable::toJSAtom(JSContext* cx, FrontendContext* fc, + TaggedParserAtomIndex index, + CompilationAtomCache& atomCache) const { + // This function can be called before we instantiate atoms based on + // AtomizeFlag. + + if (index.isParserAtomIndex()) { + auto atomIndex = index.toParserAtomIndex(); + + // If we already instantiated this parser atom, it should always be JSAtom. + // `asAtom()` called in getAtomAt asserts that. + JSAtom* atom = atomCache.getAtomAt(atomIndex); + if (atom) { + return atom; + } + + // For consistency, mark atomize. + ParserAtom* parserAtom = getParserAtom(atomIndex); + parserAtom->markAtomize(ParserAtom::Atomize::Yes); + return parserAtom->instantiateAtom(cx, fc, atomIndex, atomCache); + } + + if (index.isWellKnownAtomId()) { + return GetWellKnownAtom(cx, index.toWellKnownAtomId()); + } + + if (index.isLength1StaticParserString()) { + char16_t ch = static_cast(index.toLength1StaticParserString()); + return cx->staticStrings().getUnit(ch); + } + + if (index.isLength2StaticParserString()) { + size_t s = static_cast(index.toLength2StaticParserString()); + return cx->staticStrings().getLength2FromIndex(s); + } + + MOZ_ASSERT(index.isLength3StaticParserString()); + uint32_t s = uint32_t(index.toLength3StaticParserString()); + return cx->staticStrings().getUint(s); +} + +bool ParserAtomsTable::appendTo(StringBuffer& buffer, + TaggedParserAtomIndex index) const { + if (index.isParserAtomIndex()) { + const auto* atom = getParserAtom(index.toParserAtomIndex()); + size_t length = atom->length(); + return atom->hasLatin1Chars() ? buffer.append(atom->latin1Chars(), length) + : buffer.append(atom->twoByteChars(), length); + } + + if (index.isWellKnownAtomId()) { + const auto& info = GetWellKnownAtomInfo(index.toWellKnownAtomId()); + return buffer.append(info.content, info.length); + } + + if (index.isLength1StaticParserString()) { + Latin1Char content[1]; + getLength1Content(index.toLength1StaticParserString(), content); + return buffer.append(content[0]); + } + + if (index.isLength2StaticParserString()) { + char content[2]; + getLength2Content(index.toLength2StaticParserString(), content); + return buffer.append(content, 2); + } + + MOZ_ASSERT(index.isLength3StaticParserString()); + char content[3]; + getLength3Content(index.toLength3StaticParserString(), content); + return buffer.append(content, 3); +} + +bool InstantiateMarkedAtoms(JSContext* cx, FrontendContext* fc, + const ParserAtomSpan& entries, + CompilationAtomCache& atomCache) { + MOZ_ASSERT(cx->zone()); + + for (size_t i = 0; i < entries.size(); i++) { + const auto& entry = entries[i]; + if (!entry) { + continue; + } + if (!entry->isUsedByStencil()) { + continue; + } + + auto index = ParserAtomIndex(i); + if (atomCache.hasAtomAt(index)) { + continue; + } + + if (!entry->isInstantiatedAsJSAtom()) { + if (!entry->instantiateString(cx, fc, index, atomCache)) { + return false; + } + } else { + if (!entry->instantiateAtom(cx, fc, index, atomCache)) { + return false; + } + } + } + return true; +} + +bool InstantiateMarkedAtomsAsPermanent(JSContext* cx, FrontendContext* fc, + AtomSet& atomSet, + const ParserAtomSpan& entries, + CompilationAtomCache& atomCache) { + MOZ_ASSERT(!cx->zone()); + + for (size_t i = 0; i < entries.size(); i++) { + const auto& entry = entries[i]; + if (!entry) { + continue; + } + if (!entry->isUsedByStencil()) { + continue; + } + + auto index = ParserAtomIndex(i); + if (atomCache.hasAtomAt(index)) { + MOZ_ASSERT(atomCache.getAtomAt(index)->isPermanentAtom()); + continue; + } + + if (!entry->instantiatePermanentAtom(cx, fc, atomSet, index, atomCache)) { + return false; + } + } + return true; +} + +/* static */ +WellKnownParserAtoms WellKnownParserAtoms::singleton_; + +template +TaggedParserAtomIndex WellKnownParserAtoms::lookupChar16Seq( + const SpecificParserAtomLookup& lookup) const { + EntryMap::Ptr ptr = wellKnownMap_.readonlyThreadsafeLookup(lookup); + if (ptr) { + return ptr->value(); + } + return TaggedParserAtomIndex::null(); +} + +TaggedParserAtomIndex WellKnownParserAtoms::lookupTinyIndexUTF8( + const mozilla::Utf8Unit* utf8Ptr, size_t nbyte) const { + // Check for tiny strings which are abundant in minified code. + if (nbyte == 2 && IsLatin1(utf8Ptr[0], utf8Ptr[1])) { + // Special case the length-1 non-ASCII range. + InflatedChar16Sequence seq(utf8Ptr, 2); + char16_t u = seq.next(); + const Latin1Char c = u; + MOZ_ASSERT(!seq.hasMore()); + auto tiny = lookupTinyIndex(&c, 1); + MOZ_ASSERT(tiny); + return tiny; + } + + // NOTE: Other than length-1 non-ASCII range, the tiny atoms are all + // ASCII-only so we can directly look at the UTF-8 data without + // worrying about surrogates. + return lookupTinyIndex(reinterpret_cast(utf8Ptr), nbyte); +} + +bool WellKnownParserAtoms::initSingle(const WellKnownAtomInfo& info, + TaggedParserAtomIndex index) { + unsigned int len = info.length; + const Latin1Char* str = reinterpret_cast(info.content); + + // Well-known atoms are all currently ASCII with length <= MaxWellKnownLength. + MOZ_ASSERT(len <= MaxWellKnownLength); + MOZ_ASSERT(mozilla::IsAscii(mozilla::Span(info.content, len))); + + // Strings matched by lookupTinyIndex are stored in static table and aliases + // should be initialized directly in WellKnownParserAtoms::init. + MOZ_ASSERT(lookupTinyIndex(str, len) == TaggedParserAtomIndex::null(), + "Well-known atom matches a tiny StaticString. Did you add it to " + "the wrong CommonPropertyNames.h list?"); + + InflatedChar16Sequence seq(str, len); + SpecificParserAtomLookup lookup(seq, info.hash); + + // Save name for returning after moving entry into set. + if (!wellKnownMap_.putNew(lookup, &info, index)) { + return false; + } + + return true; +} + +bool WellKnownParserAtoms::init() { + MOZ_ASSERT(wellKnownMap_.empty()); + + // Add well-known strings to the HashMap. The HashMap is used for dynamic + // lookups later and does not change once this init method is complete. +#define COMMON_NAME_INIT_(_, NAME, _2) \ + if (!initSingle(GetWellKnownAtomInfo(WellKnownAtomId::NAME), \ + TaggedParserAtomIndex::WellKnown::NAME())) { \ + return false; \ + } + FOR_EACH_NONTINY_COMMON_PROPERTYNAME(COMMON_NAME_INIT_) +#undef COMMON_NAME_INIT_ +#define COMMON_NAME_INIT_(NAME, _) \ + if (!initSingle(GetWellKnownAtomInfo(WellKnownAtomId::NAME), \ + TaggedParserAtomIndex::WellKnown::NAME())) { \ + return false; \ + } + JS_FOR_EACH_PROTOTYPE(COMMON_NAME_INIT_) +#undef COMMON_NAME_INIT_ +#define COMMON_NAME_INIT_(NAME) \ + if (!initSingle(GetWellKnownAtomInfo(WellKnownAtomId::NAME), \ + TaggedParserAtomIndex::WellKnown::NAME())) { \ + return false; \ + } + JS_FOR_EACH_WELL_KNOWN_SYMBOL(COMMON_NAME_INIT_) +#undef COMMON_NAME_INIT_ + + return true; +} + +void WellKnownParserAtoms::free() { wellKnownMap_.clear(); } + +/* static */ bool WellKnownParserAtoms::initSingleton() { + return singleton_.init(); +} + +/* static */ void WellKnownParserAtoms::freeSingleton() { singleton_.free(); } + +} /* namespace frontend */ +} /* namespace js */ diff --git a/js/src/frontend/ParserAtom.h b/js/src/frontend/ParserAtom.h new file mode 100644 index 0000000000..86a033af2c --- /dev/null +++ b/js/src/frontend/ParserAtom.h @@ -0,0 +1,905 @@ +/* -*- 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/. */ + +#ifndef frontend_ParserAtom_h +#define frontend_ParserAtom_h + +#include "mozilla/MemoryReporting.h" // mozilla::MallocSizeOf +#include "mozilla/Range.h" // mozilla::Range +#include "mozilla/Span.h" // mozilla::Span +#include "mozilla/TextUtils.h" + +#include +#include + +#include "jstypes.h" +#include "NamespaceImports.h" + +#include "frontend/TypedIndex.h" // TypedIndex +#include "js/HashTable.h" // HashMap +#include "js/ProtoKey.h" // JS_FOR_EACH_PROTOTYPE +#include "js/Symbol.h" // JS_FOR_EACH_WELL_KNOWN_SYMBOL +#include "js/TypeDecls.h" // Latin1Char +#include "js/Utility.h" // UniqueChars +#include "js/Vector.h" // Vector +#include "threading/Mutex.h" // Mutex +#include "util/Text.h" // InflatedChar16Sequence +#include "vm/CommonPropertyNames.h" +#include "vm/StaticStrings.h" +#include "vm/WellKnownAtom.h" // WellKnownAtomId, WellKnownAtomInfo + +struct JS_PUBLIC_API JSContext; + +class JSAtom; +class JSString; + +namespace mozilla { +union Utf8Unit; +} + +namespace js { + +class AtomSet; +class JS_PUBLIC_API GenericPrinter; +class LifoAlloc; +class StringBuffer; + +namespace frontend { + +struct CompilationAtomCache; +struct CompilationStencil; + +template +class SpecificParserAtomLookup; + +// These types correspond into indices in the StaticStrings arrays. +enum class Length1StaticParserString : uint8_t; +enum class Length2StaticParserString : uint16_t; +enum class Length3StaticParserString : uint8_t; + +class ParserAtom; +using ParserAtomIndex = TypedIndex; + +// ParserAtomIndex, WellKnownAtomId, Length1StaticParserString, +// Length2StaticParserString, Length3StaticParserString, or null. +// +// 0x0000_0000 Null atom +// +// 0x1YYY_YYYY 28-bit ParserAtom +// +// 0x2000_YYYY Well-known atom ID +// 0x2001_YYYY Static length-1 atom : whole Latin1 range +// 0x2002_YYYY Static length-2 atom : `[A-Za-z0-9$_]{2}` +// 0x2003_YYYY Static length-3 atom : decimal "100" to "255" +class TaggedParserAtomIndex { + uint32_t data_; + + public: + static constexpr size_t IndexBit = 28; + static constexpr size_t IndexMask = BitMask(IndexBit); + + static constexpr size_t TagShift = IndexBit; + static constexpr size_t TagBit = 4; + static constexpr size_t TagMask = BitMask(TagBit) << TagShift; + + enum class Kind : uint32_t { + Null = 0, + ParserAtomIndex, + WellKnown, + }; + + private: + static constexpr size_t SmallIndexBit = 16; + static constexpr size_t SmallIndexMask = BitMask(SmallIndexBit); + + static constexpr size_t SubTagShift = SmallIndexBit; + static constexpr size_t SubTagBit = 2; + static constexpr size_t SubTagMask = BitMask(SubTagBit) << SubTagShift; + + public: + static constexpr uint32_t NullTag = uint32_t(Kind::Null) << TagShift; + static constexpr uint32_t ParserAtomIndexTag = uint32_t(Kind::ParserAtomIndex) + << TagShift; + static constexpr uint32_t WellKnownTag = uint32_t(Kind::WellKnown) + << TagShift; + + private: + static constexpr uint32_t WellKnownSubTag = 0 << SubTagShift; + static constexpr uint32_t Length1StaticSubTag = 1 << SubTagShift; + static constexpr uint32_t Length2StaticSubTag = 2 << SubTagShift; + static constexpr uint32_t Length3StaticSubTag = 3 << SubTagShift; + + public: + static constexpr uint32_t IndexLimit = Bit(IndexBit); + static constexpr uint32_t SmallIndexLimit = Bit(SmallIndexBit); + + static constexpr size_t Length1StaticLimit = 256U; + static constexpr size_t Length2StaticLimit = + StaticStrings::NUM_LENGTH2_ENTRIES; + static constexpr size_t Length3StaticLimit = 256U; + + private: + explicit TaggedParserAtomIndex(uint32_t data) : data_(data) {} + + public: + constexpr TaggedParserAtomIndex() : data_(NullTag) {} + + explicit constexpr TaggedParserAtomIndex(ParserAtomIndex index) + : data_(index.index | ParserAtomIndexTag) { + MOZ_ASSERT(index.index < IndexLimit); + } + explicit constexpr TaggedParserAtomIndex(WellKnownAtomId index) + : data_(uint32_t(index) | WellKnownTag | WellKnownSubTag) { + MOZ_ASSERT(uint32_t(index) < SmallIndexLimit); + + // Length1Static/Length2Static string shouldn't use WellKnownAtomId. +#define CHECK_(_, NAME, _2) MOZ_ASSERT(index != WellKnownAtomId::NAME); + FOR_EACH_NON_EMPTY_TINY_PROPERTYNAME(CHECK_) +#undef CHECK_ + } + explicit constexpr TaggedParserAtomIndex(Length1StaticParserString index) + : data_(uint32_t(index) | WellKnownTag | Length1StaticSubTag) {} + explicit constexpr TaggedParserAtomIndex(Length2StaticParserString index) + : data_(uint32_t(index) | WellKnownTag | Length2StaticSubTag) {} + explicit constexpr TaggedParserAtomIndex(Length3StaticParserString index) + : data_(uint32_t(index) | WellKnownTag | Length3StaticSubTag) {} + + class WellKnown { + public: +#define METHOD_(_, NAME, _2) \ + static constexpr TaggedParserAtomIndex NAME() { \ + return TaggedParserAtomIndex(WellKnownAtomId::NAME); \ + } + FOR_EACH_NONTINY_COMMON_PROPERTYNAME(METHOD_) +#undef METHOD_ + +#define METHOD_(NAME, _) \ + static constexpr TaggedParserAtomIndex NAME() { \ + return TaggedParserAtomIndex(WellKnownAtomId::NAME); \ + } + JS_FOR_EACH_PROTOTYPE(METHOD_) +#undef METHOD_ + +#define METHOD_(NAME) \ + static constexpr TaggedParserAtomIndex NAME() { \ + return TaggedParserAtomIndex(WellKnownAtomId::NAME); \ + } + JS_FOR_EACH_WELL_KNOWN_SYMBOL(METHOD_) +#undef METHOD_ + +#define METHOD_(_, NAME, STR) \ + static constexpr TaggedParserAtomIndex NAME() { \ + return TaggedParserAtomIndex(Length1StaticParserString((STR)[0])); \ + } + FOR_EACH_LENGTH1_PROPERTYNAME(METHOD_) +#undef METHOD_ + +#define METHOD_(_, NAME, STR) \ + static constexpr TaggedParserAtomIndex NAME() { \ + return TaggedParserAtomIndex(Length2StaticParserString( \ + (StaticStrings::getLength2IndexStatic((STR)[0], (STR)[1])))); \ + } + FOR_EACH_LENGTH2_PROPERTYNAME(METHOD_) +#undef METHOD_ + + static constexpr TaggedParserAtomIndex empty() { + return TaggedParserAtomIndex(WellKnownAtomId::empty); + } + }; + + // The value of rawData() for WellKnown TaggedParserAtomIndex. + // For using in switch-case. + class WellKnownRawData { + public: +#define METHOD_(_, NAME, _2) \ + static constexpr uint32_t NAME() { \ + return uint32_t(WellKnownAtomId::NAME) | WellKnownTag | WellKnownSubTag; \ + } + FOR_EACH_NONTINY_COMMON_PROPERTYNAME(METHOD_) +#undef METHOD_ + +#define METHOD_(NAME, _) \ + static constexpr uint32_t NAME() { \ + return uint32_t(WellKnownAtomId::NAME) | WellKnownTag | WellKnownSubTag; \ + } + JS_FOR_EACH_PROTOTYPE(METHOD_) +#undef METHOD_ + +#define METHOD_(NAME) \ + static constexpr uint32_t NAME() { \ + return uint32_t(WellKnownAtomId::NAME) | WellKnownTag | WellKnownSubTag; \ + } + JS_FOR_EACH_WELL_KNOWN_SYMBOL(METHOD_) +#undef METHOD_ + +#define METHOD_(_, NAME, STR) \ + static constexpr uint32_t NAME() { \ + return uint32_t((STR)[0]) | WellKnownTag | Length1StaticSubTag; \ + } + FOR_EACH_LENGTH1_PROPERTYNAME(METHOD_) +#undef METHOD_ + +#define METHOD_(_, NAME, STR) \ + static constexpr uint32_t NAME() { \ + return uint32_t( \ + StaticStrings::getLength2IndexStatic((STR)[0], (STR)[1])) | \ + WellKnownTag | Length2StaticSubTag; \ + } + FOR_EACH_LENGTH2_PROPERTYNAME(METHOD_) +#undef METHOD_ + + static constexpr uint32_t empty() { + return uint32_t(WellKnownAtomId::empty) | WellKnownTag | WellKnownSubTag; + } + }; + + // NOTE: this is not well-known "null". + static TaggedParserAtomIndex null() { return TaggedParserAtomIndex(); } + +#ifdef DEBUG + void validateRaw(); +#endif + + static TaggedParserAtomIndex fromRaw(uint32_t data) { + auto result = TaggedParserAtomIndex(data); +#ifdef DEBUG + result.validateRaw(); +#endif + return result; + } + + bool isParserAtomIndex() const { + return (data_ & TagMask) == ParserAtomIndexTag; + } + bool isWellKnownAtomId() const { + return (data_ & (TagMask | SubTagMask)) == (WellKnownTag | WellKnownSubTag); + } + bool isLength1StaticParserString() const { + return (data_ & (TagMask | SubTagMask)) == + (WellKnownTag | Length1StaticSubTag); + } + bool isLength2StaticParserString() const { + return (data_ & (TagMask | SubTagMask)) == + (WellKnownTag | Length2StaticSubTag); + } + bool isLength3StaticParserString() const { + return (data_ & (TagMask | SubTagMask)) == + (WellKnownTag | Length3StaticSubTag); + } + bool isNull() const { + bool result = !data_; + MOZ_ASSERT_IF(result, (data_ & TagMask) == NullTag); + return result; + } + HashNumber staticOrWellKnownHash() const; + + ParserAtomIndex toParserAtomIndex() const { + MOZ_ASSERT(isParserAtomIndex()); + return ParserAtomIndex(data_ & IndexMask); + } + WellKnownAtomId toWellKnownAtomId() const { + MOZ_ASSERT(isWellKnownAtomId()); + return WellKnownAtomId(data_ & SmallIndexMask); + } + Length1StaticParserString toLength1StaticParserString() const { + MOZ_ASSERT(isLength1StaticParserString()); + return Length1StaticParserString(data_ & SmallIndexMask); + } + Length2StaticParserString toLength2StaticParserString() const { + MOZ_ASSERT(isLength2StaticParserString()); + return Length2StaticParserString(data_ & SmallIndexMask); + } + Length3StaticParserString toLength3StaticParserString() const { + MOZ_ASSERT(isLength3StaticParserString()); + return Length3StaticParserString(data_ & SmallIndexMask); + } + + uint32_t* rawDataRef() { return &data_; } + uint32_t rawData() const { return data_; } + + bool operator==(const TaggedParserAtomIndex& rhs) const { + return data_ == rhs.data_; + } + bool operator!=(const TaggedParserAtomIndex& rhs) const { + return data_ != rhs.data_; + } + + explicit operator bool() const { return !isNull(); } +}; + +// Trivial variant of TaggedParserAtomIndex, to use in collection that requires +// trivial type. +// Provides minimal set of methods to use in collection. +class TrivialTaggedParserAtomIndex { + uint32_t data_; + + public: + static TrivialTaggedParserAtomIndex from(TaggedParserAtomIndex index) { + TrivialTaggedParserAtomIndex result; + result.data_ = index.rawData(); + return result; + } + + operator TaggedParserAtomIndex() const { + return TaggedParserAtomIndex::fromRaw(data_); + } + + static TrivialTaggedParserAtomIndex null() { + TrivialTaggedParserAtomIndex result; + result.data_ = 0; + return result; + } + + bool isNull() const { + static_assert(TaggedParserAtomIndex::NullTag == 0); + return data_ == 0; + } + + uint32_t rawData() const { return data_; } + + bool operator==(const TrivialTaggedParserAtomIndex& rhs) const { + return data_ == rhs.data_; + } + bool operator!=(const TrivialTaggedParserAtomIndex& rhs) const { + return data_ != rhs.data_; + } + + explicit operator bool() const { return !isNull(); } +}; + +/** + * A ParserAtom is an in-parser representation of an interned atomic + * string. It mostly mirrors the information carried by a JSAtom*. + * + * The atom contents are stored in one of two locations: + * 1. Inline Latin1Char storage (immediately after the ParserAtom memory). + * 2. Inline char16_t storage (immediately after the ParserAtom memory). + */ +class alignas(alignof(uint32_t)) ParserAtom { + friend class ParserAtomsTable; + friend class WellKnownParserAtoms; + + static const uint16_t MAX_LATIN1_CHAR = 0xff; + + // Bit flags inside flags_. + static constexpr uint32_t HasTwoByteCharsFlag = 1 << 0; + static constexpr uint32_t UsedByStencilFlag = 1 << 1; + static constexpr uint32_t AtomizeFlag = 1 << 2; + + public: + // Whether to atomize the ParserAtom during instantiation. + // + // If this ParserAtom is used by opcode with JOF_ATOM, or used as a binding + // in scope, it needs to be instantiated as JSAtom. + // Otherwise, it needs to be instantiated as LinearString, to reduce the + // cost of atomization. + enum class Atomize : uint32_t { + No = 0, + Yes = AtomizeFlag, + }; + + private: + // Helper routine to read some sequence of two-byte chars, and write them + // into a target buffer of a particular character width. + // + // The characters in the sequence must have been verified prior + template + static void drainChar16Seq(CharT* buf, InflatedChar16Sequence seq, + uint32_t length) { + static_assert( + std::is_same_v || std::is_same_v, + "Invalid target buffer type."); + CharT* cur = buf; + while (seq.hasMore()) { + char16_t ch = seq.next(); + if constexpr (std::is_same_v) { + MOZ_ASSERT(ch <= MAX_LATIN1_CHAR); + } + MOZ_ASSERT(cur < (buf + length)); + *cur = ch; + cur++; + } + } + + private: + // The JSAtom-compatible hash of the string. + HashNumber hash_ = 0; + + // The length of the buffer in chars_. + uint32_t length_ = 0; + + uint32_t flags_ = 0; + + // End of fields. + + ParserAtom(uint32_t length, HashNumber hash, bool hasTwoByteChars) + : hash_(hash), + length_(length), + flags_(hasTwoByteChars ? HasTwoByteCharsFlag : 0) {} + + public: + // The constexpr constructor is used by XDR + constexpr ParserAtom() = default; + + // ParserAtoms may own their content buffers in variant_, and thus + // cannot be copy-constructed - as a new chars would need to be allocated. + ParserAtom(const ParserAtom&) = delete; + ParserAtom(ParserAtom&& other) = delete; + + template + static ParserAtom* allocate(FrontendContext* fc, LifoAlloc& alloc, + InflatedChar16Sequence seq, + uint32_t length, HashNumber hash); + + bool hasLatin1Chars() const { return !(flags_ & HasTwoByteCharsFlag); } + bool hasTwoByteChars() const { return flags_ & HasTwoByteCharsFlag; } + + bool isAscii() const { + if (hasTwoByteChars()) { + return false; + } + for (Latin1Char ch : latin1Range()) { + if (!mozilla::IsAscii(ch)) { + return false; + } + } + return true; + } + + bool isPrivateName() const { + if (length() < 2) { + return false; + } + + return charAt(0) == '#'; + } + + HashNumber hash() const { return hash_; } + uint32_t length() const { return length_; } + + bool isUsedByStencil() const { return flags_ & UsedByStencilFlag; } + + private: + bool isMarkedAtomize() const { return flags_ & AtomizeFlag; } + + static constexpr uint32_t MinimumLengthForNonAtom = 8; + + public: + bool isInstantiatedAsJSAtom() const; + + template + bool equalsSeq(HashNumber hash, InflatedChar16Sequence seq) const; + + // Convert NotInstantiated and usedByStencil entry to a js-atom. + JSString* instantiateString(JSContext* cx, FrontendContext* fc, + ParserAtomIndex index, + CompilationAtomCache& atomCache) const; + JSAtom* instantiateAtom(JSContext* cx, FrontendContext* fc, + ParserAtomIndex index, + CompilationAtomCache& atomCache) const; + JSAtom* instantiatePermanentAtom(JSContext* cx, FrontendContext* fc, + AtomSet& atomSet, ParserAtomIndex index, + CompilationAtomCache& atomCache) const; + + private: + void markUsedByStencil(Atomize atomize) { + flags_ |= UsedByStencilFlag | uint32_t(atomize); + } + void markAtomize(Atomize atomize) { flags_ |= uint32_t(atomize); } + + template + const CharT* chars() const { + MOZ_ASSERT(sizeof(CharT) == (hasTwoByteChars() ? 2 : 1)); + return reinterpret_cast(this + 1); + } + + template + CharT* chars() { + MOZ_ASSERT(sizeof(CharT) == (hasTwoByteChars() ? 2 : 1)); + return reinterpret_cast(this + 1); + } + + const Latin1Char* latin1Chars() const { return chars(); } + const char16_t* twoByteChars() const { return chars(); } + mozilla::Range latin1Range() const { + return mozilla::Range(latin1Chars(), length_); + } + mozilla::Range twoByteRange() const { + return mozilla::Range(twoByteChars(), length_); + } + + // Returns index-th char. + // Boundary check isn't performed. + char16_t charAt(size_t index) const { + MOZ_ASSERT(index < length()); + if (hasLatin1Chars()) { + return latin1Chars()[index]; + } + return twoByteChars()[index]; + } + + public: +#if defined(DEBUG) || defined(JS_JITSPEW) + void dump() const; + void dumpCharsNoQuote(js::GenericPrinter& out) const; +#endif +}; + +/** + * A lookup structure that allows for querying ParserAtoms in + * a hashtable using a flexible input type that supports string + * representations of various forms. + */ +class ParserAtomLookup { + protected: + HashNumber hash_; + + ParserAtomLookup(HashNumber hash) : hash_(hash) {} + + public: + HashNumber hash() const { return hash_; } + + virtual bool equalsEntry(const ParserAtom* entry) const = 0; + virtual bool equalsEntry(const WellKnownAtomInfo* info) const = 0; +}; + +struct ParserAtomLookupHasher { + using Lookup = ParserAtomLookup; + + static inline HashNumber hash(const Lookup& l) { return l.hash(); } + static inline bool match(const ParserAtom* entry, const Lookup& l) { + return l.equalsEntry(entry); + } +}; + +struct WellKnownAtomInfoHasher { + using Lookup = ParserAtomLookup; + + static inline HashNumber hash(const Lookup& l) { return l.hash(); } + static inline bool match(const WellKnownAtomInfo* info, const Lookup& l) { + return l.equalsEntry(info); + } +}; + +using ParserAtomVector = Vector; +using ParserAtomSpan = mozilla::Span; + +/** + * WellKnownParserAtoms allows the parser to lookup up specific atoms in + * constant time. + */ +class WellKnownParserAtoms { + static WellKnownParserAtoms singleton_; + + // Common property and prototype names are tracked in a hash table. This table + // does not key for any items already in a direct-indexing tiny atom table. + using EntryMap = HashMap; + EntryMap wellKnownMap_; + + bool initSingle(const WellKnownAtomInfo& info, TaggedParserAtomIndex index); + + bool init(); + void free(); + + public: + static bool initSingleton(); + static void freeSingleton(); + + static WellKnownParserAtoms& getSingleton() { + MOZ_ASSERT(!singleton_.wellKnownMap_.empty()); + return singleton_; + } + + // Maximum length of any well known atoms. This can be increased if needed. + static constexpr size_t MaxWellKnownLength = 32; + + template + TaggedParserAtomIndex lookupChar16Seq( + const SpecificParserAtomLookup& lookup) const; + + template + TaggedParserAtomIndex lookupTinyIndex(CharsT chars, size_t length) const { + static_assert(std::is_same_v || + std::is_same_v || + std::is_same_v || + std::is_same_v, + "This assert mostly explicitly documents the calling types, " + "and forces that to be updated if new types show up."); + switch (length) { + case 0: + return TaggedParserAtomIndex::WellKnown::empty(); + + case 1: { + if (char16_t(chars[0]) < TaggedParserAtomIndex::Length1StaticLimit) { + return TaggedParserAtomIndex(Length1StaticParserString(chars[0])); + } + break; + } + + case 2: + if (StaticStrings::fitsInSmallChar(chars[0]) && + StaticStrings::fitsInSmallChar(chars[1])) { + return TaggedParserAtomIndex(Length2StaticParserString( + StaticStrings::getLength2Index(chars[0], chars[1]))); + } + break; + + case 3: { + int i; + if (StaticStrings::fitsInLength3Static(chars[0], chars[1], chars[2], + &i)) { + return TaggedParserAtomIndex(Length3StaticParserString(i)); + } + break; + } + } + + // No match on tiny Atoms + return TaggedParserAtomIndex::null(); + } + + TaggedParserAtomIndex lookupTinyIndexUTF8(const mozilla::Utf8Unit* utf8Ptr, + size_t nbyte) const; + + size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const { + return wellKnownMap_.shallowSizeOfExcludingThis(mallocSizeOf); + } +}; + +bool InstantiateMarkedAtoms(JSContext* cx, FrontendContext* fc, + const ParserAtomSpan& entries, + CompilationAtomCache& atomCache); + +bool InstantiateMarkedAtomsAsPermanent(JSContext* cx, FrontendContext* fc, + AtomSet& atomSet, + const ParserAtomSpan& entries, + CompilationAtomCache& atomCache); + +/** + * A ParserAtomsTable owns and manages the vector of ParserAtom entries + * associated with a given compile session. + */ +class ParserAtomsTable { + friend struct CompilationStencil; + + private: + LifoAlloc* alloc_; + + // The ParserAtom are owned by the LifoAlloc. + using EntryMap = HashMap; + EntryMap entryMap_; + ParserAtomVector entries_; + + public: + explicit ParserAtomsTable(LifoAlloc& alloc); + ParserAtomsTable(ParserAtomsTable&&) = default; + ParserAtomsTable& operator=(ParserAtomsTable&& other) noexcept { + entryMap_ = std::move(other.entryMap_); + entries_ = std::move(other.entries_); + return *this; + } + + void fixupAlloc(LifoAlloc& alloc) { alloc_ = &alloc; } + + size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const { + return entryMap_.shallowSizeOfExcludingThis(mallocSizeOf) + + entries_.sizeOfExcludingThis(mallocSizeOf); + } + size_t sizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const { + return mallocSizeOf(this) + sizeOfExcludingThis(mallocSizeOf); + } + + private: + // Internal APIs for interning to the table after well-known atoms cases have + // been tested. + TaggedParserAtomIndex addEntry(FrontendContext* fc, EntryMap::AddPtr& addPtr, + ParserAtom* entry); + template + TaggedParserAtomIndex internChar16Seq(FrontendContext* fc, + EntryMap::AddPtr& addPtr, + HashNumber hash, + InflatedChar16Sequence seq, + uint32_t length); + + template + TaggedParserAtomIndex internExternalParserAtomImpl(FrontendContext* fc, + const ParserAtom* atom); + + public: + TaggedParserAtomIndex internAscii(FrontendContext* fc, const char* asciiPtr, + uint32_t length); + + TaggedParserAtomIndex internLatin1(FrontendContext* fc, + const JS::Latin1Char* latin1Ptr, + uint32_t length); + + TaggedParserAtomIndex internUtf8(FrontendContext* fc, + const mozilla::Utf8Unit* utf8Ptr, + uint32_t nbyte); + + TaggedParserAtomIndex internChar16(FrontendContext* fc, + const char16_t* char16Ptr, + uint32_t length); + + TaggedParserAtomIndex internJSAtom(FrontendContext* fc, + CompilationAtomCache& atomCache, + JSAtom* atom); + + // Intern ParserAtom data from other ParserAtomTable. + // This copies flags as well. + TaggedParserAtomIndex internExternalParserAtom(FrontendContext* fc, + const ParserAtom* atom); + + // The atomIndex given as argument is in relation with the context Stencil. + // The atomIndex might be a well-known or static, in which case this function + // is a no-op. + TaggedParserAtomIndex internExternalParserAtomIndex( + FrontendContext* fc, const CompilationStencil& context, + TaggedParserAtomIndex atomIndex); + + // Compare an internal atom index with an external atom index coming from the + // stencil given as argument. + bool isEqualToExternalParserAtomIndex(TaggedParserAtomIndex internal, + const CompilationStencil& context, + TaggedParserAtomIndex external) const; + + bool addPlaceholder(FrontendContext* fc); + + private: + const ParserAtom* getWellKnown(WellKnownAtomId atomId) const; + ParserAtom* getParserAtom(ParserAtomIndex index) const; + + public: + const ParserAtomVector& entries() const { return entries_; } + + // Accessors for querying atom properties. + bool isIdentifier(TaggedParserAtomIndex index) const; + bool isPrivateName(TaggedParserAtomIndex index) const; + bool isExtendedUnclonedSelfHostedFunctionName( + TaggedParserAtomIndex index) const; + bool isModuleExportName(TaggedParserAtomIndex index) const; + bool isIndex(TaggedParserAtomIndex index, uint32_t* indexp) const; + bool isInstantiatedAsJSAtom(TaggedParserAtomIndex index) const; + uint32_t length(TaggedParserAtomIndex index) const; + HashNumber hash(TaggedParserAtomIndex index) const; + + // Methods for atom. + void markUsedByStencil(TaggedParserAtomIndex index, + ParserAtom::Atomize atomize) const; + void markAtomize(TaggedParserAtomIndex index, + ParserAtom::Atomize atomize) const; + double toNumber(TaggedParserAtomIndex index) const; + UniqueChars toNewUTF8CharsZ(FrontendContext* fc, + TaggedParserAtomIndex index) const; + UniqueChars toPrintableString(TaggedParserAtomIndex index) const; + UniqueChars toQuotedString(TaggedParserAtomIndex index) const; + JSAtom* toJSAtom(JSContext* cx, FrontendContext* fc, + TaggedParserAtomIndex index, + CompilationAtomCache& atomCache) const; + + private: + JSAtom* toWellKnownJSAtom(JSContext* cx, TaggedParserAtomIndex index) const; + + public: + bool appendTo(StringBuffer& buffer, TaggedParserAtomIndex index) const; + + public: +#if defined(DEBUG) || defined(JS_JITSPEW) + void dump(TaggedParserAtomIndex index) const; + void dumpCharsNoQuote(js::GenericPrinter& out, + TaggedParserAtomIndex index) const; + + static void dumpCharsNoQuote(js::GenericPrinter& out, WellKnownAtomId id); + static void dumpCharsNoQuote(js::GenericPrinter& out, + Length1StaticParserString index); + static void dumpCharsNoQuote(js::GenericPrinter& out, + Length2StaticParserString index); + static void dumpCharsNoQuote(js::GenericPrinter& out, + Length3StaticParserString index); +#endif + + static void getLength1Content(Length1StaticParserString s, + Latin1Char contents[1]) { + contents[0] = Latin1Char(s); + } + + static void getLength2Content(Length2StaticParserString s, char contents[2]) { + contents[0] = StaticStrings::firstCharOfLength2(size_t(s)); + contents[1] = StaticStrings::secondCharOfLength2(size_t(s)); + } + + static void getLength3Content(Length3StaticParserString s, char contents[3]) { + contents[0] = StaticStrings::firstCharOfLength3(int32_t(s)); + contents[1] = StaticStrings::secondCharOfLength3(int32_t(s)); + contents[2] = StaticStrings::thirdCharOfLength3(int32_t(s)); + } +}; + +// Lightweight version of ParserAtomsTable. +// This doesn't support deduplication. +// Used while decoding XDR. +class ParserAtomSpanBuilder { + ParserAtomSpan& entries_; + + public: + explicit ParserAtomSpanBuilder(ParserAtomSpan& entries) : entries_(entries) {} + + bool allocate(FrontendContext* fc, LifoAlloc& alloc, size_t count); + + void set(ParserAtomIndex index, const ParserAtom* atom) { + entries_[index] = const_cast(atom); + } +}; + +template +class SpecificParserAtomLookup : public ParserAtomLookup { + // The sequence of characters to look up. + InflatedChar16Sequence seq_; + + public: + explicit SpecificParserAtomLookup(const InflatedChar16Sequence& seq) + : SpecificParserAtomLookup(seq, seq.computeHash()) {} + + SpecificParserAtomLookup(const InflatedChar16Sequence& seq, + HashNumber hash) + : ParserAtomLookup(hash), seq_(seq) { + MOZ_ASSERT(seq_.computeHash() == hash); + } + + virtual bool equalsEntry(const ParserAtom* entry) const override { + return entry->equalsSeq(hash_, seq_); + } + + virtual bool equalsEntry(const WellKnownAtomInfo* info) const override { + // Compare hashes first. + if (info->hash != hash_) { + return false; + } + + InflatedChar16Sequence seq = seq_; + for (uint32_t i = 0; i < info->length; i++) { + if (!seq.hasMore() || char16_t(info->content[i]) != seq.next()) { + return false; + } + } + return !seq.hasMore(); + } +}; + +template +inline bool ParserAtom::equalsSeq(HashNumber hash, + InflatedChar16Sequence seq) const { + // Compare hashes first. + if (hash_ != hash) { + return false; + } + + if (hasTwoByteChars()) { + const char16_t* chars = twoByteChars(); + for (uint32_t i = 0; i < length_; i++) { + if (!seq.hasMore() || chars[i] != seq.next()) { + return false; + } + } + } else { + const Latin1Char* chars = latin1Chars(); + for (uint32_t i = 0; i < length_; i++) { + if (!seq.hasMore() || char16_t(chars[i]) != seq.next()) { + return false; + } + } + } + return !seq.hasMore(); +} + +JSAtom* GetWellKnownAtom(JSContext* cx, WellKnownAtomId atomId); + +} /* namespace frontend */ +} /* namespace js */ + +#endif // frontend_ParserAtom_h diff --git a/js/src/frontend/PrivateOpEmitter.cpp b/js/src/frontend/PrivateOpEmitter.cpp new file mode 100644 index 0000000000..9e617b209e --- /dev/null +++ b/js/src/frontend/PrivateOpEmitter.cpp @@ -0,0 +1,331 @@ +/* -*- 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/PrivateOpEmitter.h" + +#include "frontend/BytecodeEmitter.h" +#include "frontend/NameOpEmitter.h" +#include "vm/Opcodes.h" +#include "vm/ThrowMsgKind.h" // ThrowMsgKind + +using namespace js; +using namespace js::frontend; + +PrivateOpEmitter::PrivateOpEmitter(BytecodeEmitter* bce, Kind kind, + TaggedParserAtomIndex name) + : bce_(bce), kind_(kind), name_(name) { + MOZ_ASSERT(kind_ != Kind::Delete); +} + +bool PrivateOpEmitter::init() { + // Static analysis needs us to initialise this to something, so use Dynamic() + NameLocation loc = NameLocation::Dynamic(); + bce_->lookupPrivate(name_, loc, brandLoc_); + loc_ = mozilla::Some(loc); + return true; +} + +bool PrivateOpEmitter::emitLoad(TaggedParserAtomIndex name, + const NameLocation& loc) { + NameOpEmitter noe(bce_, name, loc, NameOpEmitter::Kind::Get); + return noe.emitGet(); +} + +bool PrivateOpEmitter::emitLoadPrivateBrand() { + return emitLoad(TaggedParserAtomIndex::WellKnown::dotPrivateBrand(), + *brandLoc_); +} + +bool PrivateOpEmitter::emitBrandCheck() { + MOZ_ASSERT(state_ == State::Reference); + + if (isBrandCheck()) { + // Emit a CheckPrivateField CheckRhs; note: The message is irrelvant here, + // it will never be thrown, so DoubleInit was chosen arbitrarily. + if (!bce_->emitCheckPrivateField(ThrowCondition::OnlyCheckRhs, + ThrowMsgKind::PrivateDoubleInit)) { + // [stack] OBJ KEY BBOOL + return false; + } + + return true; + } + + // [stack] OBJ KEY + if (isFieldInit()) { + if (!bce_->emitCheckPrivateField(ThrowCondition::ThrowHas, + ThrowMsgKind::PrivateDoubleInit)) { + // [stack] OBJ KEY false + return false; + } + } else { + bool assigning = + isSimpleAssignment() || isCompoundAssignment() || isIncDec(); + if (!bce_->emitCheckPrivateField(ThrowCondition::ThrowHasNot, + assigning + ? ThrowMsgKind::MissingPrivateOnSet + : ThrowMsgKind::MissingPrivateOnGet)) { + // [stack] OBJ KEY true + return false; + } + } + + return true; +} + +bool PrivateOpEmitter::emitReference() { + MOZ_ASSERT(state_ == State::Start); + + if (!init()) { + return false; + } + + if (brandLoc_) { + if (!emitLoadPrivateBrand()) { + // [stack] OBJ BRAND + return false; + } + } else { + if (!emitLoad(name_, loc_.ref())) { + // [stack] OBJ NAME + return false; + } + } +#ifdef DEBUG + state_ = State::Reference; +#endif + return true; +} + +bool PrivateOpEmitter::skipReference() { + MOZ_ASSERT(state_ == State::Start); + + if (!init()) { + return false; + } + +#ifdef DEBUG + state_ = State::Reference; +#endif + return true; +} + +bool PrivateOpEmitter::emitGet() { + MOZ_ASSERT(state_ == State::Reference); + + // [stack] OBJ NAME + + if (brandLoc_) { + // Note that the decision of what we leave on the stack depends on kind_, + // not loc_->bindingKind(). We can't emit code for a call just because this + // private member is a method. `obj.#method` is allowed without a call, + // just fetching the function object (it's useful in code like + // `obj.#method.bind(...)`). Even if the user says `obj.#method += 7`, we + // emit honest bytecode for the brand check, method load, and addition, and + // throw the error later. This preserves stack nuses/ndefs balance. + if (!emitBrandCheck()) { + // [stack] OBJ BRAND true + return false; + } + + if (isCompoundAssignment()) { + if (!bce_->emit1(JSOp::Pop)) { + // [stack] OBJ BRAND + return false; + } + } else if (isCall()) { + if (!bce_->emitPopN(2)) { + // [stack] OBJ + return false; + } + } else { + if (!bce_->emitPopN(3)) { + // [stack] + return false; + } + } + + if (!emitLoad(name_, loc_.ref())) { + // [stack] OBJ BRAND METHOD # if isCompoundAssignment + // [stack] OBJ METHOD # if call + // [stack] METHOD # otherwise + return false; + } + } else { + if (isCall()) { + if (!bce_->emitDupAt(1)) { + // [stack] OBJ NAME OBJ + return false; + } + if (!bce_->emit1(JSOp::Swap)) { + // [stack] OBJ OBJ NAME + return false; + } + } + // [stack] OBJ? OBJ NAME + if (!emitBrandCheck()) { + // [stack] OBJ? OBJ NAME true + return false; + } + if (!bce_->emit1(JSOp::Pop)) { + // [stack] OBJ? OBJ NAME + return false; + } + + if (isCompoundAssignment()) { + if (!bce_->emit1(JSOp::Dup2)) { + // [stack] OBJ NAME OBJ NAME + return false; + } + } + + if (!bce_->emitElemOpBase(JSOp::GetElem)) { + // [stack] OBJ NAME VALUE # if isCompoundAssignment + // [stack] OBJ METHOD # if Call + // [stack] VALUE # otherwise + return false; + } + } + + if (isCall()) { + if (!bce_->emit1(JSOp::Swap)) { + // [stack] METHOD OBJ + return false; + } + } + + // [stack] OBJ NAME VALUE # if isCompoundAssignment + // [stack] METHOD OBJ # if call + // [stack] VALUE # otherwise + +#ifdef DEBUG + state_ = State::Get; +#endif + return true; +} + +bool PrivateOpEmitter::emitGetForCallOrNew() { return emitGet(); } + +bool PrivateOpEmitter::emitAssignment() { + MOZ_ASSERT(isSimpleAssignment() || isFieldInit() || isCompoundAssignment()); + MOZ_ASSERT_IF(!isCompoundAssignment(), state_ == State::Reference); + MOZ_ASSERT_IF(isCompoundAssignment(), state_ == State::Get); + + // [stack] OBJ KEY RHS + + if (brandLoc_) { + if (!bce_->emit2(JSOp::ThrowMsg, + uint8_t(ThrowMsgKind::AssignToPrivateMethod))) { + return false; + } + + // Balance the expression stack. + if (!bce_->emitPopN(2)) { + // [stack] OBJ + return false; + } + } else { + // Emit a brand check. If this is compound assignment, emitGet() already + // emitted a check for this object and key. There's no point checking + // again--a private field can't be removed from an object. + if (!isCompoundAssignment()) { + if (!bce_->emitUnpickN(2)) { + // [stack] RHS OBJ KEY + return false; + } + if (!emitBrandCheck()) { + // [stack] RHS OBJ KEY BOOL + return false; + } + if (!bce_->emit1(JSOp::Pop)) { + // [stack] RHS OBJ KEY + return false; + } + if (!bce_->emitPickN(2)) { + // [stack] OBJ KEY RHS + return false; + } + } + + JSOp setOp = isFieldInit() ? JSOp::InitElem : JSOp::StrictSetElem; + if (!bce_->emitElemOpBase(setOp)) { + // [stack] RHS + return false; + } + } + +#ifdef DEBUG + state_ = State::Assignment; +#endif + return true; +} + +bool PrivateOpEmitter::emitIncDec(ValueUsage valueUsage) { + MOZ_ASSERT(state_ == State::Reference); + MOZ_ASSERT(isIncDec()); + // [stack] OBJ NAME + + if (!bce_->emitDupAt(1, 2)) { + // [stack] OBJ NAME OBJ NAME + return false; + } + + if (!emitGet()) { + // [stack] OBJ NAME VALUE + return false; + } + + MOZ_ASSERT(state_ == State::Get); + + JSOp incOp = isInc() ? JSOp::Inc : JSOp::Dec; + + if (!bce_->emit1(JSOp::ToNumeric)) { + // [stack] OBJ NAME N + return false; + } + if (isPostIncDec() && valueUsage == ValueUsage::WantValue) { + // [stack] OBJ NAME N + if (!bce_->emit1(JSOp::Dup)) { + // [stack] OBJ NAME N N + return false; + } + if (!bce_->emit2(JSOp::Unpick, 3)) { + // [stack] N OBJ NAME N + return false; + } + } + if (!bce_->emit1(incOp)) { + // [stack] N? OBJ NAME N+1 + return false; + } + + if (brandLoc_) { + if (!bce_->emit2(JSOp::ThrowMsg, + uint8_t(ThrowMsgKind::AssignToPrivateMethod))) { + return false; + } + + // Balance the expression stack. + if (!bce_->emitPopN(2)) { + // [stack] N? N+1 + return false; + } + } else { + if (!bce_->emitElemOpBase(JSOp::StrictSetElem)) { + // [stack] N? N+1 + return false; + } + } + + if (isPostIncDec() && valueUsage == ValueUsage::WantValue) { + if (!bce_->emit1(JSOp::Pop)) { + // [stack] N + return false; + } + } + + return true; +} diff --git a/js/src/frontend/PrivateOpEmitter.h b/js/src/frontend/PrivateOpEmitter.h new file mode 100644 index 0000000000..558541b05c --- /dev/null +++ b/js/src/frontend/PrivateOpEmitter.h @@ -0,0 +1,231 @@ +/* -*- 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/. */ + +#ifndef frontend_PrivateOpEmitter_h +#define frontend_PrivateOpEmitter_h + +#include "mozilla/Attributes.h" +#include "mozilla/Maybe.h" + +#include + +#include "frontend/NameAnalysisTypes.h" // NameLocation +#include "frontend/ParserAtom.h" // TaggedParserAtomIndex + +namespace js { +namespace frontend { + +struct BytecodeEmitter; +enum class ValueUsage; + +// Class for emitting bytecode for operations on private members of objects. +// +// Usage is similar to PropOpEmitter, but the name of the private member must +// be passed to the constructor; prepare*() methods aren't necessary; and +// `delete obj.#member` and `super.#member` aren't supported because both are +// SyntaxErrors. +// +// Usage: (error checking is omitted for simplicity) +// +// `obj.#member;` +// PrivateOpEmitter xoe(this, +// privateName, +// PrivateOpEmitter::Kind::Get); +// emit(obj); +// xoe.emitReference(); +// xoe.emitGet(); +// +// `obj.#member();` +// PrivateOpEmitter xoe(this, +// privateName, +// PrivateOpEmitter::Kind::Call); +// emit(obj); +// xoe.emitReference(); +// xoe.emitGet(); +// emit_call_here(); +// +// `new obj.#member();` +// The same, but use PrivateOpEmitter::Kind::Get. +// +// `obj.#field++;` +// PrivateOpEmitter xoe(this, +// privateName, +// PrivateOpEmitter::Kind::PostIncrement); +// emit(obj); +// xoe.emitReference(); +// xoe.emitIncDec(); +// +// `obj.#field = value;` +// PrivateOpEmitter xoe(this, +// privateName, +// PrivateOpEmitter::Kind::SimpleAssignment); +// emit(obj); +// xoe.emitReference(); +// emit(value); +// xoe.emitAssignment(); +// +// `obj.#field += value;` +// PrivateOpEmitter xoe(this, +// privateName, +// PrivateOpEmitter::Kind::CompoundAssignment); +// emit(obj); +// xoe.emitReference(); +// emit(JSOp::Dup2); +// xoe.emitGet(); +// emit(value); +// emit_add_op_here(); +// xoe.emitAssignment(); +// +class MOZ_STACK_CLASS PrivateOpEmitter { + public: + enum class Kind { + Get, + Call, + Delete, + PostIncrement, + PreIncrement, + PostDecrement, + PreDecrement, + SimpleAssignment, + PropInit, + CompoundAssignment, + ErgonomicBrandCheck, + }; + + private: + BytecodeEmitter* bce_; + + Kind kind_; + + // Name of the private member, e.g. "#field". + TaggedParserAtomIndex name_; + + // Location of the slot containing the private name symbol; or, for a + // non-static private method, the slot containing the method. + mozilla::Maybe loc_; + + // For non-static private method accesses, the location of the relevant + // `.privateBrand` binding. Otherwise, `Nothing`. + mozilla::Maybe brandLoc_{}; + +#ifdef DEBUG + // The state of this emitter. + // + // emitReference + // +-------+ skipReference +-----------+ + // | Start |---------------->| Reference | + // +-------+ +-----+-----+ + // | + // +---------------------------+ + // | + // | + // | [Get] + // | emitGet + // | [Call] [Get] [CompoundAssignment] + // | emitGetForCallOrNew +-----+ emitAssignment + // +---------------------->| Get |-------------------+ + // | +-----+ | + // | [PostIncrement] | + // | [PreIncrement] | + // | [PostDecrement] | + // | [PreDecrement] | + // | emitIncDec | + // +------------------------------------------------>+ + // | | + // | [SimpleAssignment] | + // | [PropInit] V + // | emitAssignment +------------+ + // +------------------------------------------>| Assignment | + // +------------+ + enum class State { + // The initial state. + Start, + + // After calling emitReference or skipReference. + Reference, + + // After calling emitGet. + Get, + + // After calling emitAssignment or emitIncDec. + Assignment, + }; + State state_ = State::Start; +#endif + + public: + PrivateOpEmitter(BytecodeEmitter* bce, Kind kind, TaggedParserAtomIndex name); + + private: + [[nodiscard]] bool isCall() const { return kind_ == Kind::Call; } + + [[nodiscard]] bool isSimpleAssignment() const { + return kind_ == Kind::SimpleAssignment; + } + + [[nodiscard]] bool isFieldInit() const { return kind_ == Kind::PropInit; } + + [[nodiscard]] bool isBrandCheck() const { + return kind_ == Kind::ErgonomicBrandCheck; + } + + [[nodiscard]] bool isCompoundAssignment() const { + return kind_ == Kind::CompoundAssignment; + } + + [[nodiscard]] bool isIncDec() const { + return isPostIncDec() || isPreIncDec(); + } + + [[nodiscard]] bool isPostIncDec() const { + return kind_ == Kind::PostIncrement || kind_ == Kind::PostDecrement; + } + + [[nodiscard]] bool isPreIncDec() const { + return kind_ == Kind::PreIncrement || kind_ == Kind::PreDecrement; + } + + [[nodiscard]] bool isInc() const { + return kind_ == Kind::PostIncrement || kind_ == Kind::PreIncrement; + } + + [[nodiscard]] bool init(); + + // Emit a GetAliasedLexical or similar instruction. + [[nodiscard]] bool emitLoad(TaggedParserAtomIndex name, + const NameLocation& loc); + + [[nodiscard]] bool emitLoadPrivateBrand(); + + public: + // Emit bytecode to check for the presence/absence of a private field/brand. + // + // Given OBJ KEY on the stack, where KEY is a private name symbol, the + // emitted code will throw if OBJ does not have the given KEY. + // + // If `isFieldInit()`, the check is reversed: the code will throw if OBJ + // already has the KEY. + // + // If `isBrandCheck()`, the check verifies RHS is an object (throwing if not). + // + // The bytecode leaves OBJ KEY BOOL on the stack. Caller is responsible for + // consuming or popping it. + [[nodiscard]] bool emitBrandCheck(); + + [[nodiscard]] bool emitReference(); + [[nodiscard]] bool skipReference(); + [[nodiscard]] bool emitGet(); + [[nodiscard]] bool emitGetForCallOrNew(); + [[nodiscard]] bool emitAssignment(); + [[nodiscard]] bool emitIncDec(ValueUsage valueUsage); + + [[nodiscard]] size_t numReferenceSlots() { return 2; } +}; + +} /* namespace frontend */ +} /* namespace js */ + +#endif /* frontend_PrivateOpEmitter_h */ diff --git a/js/src/frontend/PropOpEmitter.cpp b/js/src/frontend/PropOpEmitter.cpp new file mode 100644 index 0000000000..ff08bd8580 --- /dev/null +++ b/js/src/frontend/PropOpEmitter.cpp @@ -0,0 +1,244 @@ +/* -*- 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/PropOpEmitter.h" + +#include "frontend/BytecodeEmitter.h" +#include "frontend/ParserAtom.h" // ParserAtom +#include "frontend/SharedContext.h" +#include "vm/Opcodes.h" +#include "vm/ThrowMsgKind.h" // ThrowMsgKind + +using namespace js; +using namespace js::frontend; + +PropOpEmitter::PropOpEmitter(BytecodeEmitter* bce, Kind kind, ObjKind objKind) + : bce_(bce), kind_(kind), objKind_(objKind) {} + +bool PropOpEmitter::prepareAtomIndex(TaggedParserAtomIndex prop) { + return bce_->makeAtomIndex(prop, ParserAtom::Atomize::Yes, &propAtomIndex_); +} + +bool PropOpEmitter::prepareForObj() { + MOZ_ASSERT(state_ == State::Start); + +#ifdef DEBUG + state_ = State::Obj; +#endif + return true; +} + +bool PropOpEmitter::emitGet(TaggedParserAtomIndex prop) { + MOZ_ASSERT(state_ == State::Obj); + + if (!prepareAtomIndex(prop)) { + return false; + } + if (isCall()) { + if (!bce_->emit1(JSOp::Dup)) { + // [stack] # if Super + // [stack] THIS THIS + // [stack] # otherwise + // [stack] OBJ OBJ + return false; + } + } + if (isSuper()) { + if (!bce_->emitSuperBase()) { + // [stack] THIS? THIS SUPERBASE + return false; + } + } + if (isIncDec() || isCompoundAssignment()) { + if (isSuper()) { + if (!bce_->emit1(JSOp::Dup2)) { + // [stack] THIS SUPERBASE THIS SUPERBASE + return false; + } + } else { + if (!bce_->emit1(JSOp::Dup)) { + // [stack] OBJ OBJ + return false; + } + } + } + + JSOp op = isSuper() ? JSOp::GetPropSuper : JSOp::GetProp; + if (!bce_->emitAtomOp(op, propAtomIndex_)) { + // [stack] # if Get + // [stack] PROP + // [stack] # if Call + // [stack] THIS PROP + // [stack] # if Inc/Dec/Compound, Super] + // [stack] THIS SUPERBASE PROP + // [stack] # if Inc/Dec/Compound, other + // [stack] OBJ PROP + return false; + } + if (isCall()) { + if (!bce_->emit1(JSOp::Swap)) { + // [stack] PROP THIS + return false; + } + } + +#ifdef DEBUG + state_ = State::Get; +#endif + return true; +} + +bool PropOpEmitter::prepareForRhs() { + MOZ_ASSERT(isSimpleAssignment() || isPropInit() || isCompoundAssignment()); + MOZ_ASSERT_IF(isSimpleAssignment() || isPropInit(), state_ == State::Obj); + MOZ_ASSERT_IF(isCompoundAssignment(), state_ == State::Get); + + if (isSimpleAssignment() || isPropInit()) { + // For CompoundAssignment, SuperBase is already emitted by emitGet. + if (isSuper()) { + if (!bce_->emitSuperBase()) { + // [stack] THIS SUPERBASE + return false; + } + } + } + +#ifdef DEBUG + state_ = State::Rhs; +#endif + return true; +} + +bool PropOpEmitter::skipObjAndRhs() { + MOZ_ASSERT(state_ == State::Start); + MOZ_ASSERT(isSimpleAssignment() || isPropInit()); + +#ifdef DEBUG + state_ = State::Rhs; +#endif + return true; +} + +bool PropOpEmitter::emitDelete(TaggedParserAtomIndex prop) { + MOZ_ASSERT_IF(!isSuper(), state_ == State::Obj); + MOZ_ASSERT_IF(isSuper(), state_ == State::Start); + MOZ_ASSERT(isDelete()); + + if (!prepareAtomIndex(prop)) { + return false; + } + if (isSuper()) { + if (!bce_->emitSuperBase()) { + // [stack] THIS SUPERBASE + return false; + } + + // Unconditionally throw when attempting to delete a super-reference. + if (!bce_->emit2(JSOp::ThrowMsg, uint8_t(ThrowMsgKind::CantDeleteSuper))) { + // [stack] THIS SUPERBASE + return false; + } + + // Another wrinkle: Balance the stack from the emitter's point of view. + // Execution will not reach here, as the last bytecode threw. + if (!bce_->emit1(JSOp::Pop)) { + // [stack] THIS + return false; + } + } else { + JSOp op = bce_->sc->strict() ? JSOp::StrictDelProp : JSOp::DelProp; + if (!bce_->emitAtomOp(op, propAtomIndex_)) { + // [stack] SUCCEEDED + return false; + } + } + +#ifdef DEBUG + state_ = State::Delete; +#endif + return true; +} + +bool PropOpEmitter::emitAssignment(TaggedParserAtomIndex prop) { + MOZ_ASSERT(isSimpleAssignment() || isPropInit() || isCompoundAssignment()); + MOZ_ASSERT(state_ == State::Rhs); + + if (isSimpleAssignment() || isPropInit()) { + if (!prepareAtomIndex(prop)) { + return false; + } + } + + MOZ_ASSERT_IF(isPropInit(), !isSuper()); + JSOp setOp = isPropInit() ? JSOp::InitProp + : isSuper() ? bce_->sc->strict() ? JSOp::StrictSetPropSuper + : JSOp::SetPropSuper + : bce_->sc->strict() ? JSOp::StrictSetProp + : JSOp::SetProp; + if (!bce_->emitAtomOp(setOp, propAtomIndex_)) { + // [stack] VAL + return false; + } + +#ifdef DEBUG + state_ = State::Assignment; +#endif + return true; +} + +bool PropOpEmitter::emitIncDec(TaggedParserAtomIndex prop, + ValueUsage valueUsage) { + MOZ_ASSERT(state_ == State::Obj); + MOZ_ASSERT(isIncDec()); + + if (!emitGet(prop)) { + return false; + } + + MOZ_ASSERT(state_ == State::Get); + + JSOp incOp = isInc() ? JSOp::Inc : JSOp::Dec; + + if (!bce_->emit1(JSOp::ToNumeric)) { + // [stack] ... N + return false; + } + if (isPostIncDec() && valueUsage == ValueUsage::WantValue) { + // [stack] OBJ SUPERBASE? N + if (!bce_->emit1(JSOp::Dup)) { + // [stack] .. N N + return false; + } + if (!bce_->emit2(JSOp::Unpick, 2 + isSuper())) { + // [stack] N OBJ SUPERBASE? N + return false; + } + } + if (!bce_->emit1(incOp)) { + // [stack] ... N+1 + return false; + } + + JSOp setOp = isSuper() ? bce_->sc->strict() ? JSOp::StrictSetPropSuper + : JSOp::SetPropSuper + : bce_->sc->strict() ? JSOp::StrictSetProp + : JSOp::SetProp; + if (!bce_->emitAtomOp(setOp, propAtomIndex_)) { + // [stack] N? N+1 + return false; + } + if (isPostIncDec() && valueUsage == ValueUsage::WantValue) { + if (!bce_->emit1(JSOp::Pop)) { + // [stack] N + return false; + } + } + +#ifdef DEBUG + state_ = State::IncDec; +#endif + return true; +} diff --git a/js/src/frontend/PropOpEmitter.h b/js/src/frontend/PropOpEmitter.h new file mode 100644 index 0000000000..76402025ea --- /dev/null +++ b/js/src/frontend/PropOpEmitter.h @@ -0,0 +1,254 @@ +/* -*- 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/. */ + +#ifndef frontend_PropOpEmitter_h +#define frontend_PropOpEmitter_h + +#include "mozilla/Attributes.h" + +#include "vm/SharedStencil.h" // GCThingIndex + +namespace js { +namespace frontend { + +struct BytecodeEmitter; +class TaggedParserAtomIndex; +enum class ValueUsage; + +// Class for emitting bytecode for property operation. +// +// Usage: (check for the return value is omitted for simplicity) +// +// `obj.prop;` +// PropOpEmitter poe(this, +// PropOpEmitter::Kind::Get, +// PropOpEmitter::ObjKind::Other); +// poe.prepareForObj(); +// emit(obj); +// poe.emitGet(atom_of_prop); +// +// `super.prop;` +// PropOpEmitter poe(this, +// PropOpEmitter::Kind::Get, +// PropOpEmitter::ObjKind::Super); +// poe.prepareForObj(); +// emit(obj); +// poe.emitGet(atom_of_prop); +// +// `obj.prop();` +// PropOpEmitter poe(this, +// PropOpEmitter::Kind::Call, +// PropOpEmitter::ObjKind::Other); +// poe.prepareForObj(); +// emit(obj); +// poe.emitGet(atom_of_prop); +// emit_call_here(); +// +// `new obj.prop();` +// PropOpEmitter poe(this, +// PropOpEmitter::Kind::Call, +// PropOpEmitter::ObjKind::Other); +// poe.prepareForObj(); +// emit(obj); +// poe.emitGet(atom_of_prop); +// emit_call_here(); +// +// `delete obj.prop;` +// PropOpEmitter poe(this, +// PropOpEmitter::Kind::Delete, +// PropOpEmitter::ObjKind::Other); +// poe.prepareForObj(); +// emit(obj); +// poe.emitDelete(atom_of_prop); +// +// `delete super.prop;` +// PropOpEmitter poe(this, +// PropOpEmitter::Kind::Delete, +// PropOpEmitter::ObjKind::Other); +// poe.emitDelete(atom_of_prop); +// +// `obj.prop++;` +// PropOpEmitter poe(this, +// PropOpEmitter::Kind::PostIncrement, +// PropOpEmitter::ObjKind::Other); +// poe.prepareForObj(); +// emit(obj); +// poe.emitIncDec(atom_of_prop); +// +// `obj.prop = value;` +// PropOpEmitter poe(this, +// PropOpEmitter::Kind::SimpleAssignment, +// PropOpEmitter::ObjKind::Other); +// poe.prepareForObj(); +// emit(obj); +// poe.prepareForRhs(); +// emit(value); +// poe.emitAssignment(atom_of_prop); +// +// `obj.prop += value;` +// PropOpEmitter poe(this, +// PropOpEmitter::Kind::CompoundAssignment, +// PropOpEmitter::ObjKind::Other); +// poe.prepareForObj(); +// emit(obj); +// poe.emitGet(atom_of_prop); +// poe.prepareForRhs(); +// emit(value); +// emit_add_op_here(); +// poe.emitAssignment(nullptr); // nullptr for CompoundAssignment +// +class MOZ_STACK_CLASS PropOpEmitter { + public: + enum class Kind { + Get, + Call, + Delete, + PostIncrement, + PreIncrement, + PostDecrement, + PreDecrement, + SimpleAssignment, + PropInit, + CompoundAssignment + }; + enum class ObjKind { Super, Other }; + + private: + BytecodeEmitter* bce_; + + Kind kind_; + ObjKind objKind_; + + // The index for the property name's atom. + GCThingIndex propAtomIndex_; + +#ifdef DEBUG + // The state of this emitter. + // + // skipObjAndRhs + // +----------------------------+ + // | | + // +-------+ | prepareForObj +-----+ | + // | Start |-+-------------->| Obj |-+ | + // +-------+ +-----+ | | + // | | + // +---------------------------------+ | + // | | + // | | + // | [Get] | + // | [Call] | + // | emitGet +-----+ | + // +---------->| Get | | + // | +-----+ | + // | | + // | [Delete] | + // | emitDelete +--------+ | + // +------------->| Delete | | + // | +--------+ | + // | | + // | [PostIncrement] | + // | [PreIncrement] | + // | [PostDecrement] | + // | [PreDecrement] | + // | emitIncDec +--------+ | + // +------------->| IncDec | | + // | +--------+ | + // | | + // | [SimpleAssignment] | + // | [PropInit] | + // | prepareForRhs | +-----+ + // +--------------------->+-------------->+->| Rhs |-+ + // | ^ +-----+ | + // | | | + // | | +---------+ + // | [CompoundAssignment] | | + // | emitGet +-----+ | | emitAssignment +------------+ + // +---------->| Get |----+ + -------------->| Assignment | + // +-----+ +------------+ + enum class State { + // The initial state. + Start, + + // After calling prepareForObj. + Obj, + + // After calling emitGet. + Get, + + // After calling emitDelete. + Delete, + + // After calling emitIncDec. + IncDec, + + // After calling prepareForRhs or skipObjAndRhs. + Rhs, + + // After calling emitAssignment. + Assignment, + }; + State state_ = State::Start; +#endif + + public: + PropOpEmitter(BytecodeEmitter* bce, Kind kind, ObjKind objKind); + + private: + [[nodiscard]] bool isCall() const { return kind_ == Kind::Call; } + + [[nodiscard]] bool isSuper() const { return objKind_ == ObjKind::Super; } + + [[nodiscard]] bool isSimpleAssignment() const { + return kind_ == Kind::SimpleAssignment; + } + + [[nodiscard]] bool isPropInit() const { return kind_ == Kind::PropInit; } + + [[nodiscard]] bool isDelete() const { return kind_ == Kind::Delete; } + + [[nodiscard]] bool isCompoundAssignment() const { + return kind_ == Kind::CompoundAssignment; + } + + [[nodiscard]] bool isIncDec() const { + return isPostIncDec() || isPreIncDec(); + } + + [[nodiscard]] bool isPostIncDec() const { + return kind_ == Kind::PostIncrement || kind_ == Kind::PostDecrement; + } + + [[nodiscard]] bool isPreIncDec() const { + return kind_ == Kind::PreIncrement || kind_ == Kind::PreDecrement; + } + + [[nodiscard]] bool isInc() const { + return kind_ == Kind::PostIncrement || kind_ == Kind::PreIncrement; + } + + [[nodiscard]] bool prepareAtomIndex(TaggedParserAtomIndex prop); + + public: + [[nodiscard]] bool prepareForObj(); + + [[nodiscard]] bool emitGet(TaggedParserAtomIndex prop); + + [[nodiscard]] bool prepareForRhs(); + [[nodiscard]] bool skipObjAndRhs(); + + [[nodiscard]] bool emitDelete(TaggedParserAtomIndex prop); + + // `prop` can be nullptr for CompoundAssignment. + [[nodiscard]] bool emitAssignment(TaggedParserAtomIndex prop); + + [[nodiscard]] bool emitIncDec(TaggedParserAtomIndex prop, + ValueUsage valueUsage); +}; + +} /* namespace frontend */ +} /* namespace js */ + +#endif /* frontend_PropOpEmitter_h */ diff --git a/js/src/frontend/ReservedWords.h b/js/src/frontend/ReservedWords.h new file mode 100644 index 0000000000..723f310ecf --- /dev/null +++ b/js/src/frontend/ReservedWords.h @@ -0,0 +1,78 @@ +/* -*- 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/. */ + +/* A higher-order macro for enumerating reserved word tokens. */ + +#ifndef vm_ReservedWords_h +#define vm_ReservedWords_h + +#define FOR_EACH_JAVASCRIPT_RESERVED_WORD(MACRO) \ + MACRO(false, false_, TokenKind::False) \ + MACRO(true, true_, TokenKind::True) \ + MACRO(null, null, TokenKind::Null) \ + \ + /* Keywords. */ \ + MACRO(break, break_, TokenKind::Break) \ + MACRO(case, case_, TokenKind::Case) \ + MACRO(catch, catch_, TokenKind::Catch) \ + MACRO(const, const_, TokenKind::Const) \ + MACRO(continue, continue_, TokenKind::Continue) \ + MACRO(debugger, debugger, TokenKind::Debugger) \ + MACRO(default, default_, TokenKind::Default) \ + MACRO(delete, delete_, TokenKind::Delete) \ + MACRO(do, do_, TokenKind::Do) \ + MACRO(else, else_, TokenKind::Else) \ + MACRO(finally, finally_, TokenKind::Finally) \ + MACRO(for, for_, TokenKind::For) \ + MACRO(function, function, TokenKind::Function) \ + MACRO(if, if_, TokenKind::If) \ + MACRO(in, in, TokenKind::In) \ + MACRO(instanceof, instanceof, TokenKind::InstanceOf) \ + MACRO(new, new_, TokenKind::New) \ + MACRO(return, return_, TokenKind::Return) \ + MACRO(switch, switch_, TokenKind::Switch) \ + MACRO(this, this_, TokenKind::This) \ + MACRO(throw, throw_, TokenKind::Throw) \ + MACRO(try, try_, TokenKind::Try) \ + MACRO(typeof, typeof_, TokenKind::TypeOf) \ + MACRO(var, var, TokenKind::Var) \ + MACRO(void, void_, TokenKind::Void) \ + MACRO(while, while_, TokenKind::While) \ + MACRO(with, with, TokenKind::With) \ + MACRO(import, import, TokenKind::Import) \ + MACRO(export, export_, TokenKind::Export) \ + MACRO(class, class_, TokenKind::Class) \ + MACRO(extends, extends, TokenKind::Extends) \ + IF_DECORATORS(MACRO(accessor, accessor, TokenKind::Accessor)) \ + MACRO(super, super, TokenKind::Super) \ + \ + /* Future reserved words. */ \ + MACRO(enum, enum_, TokenKind::Enum) \ + \ + /* Future reserved words, but only in strict mode. */ \ + MACRO(implements, implements, TokenKind::Implements) \ + MACRO(interface, interface, TokenKind::Interface) \ + MACRO(package, package, TokenKind::Package) \ + MACRO(private, private_, TokenKind::Private) \ + MACRO(protected, protected_, TokenKind::Protected) \ + MACRO(public, public_, TokenKind::Public) \ + \ + /* Contextual keywords. */ \ + MACRO(as, as, TokenKind::As) \ + MACRO(assert, assert_, TokenKind::Assert) \ + MACRO(async, async, TokenKind::Async) \ + MACRO(await, await, TokenKind::Await) \ + MACRO(from, from, TokenKind::From) \ + MACRO(get, get, TokenKind::Get) \ + MACRO(let, let, TokenKind::Let) \ + MACRO(meta, meta, TokenKind::Meta) \ + MACRO(of, of, TokenKind::Of) \ + MACRO(set, set, TokenKind::Set) \ + MACRO(static, static_, TokenKind::Static) \ + MACRO(target, target, TokenKind::Target) \ + MACRO(yield, yield, TokenKind::Yield) + +#endif /* vm_ReservedWords_h */ diff --git a/js/src/frontend/ScopeBindingCache.h b/js/src/frontend/ScopeBindingCache.h new file mode 100644 index 0000000000..ee3dc35e33 --- /dev/null +++ b/js/src/frontend/ScopeBindingCache.h @@ -0,0 +1,281 @@ +/* -*- 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/. */ + +#ifndef frontend_ScopeBindingCache_h +#define frontend_ScopeBindingCache_h + +#include "mozilla/Attributes.h" // MOZ_STACK_CLASS +#include "mozilla/HashTable.h" // mozilla::HashMap + +#include "jstypes.h" // JS_PUBLIC_API + +#include "frontend/NameAnalysisTypes.h" // NameLocation +#include "frontend/ParserAtom.h" // TaggedPArserAtomIndex, ParserAtomsTable + +#include "js/Utility.h" // AutoEnterOOMUnsafeRegion + +#include "vm/Scope.h" // AbstractBaseScopeData +#include "vm/StringType.h" // JSAtom + +namespace js { +namespace frontend { + +struct CompilationAtomCache; +struct CompilationStencil; +struct ScopeStencilRef; +struct CompilationStencilMerger; + +// Generic atom wrapper which provides a way to interpret any Atom given +// contextual information. Thus, this structure offers the ability to compare +// Atom from different domains. +// +// This structure provides a `hash` field which is universal across all Atom +// representations. Thus, Atoms from different contexes can be registered in a +// hash table and looked up with a different Atom kind. +struct GenericAtom { + // Emitter names are TaggedParserAtomIndex which are registered in the + // ParserAtomsTable of an extensible compilation stencil, frequently related + // to bytecode emitter, which lookup names in the scope chain to replace names + // by variable locations. + struct EmitterName { + FrontendContext* fc; + ParserAtomsTable& parserAtoms; + CompilationAtomCache& atomCache; + TaggedParserAtomIndex index; + + EmitterName(FrontendContext* fc, ParserAtomsTable& parserAtoms, + CompilationAtomCache& atomCache, TaggedParserAtomIndex index) + : fc(fc), + parserAtoms(parserAtoms), + atomCache(atomCache), + index(index) {} + }; + + // Stencil names are TaggedParserAtomIndex which are registered in a + // ParserAtomVector of a compilation stencil, frequently related to the result + // of a compilation. It can be seen while manipulating names of a scope chain + // while delazifying functions using a stencil for context. + struct StencilName { + const CompilationStencil& stencil; + TaggedParserAtomIndex index; + }; + + // Any names are references to different Atom representation, including some + // which are interpretable given some contexts such as EmitterName and + // StencilName. + using AnyName = mozilla::Variant; + + HashNumber hash; + AnyName ref; + + // Constructor for atoms managed by an ExtensibleCompilationState, while + // compiling a script. + GenericAtom(FrontendContext* fc, ParserAtomsTable& parserAtoms, + CompilationAtomCache& atomCache, TaggedParserAtomIndex index); + + // Constructors for atoms managed by a CompilationStencil or a + // BorrowingCompilationStencil, which provide contextual information from an + // already compiled script. + GenericAtom(const CompilationStencil& context, TaggedParserAtomIndex index); + GenericAtom(ScopeStencilRef& scope, TaggedParserAtomIndex index); + + // Constructor for atoms managed by the Garbage Collector, while providing + // contextual scope information when delazifying functions on the main thread. + GenericAtom(const Scope*, JSAtom* ptr) : GenericAtom(ptr) {} + explicit GenericAtom(JSAtom* ptr) : ref(ptr) { hash = ptr->hash(); } + + bool operator==(const GenericAtom& other) const; +}; + +template +struct BindingHasher; + +template <> +struct BindingHasher { + // This is a GenericAtom::StencilName stripped from its context which is the + // same for every key. + using Key = TaggedParserAtomIndex; + struct Lookup { + // When building a BindingMap, we assume that the TaggedParserAtomIndex is + // coming from an existing Stencil, and is not an EmitterName. + const CompilationStencil& keyStencil; + GenericAtom other; + + Lookup(ScopeStencilRef& scope_ref, const GenericAtom& other); + }; + + static HashNumber hash(const Lookup& aLookup) { return aLookup.other.hash; } + + static bool match(const Key& aKey, const Lookup& aLookup) { + GenericAtom key(aLookup.keyStencil, aKey); + return key == aLookup.other; + } +}; + +template <> +struct BindingHasher { + using Key = JSAtom*; + struct Lookup { + GenericAtom other; + + template + Lookup(const Any&, const GenericAtom& other) : other(other) {} + }; + + static HashNumber hash(const Lookup& aLookup) { return aLookup.other.hash; } + + static bool match(const Key& aKey, const Lookup& aLookup) { + GenericAtom key(aKey); + return key == aLookup.other; + } +}; + +// Map the bound names to their respective name location. This is used to avoid +// doing a linear lookup over the list of bindings each time we are looking for +// a single name. +// +// The names given used as a key are either JSAtom in the case of a on-demand +// delazification, or a TaggedParserAtomIndex in case of a +// concurrent-delazification. In both case Lookup arguments are not trivially +// created out of a key, as in the case of a TaggedParserAtomIndex, the +// CompilationStencil should be provided to interpret the TaggedParserAtomIndex +// which are stored in this hash table. +template +struct BindingMap { + using Lookup = typename BindingHasher::Lookup; + using Map = + HashMap, js::SystemAllocPolicy>; + + Map hashMap; + mozilla::Maybe catchAll; +}; + +// For each list of bound names, map the list of bound names to the hash table +// which is used to reduce the time needed per lookup. +// +// The name parameter are either JSAtom in the case of a on-demand +// delazification, or a TaggedParserAtomIndex in case of a +// concurrent-delazification. +template +using ScopeBindingMap = + HashMap*, BindingMap, + DefaultHasher*>, + js::SystemAllocPolicy>; + +// Common interface for a cache holding the mapping of Scope to a hash table +// which mirror the binding mapping stored in the scope. +class ScopeBindingCache { + public: + using CacheGeneration = size_t; + + virtual CacheGeneration getCurrentGeneration() const = 0; + + // Check whether the cache provided as argument is capable of storing the type + // of scope given as arguments. + virtual bool canCacheFor(Scope* ptr); + virtual bool canCacheFor(ScopeStencilRef ref); + + // Create a new BindingMap cache for a given scope. This cache should then be + // filled with all names which might be looked up. + virtual BindingMap* createCacheFor(Scope* ptr); + virtual BindingMap* createCacheFor( + ScopeStencilRef ref); + + // Return the BindingMap created for the associated scope, unless the + // generation value does not match the one stored internally, in which case a + // null pointer is always returned. + virtual BindingMap* lookupScope(Scope* ptr, CacheGeneration gen); + virtual BindingMap* lookupScope(ScopeStencilRef ref, + CacheGeneration gen); +}; + +// NoScopeBindingCache is a no-op which does not implement a ScopeBindingCache. +// +// This is useful when compiling a global script or module, where we are not +// interested in looking up anything from the enclosing scope chain. +class NoScopeBindingCache final : public ScopeBindingCache { + public: + CacheGeneration getCurrentGeneration() const override { return 1; }; + + bool canCacheFor(Scope* ptr) override; + bool canCacheFor(ScopeStencilRef ref) override; +}; + +// StencilScopeBindingCache provides an interface to cache the bindings provided +// by a CompilationStencilMerger. +// +// This cache lives on the stack and its content would be invalidated once going +// out of scope. The constructor expects a reference to a +// CompilationStencilMerger, that is expected to: +// - out-live this class. +// - contain the enclosing scope which are manipulated by this class. +// - be the receiver of delazified functions. +class MOZ_STACK_CLASS StencilScopeBindingCache final + : public ScopeBindingCache { + ScopeBindingMap scopeMap; +#ifdef DEBUG + const CompilationStencilMerger& merger_; +#endif + + public: + explicit StencilScopeBindingCache(const CompilationStencilMerger& merger) +#ifdef DEBUG + : merger_(merger) +#endif + { + } + + // The cache content is always valid as long as it does not out-live the + // CompilationStencilMerger. No need for a generation number. + CacheGeneration getCurrentGeneration() const override { return 1; } + + bool canCacheFor(ScopeStencilRef ref) override; + BindingMap* createCacheFor( + ScopeStencilRef ref) override; + BindingMap* lookupScope(ScopeStencilRef ref, + CacheGeneration gen) override; +}; + +// RuntimeScopeBindingCache is used to hold the binding map for each scope which +// is hold by a Scope managed by the garbage collector. +// +// This cache is not thread safe. +// +// The generation number is used to assert the validity of the cached content. +// During a GC, the cached content is thrown away and getCurrentGeneration +// returns a different number. When the generation number differs from the +// initialization of the cached content, the cache content might be renewed or +// ignored. +class RuntimeScopeBindingCache final : public ScopeBindingCache { + ScopeBindingMap scopeMap; + + // This value is initialized to 1, such that we can differentiate it from the + // typical 0-init of size_t values, when non-initialized. + size_t cacheGeneration = 1; + + public: + CacheGeneration getCurrentGeneration() const override { + return cacheGeneration; + } + + bool canCacheFor(Scope* ptr) override; + BindingMap* createCacheFor(Scope* ptr) override; + BindingMap* lookupScope(Scope* ptr, CacheGeneration gen) override; + + // The ScopeBindingCache is not instrumented for tracing weakly the keys used + // for mapping to the NameLocation. Instead, we always purge during compaction + // or collection, and increment the cacheGeneration to notify all consumers + // that the cache can no longer be used without being re-populated. + void purge() { + cacheGeneration++; + scopeMap.clearAndCompact(); + } +}; + +} // namespace frontend +} // namespace js + +#endif // frontend_ScopeBindingCache_h diff --git a/js/src/frontend/ScopeIndex.h b/js/src/frontend/ScopeIndex.h new file mode 100644 index 0000000000..de8087211c --- /dev/null +++ b/js/src/frontend/ScopeIndex.h @@ -0,0 +1,32 @@ +/* -*- 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/. */ + +#ifndef frontend_ScopeIndex_h +#define frontend_ScopeIndex_h + +#include // uint32_t, UINT32_MAX + +#include "frontend/TypedIndex.h" // TypedIndex + +namespace js { + +class Scope; + +class ScopeIndex : public frontend::TypedIndex { + // Delegate constructors; + using Base = frontend::TypedIndex; + using Base::Base; + + static constexpr uint32_t InvalidIndex = UINT32_MAX; + + public: + static constexpr ScopeIndex invalid() { return ScopeIndex(InvalidIndex); } + bool isValid() const { return index != InvalidIndex; } +}; + +} /* namespace js */ + +#endif /* frontend_ScopeIndex_h */ diff --git a/js/src/frontend/ScriptIndex.h b/js/src/frontend/ScriptIndex.h new file mode 100644 index 0000000000..ed16b6be57 --- /dev/null +++ b/js/src/frontend/ScriptIndex.h @@ -0,0 +1,42 @@ +/* -*- 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/. */ + +#ifndef frontend_ScriptIndex_h +#define frontend_ScriptIndex_h + +#include + +#include "frontend/TypedIndex.h" // TypedIndex +#include "js/GCPolicyAPI.h" // JS::GCPolicy, JS::IgnoreGCPolicy + +namespace js { +namespace frontend { + +class ScriptStencil; + +using ScriptIndex = TypedIndex; + +// Represents a range for scripts [start, limit). +struct ScriptIndexRange { + ScriptIndex start; + ScriptIndex limit; + + ScriptIndexRange(ScriptIndex start, ScriptIndex limit) + : start(start), limit(limit) { + MOZ_ASSERT(start < limit); + } +}; + +} /* namespace frontend */ +} /* namespace js */ + +namespace JS { +template <> +struct GCPolicy + : public IgnoreGCPolicy {}; +} // namespace JS + +#endif /* frontend_ScriptIndex_h */ diff --git a/js/src/frontend/SelfHostedIter.h b/js/src/frontend/SelfHostedIter.h new file mode 100644 index 0000000000..f2c454716a --- /dev/null +++ b/js/src/frontend/SelfHostedIter.h @@ -0,0 +1,28 @@ +/* -*- 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/. */ + +#ifndef frontend_SelfHostedIter_h +#define frontend_SelfHostedIter_h + +namespace js::frontend { + +// `for-of`, `for-await-of`, and spread operations are allowed on +// self-hosted JS code only when the operand is explicitly marked with +// `allowContentIter()`. +// +// This value is effectful only when emitting self-hosted JS code. +enum class SelfHostedIter { + // The operand is not marked. + // Also means "don't care" for non-self-hosted JS case. + Deny, + + // The operand is marked. + Allow, +}; + +} /* namespace js::frontend */ + +#endif /* frontend_SelfHostedIter_h */ diff --git a/js/src/frontend/SharedContext-inl.h b/js/src/frontend/SharedContext-inl.h new file mode 100644 index 0000000000..9175fcc0e2 --- /dev/null +++ b/js/src/frontend/SharedContext-inl.h @@ -0,0 +1,23 @@ +/* -*- 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/. */ + +#ifndef frontend_SharedContext_inl_h +#define frontend_SharedContext_inl_h + +#include "frontend/SharedContext.h" +#include "frontend/ParseContext.h" + +namespace js { +namespace frontend { + +inline Directives::Directives(ParseContext* parent) + : strict_(parent->sc()->strict()), asmJS_(parent->useAsmOrInsideUseAsm()) {} + +} // namespace frontend + +} // namespace js + +#endif // frontend_SharedContext_inl_h diff --git a/js/src/frontend/SharedContext.cpp b/js/src/frontend/SharedContext.cpp new file mode 100644 index 0000000000..7fa3b724fb --- /dev/null +++ b/js/src/frontend/SharedContext.cpp @@ -0,0 +1,409 @@ +/* -*- 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/SharedContext.h" + +#include "mozilla/RefPtr.h" + +#include "frontend/CompilationStencil.h" +#include "frontend/FunctionSyntaxKind.h" // FunctionSyntaxKind +#include "frontend/ModuleSharedContext.h" +#include "frontend/ParseContext.h" +#include "frontend/ParseNode.h" +#include "frontend/ParserAtom.h" +#include "frontend/ScopeIndex.h" +#include "frontend/ScriptIndex.h" +#include "frontend/Stencil.h" +#include "js/CompileOptions.h" +#include "js/Vector.h" +#include "vm/FunctionFlags.h" // js::FunctionFlags +#include "vm/GeneratorAndAsyncKind.h" // js::GeneratorKind, js::FunctionAsyncKind +#include "vm/JSScript.h" // js::FillImmutableFlagsFromCompileOptionsForTopLevel, js::FillImmutableFlagsFromCompileOptionsForFunction +#include "vm/StencilEnums.h" // ImmutableScriptFlagsEnum + +#include "frontend/ParseContext-inl.h" + +namespace js { + +class ModuleBuilder; + +namespace frontend { + +SharedContext::SharedContext(FrontendContext* fc, Kind kind, + const JS::ReadOnlyCompileOptions& options, + Directives directives, SourceExtent extent) + : fc_(fc), + extent_(extent), + allowNewTarget_(false), + allowSuperProperty_(false), + allowSuperCall_(false), + allowArguments_(true), + inWith_(false), + inClass_(false), + localStrict(false), + hasExplicitUseStrict_(false), + isScriptExtraFieldCopiedToStencil(false) { + // Compute the script kind "input" flags. + if (kind == Kind::FunctionBox) { + setFlag(ImmutableFlags::IsFunction); + } else if (kind == Kind::Module) { + MOZ_ASSERT(!options.nonSyntacticScope); + setFlag(ImmutableFlags::IsModule); + } else if (kind == Kind::Eval) { + setFlag(ImmutableFlags::IsForEval); + } else { + MOZ_ASSERT(kind == Kind::Global); + } + + // Initialize the transitive "input" flags. These are applied to all + // SharedContext in this compilation and generally cannot be determined from + // the source text alone. + if (isTopLevelContext()) { + js::FillImmutableFlagsFromCompileOptionsForTopLevel(options, + immutableFlags_); + } else { + js::FillImmutableFlagsFromCompileOptionsForFunction(options, + immutableFlags_); + } + + // Initialize the strict flag. This may be updated by the parser as we observe + // further directives in the body. + setFlag(ImmutableFlags::Strict, directives.strict()); +} + +GlobalSharedContext::GlobalSharedContext( + FrontendContext* fc, ScopeKind scopeKind, + const JS::ReadOnlyCompileOptions& options, Directives directives, + SourceExtent extent) + : SharedContext(fc, Kind::Global, options, directives, extent), + scopeKind_(scopeKind), + bindings(nullptr) { + MOZ_ASSERT(scopeKind == ScopeKind::Global || + scopeKind == ScopeKind::NonSyntactic); + MOZ_ASSERT(thisBinding_ == ThisBinding::Global); +} + +EvalSharedContext::EvalSharedContext(FrontendContext* fc, + CompilationState& compilationState, + SourceExtent extent) + : SharedContext(fc, Kind::Eval, compilationState.input.options, + compilationState.directives, extent), + bindings(nullptr) { + // Eval inherits syntax and binding rules from enclosing environment. + allowNewTarget_ = compilationState.scopeContext.allowNewTarget; + allowSuperProperty_ = compilationState.scopeContext.allowSuperProperty; + allowSuperCall_ = compilationState.scopeContext.allowSuperCall; + allowArguments_ = compilationState.scopeContext.allowArguments; + thisBinding_ = compilationState.scopeContext.thisBinding; + inWith_ = compilationState.scopeContext.inWith; +} + +SuspendableContext::SuspendableContext( + FrontendContext* fc, Kind kind, const JS::ReadOnlyCompileOptions& options, + Directives directives, SourceExtent extent, bool isGenerator, bool isAsync) + : SharedContext(fc, kind, options, directives, extent) { + setFlag(ImmutableFlags::IsGenerator, isGenerator); + setFlag(ImmutableFlags::IsAsync, isAsync); +} + +FunctionBox::FunctionBox(FrontendContext* fc, SourceExtent extent, + CompilationState& compilationState, + Directives directives, GeneratorKind generatorKind, + FunctionAsyncKind asyncKind, bool isInitialCompilation, + TaggedParserAtomIndex atom, FunctionFlags flags, + ScriptIndex index) + : SuspendableContext(fc, Kind::FunctionBox, compilationState.input.options, + directives, extent, + generatorKind == GeneratorKind::Generator, + asyncKind == FunctionAsyncKind::AsyncFunction), + compilationState_(compilationState), + atom_(atom), + funcDataIndex_(index), + flags_(FunctionFlags::clearMutableflags(flags)), + emitBytecode(false), + wasEmittedByEnclosingScript_(false), + isAnnexB(false), + useAsm(false), + hasParameterExprs(false), + hasDestructuringArgs(false), + hasDuplicateParameters(false), + hasExprBody_(false), + allowReturn_(true), + isFunctionFieldCopiedToStencil(false), + isInitialCompilation(isInitialCompilation), + isStandalone(false) {} + +void FunctionBox::initFromLazyFunction(const ScriptStencilExtra& extra, + ScopeContext& scopeContext, + FunctionSyntaxKind kind) { + initFromScriptStencilExtra(extra); + initStandaloneOrLazy(scopeContext, kind); +} + +void FunctionBox::initFromScriptStencilExtra(const ScriptStencilExtra& extra) { + immutableFlags_ = extra.immutableFlags; + extent_ = extra.extent; +} + +void FunctionBox::initWithEnclosingParseContext(ParseContext* enclosing, + FunctionSyntaxKind kind) { + SharedContext* sc = enclosing->sc(); + + // HasModuleGoal and useAsm are inherited from enclosing context. + useAsm = sc->isFunctionBox() && sc->asFunctionBox()->useAsmOrInsideUseAsm(); + setHasModuleGoal(sc->hasModuleGoal()); + + // Arrow functions don't have their own `this` binding. + if (flags_.isArrow()) { + allowNewTarget_ = sc->allowNewTarget(); + allowSuperProperty_ = sc->allowSuperProperty(); + allowSuperCall_ = sc->allowSuperCall(); + allowArguments_ = sc->allowArguments(); + thisBinding_ = sc->thisBinding(); + } else { + if (IsConstructorKind(kind)) { + // Record this function into the enclosing class statement so that + // finishClassConstructor can final processing. Due to aborted syntax + // parses (eg, because of asm.js), this may have already been set with an + // early FunctionBox. In that case, the FunctionNode should still match. + auto classStmt = + enclosing->findInnermostStatement(); + MOZ_ASSERT(classStmt); + MOZ_ASSERT(classStmt->constructorBox == nullptr || + classStmt->constructorBox->functionNode == this->functionNode); + classStmt->constructorBox = this; + } + + allowNewTarget_ = true; + allowSuperProperty_ = flags_.allowSuperProperty(); + + if (kind == FunctionSyntaxKind::DerivedClassConstructor) { + setDerivedClassConstructor(); + allowSuperCall_ = true; + thisBinding_ = ThisBinding::DerivedConstructor; + } else { + thisBinding_ = ThisBinding::Function; + } + + if (kind == FunctionSyntaxKind::FieldInitializer || + kind == FunctionSyntaxKind::StaticClassBlock) { + setSyntheticFunction(); + allowArguments_ = false; + if (kind == FunctionSyntaxKind::StaticClassBlock) { + allowSuperCall_ = false; + allowReturn_ = false; + } + } + } + + if (sc->inWith()) { + inWith_ = true; + } else { + auto isWith = [](ParseContext::Statement* stmt) { + return stmt->kind() == StatementKind::With; + }; + + inWith_ = enclosing->findInnermostStatement(isWith); + } + + if (sc->inClass()) { + inClass_ = true; + } else { + auto isClass = [](ParseContext::Statement* stmt) { + return stmt->kind() == StatementKind::Class; + }; + + inClass_ = enclosing->findInnermostStatement(isClass); + } +} + +void FunctionBox::initStandalone(ScopeContext& scopeContext, + FunctionSyntaxKind kind) { + initStandaloneOrLazy(scopeContext, kind); + + isStandalone = true; +} + +void FunctionBox::initStandaloneOrLazy(ScopeContext& scopeContext, + FunctionSyntaxKind kind) { + if (flags_.isArrow()) { + allowNewTarget_ = scopeContext.allowNewTarget; + allowSuperProperty_ = scopeContext.allowSuperProperty; + allowSuperCall_ = scopeContext.allowSuperCall; + allowArguments_ = scopeContext.allowArguments; + thisBinding_ = scopeContext.thisBinding; + } else { + allowNewTarget_ = true; + allowSuperProperty_ = flags_.allowSuperProperty(); + + if (kind == FunctionSyntaxKind::DerivedClassConstructor) { + setDerivedClassConstructor(); + allowSuperCall_ = true; + thisBinding_ = ThisBinding::DerivedConstructor; + } else { + thisBinding_ = ThisBinding::Function; + } + + if (kind == FunctionSyntaxKind::FieldInitializer) { + setSyntheticFunction(); + allowArguments_ = false; + } + } + + inWith_ = scopeContext.inWith; + inClass_ = scopeContext.inClass; +} + +void FunctionBox::setEnclosingScopeForInnerLazyFunction(ScopeIndex scopeIndex) { + // For lazy functions inside a function which is being compiled, we cache + // the incomplete scope object while compiling, and store it to the + // BaseScript once the enclosing script successfully finishes compilation + // in FunctionBox::finish. + MOZ_ASSERT(enclosingScopeIndex_.isNothing()); + enclosingScopeIndex_ = mozilla::Some(scopeIndex); + if (isFunctionFieldCopiedToStencil) { + copyUpdatedEnclosingScopeIndex(); + } +} + +bool FunctionBox::setAsmJSModule(const JS::WasmModule* module) { + MOZ_ASSERT(!isFunctionFieldCopiedToStencil); + + MOZ_ASSERT(flags_.kind() == FunctionFlags::NormalFunction); + + // Update flags we will use to allocate the JSFunction. + flags_.clearBaseScript(); + flags_.setIsExtended(); + flags_.setKind(FunctionFlags::AsmJS); + + if (!compilationState_.asmJS) { + compilationState_.asmJS = + fc_->getAllocator()->new_(); + if (!compilationState_.asmJS) { + return false; + } + } + + if (!compilationState_.asmJS->moduleMap.putNew(index(), module)) { + js::ReportOutOfMemory(fc_); + return false; + } + return true; +} + +ModuleSharedContext::ModuleSharedContext( + FrontendContext* fc, const JS::ReadOnlyCompileOptions& options, + ModuleBuilder& builder, SourceExtent extent) + : SuspendableContext(fc, Kind::Module, options, Directives(true), extent, + /* isGenerator = */ false, + /* isAsync = */ false), + bindings(nullptr), + builder(builder) { + thisBinding_ = ThisBinding::Module; + setFlag(ImmutableFlags::HasModuleGoal); +} + +ScriptStencil& FunctionBox::functionStencil() const { + return compilationState_.scriptData[funcDataIndex_]; +} + +ScriptStencilExtra& FunctionBox::functionExtraStencil() const { + return compilationState_.scriptExtra[funcDataIndex_]; +} + +void SharedContext::copyScriptExtraFields(ScriptStencilExtra& scriptExtra) { + MOZ_ASSERT(!isScriptExtraFieldCopiedToStencil); + + scriptExtra.immutableFlags = immutableFlags_; + scriptExtra.extent = extent_; + + isScriptExtraFieldCopiedToStencil = true; +} + +void FunctionBox::finishScriptFlags() { + MOZ_ASSERT(!isScriptExtraFieldCopiedToStencil); + + using ImmutableFlags = ImmutableScriptFlagsEnum; + immutableFlags_.setFlag(ImmutableFlags::HasMappedArgsObj, hasMappedArgsObj()); +} + +void FunctionBox::copyFunctionFields(ScriptStencil& script) { + MOZ_ASSERT(&script == &functionStencil()); + MOZ_ASSERT(!isFunctionFieldCopiedToStencil); + + if (atom_) { + compilationState_.parserAtoms.markUsedByStencil(atom_, + ParserAtom::Atomize::Yes); + script.functionAtom = atom_; + } + script.functionFlags = flags_; + if (enclosingScopeIndex_) { + script.setLazyFunctionEnclosingScopeIndex(*enclosingScopeIndex_); + } + if (wasEmittedByEnclosingScript_) { + script.setWasEmittedByEnclosingScript(); + } + + isFunctionFieldCopiedToStencil = true; +} + +void FunctionBox::copyFunctionExtraFields(ScriptStencilExtra& scriptExtra) { + if (useMemberInitializers()) { + scriptExtra.setMemberInitializers(memberInitializers()); + } + + scriptExtra.nargs = nargs_; +} + +void FunctionBox::copyUpdatedImmutableFlags() { + if (isInitialCompilation) { + ScriptStencilExtra& scriptExtra = functionExtraStencil(); + scriptExtra.immutableFlags = immutableFlags_; + } +} + +void FunctionBox::copyUpdatedExtent() { + ScriptStencilExtra& scriptExtra = functionExtraStencil(); + scriptExtra.extent = extent_; +} + +void FunctionBox::copyUpdatedMemberInitializers() { + MOZ_ASSERT(useMemberInitializers()); + if (isInitialCompilation) { + ScriptStencilExtra& scriptExtra = functionExtraStencil(); + scriptExtra.setMemberInitializers(memberInitializers()); + } else { + // We are delazifying and the original PrivateScriptData has the member + // initializer information already. See: JSScript::fullyInitFromStencil. + } +} + +void FunctionBox::copyUpdatedEnclosingScopeIndex() { + ScriptStencil& script = functionStencil(); + if (enclosingScopeIndex_) { + script.setLazyFunctionEnclosingScopeIndex(*enclosingScopeIndex_); + } +} + +void FunctionBox::copyUpdatedAtomAndFlags() { + ScriptStencil& script = functionStencil(); + if (atom_) { + compilationState_.parserAtoms.markUsedByStencil(atom_, + ParserAtom::Atomize::Yes); + script.functionAtom = atom_; + } + script.functionFlags = flags_; +} + +void FunctionBox::copyUpdatedWasEmitted() { + ScriptStencil& script = functionStencil(); + if (wasEmittedByEnclosingScript_) { + script.setWasEmittedByEnclosingScript(); + } +} + +} // namespace frontend +} // namespace js diff --git a/js/src/frontend/SharedContext.h b/js/src/frontend/SharedContext.h new file mode 100644 index 0000000000..ded2e0439f --- /dev/null +++ b/js/src/frontend/SharedContext.h @@ -0,0 +1,747 @@ +/* -*- 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/. */ + +#ifndef frontend_SharedContext_h +#define frontend_SharedContext_h + +#include "mozilla/Assertions.h" +#include "mozilla/Attributes.h" +#include "mozilla/Maybe.h" + +#include +#include + +#include "jstypes.h" + +#include "frontend/FunctionSyntaxKind.h" // FunctionSyntaxKind +#include "frontend/ParserAtom.h" // TaggedParserAtomIndex +#include "frontend/ScopeIndex.h" // ScopeIndex +#include "frontend/ScriptIndex.h" // ScriptIndex +#include "vm/FunctionFlags.h" // js::FunctionFlags +#include "vm/GeneratorAndAsyncKind.h" // js::GeneratorKind, js::FunctionAsyncKind +#include "vm/Scope.h" +#include "vm/ScopeKind.h" +#include "vm/SharedStencil.h" +#include "vm/StencilEnums.h" + +namespace JS { +class JS_PUBLIC_API ReadOnlyCompileOptions; +struct WasmModule; +} // namespace JS + +namespace js { + +class FrontendContext; + +namespace frontend { + +struct CompilationState; +class FunctionBox; +class FunctionNode; +class ParseContext; +class ScriptStencil; +class ScriptStencilExtra; +struct ScopeContext; + +enum class StatementKind : uint8_t { + Label, + Block, + If, + Switch, + With, + Catch, + Try, + Finally, + ForLoopLexicalHead, + ForLoop, + ForInLoop, + ForOfLoop, + DoLoop, + WhileLoop, + Class, + + // Used only by BytecodeEmitter. + Spread, + YieldStar, +}; + +static inline bool StatementKindIsLoop(StatementKind kind) { + return kind == StatementKind::ForLoop || kind == StatementKind::ForInLoop || + kind == StatementKind::ForOfLoop || kind == StatementKind::DoLoop || + kind == StatementKind::WhileLoop || kind == StatementKind::Spread || + kind == StatementKind::YieldStar; +} + +static inline bool StatementKindIsUnlabeledBreakTarget(StatementKind kind) { + return StatementKindIsLoop(kind) || kind == StatementKind::Switch; +} + +// List of directives that may be encountered in a Directive Prologue +// (ES5 15.1). +class Directives { + bool strict_; + bool asmJS_; + + public: + explicit Directives(bool strict) : strict_(strict), asmJS_(false) {} + explicit Directives(ParseContext* parent); + + void setStrict() { strict_ = true; } + bool strict() const { return strict_; } + + void setAsmJS() { asmJS_ = true; } + bool asmJS() const { return asmJS_; } + + Directives& operator=(Directives rhs) { + strict_ = rhs.strict_; + asmJS_ = rhs.asmJS_; + return *this; + } + bool operator==(const Directives& rhs) const { + return strict_ == rhs.strict_ && asmJS_ == rhs.asmJS_; + } + bool operator!=(const Directives& rhs) const { return !(*this == rhs); } +}; + +// The kind of this-binding for the current scope. Note that arrow functions +// have a lexical this-binding so their ThisBinding is the same as the +// ThisBinding of their enclosing scope and can be any value. Derived +// constructors require TDZ checks when accessing the binding. +enum class ThisBinding : uint8_t { + Global, + Module, + Function, + DerivedConstructor +}; + +// If Yes, the script inherits it's "this" environment and binding from the +// enclosing script. This is true for arrow-functions and eval scripts. +enum class InheritThis { No, Yes }; + +class GlobalSharedContext; +class EvalSharedContext; +class ModuleSharedContext; +class SuspendableContext; + +#define IMMUTABLE_FLAG_GETTER_SETTER(lowerName, name) \ + GENERIC_FLAG_GETTER_SETTER(ImmutableFlags, lowerName, name) + +#define IMMUTABLE_FLAG_GETTER(lowerName, name) \ + GENERIC_FLAG_GETTER(ImmutableFlags, lowerName, name) + +/* + * The struct SharedContext is part of the current parser context (see + * ParseContext). It stores information that is reused between the parser and + * the bytecode emitter. + */ +class SharedContext { + public: + FrontendContext* const fc_; + + protected: + // See: BaseScript::immutableFlags_ + ImmutableScriptFlags immutableFlags_ = {}; + + // The location of this script in the source. Note that the value here differs + // from the final BaseScript for the case of standalone functions. + // This field is copied to ScriptStencil, and shouldn't be modified after the + // copy. + SourceExtent extent_ = {}; + + protected: + // See: ThisBinding + ThisBinding thisBinding_ = ThisBinding::Global; + + // These flags do not have corresponding script flags and may be inherited + // from the scope chain in the case of eval and arrows. + bool allowNewTarget_ : 1; + bool allowSuperProperty_ : 1; + bool allowSuperCall_ : 1; + bool allowArguments_ : 1; + bool inWith_ : 1; + bool inClass_ : 1; + + // See `strict()` below. + bool localStrict : 1; + + // True if "use strict"; appears in the body instead of being inherited. + bool hasExplicitUseStrict_ : 1; + + // Tracks if script-related fields are already copied to ScriptStencilExtra. + // + // If this field is true, those fileds shouldn't be modified. + // + // For FunctionBox, some fields are allowed to be modified, but the + // modification should be synced with ScriptStencilExtra by + // FunctionBox::copyUpdated* methods. + bool isScriptExtraFieldCopiedToStencil : 1; + + // End of fields. + + enum class Kind : uint8_t { FunctionBox, Global, Eval, Module }; + + // Alias enum into SharedContext + using ImmutableFlags = ImmutableScriptFlagsEnum; + + [[nodiscard]] bool hasFlag(ImmutableFlags flag) const { + return immutableFlags_.hasFlag(flag); + } + void setFlag(ImmutableFlags flag, bool b = true) { + MOZ_ASSERT(!isScriptExtraFieldCopiedToStencil); + immutableFlags_.setFlag(flag, b); + } + void clearFlag(ImmutableFlags flag) { + MOZ_ASSERT(!isScriptExtraFieldCopiedToStencil); + immutableFlags_.clearFlag(flag); + } + + public: + SharedContext(FrontendContext* fc, Kind kind, + const JS::ReadOnlyCompileOptions& options, + Directives directives, SourceExtent extent); + + IMMUTABLE_FLAG_GETTER_SETTER(isForEval, IsForEval) + IMMUTABLE_FLAG_GETTER_SETTER(isModule, IsModule) + IMMUTABLE_FLAG_GETTER_SETTER(isFunction, IsFunction) + IMMUTABLE_FLAG_GETTER_SETTER(selfHosted, SelfHosted) + IMMUTABLE_FLAG_GETTER_SETTER(forceStrict, ForceStrict) + IMMUTABLE_FLAG_GETTER_SETTER(hasNonSyntacticScope, HasNonSyntacticScope) + IMMUTABLE_FLAG_GETTER_SETTER(noScriptRval, NoScriptRval) + IMMUTABLE_FLAG_GETTER(treatAsRunOnce, TreatAsRunOnce) + // Strict: custom logic below + IMMUTABLE_FLAG_GETTER_SETTER(hasModuleGoal, HasModuleGoal) + IMMUTABLE_FLAG_GETTER_SETTER(hasInnerFunctions, HasInnerFunctions) + IMMUTABLE_FLAG_GETTER_SETTER(hasDirectEval, HasDirectEval) + IMMUTABLE_FLAG_GETTER_SETTER(bindingsAccessedDynamically, + BindingsAccessedDynamically) + IMMUTABLE_FLAG_GETTER_SETTER(hasCallSiteObj, HasCallSiteObj) + + const SourceExtent& extent() const { return extent_; } + + bool isFunctionBox() const { return isFunction(); } + inline FunctionBox* asFunctionBox(); + bool isModuleContext() const { return isModule(); } + inline ModuleSharedContext* asModuleContext(); + bool isSuspendableContext() const { return isFunction() || isModule(); } + inline SuspendableContext* asSuspendableContext(); + bool isGlobalContext() const { + return !(isFunction() || isModule() || isForEval()); + } + inline GlobalSharedContext* asGlobalContext(); + bool isEvalContext() const { return isForEval(); } + inline EvalSharedContext* asEvalContext(); + + bool isTopLevelContext() const { return !isFunction(); } + + ThisBinding thisBinding() const { return thisBinding_; } + bool hasFunctionThisBinding() const { + return thisBinding() == ThisBinding::Function || + thisBinding() == ThisBinding::DerivedConstructor; + } + bool needsThisTDZChecks() const { + return thisBinding() == ThisBinding::DerivedConstructor; + } + + bool isSelfHosted() const { return selfHosted(); } + bool allowNewTarget() const { return allowNewTarget_; } + bool allowSuperProperty() const { return allowSuperProperty_; } + bool allowSuperCall() const { return allowSuperCall_; } + bool allowArguments() const { return allowArguments_; } + bool inWith() const { return inWith_; } + bool inClass() const { return inClass_; } + + bool hasExplicitUseStrict() const { return hasExplicitUseStrict_; } + void setExplicitUseStrict() { hasExplicitUseStrict_ = true; } + + ImmutableScriptFlags immutableFlags() { return immutableFlags_; } + + bool allBindingsClosedOver() { return bindingsAccessedDynamically(); } + + // The ImmutableFlag tracks if the entire script is strict, while the + // localStrict flag indicates the current region (such as class body) should + // be treated as strict. The localStrict flag will always be reset to false + // before the end of the script. + bool strict() const { return hasFlag(ImmutableFlags::Strict) || localStrict; } + void setStrictScript() { setFlag(ImmutableFlags::Strict); } + bool setLocalStrictMode(bool strict) { + bool retVal = localStrict; + localStrict = strict; + return retVal; + } + + void copyScriptExtraFields(ScriptStencilExtra& scriptExtra); +}; + +class MOZ_STACK_CLASS GlobalSharedContext : public SharedContext { + ScopeKind scopeKind_; + + public: + GlobalScope::ParserData* bindings; + + GlobalSharedContext(FrontendContext* fc, ScopeKind scopeKind, + const JS::ReadOnlyCompileOptions& options, + Directives directives, SourceExtent extent); + + ScopeKind scopeKind() const { return scopeKind_; } +}; + +inline GlobalSharedContext* SharedContext::asGlobalContext() { + MOZ_ASSERT(isGlobalContext()); + return static_cast(this); +} + +class MOZ_STACK_CLASS EvalSharedContext : public SharedContext { + public: + EvalScope::ParserData* bindings; + + EvalSharedContext(FrontendContext* fc, CompilationState& compilationState, + SourceExtent extent); +}; + +inline EvalSharedContext* SharedContext::asEvalContext() { + MOZ_ASSERT(isEvalContext()); + return static_cast(this); +} + +enum class HasHeritage { No, Yes }; + +class SuspendableContext : public SharedContext { + public: + SuspendableContext(FrontendContext* fc, Kind kind, + const JS::ReadOnlyCompileOptions& options, + Directives directives, SourceExtent extent, + bool isGenerator, bool isAsync); + + IMMUTABLE_FLAG_GETTER_SETTER(isAsync, IsAsync) + IMMUTABLE_FLAG_GETTER_SETTER(isGenerator, IsGenerator) + + bool needsFinalYield() const { return isGenerator() || isAsync(); } + bool needsDotGeneratorName() const { return isGenerator() || isAsync(); } + bool needsClearSlotsOnExit() const { return isGenerator() || isAsync(); } + bool needsIteratorResult() const { return isGenerator() && !isAsync(); } + bool needsPromiseResult() const { return isAsync() && !isGenerator(); } +}; + +class FunctionBox : public SuspendableContext { + friend struct GCThingList; + + CompilationState& compilationState_; + + // If this FunctionBox refers to a lazy child of the function being + // compiled, this field holds the child's immediately enclosing scope's index. + // Once compilation succeeds, we will store the scope pointed by this in the + // child's BaseScript. (Debugger may become confused if lazy scripts refer to + // partially initialized enclosing scopes, so we must avoid storing the + // scope in the BaseScript until compilation has completed + // successfully.) + // This is copied to ScriptStencil. + // Any update after the copy should be synced to the ScriptStencil. + mozilla::Maybe enclosingScopeIndex_; + + // Names from the named lambda scope, if a named lambda. + LexicalScope::ParserData* namedLambdaBindings_ = nullptr; + + // Names from the function scope. + FunctionScope::ParserData* functionScopeBindings_ = nullptr; + + // Names from the extra 'var' scope of the function, if the parameter list + // has expressions. + VarScope::ParserData* extraVarScopeBindings_ = nullptr; + + // The explicit or implicit name of the function. The FunctionFlags indicate + // the kind of name. + // This is copied to ScriptStencil. + // Any update after the copy should be synced to the ScriptStencil. + TaggedParserAtomIndex atom_; + + // Index into CompilationStencil::scriptData. + ScriptIndex funcDataIndex_ = ScriptIndex(-1); + + // See: FunctionFlags + // This is copied to ScriptStencil. + // Any update after the copy should be synced to the ScriptStencil. + FunctionFlags flags_ = {}; + + // See: ImmutableScriptData::funLength + uint16_t length_ = 0; + + // JSFunction::nargs_ + // This field is copied to ScriptStencil, and shouldn't be modified after the + // copy. + uint16_t nargs_ = 0; + + // See: PrivateScriptData::memberInitializers_ + // This field is copied to ScriptStencil, and shouldn't be modified after the + // copy. + MemberInitializers memberInitializers_ = MemberInitializers::Invalid(); + + public: + // Back pointer used by asm.js for error messages. + FunctionNode* functionNode = nullptr; + + // True if bytecode will be emitted for this function in the current + // compilation. + bool emitBytecode : 1; + + // This is set by the BytecodeEmitter of the enclosing script when a reference + // to this function is generated. This is also used to determine a hoisted + // function already is referenced by the bytecode. + bool wasEmittedByEnclosingScript_ : 1; + + // Need to emit a synthesized Annex B assignment + bool isAnnexB : 1; + + // Track if we saw "use asm". + // If we successfully validated it, `flags_` is seto to `AsmJS` kind. + bool useAsm : 1; + + // Analysis of parameter list + bool hasParameterExprs : 1; + bool hasDestructuringArgs : 1; + bool hasDuplicateParameters : 1; + + // Arrow function with expression body like: `() => 1`. + bool hasExprBody_ : 1; + + // Used to issue an early error in static class blocks. + bool allowReturn_ : 1; + + // Tracks if function-related fields are already copied to ScriptStencil. + // If this field is true, modification to those fields should be synced with + // ScriptStencil by copyUpdated* methods. + bool isFunctionFieldCopiedToStencil : 1; + + // True if this is part of initial compilation. + // False if this is part of delazification. + bool isInitialCompilation : 1; + + // True if this is standalone function + // (new Function() including generator/async, or event handler). + bool isStandalone : 1; + + // End of fields. + + FunctionBox(FrontendContext* fc, SourceExtent extent, + CompilationState& compilationState, Directives directives, + GeneratorKind generatorKind, FunctionAsyncKind asyncKind, + bool isInitialCompilation, TaggedParserAtomIndex atom, + FunctionFlags flags, ScriptIndex index); + + ScriptStencil& functionStencil() const; + ScriptStencilExtra& functionExtraStencil() const; + + LexicalScope::ParserData* namedLambdaBindings() { + return namedLambdaBindings_; + } + void setNamedLambdaBindings(LexicalScope::ParserData* bindings) { + namedLambdaBindings_ = bindings; + } + + FunctionScope::ParserData* functionScopeBindings() { + return functionScopeBindings_; + } + void setFunctionScopeBindings(FunctionScope::ParserData* bindings) { + functionScopeBindings_ = bindings; + } + + VarScope::ParserData* extraVarScopeBindings() { + return extraVarScopeBindings_; + } + void setExtraVarScopeBindings(VarScope::ParserData* bindings) { + extraVarScopeBindings_ = bindings; + } + + void initFromLazyFunction(const ScriptStencilExtra& extra, + ScopeContext& scopeContext, + FunctionSyntaxKind kind); + void initFromScriptStencilExtra(const ScriptStencilExtra& extra); + void initStandalone(ScopeContext& scopeContext, FunctionSyntaxKind kind); + + private: + void initStandaloneOrLazy(ScopeContext& scopeContext, + FunctionSyntaxKind kind); + + public: + void initWithEnclosingParseContext(ParseContext* enclosing, + FunctionSyntaxKind kind); + + void setEnclosingScopeForInnerLazyFunction(ScopeIndex scopeIndex); + + bool wasEmittedByEnclosingScript() const { + return wasEmittedByEnclosingScript_; + } + void setWasEmittedByEnclosingScript(bool wasEmitted) { + wasEmittedByEnclosingScript_ = wasEmitted; + if (isFunctionFieldCopiedToStencil) { + copyUpdatedWasEmitted(); + } + } + + [[nodiscard]] bool setAsmJSModule(const JS::WasmModule* module); + bool isAsmJSModule() const { return flags_.isAsmJSNative(); } + + bool hasEnclosingScopeIndex() const { return enclosingScopeIndex_.isSome(); } + ScopeIndex getEnclosingScopeIndex() const { return *enclosingScopeIndex_; } + + IMMUTABLE_FLAG_GETTER_SETTER(isAsync, IsAsync) + IMMUTABLE_FLAG_GETTER_SETTER(isGenerator, IsGenerator) + IMMUTABLE_FLAG_GETTER_SETTER(funHasExtensibleScope, FunHasExtensibleScope) + IMMUTABLE_FLAG_GETTER_SETTER(functionHasThisBinding, FunctionHasThisBinding) + IMMUTABLE_FLAG_GETTER_SETTER(functionHasNewTargetBinding, + FunctionHasNewTargetBinding) + // NeedsHomeObject: custom logic below. + // IsDerivedClassConstructor: custom logic below. + // IsFieldInitializer: custom logic below. + IMMUTABLE_FLAG_GETTER(useMemberInitializers, UseMemberInitializers) + IMMUTABLE_FLAG_GETTER_SETTER(hasRest, HasRest) + IMMUTABLE_FLAG_GETTER_SETTER(needsFunctionEnvironmentObjects, + NeedsFunctionEnvironmentObjects) + IMMUTABLE_FLAG_GETTER_SETTER(functionHasExtraBodyVarScope, + FunctionHasExtraBodyVarScope) + IMMUTABLE_FLAG_GETTER_SETTER(shouldDeclareArguments, ShouldDeclareArguments) + IMMUTABLE_FLAG_GETTER_SETTER(needsArgsObj, NeedsArgsObj) + // HasMappedArgsObj: custom logic below. + + bool needsCallObjectRegardlessOfBindings() const { + // Always create a CallObject if: + // - The scope is extensible at runtime due to sloppy eval. + // - The function is a generator or async function. (The debugger reads the + // generator object directly from the frame.) + + return funHasExtensibleScope() || isGenerator() || isAsync(); + } + + bool needsExtraBodyVarEnvironmentRegardlessOfBindings() const { + MOZ_ASSERT(hasParameterExprs); + return funHasExtensibleScope(); + } + + GeneratorKind generatorKind() const { + return isGenerator() ? GeneratorKind::Generator + : GeneratorKind::NotGenerator; + } + + FunctionAsyncKind asyncKind() const { + return isAsync() ? FunctionAsyncKind::AsyncFunction + : FunctionAsyncKind::SyncFunction; + } + + bool needsFinalYield() const { return isGenerator() || isAsync(); } + bool needsDotGeneratorName() const { return isGenerator() || isAsync(); } + bool needsClearSlotsOnExit() const { return isGenerator() || isAsync(); } + bool needsIteratorResult() const { return isGenerator() && !isAsync(); } + bool needsPromiseResult() const { return isAsync() && !isGenerator(); } + + bool isArrow() const { return flags_.isArrow(); } + bool isLambda() const { return flags_.isLambda(); } + + bool hasExprBody() const { return hasExprBody_; } + void setHasExprBody() { + MOZ_ASSERT(isArrow()); + hasExprBody_ = true; + } + + bool allowReturn() const { return allowReturn_; } + + bool isNamedLambda() const { return flags_.isNamedLambda(!!explicitName()); } + bool isGetter() const { return flags_.isGetter(); } + bool isSetter() const { return flags_.isSetter(); } + bool isMethod() const { return flags_.isMethod(); } + bool isClassConstructor() const { return flags_.isClassConstructor(); } + + bool isInterpreted() const { return flags_.hasBaseScript(); } + + FunctionFlags::FunctionKind kind() { return flags_.kind(); } + + bool hasInferredName() const { return flags_.hasInferredName(); } + bool hasGuessedAtom() const { return flags_.hasGuessedAtom(); } + + TaggedParserAtomIndex displayAtom() const { return atom_; } + TaggedParserAtomIndex explicitName() const { + return (hasInferredName() || hasGuessedAtom()) + ? TaggedParserAtomIndex::null() + : atom_; + } + + // NOTE: We propagate to any existing functions for now. This handles both the + // delazification case where functions already exist, and also handles + // code-coverage which is not yet deferred. + void setInferredName(TaggedParserAtomIndex atom) { + atom_ = atom; + flags_.setInferredName(); + if (isFunctionFieldCopiedToStencil) { + copyUpdatedAtomAndFlags(); + } + } + void setGuessedAtom(TaggedParserAtomIndex atom) { + atom_ = atom; + flags_.setGuessedAtom(); + if (isFunctionFieldCopiedToStencil) { + copyUpdatedAtomAndFlags(); + } + } + + bool needsHomeObject() const { + return hasFlag(ImmutableFlags::NeedsHomeObject); + } + void setNeedsHomeObject() { + MOZ_ASSERT(flags_.allowSuperProperty()); + setFlag(ImmutableFlags::NeedsHomeObject); + flags_.setIsExtended(); + } + + bool isDerivedClassConstructor() const { + return hasFlag(ImmutableFlags::IsDerivedClassConstructor); + } + void setDerivedClassConstructor() { + MOZ_ASSERT(flags_.isClassConstructor()); + setFlag(ImmutableFlags::IsDerivedClassConstructor); + } + + bool isSyntheticFunction() const { + return hasFlag(ImmutableFlags::IsSyntheticFunction); + } + void setSyntheticFunction() { + // Field initializer, class constructor or getter or setter + // synthesized from accessor keyword. + MOZ_ASSERT(flags_.isMethod() || flags_.isGetter() || flags_.isSetter()); + setFlag(ImmutableFlags::IsSyntheticFunction); + } + + bool hasSimpleParameterList() const { + return !hasRest() && !hasParameterExprs && !hasDestructuringArgs; + } + + bool hasMappedArgsObj() const { + return !strict() && hasSimpleParameterList(); + } + + // Return whether this or an enclosing function is being parsed and + // validated as asm.js. Note: if asm.js validation fails, this will be false + // while the function is being reparsed. This flag can be used to disable + // certain parsing features that are necessary in general, but unnecessary + // for validated asm.js. + bool useAsmOrInsideUseAsm() const { return useAsm; } + + void setStart(uint32_t offset, uint32_t line, uint32_t column) { + MOZ_ASSERT(!isScriptExtraFieldCopiedToStencil); + extent_.sourceStart = offset; + extent_.lineno = line; + extent_.column = column; + } + + void setEnd(uint32_t end) { + MOZ_ASSERT(!isScriptExtraFieldCopiedToStencil); + // For all functions except class constructors, the buffer and + // toString ending positions are the same. Class constructors override + // the toString ending position with the end of the class definition. + extent_.sourceEnd = end; + extent_.toStringEnd = end; + } + + void setCtorToStringEnd(uint32_t end) { + extent_.toStringEnd = end; + if (isScriptExtraFieldCopiedToStencil) { + copyUpdatedExtent(); + } + } + + void setCtorFunctionHasThisBinding() { + immutableFlags_.setFlag(ImmutableFlags::FunctionHasThisBinding, true); + if (isScriptExtraFieldCopiedToStencil) { + copyUpdatedImmutableFlags(); + } + } + + void setIsInlinableLargeFunction() { + immutableFlags_.setFlag(ImmutableFlags::IsInlinableLargeFunction, true); + if (isScriptExtraFieldCopiedToStencil) { + copyUpdatedImmutableFlags(); + } + } + + void setUsesArgumentsIntrinsics() { + immutableFlags_.setFlag(ImmutableFlags::UsesArgumentsIntrinsics, true); + if (isScriptExtraFieldCopiedToStencil) { + copyUpdatedImmutableFlags(); + } + } + + uint16_t length() { return length_; } + void setLength(uint16_t length) { length_ = length; } + + void setArgCount(uint16_t args) { + MOZ_ASSERT(!isFunctionFieldCopiedToStencil); + nargs_ = args; + } + + size_t nargs() { return nargs_; } + + const MemberInitializers& memberInitializers() const { + MOZ_ASSERT(useMemberInitializers()); + return memberInitializers_; + } + void setMemberInitializers(MemberInitializers memberInitializers) { + immutableFlags_.setFlag(ImmutableFlags::UseMemberInitializers, true); + memberInitializers_ = memberInitializers; + if (isScriptExtraFieldCopiedToStencil) { + copyUpdatedImmutableFlags(); + copyUpdatedMemberInitializers(); + } + } + + ScriptIndex index() { return funcDataIndex_; } + + void finishScriptFlags(); + void copyFunctionFields(ScriptStencil& script); + void copyFunctionExtraFields(ScriptStencilExtra& scriptExtra); + + // * setCtorFunctionHasThisBinding can be called to a class constructor + // with a lazy function, while parsing enclosing class + // * setIsInlinableLargeFunction can be called by BCE to update flags of the + // previous top-level function, but only in self-hosted mode. + void copyUpdatedImmutableFlags(); + + // * setCtorToStringEnd bcan be called to a class constructor with a lazy + // function, while parsing enclosing class + void copyUpdatedExtent(); + + // * setMemberInitializers can be called to a class constructor with a lazy + // function, while emitting enclosing script + void copyUpdatedMemberInitializers(); + + // * setEnclosingScopeForInnerLazyFunction can be called to a lazy function, + // while emitting enclosing script + void copyUpdatedEnclosingScopeIndex(); + + // * setInferredName can be called to a lazy function, while emitting + // enclosing script + // * setGuessedAtom can be called to both lazy/non-lazy functions, + // while running NameFunctions + void copyUpdatedAtomAndFlags(); + + // * setWasEmitted can be called to a lazy function, while emitting + // enclosing script + void copyUpdatedWasEmitted(); +}; + +#undef FLAG_GETTER_SETTER +#undef IMMUTABLE_FLAG_GETTER_SETTER + +inline FunctionBox* SharedContext::asFunctionBox() { + MOZ_ASSERT(isFunctionBox()); + return static_cast(this); +} + +inline SuspendableContext* SharedContext::asSuspendableContext() { + MOZ_ASSERT(isSuspendableContext()); + return static_cast(this); +} + +} // namespace frontend +} // namespace js + +#endif /* frontend_SharedContext_h */ diff --git a/js/src/frontend/SourceNotes.cpp b/js/src/frontend/SourceNotes.cpp new file mode 100644 index 0000000000..3c16d41e7b --- /dev/null +++ b/js/src/frontend/SourceNotes.cpp @@ -0,0 +1,13 @@ +/* -*- 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/SourceNotes.h" + +const js::SrcNote::Spec js::SrcNote::specs_[] = { +#define DEFINE_SRC_NOTE_SPEC(sym, name, arity) {name, arity}, + FOR_EACH_SRC_NOTE_TYPE(DEFINE_SRC_NOTE_SPEC) +#undef DEFINE_SRC_NOTE_SPEC +}; diff --git a/js/src/frontend/SourceNotes.h b/js/src/frontend/SourceNotes.h new file mode 100644 index 0000000000..dd382bc7dc --- /dev/null +++ b/js/src/frontend/SourceNotes.h @@ -0,0 +1,428 @@ +/* -*- 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/. */ + +#ifndef frontend_SourceNotes_h +#define frontend_SourceNotes_h + +#include "mozilla/Assertions.h" // MOZ_ASSERT + +#include // std::min +#include // ptrdiff_t, size_t +#include // int8_t, uint8_t, uint32_t + +#include "jstypes.h" // js::{Bit, BitMask} + +namespace js { + +/* + * Source notes generated along with bytecode for decompiling and debugging. + * A source note is a uint8_t with 4 bits of type and 4 of offset from the pc + * of the previous note. If 4 bits of offset aren't enough, extended delta + * notes (XDelta) consisting of 1 set high order bit followed by 7 offset + * bits are emitted before the next note. Some notes have operand offsets + * encoded immediately after them, in note bytes or byte-triples. + * + * Source Note Extended Delta + * +7-6-5-4+3-2-1-0+ +7+6-5-4-3-2-1-0+ + * | type | delta | |1| ext-delta | + * +-------+-------+ +-+-------------+ + * + * At most one "gettable" note (i.e., a note of type other than NewLine, + * ColSpan, SetLine, and XDelta) applies to a given bytecode. + * + * NB: the js::SrcNote::specs_ array is indexed by this enum, so its + * initializers need to match the order here. + */ + +#define FOR_EACH_SRC_NOTE_TYPE(M) \ + /* Terminates a note vector. */ \ + M(Null, "null", 0) \ + /* += or another assign-op follows. */ \ + M(AssignOp, "assignop", 0) \ + /* All notes above here are "gettable". See SrcNote::isGettable below. */ \ + M(ColSpan, "colspan", int8_t(SrcNote::ColSpan::Operands::Count)) \ + /* Bytecode follows a source newline. */ \ + M(NewLine, "newline", 0) \ + M(SetLine, "setline", int8_t(SrcNote::SetLine::Operands::Count)) \ + /* Bytecode is a recommended breakpoint. */ \ + M(Breakpoint, "breakpoint", 0) \ + /* Bytecode is the first in a new steppable area. */ \ + M(StepSep, "step-sep", 0) \ + M(Unused7, "unused", 0) \ + /* 8-15 (0b1xxx) are for extended delta notes. */ \ + M(XDelta, "xdelta", 0) + +// Note: need to add a new source note? If there's no Unused* note left, +// consider bumping SrcNoteType::XDelta to 12-15 and change +// SrcNote::XDeltaBits from 7 to 6. + +enum class SrcNoteType : uint8_t { +#define DEFINE_SRC_NOTE_TYPE(sym, name, arity) sym, + FOR_EACH_SRC_NOTE_TYPE(DEFINE_SRC_NOTE_TYPE) +#undef DEFINE_SRC_NOTE_TYPE + + Last, + LastGettable = AssignOp +}; + +static_assert(uint8_t(SrcNoteType::XDelta) == 8, "XDelta should be 8"); + +class SrcNote { + struct Spec { + const char* name_; + int8_t arity_; + }; + + static const Spec specs_[]; + + static constexpr unsigned TypeBits = 4; + static constexpr unsigned DeltaBits = 4; + static constexpr unsigned XDeltaBits = 7; + + static constexpr uint8_t TypeMask = js::BitMask(TypeBits) << DeltaBits; + static constexpr ptrdiff_t DeltaMask = js::BitMask(DeltaBits); + static constexpr ptrdiff_t XDeltaMask = js::BitMask(XDeltaBits); + + static constexpr ptrdiff_t DeltaLimit = js::Bit(DeltaBits); + static constexpr ptrdiff_t XDeltaLimit = js::Bit(XDeltaBits); + + static constexpr inline uint8_t toShiftedTypeBits(SrcNoteType type) { + return (uint8_t(type) << DeltaBits); + } + + static inline uint8_t noteValue(SrcNoteType type, ptrdiff_t delta) { + MOZ_ASSERT((delta & DeltaMask) == delta); + return noteValueUnchecked(type, delta); + } + + static constexpr inline uint8_t noteValueUnchecked(SrcNoteType type, + ptrdiff_t delta) { + return toShiftedTypeBits(type) | (delta & DeltaMask); + } + + static inline uint8_t xDeltaValue(ptrdiff_t delta) { + return toShiftedTypeBits(SrcNoteType::XDelta) | (delta & XDeltaMask); + } + + uint8_t value_; + + constexpr explicit SrcNote(uint8_t value) : value_(value) {} + + public: + constexpr SrcNote() : value_(noteValueUnchecked(SrcNoteType::Null, 0)){}; + + SrcNote(const SrcNote& other) = default; + SrcNote& operator=(const SrcNote& other) = default; + + SrcNote(SrcNote&& other) = default; + SrcNote& operator=(SrcNote&& other) = default; + + static constexpr SrcNote terminator() { return SrcNote(); } + + private: + inline uint8_t typeBits() const { return (value_ >> DeltaBits); } + + inline bool isXDelta() const { + return typeBits() >= uint8_t(SrcNoteType::XDelta); + } + + inline bool isFourBytesOperand() const { + return value_ & FourBytesOperandFlag; + } + + // number of operands + inline unsigned arity() const { + MOZ_ASSERT(uint8_t(type()) < uint8_t(SrcNoteType::Last)); + return specs_[uint8_t(type())].arity_; + } + + public: + inline SrcNoteType type() const { + if (isXDelta()) { + return SrcNoteType::XDelta; + } + return SrcNoteType(typeBits()); + } + + // name for disassembly/debugging output + const char* name() const { + MOZ_ASSERT(uint8_t(type()) < uint8_t(SrcNoteType::Last)); + return specs_[uint8_t(type())].name_; + } + + inline bool isGettable() const { + return uint8_t(type()) <= uint8_t(SrcNoteType::LastGettable); + } + + inline bool isTerminator() const { + return value_ == uint8_t(SrcNoteType::Null); + } + + inline ptrdiff_t delta() const { + if (isXDelta()) { + return value_ & XDeltaMask; + } + return value_ & DeltaMask; + } + + private: + /* + * Operand fields follow certain notes and are frequency-encoded: an operand + * in [0,0x7f] consumes one byte, an operand in [0x80,0x7fffffff] takes four, + * and the high bit of the first byte is set. + */ + static constexpr unsigned FourBytesOperandFlag = 0x80; + static constexpr unsigned FourBytesOperandMask = 0x7f; + + static constexpr unsigned OperandBits = 31; + + public: + static constexpr size_t MaxOperand = (size_t(1) << OperandBits) - 1; + + static inline bool isRepresentableOperand(ptrdiff_t operand) { + return 0 <= operand && size_t(operand) <= MaxOperand; + } + + class ColSpan { + public: + enum class Operands { + // The column span (the diff between the column corresponds to the + // current op and last known column). + Span, + Count + }; + + private: + /* + * SrcNoteType::ColSpan values represent changes to the column number. + * Colspans are signed: negative changes arise in describing constructs like + * for(;;) loops, that generate code in non-source order. (Negative colspans + * also have a history of indicating bugs in updating ParseNodes' source + * locations.) + * + * We store colspans in operands. However, unlike normal operands, colspans + * are signed, so we truncate colspans (toOperand) for storage as + * operands, and sign-extend operands into colspans when we read them + * (fromOperand). + */ + static constexpr ptrdiff_t ColSpanSignBit = 1 << (OperandBits - 1); + + static inline ptrdiff_t fromOperand(ptrdiff_t operand) { + // There should be no bits set outside the field we're going to + // sign-extend. + MOZ_ASSERT(!(operand & ~((1U << OperandBits) - 1))); + + // Sign-extend the least significant OperandBits bits. + return (operand ^ ColSpanSignBit) - ColSpanSignBit; + } + + public: + static constexpr ptrdiff_t MinColSpan = -ColSpanSignBit; + static constexpr ptrdiff_t MaxColSpan = ColSpanSignBit - 1; + + static inline ptrdiff_t toOperand(ptrdiff_t colspan) { + // Truncate the two's complement colspan, for storage as an operand. + ptrdiff_t operand = colspan & ((1U << OperandBits) - 1); + + // When we read this back, we'd better get the value we stored. + MOZ_ASSERT(fromOperand(operand) == colspan); + return operand; + } + + static inline ptrdiff_t getSpan(const SrcNote* sn); + }; + + class SetLine { + public: + enum class Operands { + // The file-absolute source line number of the current op. + Line, + Count + }; + + private: + static inline size_t fromOperand(ptrdiff_t operand) { + return size_t(operand); + } + + public: + static inline unsigned lengthFor(unsigned line, size_t initialLine) { + unsigned operandSize = toOperand(line, initialLine) > + ptrdiff_t(SrcNote::FourBytesOperandMask) + ? 4 + : 1; + return 1 /* SetLine */ + operandSize; + } + + static inline ptrdiff_t toOperand(size_t line, size_t initialLine) { + MOZ_ASSERT(line >= initialLine); + return ptrdiff_t(line - initialLine); + } + + static inline size_t getLine(const SrcNote* sn, size_t initialLine); + }; + + friend class SrcNoteWriter; + friend class SrcNoteReader; + friend class SrcNoteIterator; +}; + +class SrcNoteWriter { + public: + // Write a source note with given `type`, and `delta` from the last source + // note. This writes the source note itself, and `XDelta`s if necessary. + // + // This doesn't write or allocate space for operands. + // If the source note is not nullary, the caller is responsible for calling + // `writeOperand` immediately after this. + // + // `allocator` is called with the number of bytes required to store the notes. + // `allocator` can be called multiple times for each source note. + // The last call corresponds to the source note for `type`. + template + static bool writeNote(SrcNoteType type, ptrdiff_t delta, T allocator) { + while (delta >= SrcNote::DeltaLimit) { + ptrdiff_t xdelta = std::min(delta, SrcNote::XDeltaMask); + SrcNote* sn = allocator(1); + if (!sn) { + return false; + } + sn->value_ = SrcNote::xDeltaValue(xdelta); + delta -= xdelta; + } + + SrcNote* sn = allocator(1); + if (!sn) { + return false; + } + sn->value_ = SrcNote::noteValue(type, delta); + return true; + } + + // Write source note operand. + // + // `allocator` is called with the number of bytes required to store the + // operand. `allocator` is called only once. + template + static bool writeOperand(ptrdiff_t operand, T allocator) { + if (operand > ptrdiff_t(SrcNote::FourBytesOperandMask)) { + SrcNote* sn = allocator(4); + if (!sn) { + return false; + } + + sn[0].value_ = (SrcNote::FourBytesOperandFlag | (operand >> 24)); + sn[1].value_ = operand >> 16; + sn[2].value_ = operand >> 8; + sn[3].value_ = operand; + } else { + SrcNote* sn = allocator(1); + if (!sn) { + return false; + } + + sn[0].value_ = operand; + } + + return true; + } +}; + +class SrcNoteReader { + template + static T getOperandHead(T sn, unsigned which) { + MOZ_ASSERT(sn->type() != SrcNoteType::XDelta); + MOZ_ASSERT(uint8_t(which) < sn->arity()); + + T curr = sn + 1; + for (; which; which--) { + if (curr->isFourBytesOperand()) { + curr += 4; + } else { + curr++; + } + } + return curr; + } + + public: + // Return the operand of source note `sn`, specified by `which`. + static ptrdiff_t getOperand(const SrcNote* sn, unsigned which) { + const SrcNote* head = getOperandHead(sn, which); + + if (head->isFourBytesOperand()) { + return ptrdiff_t( + (uint32_t(head[0].value_ & SrcNote::FourBytesOperandMask) << 24) | + (uint32_t(head[1].value_) << 16) | (uint32_t(head[2].value_) << 8) | + uint32_t(head[3].value_)); + } + + return ptrdiff_t(head[0].value_); + } +}; + +/* static */ +inline ptrdiff_t SrcNote::ColSpan::getSpan(const SrcNote* sn) { + return fromOperand(SrcNoteReader::getOperand(sn, unsigned(Operands::Span))); +} + +/* static */ +inline size_t SrcNote::SetLine::getLine(const SrcNote* sn, size_t initialLine) { + return initialLine + + fromOperand(SrcNoteReader::getOperand(sn, unsigned(Operands::Line))); +} + +// Iterate over SrcNote array, until it hits terminator. +// +// Usage: +// for (SrcNoteIterator iter(notes); !iter.atEnd(); ++iter) { +// auto sn = *iter; // `sn` is `const SrcNote*` typed. +// ... +// } +class SrcNoteIterator { + const SrcNote* current_; + + void next() { + unsigned arity = current_->arity(); + current_++; + + for (; arity; arity--) { + if (current_->isFourBytesOperand()) { + current_ += 4; + } else { + current_++; + } + } + } + + public: + SrcNoteIterator() = delete; + + SrcNoteIterator(const SrcNoteIterator& other) = delete; + SrcNoteIterator& operator=(const SrcNoteIterator& other) = delete; + + SrcNoteIterator(SrcNoteIterator&& other) = default; + SrcNoteIterator& operator=(SrcNoteIterator&& other) = default; + + explicit SrcNoteIterator(const SrcNote* sn) : current_(sn) {} + + bool atEnd() const { return current_->isTerminator(); } + + const SrcNote* operator*() const { return current_; } + + // Pre-increment + SrcNoteIterator& operator++() { + next(); + return *this; + } + + // Post-increment + SrcNoteIterator operator++(int) = delete; +}; + +} // namespace js + +#endif /* frontend_SourceNotes_h */ diff --git a/js/src/frontend/Stencil.cpp b/js/src/frontend/Stencil.cpp new file mode 100644 index 0000000000..a42ab238df --- /dev/null +++ b/js/src/frontend/Stencil.cpp @@ -0,0 +1,5363 @@ +/* -*- 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/Stencil.h" + +#include "mozilla/AlreadyAddRefed.h" // already_AddRefed +#include "mozilla/Assertions.h" // MOZ_RELEASE_ASSERT +#include "mozilla/Maybe.h" // mozilla::Maybe +#include "mozilla/OperatorNewExtensions.h" // mozilla::KnownNotNull +#include "mozilla/PodOperations.h" // mozilla::PodCopy +#include "mozilla/RefPtr.h" // RefPtr +#include "mozilla/ScopeExit.h" // mozilla::ScopeExit +#include "mozilla/Sprintf.h" // SprintfLiteral + +#include "ds/LifoAlloc.h" // LifoAlloc +#include "frontend/AbstractScopePtr.h" // ScopeIndex +#include "frontend/BytecodeCompilation.h" // CanLazilyParse, CompileGlobalScriptToStencil +#include "frontend/BytecodeCompiler.h" // ParseModuleToStencil +#include "frontend/BytecodeSection.h" // EmitScriptThingsVector +#include "frontend/CompilationStencil.h" // CompilationStencil, CompilationState, ExtensibleCompilationStencil, CompilationGCOutput, CompilationStencilMerger +#include "frontend/FrontendContext.h" +#include "frontend/NameAnalysisTypes.h" // EnvironmentCoordinate +#include "frontend/ScopeBindingCache.h" // ScopeBindingCache +#include "frontend/SharedContext.h" +#include "frontend/StencilXdr.h" // XDRStencilEncoder, XDRStencilDecoder +#include "gc/AllocKind.h" // gc::AllocKind +#include "gc/Tracer.h" // TraceNullableRoot +#include "js/CallArgs.h" // JSNative +#include "js/CompileOptions.h" // JS::DecodeOptions +#include "js/experimental/JSStencil.h" // JS::Stencil +#include "js/GCAPI.h" // JS::AutoCheckCannotGC +#include "js/Printer.h" // js::Fprinter +#include "js/RootingAPI.h" // Rooted +#include "js/Transcoding.h" // JS::TranscodeBuffer +#include "js/Value.h" // ObjectValue +#include "js/WasmModule.h" // JS::WasmModule +#include "vm/BigIntType.h" // ParseBigIntLiteral, BigIntLiteralIsZero +#include "vm/BindingKind.h" // BindingKind +#include "vm/EnvironmentObject.h" +#include "vm/GeneratorAndAsyncKind.h" // GeneratorKind, FunctionAsyncKind +#include "vm/JSContext.h" // JSContext +#include "vm/JSFunction.h" // JSFunction, GetFunctionPrototype, NewFunctionWithProto +#include "vm/JSObject.h" // JSObject, TenuredObject +#include "vm/JSONPrinter.h" // js::JSONPrinter +#include "vm/JSScript.h" // BaseScript, JSScript +#include "vm/RegExpObject.h" // js::RegExpObject +#include "vm/Scope.h" // Scope, *Scope, ScopeKind::*, ScopeKindString, ScopeIter, ScopeKindIsCatch, BindingIter, GetScopeDataTrailingNames, SizeOfParserScopeData +#include "vm/ScopeKind.h" // ScopeKind +#include "vm/SelfHosting.h" // SetClonedSelfHostedFunctionName +#include "vm/StaticStrings.h" +#include "vm/StencilEnums.h" // ImmutableScriptFlagsEnum +#include "vm/StringType.h" // JSAtom, js::CopyChars +#include "wasm/AsmJS.h" // InstantiateAsmJS + +#include "vm/EnvironmentObject-inl.h" // JSObject::enclosingEnvironment +#include "vm/JSFunction-inl.h" // JSFunction::create + +using namespace js; +using namespace js::frontend; + +// These 2 functions are used to write the same code with lambda using auto +// arguments. The auto argument type is set by the Variant.match function of the +// InputScope variant. Thus dispatching to either a Scope* or to a +// ScopeStencilRef. This function can then be used as a way to specialize the +// code within the lambda without duplicating the code. +// +// Identically, an InputName is constructed using the scope type and the +// matching binding name type. This way, functions which are called by this +// lambda can manipulate an InputName and do not have to be duplicated. +// +// for (InputScopeIter si(...); si; si++) { +// si.scope().match([](auto& scope) { +// for (auto bi = InputBindingIter(scope); bi; bi++) { +// InputName name(scope, bi.name()); +// } +// }); +// } +static js::BindingIter InputBindingIter(Scope* ptr) { + return js::BindingIter(ptr); +} + +static ParserBindingIter InputBindingIter(const ScopeStencilRef& ref) { + return ParserBindingIter(ref); +} + +InputName InputScript::displayAtom() const { + return script_.match( + [](BaseScript* ptr) { + return InputName(ptr, ptr->function()->displayAtom()); + }, + [](const ScriptStencilRef& ref) { + return InputName(ref, ref.scriptData().functionAtom); + }); +} + +TaggedParserAtomIndex InputName::internInto(FrontendContext* fc, + ParserAtomsTable& parserAtoms, + CompilationAtomCache& atomCache) { + return variant_.match( + [&](JSAtom* ptr) -> TaggedParserAtomIndex { + return parserAtoms.internJSAtom(fc, atomCache, ptr); + }, + [&](NameStencilRef& ref) -> TaggedParserAtomIndex { + return parserAtoms.internExternalParserAtomIndex(fc, ref.context_, + ref.atomIndex_); + }); +} + +bool InputName::isEqualTo(FrontendContext* fc, ParserAtomsTable& parserAtoms, + CompilationAtomCache& atomCache, + TaggedParserAtomIndex other, + JSAtom** otherCached) const { + return variant_.match( + [&](const JSAtom* ptr) -> bool { + if (ptr->hash() != parserAtoms.hash(other)) { + return false; + } + + // JSAtom variant is used only on the main thread delazification, + // where JSContext is always available. + JSContext* cx = fc->maybeCurrentJSContext(); + MOZ_ASSERT(cx); + + if (!*otherCached) { + // TODO-Stencil: + // Here, we convert our name into a JSAtom*, and hard-crash on failure + // to allocate. This conversion should not be required as we should be + // able to iterate up snapshotted scope chains that use parser atoms. + // + // This will be fixed when the enclosing scopes are snapshotted. + // + // See bug 1690277. + AutoEnterOOMUnsafeRegion oomUnsafe; + *otherCached = parserAtoms.toJSAtom(cx, fc, other, atomCache); + if (!*otherCached) { + oomUnsafe.crash("InputName::isEqualTo"); + } + } else { + MOZ_ASSERT(atomCache.getExistingAtomAt(cx, other) == *otherCached); + } + return ptr == *otherCached; + }, + [&](const NameStencilRef& ref) -> bool { + return parserAtoms.isEqualToExternalParserAtomIndex(other, ref.context_, + ref.atomIndex_); + }); +} + +GenericAtom::GenericAtom(FrontendContext* fc, ParserAtomsTable& parserAtoms, + CompilationAtomCache& atomCache, + TaggedParserAtomIndex index) + : ref(EmitterName(fc, parserAtoms, atomCache, index)) { + hash = parserAtoms.hash(index); +} + +GenericAtom::GenericAtom(const CompilationStencil& context, + TaggedParserAtomIndex index) + : ref(StencilName{context, index}) { + if (index.isParserAtomIndex()) { + ParserAtom* atom = context.parserAtomData[index.toParserAtomIndex()]; + hash = atom->hash(); + } else { + hash = index.staticOrWellKnownHash(); + } +} + +GenericAtom::GenericAtom(ScopeStencilRef& scope, TaggedParserAtomIndex index) + : GenericAtom(scope.context_, index) {} + +BindingHasher::Lookup::Lookup(ScopeStencilRef& scope_ref, + const GenericAtom& other) + : keyStencil(scope_ref.context_), other(other) {} + +bool GenericAtom::operator==(const GenericAtom& other) const { + return ref.match( + [&other](const EmitterName& name) -> bool { + return other.ref.match( + [&name](const EmitterName& other) -> bool { + // We never have multiple Emitter context at the same time. + MOZ_ASSERT(name.fc == other.fc); + MOZ_ASSERT(&name.parserAtoms == &other.parserAtoms); + MOZ_ASSERT(&name.atomCache == &other.atomCache); + return name.index == other.index; + }, + [&name](const StencilName& other) -> bool { + return name.parserAtoms.isEqualToExternalParserAtomIndex( + name.index, other.stencil, other.index); + }, + [&name](JSAtom* other) -> bool { + // JSAtom variant is used only on the main thread delazification, + // where JSContext is always available. + JSContext* cx = name.fc->maybeCurrentJSContext(); + MOZ_ASSERT(cx); + AutoEnterOOMUnsafeRegion oomUnsafe; + JSAtom* namePtr = name.parserAtoms.toJSAtom( + cx, name.fc, name.index, name.atomCache); + if (!namePtr) { + oomUnsafe.crash("GenericAtom(EmitterName == JSAtom*)"); + } + return namePtr == other; + }); + }, + [&other](const StencilName& name) -> bool { + return other.ref.match( + [&name](const EmitterName& other) -> bool { + return other.parserAtoms.isEqualToExternalParserAtomIndex( + other.index, name.stencil, name.index); + }, + [&name](const StencilName& other) -> bool { + // Technically it is possible to have multiple stencils, but in + // this particular case let's assume we never encounter a case + // where we are comparing names from different stencils. + // + // The reason this assumption is safe today is that we are only + // using this in the context of a stencil-delazification, where + // the only StencilNames are coming from the CompilationStencil + // provided to CompilationInput::initFromStencil. + MOZ_ASSERT(&name.stencil == &other.stencil); + return name.index == other.index; + }, + [](JSAtom* other) -> bool { + MOZ_CRASH("Never used."); + return false; + }); + }, + [&other](JSAtom* name) -> bool { + return other.ref.match( + [&name](const EmitterName& other) -> bool { + // JSAtom variant is used only on the main thread delazification, + // where JSContext is always available. + JSContext* cx = other.fc->maybeCurrentJSContext(); + MOZ_ASSERT(cx); + AutoEnterOOMUnsafeRegion oomUnsafe; + JSAtom* otherPtr = other.parserAtoms.toJSAtom( + cx, other.fc, other.index, other.atomCache); + if (!otherPtr) { + oomUnsafe.crash("GenericAtom(JSAtom* == EmitterName)"); + } + return name == otherPtr; + }, + [](const StencilName& other) -> bool { + MOZ_CRASH("Never used."); + return false; + }, + [&name](JSAtom* other) -> bool { return name == other; }); + }); +} + +#ifdef DEBUG +template +void AssertBorrowingSpan(const SpanT& span, const VecT& vec) { + MOZ_ASSERT(span.size() == vec.length()); + MOZ_ASSERT(span.data() == vec.begin()); +} +#endif + +bool ScopeBindingCache::canCacheFor(Scope* ptr) { + MOZ_CRASH("Unexpected scope chain type: Scope*"); +} + +bool ScopeBindingCache::canCacheFor(ScopeStencilRef ref) { + MOZ_CRASH("Unexpected scope chain type: ScopeStencilRef"); +} + +BindingMap* ScopeBindingCache::createCacheFor(Scope* ptr) { + MOZ_CRASH("Unexpected scope chain type: Scope*"); +} + +BindingMap* ScopeBindingCache::lookupScope(Scope* ptr, + CacheGeneration gen) { + MOZ_CRASH("Unexpected scope chain type: Scope*"); +} + +BindingMap* ScopeBindingCache::createCacheFor( + ScopeStencilRef ref) { + MOZ_CRASH("Unexpected scope chain type: ScopeStencilRef"); +} + +BindingMap* ScopeBindingCache::lookupScope( + ScopeStencilRef ref, CacheGeneration gen) { + MOZ_CRASH("Unexpected scope chain type: ScopeStencilRef"); +} + +bool NoScopeBindingCache::canCacheFor(Scope* ptr) { return false; } + +bool NoScopeBindingCache::canCacheFor(ScopeStencilRef ref) { return false; } + +bool RuntimeScopeBindingCache::canCacheFor(Scope* ptr) { return true; } + +BindingMap* RuntimeScopeBindingCache::createCacheFor(Scope* ptr) { + BaseScopeData* dataPtr = ptr->rawData(); + BindingMap bindingCache; + if (!scopeMap.putNew(dataPtr, std::move(bindingCache))) { + return nullptr; + } + + return lookupScope(ptr, cacheGeneration); +} + +BindingMap* RuntimeScopeBindingCache::lookupScope( + Scope* ptr, CacheGeneration gen) { + MOZ_ASSERT(gen == cacheGeneration); + BaseScopeData* dataPtr = ptr->rawData(); + auto valuePtr = scopeMap.lookup(dataPtr); + if (!valuePtr) { + return nullptr; + } + return &valuePtr->value(); +} + +bool StencilScopeBindingCache::canCacheFor(ScopeStencilRef ref) { return true; } + +BindingMap* StencilScopeBindingCache::createCacheFor( + ScopeStencilRef ref) { +#ifdef DEBUG + AssertBorrowingSpan(ref.context_.scopeNames, merger_.getResult().scopeNames); +#endif + auto* dataPtr = ref.context_.scopeNames[ref.scopeIndex_]; + BindingMap bindingCache; + if (!scopeMap.putNew(dataPtr, std::move(bindingCache))) { + return nullptr; + } + + return lookupScope(ref, 1); +} + +BindingMap* StencilScopeBindingCache::lookupScope( + ScopeStencilRef ref, CacheGeneration gen) { +#ifdef DEBUG + AssertBorrowingSpan(ref.context_.scopeNames, merger_.getResult().scopeNames); +#endif + auto* dataPtr = ref.context_.scopeNames[ref.scopeIndex_]; + auto ptr = scopeMap.lookup(dataPtr); + if (!ptr) { + return nullptr; + } + return &ptr->value(); +} + +bool ScopeContext::init(FrontendContext* fc, CompilationInput& input, + ParserAtomsTable& parserAtoms, + ScopeBindingCache* scopeCache, InheritThis inheritThis, + JSObject* enclosingEnv) { + // Record the scopeCache to be used while looking up NameLocation bindings. + this->scopeCache = scopeCache; + scopeCacheGen = scopeCache->getCurrentGeneration(); + + InputScope maybeNonDefaultEnclosingScope( + input.maybeNonDefaultEnclosingScope()); + + // If this eval is in response to Debugger.Frame.eval, we may have an + // incomplete scope chain. In order to provide a better debugging experience, + // we inspect the (optional) environment chain to determine it's enclosing + // FunctionScope if there is one. If there is no such scope, we use the + // orignal scope provided. + // + // NOTE: This is used to compute the ThisBinding kind and to allow access to + // private fields and methods, while other contextual information only + // uses the actual scope passed to the compile. + auto effectiveScope = + determineEffectiveScope(maybeNonDefaultEnclosingScope, enclosingEnv); + + if (inheritThis == InheritThis::Yes) { + computeThisBinding(effectiveScope); + computeThisEnvironment(maybeNonDefaultEnclosingScope); + } + computeInScope(maybeNonDefaultEnclosingScope); + + cacheEnclosingScope(input.enclosingScope); + + if (input.target == CompilationInput::CompilationTarget::Eval) { + if (!cacheEnclosingScopeBindingForEval(fc, input, parserAtoms)) { + return false; + } + if (!cachePrivateFieldsForEval(fc, input, enclosingEnv, effectiveScope, + parserAtoms)) { + return false; + } + } + + return true; +} + +void ScopeContext::computeThisEnvironment(const InputScope& enclosingScope) { + uint32_t envCount = 0; + for (InputScopeIter si(enclosingScope); si; si++) { + if (si.kind() == ScopeKind::Function) { + // Arrow function inherit the "this" environment of the enclosing script, + // so continue ignore them. + if (!si.scope().isArrow()) { + allowNewTarget = true; + + if (si.scope().allowSuperProperty()) { + allowSuperProperty = true; + enclosingThisEnvironmentHops = envCount; + } + + if (si.scope().isClassConstructor()) { + memberInitializers = + si.scope().useMemberInitializers() + ? mozilla::Some(si.scope().getMemberInitializers()) + : mozilla::Some(MemberInitializers::Empty()); + MOZ_ASSERT(memberInitializers->valid); + } else { + if (si.scope().isSyntheticFunction()) { + allowArguments = false; + } + } + + if (si.scope().isDerivedClassConstructor()) { + allowSuperCall = true; + } + + // Found the effective "this" environment, so stop. + return; + } + } + + if (si.scope().hasEnvironment()) { + envCount++; + } + } +} + +void ScopeContext::computeThisBinding(const InputScope& scope) { + // Inspect the scope-chain. + for (InputScopeIter si(scope); si; si++) { + if (si.kind() == ScopeKind::Module) { + thisBinding = ThisBinding::Module; + return; + } + + if (si.kind() == ScopeKind::Function) { + // Arrow functions don't have their own `this` binding. + if (si.scope().isArrow()) { + continue; + } + + // Derived class constructors (and their nested arrow functions and evals) + // use ThisBinding::DerivedConstructor, which ensures TDZ checks happen + // when accessing |this|. + if (si.scope().isDerivedClassConstructor()) { + thisBinding = ThisBinding::DerivedConstructor; + } else { + thisBinding = ThisBinding::Function; + } + + return; + } + } + + thisBinding = ThisBinding::Global; +} + +void ScopeContext::computeInScope(const InputScope& enclosingScope) { + for (InputScopeIter si(enclosingScope); si; si++) { + if (si.kind() == ScopeKind::ClassBody) { + inClass = true; + } + + if (si.kind() == ScopeKind::With) { + inWith = true; + } + } +} + +void ScopeContext::cacheEnclosingScope(const InputScope& enclosingScope) { + if (enclosingScope.isNull()) { + return; + } + + enclosingScopeEnvironmentChainLength = + enclosingScope.environmentChainLength(); + enclosingScopeKind = enclosingScope.kind(); + + if (enclosingScopeKind == ScopeKind::Function) { + enclosingScopeIsArrow = enclosingScope.isArrow(); + } + + enclosingScopeHasEnvironment = enclosingScope.hasEnvironment(); + +#ifdef DEBUG + hasNonSyntacticScopeOnChain = + enclosingScope.hasOnChain(ScopeKind::NonSyntactic); + + // This computes a general answer for the query "does the enclosing scope + // have a function scope that needs a home object?", but it's only asserted + // if the parser parses eval body that contains `super` that needs a home + // object. + for (InputScopeIter si(enclosingScope); si; si++) { + if (si.kind() == ScopeKind::Function) { + if (si.scope().isArrow()) { + continue; + } + if (si.scope().allowSuperProperty() && si.scope().needsHomeObject()) { + hasFunctionNeedsHomeObjectOnChain = true; + } + break; + } + } +#endif + + // Pre-fill the scope cache by iterating over all the names. Stop iterating + // as soon as we find a scope which already has a filled scope cache. + AutoEnterOOMUnsafeRegion oomUnsafe; + for (InputScopeIter si(enclosingScope); si; si++) { + // If the current scope already exists, then there is no need to go deeper + // as the scope which are encoded after this one should already be present + // in the cache. + bool hasScopeCache = si.scope().match([&](auto& scope_ref) -> bool { + MOZ_ASSERT(scopeCache->canCacheFor(scope_ref)); + return scopeCache->lookupScope(scope_ref, scopeCacheGen); + }); + if (hasScopeCache) { + return; + } + + bool hasEnv = si.hasSyntacticEnvironment(); + auto setCacthAll = [&](NameLocation loc) { + return si.scope().match([&](auto& scope_ref) { + using BindingMapPtr = decltype(scopeCache->createCacheFor(scope_ref)); + BindingMapPtr bindingMapPtr = scopeCache->createCacheFor(scope_ref); + if (!bindingMapPtr) { + oomUnsafe.crash( + "ScopeContext::cacheEnclosingScope: scopeCache->createCacheFor"); + return; + } + + bindingMapPtr->catchAll.emplace(loc); + }); + }; + auto createEmpty = [&]() { + return si.scope().match([&](auto& scope_ref) { + using BindingMapPtr = decltype(scopeCache->createCacheFor(scope_ref)); + BindingMapPtr bindingMapPtr = scopeCache->createCacheFor(scope_ref); + if (!bindingMapPtr) { + oomUnsafe.crash( + "ScopeContext::cacheEnclosingScope: scopeCache->createCacheFor"); + return; + } + }); + }; + + switch (si.kind()) { + case ScopeKind::Function: + if (hasEnv) { + if (si.scope().funHasExtensibleScope()) { + setCacthAll(NameLocation::Dynamic()); + return; + } + + si.scope().match([&](auto& scope_ref) { + using BindingMapPtr = + decltype(scopeCache->createCacheFor(scope_ref)); + using Lookup = + typename std::remove_pointer_t::Lookup; + BindingMapPtr bindingMapPtr = scopeCache->createCacheFor(scope_ref); + if (!bindingMapPtr) { + oomUnsafe.crash( + "ScopeContext::cacheEnclosingScope: " + "scopeCache->createCacheFor"); + return; + } + + for (auto bi = InputBindingIter(scope_ref); bi; bi++) { + NameLocation loc = bi.nameLocation(); + if (loc.kind() != NameLocation::Kind::EnvironmentCoordinate) { + continue; + } + auto ctxFreeKey = bi.name(); + GenericAtom ctxKey(scope_ref, ctxFreeKey); + Lookup ctxLookup(scope_ref, ctxKey); + if (!bindingMapPtr->hashMap.put(ctxLookup, ctxFreeKey, loc)) { + oomUnsafe.crash( + "ScopeContext::cacheEnclosingScope: bindingMapPtr->put"); + return; + } + } + }); + } else { + createEmpty(); + } + break; + + case ScopeKind::StrictEval: + case ScopeKind::FunctionBodyVar: + case ScopeKind::Lexical: + case ScopeKind::NamedLambda: + case ScopeKind::StrictNamedLambda: + case ScopeKind::SimpleCatch: + case ScopeKind::Catch: + case ScopeKind::FunctionLexical: + case ScopeKind::ClassBody: + if (hasEnv) { + si.scope().match([&](auto& scope_ref) { + using BindingMapPtr = + decltype(scopeCache->createCacheFor(scope_ref)); + using Lookup = + typename std::remove_pointer_t::Lookup; + BindingMapPtr bindingMapPtr = scopeCache->createCacheFor(scope_ref); + if (!bindingMapPtr) { + oomUnsafe.crash( + "ScopeContext::cacheEnclosingScope: " + "scopeCache->createCacheFor"); + return; + } + + for (auto bi = InputBindingIter(scope_ref); bi; bi++) { + NameLocation loc = bi.nameLocation(); + if (loc.kind() != NameLocation::Kind::EnvironmentCoordinate) { + continue; + } + auto ctxFreeKey = bi.name(); + GenericAtom ctxKey(scope_ref, ctxFreeKey); + Lookup ctxLookup(scope_ref, ctxKey); + if (!bindingMapPtr->hashMap.putNew(ctxLookup, ctxFreeKey, loc)) { + oomUnsafe.crash( + "ScopeContext::cacheEnclosingScope: bindingMapPtr->put"); + return; + } + } + }); + } else { + createEmpty(); + } + break; + + case ScopeKind::Module: + // This case is used only when delazifying a function inside + // module. + // Initial compilation of module doesn't have enlcosing scope. + if (hasEnv) { + si.scope().match([&](auto& scope_ref) { + using BindingMapPtr = + decltype(scopeCache->createCacheFor(scope_ref)); + using Lookup = + typename std::remove_pointer_t::Lookup; + BindingMapPtr bindingMapPtr = scopeCache->createCacheFor(scope_ref); + if (!bindingMapPtr) { + oomUnsafe.crash( + "ScopeContext::cacheEnclosingScope: " + "scopeCache->createCacheFor"); + return; + } + + for (auto bi = InputBindingIter(scope_ref); bi; bi++) { + // Imports are on the environment but are indirect + // bindings and must be accessed dynamically instead of + // using an EnvironmentCoordinate. + NameLocation loc = bi.nameLocation(); + if (loc.kind() != NameLocation::Kind::EnvironmentCoordinate && + loc.kind() != NameLocation::Kind::Import) { + continue; + } + auto ctxFreeKey = bi.name(); + GenericAtom ctxKey(scope_ref, ctxFreeKey); + Lookup ctxLookup(scope_ref, ctxKey); + if (!bindingMapPtr->hashMap.putNew(ctxLookup, ctxFreeKey, loc)) { + oomUnsafe.crash( + "ScopeContext::cacheEnclosingScope: bindingMapPtr->put"); + return; + } + } + }); + } else { + createEmpty(); + } + break; + + case ScopeKind::Eval: + // As an optimization, if the eval doesn't have its own var + // environment and its immediate enclosing scope is a global + // scope, all accesses are global. + if (!hasEnv) { + ScopeKind kind = si.scope().enclosing().kind(); + if (kind == ScopeKind::Global || kind == ScopeKind::NonSyntactic) { + setCacthAll(NameLocation::Global(BindingKind::Var)); + return; + } + } + + setCacthAll(NameLocation::Dynamic()); + return; + + case ScopeKind::Global: + setCacthAll(NameLocation::Global(BindingKind::Var)); + return; + + case ScopeKind::With: + case ScopeKind::NonSyntactic: + setCacthAll(NameLocation::Dynamic()); + return; + + case ScopeKind::WasmInstance: + case ScopeKind::WasmFunction: + MOZ_CRASH("No direct eval inside wasm functions"); + } + } + + MOZ_CRASH("Malformed scope chain"); +} + +InputScope ScopeContext::determineEffectiveScope(InputScope& scope, + JSObject* environment) { + MOZ_ASSERT(effectiveScopeHops == 0); + // If the scope-chain is non-syntactic, we may still determine a more precise + // effective-scope to use instead. + if (environment && scope.hasOnChain(ScopeKind::NonSyntactic)) { + JSObject* env = environment; + while (env) { + // Look at target of any DebugEnvironmentProxy, but be sure to use + // enclosingEnvironment() of the proxy itself. + JSObject* unwrapped = env; + if (env->is()) { + unwrapped = &env->as().environment(); +#ifdef DEBUG + enclosingEnvironmentIsDebugProxy_ = true; +#endif + } + + if (unwrapped->is()) { + JSFunction* callee = &unwrapped->as().callee(); + return InputScope(callee->nonLazyScript()->bodyScope()); + } + + env = env->enclosingEnvironment(); + effectiveScopeHops++; + } + } + + return scope; +} + +static uint32_t DepthOfNearestVarScopeForDirectEval(const InputScope& scope) { + uint32_t depth = 0; + if (scope.isNull()) { + return depth; + } + for (InputScopeIter si(scope); si; si++) { + depth++; + switch (si.scope().kind()) { + case ScopeKind::Function: + case ScopeKind::FunctionBodyVar: + case ScopeKind::Global: + case ScopeKind::NonSyntactic: + return depth; + default: + break; + } + } + return depth; +} + +bool ScopeContext::cacheEnclosingScopeBindingForEval( + FrontendContext* fc, CompilationInput& input, + ParserAtomsTable& parserAtoms) { + enclosingLexicalBindingCache_.emplace(); + + uint32_t varScopeDepth = + DepthOfNearestVarScopeForDirectEval(input.enclosingScope); + uint32_t depth = 0; + for (InputScopeIter si(input.enclosingScope); si; si++) { + bool success = si.scope().match([&](auto& scope_ref) { + for (auto bi = InputBindingIter(scope_ref); bi; bi++) { + switch (bi.kind()) { + case BindingKind::Let: { + // Annex B.3.5 allows redeclaring simple (non-destructured) + // catch parameters with var declarations. + bool annexB35Allowance = si.kind() == ScopeKind::SimpleCatch; + if (!annexB35Allowance) { + auto kind = ScopeKindIsCatch(si.kind()) + ? EnclosingLexicalBindingKind::CatchParameter + : EnclosingLexicalBindingKind::Let; + InputName binding(scope_ref, bi.name()); + if (!addToEnclosingLexicalBindingCache( + fc, parserAtoms, input.atomCache, binding, kind)) { + return false; + } + } + break; + } + + case BindingKind::Const: { + InputName binding(scope_ref, bi.name()); + if (!addToEnclosingLexicalBindingCache( + fc, parserAtoms, input.atomCache, binding, + EnclosingLexicalBindingKind::Const)) { + return false; + } + break; + } + + case BindingKind::Synthetic: { + InputName binding(scope_ref, bi.name()); + if (!addToEnclosingLexicalBindingCache( + fc, parserAtoms, input.atomCache, binding, + EnclosingLexicalBindingKind::Synthetic)) { + return false; + } + break; + } + + case BindingKind::PrivateMethod: { + InputName binding(scope_ref, bi.name()); + if (!addToEnclosingLexicalBindingCache( + fc, parserAtoms, input.atomCache, binding, + EnclosingLexicalBindingKind::PrivateMethod)) { + return false; + } + break; + } + + case BindingKind::Import: + case BindingKind::FormalParameter: + case BindingKind::Var: + case BindingKind::NamedLambdaCallee: + break; + } + } + return true; + }); + if (!success) { + return false; + } + + if (++depth == varScopeDepth) { + break; + } + } + + return true; +} + +bool ScopeContext::addToEnclosingLexicalBindingCache( + FrontendContext* fc, ParserAtomsTable& parserAtoms, + CompilationAtomCache& atomCache, InputName& name, + EnclosingLexicalBindingKind kind) { + TaggedParserAtomIndex parserName = + name.internInto(fc, parserAtoms, atomCache); + if (!parserName) { + return false; + } + + // Same lexical binding can appear multiple times across scopes. + // + // enclosingLexicalBindingCache_ map is used for detecting conflicting + // `var` binding, and inner binding should be reported in the error. + // + // cacheEnclosingScopeBindingForEval iterates from inner scope, and + // inner-most binding is added to the map first. + // + // Do not overwrite the value with outer bindings. + auto p = enclosingLexicalBindingCache_->lookupForAdd(parserName); + if (!p) { + if (!enclosingLexicalBindingCache_->add(p, parserName, kind)) { + ReportOutOfMemory(fc); + return false; + } + } + + return true; +} + +static bool IsPrivateField(Scope*, JSAtom* atom) { + MOZ_ASSERT(atom->length() > 0); + + JS::AutoCheckCannotGC nogc; + if (atom->hasLatin1Chars()) { + return atom->latin1Chars(nogc)[0] == '#'; + } + + return atom->twoByteChars(nogc)[0] == '#'; +} + +static bool IsPrivateField(ScopeStencilRef& scope, TaggedParserAtomIndex atom) { + if (atom.isParserAtomIndex()) { + const CompilationStencil& context = scope.context_; + ParserAtom* parserAtom = context.parserAtomData[atom.toParserAtomIndex()]; + return parserAtom->isPrivateName(); + } + +#ifdef DEBUG + if (atom.isWellKnownAtomId()) { + const auto& info = GetWellKnownAtomInfo(atom.toWellKnownAtomId()); + // #constructor is a well-known term, but it is invalid private name. + MOZ_ASSERT(!(info.length > 1 && info.content[0] == '#')); + } else if (atom.isLength2StaticParserString()) { + char content[2]; + ParserAtomsTable::getLength2Content(atom.toLength2StaticParserString(), + content); + // # character is not part of the allowed character of static strings. + MOZ_ASSERT(content[0] != '#'); + } +#endif + + return false; +} + +bool ScopeContext::cachePrivateFieldsForEval(FrontendContext* fc, + CompilationInput& input, + JSObject* enclosingEnvironment, + const InputScope& effectiveScope, + ParserAtomsTable& parserAtoms) { + effectiveScopePrivateFieldCache_.emplace(); + + // We compute an environment coordinate relative to the effective scope + // environment. In order to safely consume these environment coordinates, + // we re-map them to include the hops to get the to the effective scope: + // see EmitterScope::lookupPrivate + uint32_t hops = effectiveScopeHops; + for (InputScopeIter si(effectiveScope); si; si++) { + if (si.scope().kind() == ScopeKind::ClassBody) { + uint32_t slots = 0; + bool success = si.scope().match([&](auto& scope_ref) { + for (auto bi = InputBindingIter(scope_ref); bi; bi++) { + if (bi.kind() == BindingKind::PrivateMethod || + (bi.kind() == BindingKind::Synthetic && + IsPrivateField(scope_ref, bi.name()))) { + InputName binding(scope_ref, bi.name()); + auto parserName = + binding.internInto(fc, parserAtoms, input.atomCache); + if (!parserName) { + return false; + } + + NameLocation loc = NameLocation::DebugEnvironmentCoordinate( + bi.kind(), hops, slots); + + if (!effectiveScopePrivateFieldCache_->put(parserName, loc)) { + ReportOutOfMemory(fc); + return false; + } + } + slots++; + } + return true; + }); + if (!success) { + return false; + } + } + + // Hops is only consumed by GetAliasedDebugVar, which uses this to + // traverse the debug environment chain. See the [SMDOC] for Debug + // Environment Chain, which explains why we don't check for + // isEnvironment when computing hops here (basically, debug proxies + // pretend all scopes have environments, even if they were actually + // optimized out). + hops++; + } + + return true; +} + +#ifdef DEBUG +static bool NameIsOnEnvironment(FrontendContext* fc, + ParserAtomsTable& parserAtoms, + CompilationAtomCache& atomCache, + InputScope& scope, TaggedParserAtomIndex name) { + JSAtom* jsname = nullptr; + return scope.match([&](auto& scope_ref) { + for (auto bi = InputBindingIter(scope_ref); bi; bi++) { + // If found, the name must already be on the environment or an import, + // or else there is a bug in the closed-over name analysis in the + // Parser. + InputName binding(scope_ref, bi.name()); + if (binding.isEqualTo(fc, parserAtoms, atomCache, name, &jsname)) { + BindingLocation::Kind kind = bi.location().kind(); + + if (bi.hasArgumentSlot()) { + // The following is equivalent to + // functionScope.script()->functionAllowsParameterRedeclaration() + if (scope.hasMappedArgsObj()) { + // Check for duplicate positional formal parameters. + using InputBindingIter = decltype(bi); + for (InputBindingIter bi2(bi); bi2 && bi2.hasArgumentSlot(); + bi2++) { + InputName binding2(scope_ref, bi2.name()); + if (binding2.isEqualTo(fc, parserAtoms, atomCache, name, + &jsname)) { + kind = bi2.location().kind(); + } + } + } + } + + return kind == BindingLocation::Kind::Global || + kind == BindingLocation::Kind::Environment || + kind == BindingLocation::Kind::Import; + } + } + + // If not found, assume it's on the global or dynamically accessed. + return true; + }); +} +#endif + +NameLocation ScopeContext::searchInEnclosingScope(FrontendContext* fc, + CompilationInput& input, + ParserAtomsTable& parserAtoms, + TaggedParserAtomIndex name) { + MOZ_ASSERT(input.target == + CompilationInput::CompilationTarget::Delazification || + input.target == CompilationInput::CompilationTarget::Eval); + + MOZ_ASSERT(scopeCache); + if (scopeCacheGen != scopeCache->getCurrentGeneration()) { + return searchInEnclosingScopeNoCache(fc, input, parserAtoms, name); + } + +#ifdef DEBUG + // Catch assertion failures in the NoCache variant before looking at the + // cached content. + NameLocation expect = + searchInEnclosingScopeNoCache(fc, input, parserAtoms, name); +#endif + + NameLocation found = + searchInEnclosingScopeWithCache(fc, input, parserAtoms, name); + MOZ_ASSERT(expect == found); + return found; +} + +NameLocation ScopeContext::searchInEnclosingScopeWithCache( + FrontendContext* fc, CompilationInput& input, ParserAtomsTable& parserAtoms, + TaggedParserAtomIndex name) { + MOZ_ASSERT(input.target == + CompilationInput::CompilationTarget::Delazification || + input.target == CompilationInput::CompilationTarget::Eval); + + // Generic atom of the looked up name. + GenericAtom genName(fc, parserAtoms, input.atomCache, name); + mozilla::Maybe found; + + // Number of enclosing scope we walked over. + uint8_t hops = 0; + + for (InputScopeIter si(input.enclosingScope); si; si++) { + MOZ_ASSERT(NameIsOnEnvironment(fc, parserAtoms, input.atomCache, si.scope(), + name)); + + // If the result happens to be in the cached content of the scope that we + // are iterating over, then return it. + si.scope().match([&](auto& scope_ref) { + using BindingMapPtr = + decltype(scopeCache->lookupScope(scope_ref, scopeCacheGen)); + BindingMapPtr bindingMapPtr = + scopeCache->lookupScope(scope_ref, scopeCacheGen); + MOZ_ASSERT(bindingMapPtr); + + auto& bindingMap = *bindingMapPtr; + if (bindingMap.catchAll.isSome()) { + found = bindingMap.catchAll; + return; + } + + // The scope_ref is given as argument to know where to lookup the key + // index of the hash table if the names have to be compared. + using Lookup = typename std::remove_pointer_t::Lookup; + Lookup ctxName(scope_ref, genName); + auto ptr = bindingMap.hashMap.lookup(ctxName); + if (!ptr) { + return; + } + + found.emplace(ptr->value()); + }); + + if (found.isSome()) { + // Cached entries do not store the number of hops, as it might be reused + // by multiple inner functions, which might different number of hops. + found = found.map([&hops](NameLocation loc) { + if (loc.kind() != NameLocation::Kind::EnvironmentCoordinate) { + return loc; + } + return loc.addHops(hops); + }); + return found.value(); + } + + bool hasEnv = si.hasSyntacticEnvironment(); + + if (hasEnv) { + MOZ_ASSERT(hops < ENVCOORD_HOPS_LIMIT - 1); + hops++; + } + } + + MOZ_CRASH("Malformed scope chain"); +} + +NameLocation ScopeContext::searchInEnclosingScopeNoCache( + FrontendContext* fc, CompilationInput& input, ParserAtomsTable& parserAtoms, + TaggedParserAtomIndex name) { + MOZ_ASSERT(input.target == + CompilationInput::CompilationTarget::Delazification || + input.target == CompilationInput::CompilationTarget::Eval); + + // Cached JSAtom equivalent of the TaggedParserAtomIndex `name` argument. + JSAtom* jsname = nullptr; + + // NameLocation which contains relative locations to access `name`. + mozilla::Maybe result; + + // Number of enclosing scoep we walked over. + uint8_t hops = 0; + + for (InputScopeIter si(input.enclosingScope); si; si++) { + MOZ_ASSERT(NameIsOnEnvironment(fc, parserAtoms, input.atomCache, si.scope(), + name)); + + bool hasEnv = si.hasSyntacticEnvironment(); + switch (si.kind()) { + case ScopeKind::Function: + if (hasEnv) { + if (si.scope().funHasExtensibleScope()) { + return NameLocation::Dynamic(); + } + + si.scope().match([&](auto& scope_ref) { + for (auto bi = InputBindingIter(scope_ref); bi; bi++) { + InputName binding(scope_ref, bi.name()); + if (!binding.isEqualTo(fc, parserAtoms, input.atomCache, name, + &jsname)) { + continue; + } + + BindingLocation bindLoc = bi.location(); + // hasMappedArgsObj == script.functionAllowsParameterRedeclaration + if (bi.hasArgumentSlot() && si.scope().hasMappedArgsObj()) { + // Check for duplicate positional formal parameters. + using InputBindingIter = decltype(bi); + for (InputBindingIter bi2(bi); bi2 && bi2.hasArgumentSlot(); + bi2++) { + if (bi.name() == bi2.name()) { + bindLoc = bi2.location(); + } + } + } + + MOZ_ASSERT(bindLoc.kind() == BindingLocation::Kind::Environment); + result.emplace(NameLocation::EnvironmentCoordinate( + bi.kind(), hops, bindLoc.slot())); + return; + } + }); + } + break; + + case ScopeKind::StrictEval: + case ScopeKind::FunctionBodyVar: + case ScopeKind::Lexical: + case ScopeKind::NamedLambda: + case ScopeKind::StrictNamedLambda: + case ScopeKind::SimpleCatch: + case ScopeKind::Catch: + case ScopeKind::FunctionLexical: + case ScopeKind::ClassBody: + if (hasEnv) { + si.scope().match([&](auto& scope_ref) { + for (auto bi = InputBindingIter(scope_ref); bi; bi++) { + InputName binding(scope_ref, bi.name()); + if (!binding.isEqualTo(fc, parserAtoms, input.atomCache, name, + &jsname)) { + continue; + } + + // The name must already have been marked as closed + // over. If this assertion is hit, there is a bug in the + // name analysis. + BindingLocation bindLoc = bi.location(); + MOZ_ASSERT(bindLoc.kind() == BindingLocation::Kind::Environment); + result.emplace(NameLocation::EnvironmentCoordinate( + bi.kind(), hops, bindLoc.slot())); + return; + } + }); + } + break; + + case ScopeKind::Module: + // This case is used only when delazifying a function inside + // module. + // Initial compilation of module doesn't have enlcosing scope. + if (hasEnv) { + si.scope().match([&](auto& scope_ref) { + for (auto bi = InputBindingIter(scope_ref); bi; bi++) { + InputName binding(scope_ref, bi.name()); + if (!binding.isEqualTo(fc, parserAtoms, input.atomCache, name, + &jsname)) { + continue; + } + + BindingLocation bindLoc = bi.location(); + + // Imports are on the environment but are indirect + // bindings and must be accessed dynamically instead of + // using an EnvironmentCoordinate. + if (bindLoc.kind() == BindingLocation::Kind::Import) { + MOZ_ASSERT(si.kind() == ScopeKind::Module); + result.emplace(NameLocation::Import()); + return; + } + + MOZ_ASSERT(bindLoc.kind() == BindingLocation::Kind::Environment); + result.emplace(NameLocation::EnvironmentCoordinate( + bi.kind(), hops, bindLoc.slot())); + return; + } + }); + } + break; + + case ScopeKind::Eval: + // As an optimization, if the eval doesn't have its own var + // environment and its immediate enclosing scope is a global + // scope, all accesses are global. + if (!hasEnv) { + ScopeKind kind = si.scope().enclosing().kind(); + if (kind == ScopeKind::Global || kind == ScopeKind::NonSyntactic) { + return NameLocation::Global(BindingKind::Var); + } + } + return NameLocation::Dynamic(); + + case ScopeKind::Global: + return NameLocation::Global(BindingKind::Var); + + case ScopeKind::With: + case ScopeKind::NonSyntactic: + return NameLocation::Dynamic(); + + case ScopeKind::WasmInstance: + case ScopeKind::WasmFunction: + MOZ_CRASH("No direct eval inside wasm functions"); + } + + if (result.isSome()) { + return result.value(); + } + + if (hasEnv) { + MOZ_ASSERT(hops < ENVCOORD_HOPS_LIMIT - 1); + hops++; + } + } + + MOZ_CRASH("Malformed scope chain"); +} + +mozilla::Maybe +ScopeContext::lookupLexicalBindingInEnclosingScope(TaggedParserAtomIndex name) { + auto p = enclosingLexicalBindingCache_->lookup(name); + if (!p) { + return mozilla::Nothing(); + } + + return mozilla::Some(p->value()); +} + +bool ScopeContext::effectiveScopePrivateFieldCacheHas( + TaggedParserAtomIndex name) { + return effectiveScopePrivateFieldCache_->has(name); +} + +mozilla::Maybe ScopeContext::getPrivateFieldLocation( + TaggedParserAtomIndex name) { + // The locations returned by this method are only valid for + // traversing debug environments. + // + // See the comment in cachePrivateFieldsForEval + MOZ_ASSERT(enclosingEnvironmentIsDebugProxy_); + auto p = effectiveScopePrivateFieldCache_->lookup(name); + if (!p) { + return mozilla::Nothing(); + } + return mozilla::Some(p->value()); +} + +bool CompilationInput::initScriptSource(FrontendContext* fc) { + source = do_AddRef(fc->getAllocator()->new_()); + if (!source) { + return false; + } + + return source->initFromOptions(fc, options); +} + +bool CompilationInput::initForStandaloneFunctionInNonSyntacticScope( + FrontendContext* fc, Handle functionEnclosingScope) { + MOZ_ASSERT(!functionEnclosingScope->as().isSyntactic()); + + target = CompilationTarget::StandaloneFunctionInNonSyntacticScope; + if (!initScriptSource(fc)) { + return false; + } + enclosingScope = InputScope(functionEnclosingScope); + return true; +} + +FunctionSyntaxKind CompilationInput::functionSyntaxKind() const { + if (functionFlags().isClassConstructor()) { + if (functionFlags().hasBaseScript() && isDerivedClassConstructor()) { + return FunctionSyntaxKind::DerivedClassConstructor; + } + return FunctionSyntaxKind::ClassConstructor; + } + if (functionFlags().isMethod()) { + if (functionFlags().hasBaseScript() && isSyntheticFunction()) { + // return FunctionSyntaxKind::FieldInitializer; + MOZ_ASSERT_UNREACHABLE( + "Lazy parsing of class field initializers not supported (yet)"); + } + return FunctionSyntaxKind::Method; + } + if (functionFlags().isGetter()) { + return FunctionSyntaxKind::Getter; + } + if (functionFlags().isSetter()) { + return FunctionSyntaxKind::Setter; + } + if (functionFlags().isArrow()) { + return FunctionSyntaxKind::Arrow; + } + return FunctionSyntaxKind::Statement; +} + +void InputScope::trace(JSTracer* trc) { + using ScopePtr = Scope*; + if (scope_.is()) { + ScopePtr* ptrAddr = &scope_.as(); + TraceNullableRoot(trc, ptrAddr, "compilation-input-scope"); + } +} + +void InputScript::trace(JSTracer* trc) { + using ScriptPtr = BaseScript*; + if (script_.is()) { + ScriptPtr* ptrAddr = &script_.as(); + TraceNullableRoot(trc, ptrAddr, "compilation-input-lazy"); + } +} + +void CompilationInput::trace(JSTracer* trc) { + atomCache.trace(trc); + lazy_.trace(trc); + enclosingScope.trace(trc); +} + +bool CompilationSyntaxParseCache::init(FrontendContext* fc, LifoAlloc& alloc, + ParserAtomsTable& parseAtoms, + CompilationAtomCache& atomCache, + const InputScript& lazy) { + if (!copyFunctionInfo(fc, parseAtoms, atomCache, lazy)) { + return false; + } + bool success = lazy.raw().match([&](auto& ref) { + if (!copyScriptInfo(fc, alloc, parseAtoms, atomCache, ref)) { + return false; + } + if (!copyClosedOverBindings(fc, alloc, parseAtoms, atomCache, ref)) { + return false; + } + return true; + }); + if (!success) { + return false; + } +#ifdef DEBUG + isInitialized = true; +#endif + return true; +} + +bool CompilationSyntaxParseCache::copyFunctionInfo( + FrontendContext* fc, ParserAtomsTable& parseAtoms, + CompilationAtomCache& atomCache, const InputScript& lazy) { + InputName name = lazy.displayAtom(); + if (!name.isNull()) { + displayAtom_ = name.internInto(fc, parseAtoms, atomCache); + if (!displayAtom_) { + return false; + } + } + + funExtra_.immutableFlags = lazy.immutableFlags(); + funExtra_.extent = lazy.extent(); + if (funExtra_.useMemberInitializers()) { + funExtra_.setMemberInitializers(lazy.getMemberInitializers()); + } + + return true; +} + +bool CompilationSyntaxParseCache::copyScriptInfo( + FrontendContext* fc, LifoAlloc& alloc, ParserAtomsTable& parseAtoms, + CompilationAtomCache& atomCache, BaseScript* lazy) { + using GCThingsSpan = mozilla::Span; + using ScriptDataSpan = mozilla::Span; + using ScriptExtraSpan = mozilla::Span; + cachedGCThings_ = GCThingsSpan(nullptr); + cachedScriptData_ = ScriptDataSpan(nullptr); + cachedScriptExtra_ = ScriptExtraSpan(nullptr); + + auto gcthings = lazy->gcthings(); + size_t length = gcthings.Length(); + if (length == 0) { + return true; + } + + // Reduce the length to the first element which is not a function. + for (size_t i = 0; i < length; i++) { + gc::Cell* cell = gcthings[i].asCell(); + if (!cell || !cell->is()) { + length = i; + break; + } + MOZ_ASSERT(cell->as()->is()); + } + + TaggedScriptThingIndex* gcThingsData = + alloc.newArrayUninitialized(length); + ScriptStencil* scriptData = + alloc.newArrayUninitialized(length); + ScriptStencilExtra* scriptExtra = + alloc.newArrayUninitialized(length); + if (!gcThingsData || !scriptData || !scriptExtra) { + ReportOutOfMemory(fc); + return false; + } + + for (size_t i = 0; i < length; i++) { + gc::Cell* cell = gcthings[i].asCell(); + JSFunction* fun = &cell->as()->as(); + gcThingsData[i] = TaggedScriptThingIndex(ScriptIndex(i)); + new (mozilla::KnownNotNull, &scriptData[i]) ScriptStencil(); + ScriptStencil& data = scriptData[i]; + new (mozilla::KnownNotNull, &scriptExtra[i]) ScriptStencilExtra(); + ScriptStencilExtra& extra = scriptExtra[i]; + + if (fun->displayAtom()) { + TaggedParserAtomIndex displayAtom = + parseAtoms.internJSAtom(fc, atomCache, fun->displayAtom()); + if (!displayAtom) { + return false; + } + data.functionAtom = displayAtom; + } + data.functionFlags = fun->flags(); + + BaseScript* lazy = fun->baseScript(); + extra.immutableFlags = lazy->immutableFlags(); + extra.extent = lazy->extent(); + + // Info derived from parent compilation should not be set yet for our inner + // lazy functions. Instead that info will be updated when we finish our + // compilation. + MOZ_ASSERT(lazy->hasEnclosingScript()); + } + + cachedGCThings_ = GCThingsSpan(gcThingsData, length); + cachedScriptData_ = ScriptDataSpan(scriptData, length); + cachedScriptExtra_ = ScriptExtraSpan(scriptExtra, length); + return true; +} + +bool CompilationSyntaxParseCache::copyScriptInfo( + FrontendContext* fc, LifoAlloc& alloc, ParserAtomsTable& parseAtoms, + CompilationAtomCache& atomCache, const ScriptStencilRef& lazy) { + using GCThingsSpan = mozilla::Span; + using ScriptDataSpan = mozilla::Span; + using ScriptExtraSpan = mozilla::Span; + cachedGCThings_ = GCThingsSpan(nullptr); + cachedScriptData_ = ScriptDataSpan(nullptr); + cachedScriptExtra_ = ScriptExtraSpan(nullptr); + + size_t offset = lazy.scriptData().gcThingsOffset.index; + size_t length = lazy.scriptData().gcThingsLength; + if (length == 0) { + return true; + } + + // Reduce the length to the first element which is not a function. + for (size_t i = offset; i < offset + length; i++) { + if (!lazy.context_.gcThingData[i].isFunction()) { + length = i - offset; + break; + } + } + + TaggedScriptThingIndex* gcThingsData = + alloc.newArrayUninitialized(length); + ScriptStencil* scriptData = + alloc.newArrayUninitialized(length); + ScriptStencilExtra* scriptExtra = + alloc.newArrayUninitialized(length); + if (!gcThingsData || !scriptData || !scriptExtra) { + ReportOutOfMemory(fc); + return false; + } + + for (size_t i = 0; i < length; i++) { + ScriptStencilRef inner{lazy.context_, + lazy.context_.gcThingData[i + offset].toFunction()}; + gcThingsData[i] = TaggedScriptThingIndex(ScriptIndex(i)); + new (mozilla::KnownNotNull, &scriptData[i]) ScriptStencil(); + ScriptStencil& data = scriptData[i]; + ScriptStencilExtra& extra = scriptExtra[i]; + + InputName name{inner, inner.scriptData().functionAtom}; + if (!name.isNull()) { + auto displayAtom = name.internInto(fc, parseAtoms, atomCache); + if (!displayAtom) { + return false; + } + data.functionAtom = displayAtom; + } + data.functionFlags = inner.scriptData().functionFlags; + + extra = inner.scriptExtra(); + } + + cachedGCThings_ = GCThingsSpan(gcThingsData, length); + cachedScriptData_ = ScriptDataSpan(scriptData, length); + cachedScriptExtra_ = ScriptExtraSpan(scriptExtra, length); + return true; +} + +bool CompilationSyntaxParseCache::copyClosedOverBindings( + FrontendContext* fc, LifoAlloc& alloc, ParserAtomsTable& parseAtoms, + CompilationAtomCache& atomCache, BaseScript* lazy) { + using ClosedOverBindingsSpan = mozilla::Span; + closedOverBindings_ = ClosedOverBindingsSpan(nullptr); + + // The gcthings() array contains the inner function list followed by the + // closed-over bindings data. Skip the inner function list, as it is already + // cached in cachedGCThings_. See also: BaseScript::CreateLazy. + size_t start = cachedGCThings_.Length(); + auto gcthings = lazy->gcthings(); + size_t length = gcthings.Length(); + MOZ_ASSERT(start <= length); + if (length - start == 0) { + return true; + } + + TaggedParserAtomIndex* closedOverBindings = + alloc.newArrayUninitialized(length - start); + if (!closedOverBindings) { + ReportOutOfMemory(fc); + return false; + } + + for (size_t i = start; i < length; i++) { + gc::Cell* cell = gcthings[i].asCell(); + if (!cell) { + closedOverBindings[i - start] = TaggedParserAtomIndex::null(); + continue; + } + + MOZ_ASSERT(cell->as()->isAtom()); + + auto name = static_cast(cell); + auto parserAtom = parseAtoms.internJSAtom(fc, atomCache, name); + if (!parserAtom) { + return false; + } + + closedOverBindings[i - start] = parserAtom; + } + + closedOverBindings_ = + ClosedOverBindingsSpan(closedOverBindings, length - start); + return true; +} + +bool CompilationSyntaxParseCache::copyClosedOverBindings( + FrontendContext* fc, LifoAlloc& alloc, ParserAtomsTable& parseAtoms, + CompilationAtomCache& atomCache, const ScriptStencilRef& lazy) { + using ClosedOverBindingsSpan = mozilla::Span; + closedOverBindings_ = ClosedOverBindingsSpan(nullptr); + + // The gcthings array contains the inner function list followed by the + // closed-over bindings data. Skip the inner function list, as it is already + // cached in cachedGCThings_. See also: BaseScript::CreateLazy. + size_t offset = lazy.scriptData().gcThingsOffset.index; + size_t length = lazy.scriptData().gcThingsLength; + size_t start = cachedGCThings_.Length(); + MOZ_ASSERT(start <= length); + if (length - start == 0) { + return true; + } + length -= start; + start += offset; + + // Atoms from the lazy.context (CompilationStencil) are not registered in the + // the parseAtoms table. Thus we create a new span which will contain all the + // interned atoms. + TaggedParserAtomIndex* closedOverBindings = + alloc.newArrayUninitialized(length); + if (!closedOverBindings) { + ReportOutOfMemory(fc); + return false; + } + + for (size_t i = 0; i < length; i++) { + auto gcThing = lazy.context_.gcThingData[i + start]; + if (gcThing.isNull()) { + closedOverBindings[i] = TaggedParserAtomIndex::null(); + continue; + } + + MOZ_ASSERT(gcThing.isAtom()); + InputName name(lazy, gcThing.toAtom()); + auto parserAtom = name.internInto(fc, parseAtoms, atomCache); + if (!parserAtom) { + return false; + } + + closedOverBindings[i] = parserAtom; + } + + closedOverBindings_ = ClosedOverBindingsSpan(closedOverBindings, length); + return true; +} + +void CompilationAtomCache::trace(JSTracer* trc) { atoms_.trace(trc); } + +void CompilationGCOutput::trace(JSTracer* trc) { + TraceNullableRoot(trc, &script, "compilation-gc-output-script"); + TraceNullableRoot(trc, &module, "compilation-gc-output-module"); + TraceNullableRoot(trc, &sourceObject, "compilation-gc-output-source"); + functions.trace(trc); + scopes.trace(trc); +} + +void JS::InstantiationStorage::trace(JSTracer* trc) { + if (gcOutput_) { + gcOutput_->trace(trc); + } +} + +RegExpObject* RegExpStencil::createRegExp( + JSContext* cx, const CompilationAtomCache& atomCache) const { + Rooted atom(cx, atomCache.getExistingAtomAt(cx, atom_)); + return RegExpObject::createSyntaxChecked(cx, atom, flags(), TenuredObject); +} + +RegExpObject* RegExpStencil::createRegExpAndEnsureAtom( + JSContext* cx, FrontendContext* fc, ParserAtomsTable& parserAtoms, + CompilationAtomCache& atomCache) const { + Rooted atom(cx, parserAtoms.toJSAtom(cx, fc, atom_, atomCache)); + if (!atom) { + return nullptr; + } + return RegExpObject::createSyntaxChecked(cx, atom, flags(), TenuredObject); +} + +AbstractScopePtr ScopeStencil::enclosing( + CompilationState& compilationState) const { + if (hasEnclosing()) { + return AbstractScopePtr(compilationState, enclosing()); + } + + return AbstractScopePtr::compilationEnclosingScope(compilationState); +} + +Scope* ScopeStencil::enclosingExistingScope( + const CompilationInput& input, const CompilationGCOutput& gcOutput) const { + if (hasEnclosing()) { + Scope* result = gcOutput.getScopeNoBaseIndex(enclosing()); + MOZ_ASSERT(result, "Scope must already exist to use this method"); + return result; + } + + // When creating a scope based on the input and a gc-output, we assume that + // the scope stencil that we are looking at has not been merged into another + // stencil, and thus that we still have the compilation input of the stencil. + // + // Otherwise, if this was in the case of an input generated from a Stencil + // instead of live-gc values, we would not know its associated gcOutput as it + // might not even have one yet. + return input.enclosingScope.variant().as(); +} + +Scope* ScopeStencil::createScope(JSContext* cx, CompilationInput& input, + CompilationGCOutput& gcOutput, + BaseParserScopeData* baseScopeData) const { + Rooted enclosingScope(cx, enclosingExistingScope(input, gcOutput)); + return createScope(cx, input.atomCache, enclosingScope, baseScopeData); +} + +Scope* ScopeStencil::createScope(JSContext* cx, CompilationAtomCache& atomCache, + Handle enclosingScope, + BaseParserScopeData* baseScopeData) const { + switch (kind()) { + case ScopeKind::Function: { + using ScopeType = FunctionScope; + MOZ_ASSERT(matchScopeKind(kind())); + return createSpecificScope( + cx, atomCache, enclosingScope, baseScopeData); + } + case ScopeKind::Lexical: + case ScopeKind::SimpleCatch: + case ScopeKind::Catch: + case ScopeKind::NamedLambda: + case ScopeKind::StrictNamedLambda: + case ScopeKind::FunctionLexical: { + using ScopeType = LexicalScope; + MOZ_ASSERT(matchScopeKind(kind())); + return createSpecificScope( + cx, atomCache, enclosingScope, baseScopeData); + } + case ScopeKind::ClassBody: { + using ScopeType = ClassBodyScope; + MOZ_ASSERT(matchScopeKind(kind())); + return createSpecificScope( + cx, atomCache, enclosingScope, baseScopeData); + } + case ScopeKind::FunctionBodyVar: { + using ScopeType = VarScope; + MOZ_ASSERT(matchScopeKind(kind())); + return createSpecificScope( + cx, atomCache, enclosingScope, baseScopeData); + } + case ScopeKind::Global: + case ScopeKind::NonSyntactic: { + using ScopeType = GlobalScope; + MOZ_ASSERT(matchScopeKind(kind())); + return createSpecificScope( + cx, atomCache, enclosingScope, baseScopeData); + } + case ScopeKind::Eval: + case ScopeKind::StrictEval: { + using ScopeType = EvalScope; + MOZ_ASSERT(matchScopeKind(kind())); + return createSpecificScope( + cx, atomCache, enclosingScope, baseScopeData); + } + case ScopeKind::Module: { + using ScopeType = ModuleScope; + MOZ_ASSERT(matchScopeKind(kind())); + return createSpecificScope( + cx, atomCache, enclosingScope, baseScopeData); + } + case ScopeKind::With: { + using ScopeType = WithScope; + MOZ_ASSERT(matchScopeKind(kind())); + return createSpecificScope( + cx, atomCache, enclosingScope, baseScopeData); + } + case ScopeKind::WasmFunction: + case ScopeKind::WasmInstance: { + // ScopeStencil does not support WASM + break; + } + } + MOZ_CRASH(); +} + +bool CompilationState::prepareSharedDataStorage(FrontendContext* fc) { + size_t allScriptCount = scriptData.length(); + size_t nonLazyScriptCount = nonLazyFunctionCount; + if (!scriptData[0].isFunction()) { + nonLazyScriptCount++; + } + return sharedData.prepareStorageFor(fc, nonLazyScriptCount, allScriptCount); +} + +static bool CreateLazyScript(JSContext* cx, + const CompilationAtomCache& atomCache, + const CompilationStencil& stencil, + CompilationGCOutput& gcOutput, + const ScriptStencil& script, + const ScriptStencilExtra& scriptExtra, + ScriptIndex scriptIndex, HandleFunction function) { + Rooted sourceObject(cx, gcOutput.sourceObject); + + size_t ngcthings = script.gcThingsLength; + + Rooted lazy( + cx, BaseScript::CreateRawLazy(cx, ngcthings, function, sourceObject, + scriptExtra.extent, + scriptExtra.immutableFlags)); + if (!lazy) { + return false; + } + + if (ngcthings) { + if (!EmitScriptThingsVector(cx, atomCache, stencil, gcOutput, + script.gcthings(stencil), + lazy->gcthingsForInit())) { + return false; + } + } + + if (scriptExtra.useMemberInitializers()) { + lazy->setMemberInitializers(scriptExtra.memberInitializers()); + } + + function->initScript(lazy); + + return true; +} + +// Parser-generated functions with the same prototype will share the same shape. +// By computing the correct values up front, we can save a lot of time in the +// Object creation code. For simplicity, we focus only on plain synchronous +// functions which are by far the most common. +// +// NOTE: Keep this in sync with `js::NewFunctionWithProto`. +static JSFunction* CreateFunctionFast(JSContext* cx, + CompilationAtomCache& atomCache, + Handle shape, + const ScriptStencil& script, + const ScriptStencilExtra& scriptExtra) { + MOZ_ASSERT( + !scriptExtra.immutableFlags.hasFlag(ImmutableScriptFlagsEnum::IsAsync)); + MOZ_ASSERT(!scriptExtra.immutableFlags.hasFlag( + ImmutableScriptFlagsEnum::IsGenerator)); + MOZ_ASSERT(!script.functionFlags.isAsmJSNative()); + + FunctionFlags flags = script.functionFlags; + gc::AllocKind allocKind = flags.isExtended() + ? gc::AllocKind::FUNCTION_EXTENDED + : gc::AllocKind::FUNCTION; + + JSFunction* fun = JSFunction::create(cx, allocKind, gc::Heap::Tenured, shape); + if (!fun) { + return nullptr; + } + + fun->setArgCount(scriptExtra.nargs); + fun->setFlags(flags); + + fun->initScript(nullptr); + fun->initEnvironment(nullptr); + + if (script.functionAtom) { + JSAtom* atom = atomCache.getExistingAtomAt(cx, script.functionAtom); + MOZ_ASSERT(atom); + fun->initAtom(atom); + } + + return fun; +} + +static JSFunction* CreateFunction(JSContext* cx, + CompilationAtomCache& atomCache, + const CompilationStencil& stencil, + const ScriptStencil& script, + const ScriptStencilExtra& scriptExtra, + ScriptIndex functionIndex) { + GeneratorKind generatorKind = + scriptExtra.immutableFlags.hasFlag(ImmutableScriptFlagsEnum::IsGenerator) + ? GeneratorKind::Generator + : GeneratorKind::NotGenerator; + FunctionAsyncKind asyncKind = + scriptExtra.immutableFlags.hasFlag(ImmutableScriptFlagsEnum::IsAsync) + ? FunctionAsyncKind::AsyncFunction + : FunctionAsyncKind::SyncFunction; + + // Determine the new function's proto. This must be done for singleton + // functions. + RootedObject proto(cx); + if (!GetFunctionPrototype(cx, generatorKind, asyncKind, &proto)) { + return nullptr; + } + + gc::AllocKind allocKind = script.functionFlags.isExtended() + ? gc::AllocKind::FUNCTION_EXTENDED + : gc::AllocKind::FUNCTION; + bool isAsmJS = script.functionFlags.isAsmJSNative(); + + JSNative maybeNative = isAsmJS ? InstantiateAsmJS : nullptr; + + Rooted displayAtom(cx); + if (script.functionAtom) { + displayAtom.set(atomCache.getExistingAtomAt(cx, script.functionAtom)); + MOZ_ASSERT(displayAtom); + } + RootedFunction fun( + cx, NewFunctionWithProto(cx, maybeNative, scriptExtra.nargs, + script.functionFlags, nullptr, displayAtom, + proto, allocKind, TenuredObject)); + if (!fun) { + return nullptr; + } + + if (isAsmJS) { + RefPtr asmJS = + stencil.asmJS->moduleMap.lookup(functionIndex)->value(); + + JSObject* moduleObj = asmJS->createObjectForAsmJS(cx); + if (!moduleObj) { + return nullptr; + } + + fun->setExtendedSlot(FunctionExtended::ASMJS_MODULE_SLOT, + ObjectValue(*moduleObj)); + } + + return fun; +} + +static bool InstantiateAtoms(JSContext* cx, FrontendContext* fc, + CompilationAtomCache& atomCache, + const CompilationStencil& stencil) { + return InstantiateMarkedAtoms(cx, fc, stencil.parserAtomData, atomCache); +} + +static bool InstantiateScriptSourceObject(JSContext* cx, + const JS::InstantiateOptions& options, + const CompilationStencil& stencil, + CompilationGCOutput& gcOutput) { + MOZ_ASSERT(stencil.source); + + gcOutput.sourceObject = ScriptSourceObject::create(cx, stencil.source.get()); + if (!gcOutput.sourceObject) { + return false; + } + + MOZ_ASSERT(!cx->isHelperThreadContext()); + Rooted sourceObject(cx, gcOutput.sourceObject); + if (!ScriptSourceObject::initFromOptions(cx, sourceObject, options)) { + return false; + } + + return true; +} + +// Instantiate ModuleObject. Further initialization is done after the associated +// BaseScript is instantiated in InstantiateTopLevel. +static bool InstantiateModuleObject(JSContext* cx, FrontendContext* fc, + CompilationAtomCache& atomCache, + const CompilationStencil& stencil, + CompilationGCOutput& gcOutput) { + MOZ_ASSERT(stencil.isModule()); + + gcOutput.module = ModuleObject::create(cx); + if (!gcOutput.module) { + return false; + } + + Rooted module(cx, gcOutput.module); + return stencil.moduleMetadata->initModule(cx, fc, atomCache, module); +} + +// Instantiate JSFunctions for each FunctionBox. +static bool InstantiateFunctions(JSContext* cx, FrontendContext* fc, + CompilationAtomCache& atomCache, + const CompilationStencil& stencil, + CompilationGCOutput& gcOutput) { + using ImmutableFlags = ImmutableScriptFlagsEnum; + + if (!gcOutput.functions.resize(stencil.scriptData.size())) { + ReportOutOfMemory(fc); + return false; + } + + // Most JSFunctions will be have the same Shape so we can compute it now to + // allow fast object creation. Generators / Async will use the slow path + // instead. + Rooted functionShape( + cx, GlobalObject::getFunctionShapeWithDefaultProto( + cx, /* extended = */ false)); + if (!functionShape) { + return false; + } + + Rooted extendedShape( + cx, GlobalObject::getFunctionShapeWithDefaultProto( + cx, /* extended = */ true)); + if (!extendedShape) { + return false; + } + + for (auto item : + CompilationStencil::functionScriptStencils(stencil, gcOutput)) { + const auto& scriptStencil = item.script; + const auto& scriptExtra = (*item.scriptExtra); + auto index = item.index; + + MOZ_ASSERT(!item.function); + + // Plain functions can use a fast path. + bool useFastPath = + !scriptExtra.immutableFlags.hasFlag(ImmutableFlags::IsAsync) && + !scriptExtra.immutableFlags.hasFlag(ImmutableFlags::IsGenerator) && + !scriptStencil.functionFlags.isAsmJSNative(); + + JSFunction* fun; + if (useFastPath) { + Handle shape = scriptStencil.functionFlags.isExtended() + ? extendedShape + : functionShape; + fun = + CreateFunctionFast(cx, atomCache, shape, scriptStencil, scriptExtra); + } else { + fun = CreateFunction(cx, atomCache, stencil, scriptStencil, scriptExtra, + index); + } + + if (!fun) { + return false; + } + + // Self-hosted functions may have a canonical name to use when instantiating + // into other realms. + if (scriptStencil.hasSelfHostedCanonicalName()) { + JSAtom* canonicalName = atomCache.getExistingAtomAt( + cx, scriptStencil.selfHostedCanonicalName()); + fun->setAtom(canonicalName); + } + + gcOutput.getFunctionNoBaseIndex(index) = fun; + } + + return true; +} + +// Instantiate Scope for each ScopeStencil. +// +// This should be called after InstantiateFunctions, given FunctionScope needs +// associated JSFunction pointer, and also should be called before +// InstantiateScriptStencils, given JSScript needs Scope pointer in gc things. +static bool InstantiateScopes(JSContext* cx, CompilationInput& input, + const CompilationStencil& stencil, + CompilationGCOutput& gcOutput) { + // While allocating Scope object from ScopeStencil, Scope object for the + // enclosing Scope should already be allocated. + // + // Enclosing scope of ScopeStencil can be either ScopeStencil or Scope* + // pointer. + // + // If the enclosing scope is ScopeStencil, it's guaranteed to be earlier + // element in stencil.scopeData, because enclosing_ field holds + // index into it, and newly created ScopeStencil is pushed back to the vector. + // + // If the enclosing scope is Scope*, it's CompilationInput.enclosingScope. + + MOZ_ASSERT(stencil.scopeData.size() == stencil.scopeNames.size()); + size_t scopeCount = stencil.scopeData.size(); + for (size_t i = 0; i < scopeCount; i++) { + Scope* scope = stencil.scopeData[i].createScope(cx, input, gcOutput, + stencil.scopeNames[i]); + if (!scope) { + return false; + } + gcOutput.scopes.infallibleAppend(scope); + } + + return true; +} + +// Instantiate js::BaseScripts from ScriptStencils for inner functions of the +// compilation. Note that standalone functions and functions being delazified +// are handled below with other top-levels. +static bool InstantiateScriptStencils(JSContext* cx, + CompilationAtomCache& atomCache, + const CompilationStencil& stencil, + CompilationGCOutput& gcOutput) { + MOZ_ASSERT(stencil.isInitialStencil()); + + Rooted fun(cx); + for (auto item : + CompilationStencil::functionScriptStencils(stencil, gcOutput)) { + auto& scriptStencil = item.script; + auto* scriptExtra = item.scriptExtra; + fun = item.function; + auto index = item.index; + if (scriptStencil.hasSharedData()) { + // If the function was not referenced by enclosing script's bytecode, we + // do not generate a BaseScript for it. For example, `(function(){});`. + // + // `wasEmittedByEnclosingScript` is false also for standalone + // functions. They are handled in InstantiateTopLevel. + if (!scriptStencil.wasEmittedByEnclosingScript()) { + continue; + } + + RootedScript script( + cx, JSScript::fromStencil(cx, atomCache, stencil, gcOutput, index)); + if (!script) { + return false; + } + + if (scriptStencil.allowRelazify()) { + MOZ_ASSERT(script->isRelazifiable()); + script->setAllowRelazify(); + } + } else if (scriptStencil.functionFlags.isAsmJSNative()) { + MOZ_ASSERT(fun->isAsmJSNative()); + } else { + MOZ_ASSERT(fun->isIncomplete()); + if (!CreateLazyScript(cx, atomCache, stencil, gcOutput, scriptStencil, + *scriptExtra, index, fun)) { + return false; + } + } + } + + return true; +} + +// Instantiate the Stencil for the top-level script of the compilation. This +// includes standalone functions and functions being delazified. +static bool InstantiateTopLevel(JSContext* cx, CompilationInput& input, + const CompilationStencil& stencil, + CompilationGCOutput& gcOutput) { + const ScriptStencil& scriptStencil = + stencil.scriptData[CompilationStencil::TopLevelIndex]; + + // Top-level asm.js does not generate a JSScript. + if (scriptStencil.functionFlags.isAsmJSNative()) { + return true; + } + + MOZ_ASSERT(scriptStencil.hasSharedData()); + MOZ_ASSERT(stencil.sharedData.get(CompilationStencil::TopLevelIndex)); + + if (!stencil.isInitialStencil()) { + MOZ_ASSERT(input.lazyOuterBaseScript()); + RootedScript script(cx, + JSScript::CastFromLazy(input.lazyOuterBaseScript())); + if (!JSScript::fullyInitFromStencil(cx, input.atomCache, stencil, gcOutput, + script, + CompilationStencil::TopLevelIndex)) { + return false; + } + + if (scriptStencil.allowRelazify()) { + MOZ_ASSERT(script->isRelazifiable()); + script->setAllowRelazify(); + } + + gcOutput.script = script; + return true; + } + + gcOutput.script = + JSScript::fromStencil(cx, input.atomCache, stencil, gcOutput, + CompilationStencil::TopLevelIndex); + if (!gcOutput.script) { + return false; + } + + if (scriptStencil.allowRelazify()) { + MOZ_ASSERT(gcOutput.script->isRelazifiable()); + gcOutput.script->setAllowRelazify(); + } + + const ScriptStencilExtra& scriptExtra = + stencil.scriptExtra[CompilationStencil::TopLevelIndex]; + + // Finish initializing the ModuleObject if needed. + if (scriptExtra.isModule()) { + RootedScript script(cx, gcOutput.script); + Rooted module(cx, gcOutput.module); + + script->outermostScope()->as().initModule(module); + + module->initScriptSlots(script); + + if (!ModuleObject::createEnvironment(cx, module)) { + return false; + } + + MOZ_ASSERT(!cx->isHelperThreadContext()); + if (!ModuleObject::Freeze(cx, module)) { + return false; + } + } + + return true; +} + +// When a function is first referenced by enclosing script's bytecode, we need +// to update it with information determined by the BytecodeEmitter. This applies +// to both initial and delazification parses. The functions being update may or +// may not have bytecode at this point. +static void UpdateEmittedInnerFunctions(JSContext* cx, + CompilationAtomCache& atomCache, + const CompilationStencil& stencil, + CompilationGCOutput& gcOutput) { + for (auto item : + CompilationStencil::functionScriptStencils(stencil, gcOutput)) { + auto& scriptStencil = item.script; + auto& fun = item.function; + if (!scriptStencil.wasEmittedByEnclosingScript()) { + continue; + } + + if (scriptStencil.functionFlags.isAsmJSNative() || + fun->baseScript()->hasBytecode()) { + // Non-lazy inner functions don't use the enclosingScope_ field. + MOZ_ASSERT(!scriptStencil.hasLazyFunctionEnclosingScopeIndex()); + } else { + // Apply updates from FunctionEmitter::emitLazy(). + BaseScript* script = fun->baseScript(); + + ScopeIndex index = scriptStencil.lazyFunctionEnclosingScopeIndex(); + Scope* scope = gcOutput.getScopeNoBaseIndex(index); + script->setEnclosingScope(scope); + + // Inferred and Guessed names are computed by BytecodeEmitter and so may + // need to be applied to existing JSFunctions during delazification. + if (fun->displayAtom() == nullptr) { + JSAtom* funcAtom = nullptr; + if (scriptStencil.functionFlags.hasInferredName() || + scriptStencil.functionFlags.hasGuessedAtom()) { + funcAtom = + atomCache.getExistingAtomAt(cx, scriptStencil.functionAtom); + MOZ_ASSERT(funcAtom); + } + if (scriptStencil.functionFlags.hasInferredName()) { + fun->setInferredName(funcAtom); + } + if (scriptStencil.functionFlags.hasGuessedAtom()) { + fun->setGuessedAtom(funcAtom); + } + } + } + } +} + +// During initial parse we must link lazy-functions-inside-lazy-functions to +// their enclosing script. +static void LinkEnclosingLazyScript(const CompilationStencil& stencil, + CompilationGCOutput& gcOutput) { + for (auto item : + CompilationStencil::functionScriptStencils(stencil, gcOutput)) { + auto& scriptStencil = item.script; + auto& fun = item.function; + if (!scriptStencil.functionFlags.hasBaseScript()) { + continue; + } + + if (!fun->baseScript()) { + continue; + } + + if (fun->baseScript()->hasBytecode()) { + continue; + } + + BaseScript* script = fun->baseScript(); + MOZ_ASSERT(!script->hasBytecode()); + + for (auto inner : script->gcthings()) { + if (!inner.is()) { + continue; + } + JSFunction* innerFun = &inner.as().as(); + + MOZ_ASSERT(innerFun->hasBaseScript(), + "inner function should have base script"); + if (!innerFun->hasBaseScript()) { + continue; + } + + // Check for the case that the inner function has the base script flag, + // but still doesn't have the actual base script pointer. + // `baseScript` method asserts the pointer itself, so no extra MOZ_ASSERT + // here. + if (!innerFun->baseScript()) { + continue; + } + + innerFun->setEnclosingLazyScript(script); + } + } +} + +#ifdef DEBUG +// Some fields aren't used in delazification, given the target functions and +// scripts are already instantiated, but they still should match. +static void AssertDelazificationFieldsMatch(const CompilationStencil& stencil, + CompilationGCOutput& gcOutput) { + for (auto item : + CompilationStencil::functionScriptStencils(stencil, gcOutput)) { + auto& scriptStencil = item.script; + auto* scriptExtra = item.scriptExtra; + auto& fun = item.function; + + MOZ_ASSERT(scriptExtra == nullptr); + + // Names are updated by UpdateInnerFunctions. + constexpr uint16_t HAS_INFERRED_NAME = + uint16_t(FunctionFlags::Flags::HAS_INFERRED_NAME); + constexpr uint16_t HAS_GUESSED_ATOM = + uint16_t(FunctionFlags::Flags::HAS_GUESSED_ATOM); + constexpr uint16_t MUTABLE_FLAGS = + uint16_t(FunctionFlags::Flags::MUTABLE_FLAGS); + constexpr uint16_t acceptableDifferenceForFunction = + HAS_INFERRED_NAME | HAS_GUESSED_ATOM | MUTABLE_FLAGS; + + MOZ_ASSERT((fun->flags().toRaw() | acceptableDifferenceForFunction) == + (scriptStencil.functionFlags.toRaw() | + acceptableDifferenceForFunction)); + + // Delazification shouldn't delazify inner scripts. + MOZ_ASSERT_IF(item.index == CompilationStencil::TopLevelIndex, + scriptStencil.hasSharedData()); + MOZ_ASSERT_IF(item.index > CompilationStencil::TopLevelIndex, + !scriptStencil.hasSharedData()); + } +} +#endif // DEBUG + +// When delazifying, use the existing JSFunctions. The initial and delazifying +// parse are required to generate the same sequence of functions for lazy +// parsing to work at all. +static void FunctionsFromExistingLazy(CompilationInput& input, + CompilationGCOutput& gcOutput) { + MOZ_ASSERT(gcOutput.functions.empty()); + gcOutput.functions.infallibleAppend(input.function()); + + for (JS::GCCellPtr elem : input.lazyOuterBaseScript()->gcthings()) { + if (!elem.is()) { + continue; + } + JSFunction* fun = &elem.as().as(); + gcOutput.functions.infallibleAppend(fun); + } +} + +void CompilationStencil::borrowFromExtensibleCompilationStencil( + ExtensibleCompilationStencil& extensibleStencil) { + canLazilyParse = extensibleStencil.canLazilyParse; + functionKey = extensibleStencil.functionKey; + + // Borrow the vector content as span. + scriptData = extensibleStencil.scriptData; + scriptExtra = extensibleStencil.scriptExtra; + + gcThingData = extensibleStencil.gcThingData; + + scopeData = extensibleStencil.scopeData; + scopeNames = extensibleStencil.scopeNames; + + regExpData = extensibleStencil.regExpData; + bigIntData = extensibleStencil.bigIntData; + objLiteralData = extensibleStencil.objLiteralData; + + // Borrow the parser atoms as span. + parserAtomData = extensibleStencil.parserAtoms.entries_; + + // Borrow container. + sharedData.setBorrow(&extensibleStencil.sharedData); + + // Share ref-counted data. + source = extensibleStencil.source; + asmJS = extensibleStencil.asmJS; + moduleMetadata = extensibleStencil.moduleMetadata; +} + +#ifdef DEBUG +void CompilationStencil::assertBorrowingFromExtensibleCompilationStencil( + const ExtensibleCompilationStencil& extensibleStencil) const { + MOZ_ASSERT(canLazilyParse == extensibleStencil.canLazilyParse); + MOZ_ASSERT(functionKey == extensibleStencil.functionKey); + + AssertBorrowingSpan(scriptData, extensibleStencil.scriptData); + AssertBorrowingSpan(scriptExtra, extensibleStencil.scriptExtra); + + AssertBorrowingSpan(gcThingData, extensibleStencil.gcThingData); + + AssertBorrowingSpan(scopeData, extensibleStencil.scopeData); + AssertBorrowingSpan(scopeNames, extensibleStencil.scopeNames); + + AssertBorrowingSpan(regExpData, extensibleStencil.regExpData); + AssertBorrowingSpan(bigIntData, extensibleStencil.bigIntData); + AssertBorrowingSpan(objLiteralData, extensibleStencil.objLiteralData); + + AssertBorrowingSpan(parserAtomData, extensibleStencil.parserAtoms.entries_); + + MOZ_ASSERT(sharedData.isBorrow()); + MOZ_ASSERT(sharedData.asBorrow() == &extensibleStencil.sharedData); + + MOZ_ASSERT(source == extensibleStencil.source); + MOZ_ASSERT(asmJS == extensibleStencil.asmJS); + MOZ_ASSERT(moduleMetadata == extensibleStencil.moduleMetadata); +} +#endif + +CompilationStencil::CompilationStencil( + UniquePtr&& extensibleStencil) + : alloc(LifoAllocChunkSize) { + ownedBorrowStencil = std::move(extensibleStencil); + + storageType = StorageType::OwnedExtensible; + + borrowFromExtensibleCompilationStencil(*ownedBorrowStencil); + +#ifdef DEBUG + assertNoExternalDependency(); +#endif +} + +/* static */ +bool CompilationStencil::instantiateStencils(JSContext* cx, + CompilationInput& input, + const CompilationStencil& stencil, + CompilationGCOutput& gcOutput) { + AutoReportFrontendContext fc(cx); + if (!prepareForInstantiate(&fc, input.atomCache, stencil, gcOutput)) { + return false; + } + + return instantiateStencilAfterPreparation(cx, input, stencil, gcOutput); +} + +/* static */ +bool CompilationStencil::instantiateStencilAfterPreparation( + JSContext* cx, CompilationInput& input, const CompilationStencil& stencil, + CompilationGCOutput& gcOutput) { + // Distinguish between the initial (possibly lazy) compile and any subsequent + // delazification compiles. Delazification will update existing GC things. + bool isInitialParse = stencil.isInitialStencil(); + MOZ_ASSERT(stencil.isInitialStencil() == input.isInitialStencil()); + + CompilationAtomCache& atomCache = input.atomCache; + const JS::InstantiateOptions options(input.options); + + // Phase 1: Instantiate JSAtom/JSStrings. + AutoReportFrontendContext fc(cx); + if (!InstantiateAtoms(cx, &fc, atomCache, stencil)) { + return false; + } + + // Phase 2: Instantiate ScriptSourceObject, ModuleObject, JSFunctions. + if (isInitialParse) { + if (!InstantiateScriptSourceObject(cx, options, stencil, gcOutput)) { + return false; + } + + if (stencil.moduleMetadata) { + // The enclosing script of a module is always the global scope. Fetch the + // scope of the current global and update input data. + MOZ_ASSERT(input.enclosingScope.isNull()); + input.enclosingScope = InputScope(&cx->global()->emptyGlobalScope()); + MOZ_ASSERT(input.enclosingScope.environmentChainLength() == + ModuleScope::EnclosingEnvironmentChainLength); + + if (!InstantiateModuleObject(cx, &fc, atomCache, stencil, gcOutput)) { + return false; + } + } + + if (!InstantiateFunctions(cx, &fc, atomCache, stencil, gcOutput)) { + return false; + } + } else { + MOZ_ASSERT( + stencil.scriptData[CompilationStencil::TopLevelIndex].isFunction()); + + // FunctionKey is used when caching to map a delazification stencil to a + // specific lazy script. It is not used by instantiation, but we should + // ensure it is correctly defined. + MOZ_ASSERT(stencil.functionKey == input.extent().toFunctionKey()); + + FunctionsFromExistingLazy(input, gcOutput); + MOZ_ASSERT(gcOutput.functions.length() == stencil.scriptData.size()); + +#ifdef DEBUG + AssertDelazificationFieldsMatch(stencil, gcOutput); +#endif + } + + // Phase 3: Instantiate js::Scopes. + if (!InstantiateScopes(cx, input, stencil, gcOutput)) { + return false; + } + + // Phase 4: Instantiate (inner) BaseScripts. + if (isInitialParse) { + if (!InstantiateScriptStencils(cx, atomCache, stencil, gcOutput)) { + return false; + } + } + + // Phase 5: Finish top-level handling + if (!InstantiateTopLevel(cx, input, stencil, gcOutput)) { + return false; + } + + // !! Must be infallible from here forward !! + + // Phase 6: Update lazy scripts. + if (stencil.canLazilyParse) { + UpdateEmittedInnerFunctions(cx, atomCache, stencil, gcOutput); + + if (isInitialParse) { + LinkEnclosingLazyScript(stencil, gcOutput); + } + } + + return true; +} + +// The top-level self-hosted script is created and executed in each realm that +// needs it. While the stencil has a gcthings list for the various top-level +// functions, we use special machinery to create them on demand. So instead we +// use a placeholder JSFunction that should never be called. +static bool SelfHostedDummyFunction(JSContext* cx, unsigned argc, + JS::Value* vp) { + MOZ_CRASH("Self-hosting top-level should not use functions directly"); +} + +bool CompilationStencil::instantiateSelfHostedAtoms( + JSContext* cx, AtomSet& atomSet, CompilationAtomCache& atomCache) const { + MOZ_ASSERT(isInitialStencil()); + + // We must instantiate atoms during startup so they can be made permanent + // across multiple runtimes. + AutoReportFrontendContext fc(cx); + return InstantiateMarkedAtomsAsPermanent(cx, &fc, atomSet, parserAtomData, + atomCache); +} + +JSScript* CompilationStencil::instantiateSelfHostedTopLevelForRealm( + JSContext* cx, CompilationInput& input) { + MOZ_ASSERT(isInitialStencil()); + + Rooted gcOutput(cx); + + gcOutput.get().sourceObject = SelfHostingScriptSourceObject(cx); + if (!gcOutput.get().sourceObject) { + return nullptr; + } + + // The top-level script has ScriptIndex references in its gcthings list, but + // we do not want to instantiate those functions here since they are instead + // created on demand from the stencil. Create a dummy function and populate + // the functions array of the CompilationGCOutput with references to it. + RootedFunction dummy( + cx, NewNativeFunction(cx, SelfHostedDummyFunction, 0, nullptr)); + if (!dummy) { + return nullptr; + } + if (!gcOutput.get().functions.appendN(dummy, scriptData.size())) { + ReportOutOfMemory(cx); + return nullptr; + } + + if (!InstantiateTopLevel(cx, input, *this, gcOutput.get())) { + return nullptr; + } + + return gcOutput.get().script; +} + +JSFunction* CompilationStencil::instantiateSelfHostedLazyFunction( + JSContext* cx, CompilationAtomCache& atomCache, ScriptIndex index, + Handle name) { + GeneratorKind generatorKind = scriptExtra[index].immutableFlags.hasFlag( + ImmutableScriptFlagsEnum::IsGenerator) + ? GeneratorKind::Generator + : GeneratorKind::NotGenerator; + FunctionAsyncKind asyncKind = scriptExtra[index].immutableFlags.hasFlag( + ImmutableScriptFlagsEnum::IsAsync) + ? FunctionAsyncKind::AsyncFunction + : FunctionAsyncKind::SyncFunction; + + Rooted funName(cx); + if (scriptData[index].hasSelfHostedCanonicalName()) { + // SetCanonicalName was used to override the name. + funName = atomCache.getExistingAtomAt( + cx, scriptData[index].selfHostedCanonicalName()); + } else if (name) { + // Our caller has a name it wants to use. + funName = name; + } else { + MOZ_ASSERT(scriptData[index].functionAtom); + funName = atomCache.getExistingAtomAt(cx, scriptData[index].functionAtom); + } + + RootedObject proto(cx); + if (!GetFunctionPrototype(cx, generatorKind, asyncKind, &proto)) { + return nullptr; + } + + RootedObject env(cx, &cx->global()->lexicalEnvironment()); + + RootedFunction fun( + cx, + NewFunctionWithProto(cx, nullptr, scriptExtra[index].nargs, + scriptData[index].functionFlags, env, funName, proto, + gc::AllocKind::FUNCTION_EXTENDED, TenuredObject)); + if (!fun) { + return nullptr; + } + + fun->initSelfHostedLazyScript(&cx->runtime()->selfHostedLazyScript.ref()); + + JSAtom* selfHostedName = + atomCache.getExistingAtomAt(cx, scriptData[index].functionAtom); + SetClonedSelfHostedFunctionName(fun, selfHostedName->asPropertyName()); + + return fun; +} + +bool CompilationStencil::delazifySelfHostedFunction( + JSContext* cx, CompilationAtomCache& atomCache, ScriptIndexRange range, + HandleFunction fun) { + // Determine the equivalent ScopeIndex range by looking at the outermost scope + // of the scripts defining the range. Take special care if this is the last + // script in the list. + auto getOutermostScope = [this](ScriptIndex scriptIndex) -> ScopeIndex { + MOZ_ASSERT(scriptData[scriptIndex].hasSharedData()); + auto gcthings = scriptData[scriptIndex].gcthings(*this); + return gcthings[GCThingIndex::outermostScopeIndex()].toScope(); + }; + ScopeIndex scopeIndex = getOutermostScope(range.start); + ScopeIndex scopeLimit = (range.limit < scriptData.size()) + ? getOutermostScope(range.limit) + : ScopeIndex(scopeData.size()); + + // Prepare to instantiate by reserving the output vectors. We also set a base + // index to avoid allocations in most cases. + AutoReportFrontendContext fc(cx); + Rooted gcOutput(cx); + if (!gcOutput.get().ensureReservedWithBaseIndex(&fc, range.start, range.limit, + scopeIndex, scopeLimit)) { + return false; + } + + // Phase 1: Instantiate JSAtoms. + // NOTE: The self-hosted atoms are all "permanent" and the + // CompilationAtomCache is already stored on the JSRuntime. + + // Phase 2: Instantiate ScriptSourceObject, ModuleObject, JSFunctions. + + // Get the corresponding ScriptSourceObject to use in current realm. + gcOutput.get().sourceObject = SelfHostingScriptSourceObject(cx); + if (!gcOutput.get().sourceObject) { + return false; + } + + // Delazification target function. + gcOutput.get().functions.infallibleAppend(fun); + + // Allocate inner functions. Self-hosted functions do not allocate these with + // the initial function. + for (size_t i = range.start + 1; i < range.limit; i++) { + JSFunction* innerFun = CreateFunction(cx, atomCache, *this, scriptData[i], + scriptExtra[i], ScriptIndex(i)); + if (!innerFun) { + return false; + } + gcOutput.get().functions.infallibleAppend(innerFun); + } + + // Phase 3: Instantiate js::Scopes. + // NOTE: When the enclosing scope is not a stencil, directly use the + // `emptyGlobalScope` instead of reading from CompilationInput. This is + // a special case for self-hosted delazification that allows us to reuse + // the CompilationInput between different realms. + for (size_t i = scopeIndex; i < scopeLimit; i++) { + ScopeStencil& data = scopeData[i]; + Rooted enclosingScope( + cx, data.hasEnclosing() ? gcOutput.get().getScope(data.enclosing()) + : &cx->global()->emptyGlobalScope()); + + js::Scope* scope = + data.createScope(cx, atomCache, enclosingScope, scopeNames[i]); + if (!scope) { + return false; + } + gcOutput.get().scopes.infallibleAppend(scope); + } + + // Phase 4: Instantiate (inner) BaseScripts. + ScriptIndex innerStart(range.start + 1); + for (size_t i = innerStart; i < range.limit; i++) { + if (!JSScript::fromStencil(cx, atomCache, *this, gcOutput.get(), + ScriptIndex(i))) { + return false; + } + } + + // Phase 5: Finish top-level handling + // NOTE: We do not have a `CompilationInput` handy here, so avoid using the + // `InstantiateTopLevel` helper and directly create the JSScript. Our + // caller also handles the `AllowRelazify` flag for us since self-hosted + // delazification is a special case. + if (!JSScript::fromStencil(cx, atomCache, *this, gcOutput.get(), + range.start)) { + return false; + } + + // Phase 6: Update lazy scripts. + // NOTE: Self-hosting is always fully parsed so there is nothing to do here. + + return true; +} + +/* static */ +bool CompilationStencil::prepareForInstantiate( + FrontendContext* fc, CompilationAtomCache& atomCache, + const CompilationStencil& stencil, CompilationGCOutput& gcOutput) { + // Reserve the `gcOutput` vectors. + if (!gcOutput.ensureReserved(fc, stencil.scriptData.size(), + stencil.scopeData.size())) { + return false; + } + + return atomCache.allocate(fc, stencil.parserAtomData.size()); +} + +bool CompilationStencil::serializeStencils(JSContext* cx, + CompilationInput& input, + JS::TranscodeBuffer& buf, + bool* succeededOut) const { + if (succeededOut) { + *succeededOut = false; + } + AutoReportFrontendContext fc(cx); + XDRStencilEncoder encoder(&fc, buf); + + XDRResult res = encoder.codeStencil(*this); + if (res.isErr()) { + if (JS::IsTranscodeFailureResult(res.unwrapErr())) { + buf.clear(); + return true; + } + MOZ_ASSERT(res.unwrapErr() == JS::TranscodeResult::Throw); + + return false; + } + + if (succeededOut) { + *succeededOut = true; + } + return true; +} + +bool CompilationStencil::deserializeStencils( + FrontendContext* fc, const JS::ReadOnlyCompileOptions& compileOptions, + const JS::TranscodeRange& range, bool* succeededOut) { + if (succeededOut) { + *succeededOut = false; + } + MOZ_ASSERT(parserAtomData.empty()); + XDRStencilDecoder decoder(fc, range); + JS::DecodeOptions options(compileOptions); + + XDRResult res = decoder.codeStencil(options, *this); + if (res.isErr()) { + if (JS::IsTranscodeFailureResult(res.unwrapErr())) { + return true; + } + MOZ_ASSERT(res.unwrapErr() == JS::TranscodeResult::Throw); + + return false; + } + + if (succeededOut) { + *succeededOut = true; + } + return true; +} + +ExtensibleCompilationStencil::ExtensibleCompilationStencil(ScriptSource* source) + : alloc(CompilationStencil::LifoAllocChunkSize), + source(source), + parserAtoms(alloc) {} + +ExtensibleCompilationStencil::ExtensibleCompilationStencil( + CompilationInput& input) + : canLazilyParse(CanLazilyParse(input.options)), + alloc(CompilationStencil::LifoAllocChunkSize), + source(input.source), + parserAtoms(alloc) {} + +ExtensibleCompilationStencil::ExtensibleCompilationStencil( + const JS::ReadOnlyCompileOptions& options, RefPtr source) + : canLazilyParse(CanLazilyParse(options)), + alloc(CompilationStencil::LifoAllocChunkSize), + source(std::move(source)), + parserAtoms(alloc) {} + +CompilationState::CompilationState(FrontendContext* fc, + LifoAllocScope& parserAllocScope, + CompilationInput& input) + : ExtensibleCompilationStencil(input), + directives(input.options.forceStrictMode()), + usedNames(fc), + parserAllocScope(parserAllocScope), + input(input) {} + +BorrowingCompilationStencil::BorrowingCompilationStencil( + ExtensibleCompilationStencil& extensibleStencil) + : CompilationStencil(extensibleStencil.source) { + storageType = StorageType::Borrowed; + + borrowFromExtensibleCompilationStencil(extensibleStencil); +} + +SharedDataContainer::~SharedDataContainer() { + if (isEmpty()) { + // Nothing to do. + } else if (isSingle()) { + asSingle()->Release(); + } else if (isVector()) { + js_delete(asVector()); + } else if (isMap()) { + js_delete(asMap()); + } else { + MOZ_ASSERT(isBorrow()); + // Nothing to do. + } +} + +bool SharedDataContainer::initVector(FrontendContext* fc) { + MOZ_ASSERT(isEmpty()); + + auto* vec = js_new(); + if (!vec) { + ReportOutOfMemory(fc); + return false; + } + data_ = uintptr_t(vec) | VectorTag; + return true; +} + +bool SharedDataContainer::initMap(FrontendContext* fc) { + MOZ_ASSERT(isEmpty()); + + auto* map = js_new(); + if (!map) { + ReportOutOfMemory(fc); + return false; + } + data_ = uintptr_t(map) | MapTag; + return true; +} + +bool SharedDataContainer::prepareStorageFor(FrontendContext* fc, + size_t nonLazyScriptCount, + size_t allScriptCount) { + MOZ_ASSERT(isEmpty()); + + if (nonLazyScriptCount <= 1) { + MOZ_ASSERT(isSingle()); + return true; + } + + // If the ratio of scripts with bytecode is small, allocating the Vector + // storage with the number of all scripts isn't space-efficient. + // In that case use HashMap instead. + // + // In general, we expect either all scripts to contain bytecode (priviledge + // and self-hosted), or almost none to (eg standard lazy parsing output). + constexpr size_t thresholdRatio = 8; + bool useHashMap = nonLazyScriptCount < allScriptCount / thresholdRatio; + if (useHashMap) { + if (!initMap(fc)) { + return false; + } + if (!asMap()->reserve(nonLazyScriptCount)) { + ReportOutOfMemory(fc); + return false; + } + } else { + if (!initVector(fc)) { + return false; + } + if (!asVector()->resize(allScriptCount)) { + ReportOutOfMemory(fc); + return false; + } + } + + return true; +} + +bool SharedDataContainer::cloneFrom(FrontendContext* fc, + const SharedDataContainer& other) { + MOZ_ASSERT(isEmpty()); + + if (other.isBorrow()) { + return cloneFrom(fc, *other.asBorrow()); + } + + if (other.isSingle()) { + // As we clone, we add an extra reference. + RefPtr ref(other.asSingle()); + setSingle(ref.forget()); + } else if (other.isVector()) { + if (!initVector(fc)) { + return false; + } + if (!asVector()->appendAll(*other.asVector())) { + ReportOutOfMemory(fc); + return false; + } + } else if (other.isMap()) { + if (!initMap(fc)) { + return false; + } + auto& otherMap = *other.asMap(); + if (!asMap()->reserve(otherMap.count())) { + ReportOutOfMemory(fc); + return false; + } + auto& map = *asMap(); + for (auto iter = otherMap.iter(); !iter.done(); iter.next()) { + auto& entry = iter.get(); + map.putNewInfallible(entry.key(), entry.value()); + } + } + return true; +} + +js::SharedImmutableScriptData* SharedDataContainer::get( + ScriptIndex index) const { + if (isSingle()) { + if (index == CompilationStencil::TopLevelIndex) { + return asSingle(); + } + return nullptr; + } + + if (isVector()) { + auto& vec = *asVector(); + if (index.index < vec.length()) { + return vec[index]; + } + return nullptr; + } + + if (isMap()) { + auto& map = *asMap(); + auto p = map.lookup(index); + if (p) { + return p->value(); + } + return nullptr; + } + + MOZ_ASSERT(isBorrow()); + return asBorrow()->get(index); +} + +bool SharedDataContainer::convertFromSingleToMap(FrontendContext* fc) { + MOZ_ASSERT(isSingle()); + + // Use a temporary container so that on OOM we do not break the stencil. + SharedDataContainer other; + if (!other.initMap(fc)) { + return false; + } + + if (!other.asMap()->putNew(CompilationStencil::TopLevelIndex, asSingle())) { + ReportOutOfMemory(fc); + return false; + } + + std::swap(data_, other.data_); + return true; +} + +bool SharedDataContainer::addAndShare(FrontendContext* fc, ScriptIndex index, + js::SharedImmutableScriptData* data) { + MOZ_ASSERT(!isBorrow()); + + if (isSingle()) { + MOZ_ASSERT(index == CompilationStencil::TopLevelIndex); + RefPtr ref(data); + if (!SharedImmutableScriptData::shareScriptData(fc, ref)) { + return false; + } + setSingle(ref.forget()); + return true; + } + + if (isVector()) { + auto& vec = *asVector(); + // Resized by SharedDataContainer::prepareStorageFor. + vec[index] = data; + return SharedImmutableScriptData::shareScriptData(fc, vec[index]); + } + + MOZ_ASSERT(isMap()); + auto& map = *asMap(); + // Reserved by SharedDataContainer::prepareStorageFor. + map.putNewInfallible(index, data); + auto p = map.lookup(index); + MOZ_ASSERT(p); + return SharedImmutableScriptData::shareScriptData(fc, p->value()); +} + +bool SharedDataContainer::addExtraWithoutShare( + FrontendContext* fc, ScriptIndex index, + js::SharedImmutableScriptData* data) { + MOZ_ASSERT(!isEmpty()); + + if (isSingle()) { + if (!convertFromSingleToMap(fc)) { + return false; + } + } + + if (isVector()) { + // SharedDataContainer::prepareStorageFor allocates space for all scripts. + (*asVector())[index] = data; + return true; + } + + MOZ_ASSERT(isMap()); + // SharedDataContainer::prepareStorageFor doesn't allocate space for + // delazification, and this can fail. + if (!asMap()->putNew(index, data)) { + ReportOutOfMemory(fc); + return false; + } + return true; +} + +#ifdef DEBUG +void CompilationStencil::assertNoExternalDependency() const { + if (ownedBorrowStencil) { + ownedBorrowStencil->assertNoExternalDependency(); + + assertBorrowingFromExtensibleCompilationStencil(*ownedBorrowStencil); + return; + } + + MOZ_ASSERT_IF(!scriptData.empty(), alloc.contains(scriptData.data())); + MOZ_ASSERT_IF(!scriptExtra.empty(), alloc.contains(scriptExtra.data())); + + MOZ_ASSERT_IF(!scopeData.empty(), alloc.contains(scopeData.data())); + MOZ_ASSERT_IF(!scopeNames.empty(), alloc.contains(scopeNames.data())); + for (const auto* data : scopeNames) { + MOZ_ASSERT_IF(data, alloc.contains(data)); + } + + MOZ_ASSERT_IF(!regExpData.empty(), alloc.contains(regExpData.data())); + + MOZ_ASSERT_IF(!bigIntData.empty(), alloc.contains(bigIntData.data())); + for (const auto& data : bigIntData) { + MOZ_ASSERT(data.isContainedIn(alloc)); + } + + MOZ_ASSERT_IF(!objLiteralData.empty(), alloc.contains(objLiteralData.data())); + for (const auto& data : objLiteralData) { + MOZ_ASSERT(data.isContainedIn(alloc)); + } + + MOZ_ASSERT_IF(!parserAtomData.empty(), alloc.contains(parserAtomData.data())); + for (const auto* data : parserAtomData) { + MOZ_ASSERT_IF(data, alloc.contains(data)); + } + + MOZ_ASSERT(!sharedData.isBorrow()); +} + +void ExtensibleCompilationStencil::assertNoExternalDependency() const { + for (const auto& data : bigIntData) { + MOZ_ASSERT(data.isContainedIn(alloc)); + } + + for (const auto& data : objLiteralData) { + MOZ_ASSERT(data.isContainedIn(alloc)); + } + + for (const auto* data : scopeNames) { + MOZ_ASSERT_IF(data, alloc.contains(data)); + } + + for (const auto* data : parserAtoms.entries()) { + MOZ_ASSERT_IF(data, alloc.contains(data)); + } + + MOZ_ASSERT(!sharedData.isBorrow()); +} +#endif // DEBUG + +template +[[nodiscard]] bool CopySpanToVector(FrontendContext* fc, VectorT& vec, + mozilla::Span& span) { + auto len = span.size(); + if (len == 0) { + return true; + } + + if (!vec.append(span.data(), len)) { + js::ReportOutOfMemory(fc); + return false; + } + return true; +} + +template +[[nodiscard]] bool CopyToVector(FrontendContext* fc, + mozilla::Vector& vec, + const IntoSpanT& source) { + mozilla::Span span = source; + return CopySpanToVector(fc, vec, span); +} + +// Span and Vector do not share the same method names. +template +size_t GetLength(const mozilla::Vector& vec) { + return vec.length(); +} +template +size_t GetLength(const mozilla::Span& span) { + return span.Length(); +} + +// Copy scope names from `src` into `alloc`, and returns the allocated data. +BaseParserScopeData* CopyScopeData(FrontendContext* fc, LifoAlloc& alloc, + ScopeKind kind, + const BaseParserScopeData* src) { + MOZ_ASSERT(kind != ScopeKind::With); + + size_t dataSize = SizeOfParserScopeData(kind, src->length); + + auto* dest = static_cast(alloc.alloc(dataSize)); + if (!dest) { + js::ReportOutOfMemory(fc); + return nullptr; + } + memcpy(dest, src, dataSize); + + return dest; +} + +template +bool ExtensibleCompilationStencil::cloneFromImpl(FrontendContext* fc, + const Stencil& other) { + MOZ_ASSERT(alloc.isEmpty()); + + canLazilyParse = other.canLazilyParse; + functionKey = other.functionKey; + + if (!CopyToVector(fc, scriptData, other.scriptData)) { + return false; + } + + if (!CopyToVector(fc, scriptExtra, other.scriptExtra)) { + return false; + } + + if (!CopyToVector(fc, gcThingData, other.gcThingData)) { + return false; + } + + size_t scopeSize = GetLength(other.scopeData); + if (!CopyToVector(fc, scopeData, other.scopeData)) { + return false; + } + if (!scopeNames.reserve(scopeSize)) { + js::ReportOutOfMemory(fc); + return false; + } + for (size_t i = 0; i < scopeSize; i++) { + if (other.scopeNames[i]) { + BaseParserScopeData* data = CopyScopeData( + fc, alloc, other.scopeData[i].kind(), other.scopeNames[i]); + if (!data) { + return false; + } + scopeNames.infallibleEmplaceBack(data); + } else { + scopeNames.infallibleEmplaceBack(nullptr); + } + } + + if (!CopyToVector(fc, regExpData, other.regExpData)) { + return false; + } + + // If CompilationStencil has external dependency, peform deep copy. + + size_t bigIntSize = GetLength(other.bigIntData); + if (!bigIntData.resize(bigIntSize)) { + js::ReportOutOfMemory(fc); + return false; + } + for (size_t i = 0; i < bigIntSize; i++) { + if (!bigIntData[i].init(fc, alloc, other.bigIntData[i].source())) { + return false; + } + } + + size_t objLiteralSize = GetLength(other.objLiteralData); + if (!objLiteralData.reserve(objLiteralSize)) { + js::ReportOutOfMemory(fc); + return false; + } + for (const auto& data : other.objLiteralData) { + size_t length = data.code().size(); + auto* code = alloc.newArrayUninitialized(length); + if (!code) { + js::ReportOutOfMemory(fc); + return false; + } + memcpy(code, data.code().data(), length); + objLiteralData.infallibleEmplaceBack(code, length, data.kind(), + data.flags(), data.propertyCount()); + } + + // Regardless of whether CompilationStencil has external dependency or not, + // ParserAtoms should be interned, to populate internal HashMap. + for (const auto* entry : other.parserAtomsSpan()) { + if (!entry) { + if (!parserAtoms.addPlaceholder(fc)) { + return false; + } + continue; + } + + auto index = parserAtoms.internExternalParserAtom(fc, entry); + if (!index) { + return false; + } + } + + // We copy the stencil and increment the reference count of each + // SharedImmutableScriptData. + if (!sharedData.cloneFrom(fc, other.sharedData)) { + return false; + } + + // Note: moduleMetadata and asmJS are known after the first parse, and are + // not mutated by any delazifications later on. Thus we can safely increment + // the reference counter and keep these as-is. + moduleMetadata = other.moduleMetadata; + asmJS = other.asmJS; + +#ifdef DEBUG + assertNoExternalDependency(); +#endif + + return true; +} + +bool ExtensibleCompilationStencil::cloneFrom(FrontendContext* fc, + const CompilationStencil& other) { + return cloneFromImpl(fc, other); +} +bool ExtensibleCompilationStencil::cloneFrom( + FrontendContext* fc, const ExtensibleCompilationStencil& other) { + return cloneFromImpl(fc, other); +} + +bool ExtensibleCompilationStencil::steal(FrontendContext* fc, + RefPtr&& other) { + MOZ_ASSERT(alloc.isEmpty()); + using StorageType = CompilationStencil::StorageType; + StorageType storageType = other->storageType; + if (other->refCount > 1) { + storageType = StorageType::Borrowed; + } + + if (storageType == StorageType::OwnedExtensible) { + auto& otherExtensible = other->ownedBorrowStencil; + + canLazilyParse = otherExtensible->canLazilyParse; + functionKey = otherExtensible->functionKey; + + alloc.steal(&otherExtensible->alloc); + + source = std::move(otherExtensible->source); + + scriptData = std::move(otherExtensible->scriptData); + scriptExtra = std::move(otherExtensible->scriptExtra); + gcThingData = std::move(otherExtensible->gcThingData); + scopeData = std::move(otherExtensible->scopeData); + scopeNames = std::move(otherExtensible->scopeNames); + regExpData = std::move(otherExtensible->regExpData); + bigIntData = std::move(otherExtensible->bigIntData); + objLiteralData = std::move(otherExtensible->objLiteralData); + + parserAtoms = std::move(otherExtensible->parserAtoms); + parserAtoms.fixupAlloc(alloc); + + sharedData = std::move(otherExtensible->sharedData); + moduleMetadata = std::move(otherExtensible->moduleMetadata); + asmJS = std::move(otherExtensible->asmJS); + +#ifdef DEBUG + assertNoExternalDependency(); +#endif + + return true; + } + + if (storageType == StorageType::Borrowed) { + return cloneFrom(fc, *other); + } + + MOZ_ASSERT(storageType == StorageType::Owned); + + canLazilyParse = other->canLazilyParse; + functionKey = other->functionKey; + +#ifdef DEBUG + other->assertNoExternalDependency(); + MOZ_ASSERT(other->refCount == 1); +#endif + + // If CompilationStencil has no external dependency, + // steal LifoAlloc and perform shallow copy. + alloc.steal(&other->alloc); + + if (!CopySpanToVector(fc, scriptData, other->scriptData)) { + return false; + } + + if (!CopySpanToVector(fc, scriptExtra, other->scriptExtra)) { + return false; + } + + if (!CopySpanToVector(fc, gcThingData, other->gcThingData)) { + return false; + } + + if (!CopySpanToVector(fc, scopeData, other->scopeData)) { + return false; + } + if (!CopySpanToVector(fc, scopeNames, other->scopeNames)) { + return false; + } + + if (!CopySpanToVector(fc, regExpData, other->regExpData)) { + return false; + } + + if (!CopySpanToVector(fc, bigIntData, other->bigIntData)) { + return false; + } + + if (!CopySpanToVector(fc, objLiteralData, other->objLiteralData)) { + return false; + } + + // Regardless of whether CompilationStencil has external dependency or not, + // ParserAtoms should be interned, to populate internal HashMap. + for (const auto* entry : other->parserAtomData) { + if (!entry) { + if (!parserAtoms.addPlaceholder(fc)) { + return false; + } + continue; + } + + auto index = parserAtoms.internExternalParserAtom(fc, entry); + if (!index) { + return false; + } + } + + sharedData = std::move(other->sharedData); + moduleMetadata = std::move(other->moduleMetadata); + asmJS = std::move(other->asmJS); + +#ifdef DEBUG + assertNoExternalDependency(); +#endif + + return true; +} + +bool CompilationStencil::isModule() const { + return scriptExtra[CompilationStencil::TopLevelIndex].isModule(); +} + +bool ExtensibleCompilationStencil::isModule() const { + return scriptExtra[CompilationStencil::TopLevelIndex].isModule(); +} + +mozilla::Span ScriptStencil::gcthings( + const CompilationStencil& stencil) const { + return stencil.gcThingData.Subspan(gcThingsOffset, gcThingsLength); +} + +bool BigIntStencil::init(FrontendContext* fc, LifoAlloc& alloc, + const mozilla::Span buf) { +#ifdef DEBUG + // Assert we have no separators; if we have a separator then the algorithm + // used in BigInt::literalIsZero will be incorrect. + for (char16_t c : buf) { + MOZ_ASSERT(c != '_'); + } +#endif + size_t length = buf.size(); + char16_t* p = alloc.template newArrayUninitialized(length); + if (!p) { + ReportOutOfMemory(fc); + return false; + } + mozilla::PodCopy(p, buf.data(), length); + source_ = mozilla::Span(p, length); + return true; +} + +BigInt* BigIntStencil::createBigInt(JSContext* cx) const { + mozilla::Range source(source_.data(), source_.size()); + return js::ParseBigIntLiteral(cx, source); +} + +bool BigIntStencil::isZero() const { + mozilla::Range source(source_.data(), source_.size()); + return js::BigIntLiteralIsZero(source); +} + +#ifdef DEBUG +bool BigIntStencil::isContainedIn(const LifoAlloc& alloc) const { + return alloc.contains(source_.data()); +} +#endif + +#if defined(DEBUG) || defined(JS_JITSPEW) + +void frontend::DumpTaggedParserAtomIndex(js::JSONPrinter& json, + TaggedParserAtomIndex taggedIndex, + const CompilationStencil* stencil) { + if (taggedIndex.isParserAtomIndex()) { + json.property("tag", "AtomIndex"); + auto index = taggedIndex.toParserAtomIndex(); + if (stencil && stencil->parserAtomData[index]) { + GenericPrinter& out = json.beginStringProperty("atom"); + stencil->parserAtomData[index]->dumpCharsNoQuote(out); + json.endString(); + } else { + json.property("index", size_t(index)); + } + return; + } + + if (taggedIndex.isWellKnownAtomId()) { + json.property("tag", "WellKnown"); + auto index = taggedIndex.toWellKnownAtomId(); + switch (index) { + case WellKnownAtomId::empty: + json.property("atom", ""); + break; + +# define CASE_(_, name, _2) case WellKnownAtomId::name: + FOR_EACH_NONTINY_COMMON_PROPERTYNAME(CASE_) +# undef CASE_ + +# define CASE_(name, _) case WellKnownAtomId::name: + JS_FOR_EACH_PROTOTYPE(CASE_) +# undef CASE_ + +# define CASE_(name) case WellKnownAtomId::name: + JS_FOR_EACH_WELL_KNOWN_SYMBOL(CASE_) +# undef CASE_ + + { + GenericPrinter& out = json.beginStringProperty("atom"); + ParserAtomsTable::dumpCharsNoQuote(out, index); + json.endString(); + break; + } + + default: + // This includes tiny WellKnownAtomId atoms, which is invalid. + json.property("index", size_t(index)); + break; + } + return; + } + + if (taggedIndex.isLength1StaticParserString()) { + json.property("tag", "Length1Static"); + auto index = taggedIndex.toLength1StaticParserString(); + GenericPrinter& out = json.beginStringProperty("atom"); + ParserAtomsTable::dumpCharsNoQuote(out, index); + json.endString(); + return; + } + + if (taggedIndex.isLength2StaticParserString()) { + json.property("tag", "Length2Static"); + auto index = taggedIndex.toLength2StaticParserString(); + GenericPrinter& out = json.beginStringProperty("atom"); + ParserAtomsTable::dumpCharsNoQuote(out, index); + json.endString(); + return; + } + + if (taggedIndex.isLength3StaticParserString()) { + json.property("tag", "Length3Static"); + auto index = taggedIndex.toLength3StaticParserString(); + GenericPrinter& out = json.beginStringProperty("atom"); + ParserAtomsTable::dumpCharsNoQuote(out, index); + json.endString(); + return; + } + + MOZ_ASSERT(taggedIndex.isNull()); + json.property("tag", "null"); +} + +void frontend::DumpTaggedParserAtomIndexNoQuote( + GenericPrinter& out, TaggedParserAtomIndex taggedIndex, + const CompilationStencil* stencil) { + if (taggedIndex.isParserAtomIndex()) { + auto index = taggedIndex.toParserAtomIndex(); + if (stencil && stencil->parserAtomData[index]) { + stencil->parserAtomData[index]->dumpCharsNoQuote(out); + } else { + out.printf("AtomIndex#%zu", size_t(index)); + } + return; + } + + if (taggedIndex.isWellKnownAtomId()) { + auto index = taggedIndex.toWellKnownAtomId(); + switch (index) { + case WellKnownAtomId::empty: + out.put("#"); + break; + +# define CASE_(_, name, _2) case WellKnownAtomId::name: + FOR_EACH_NONTINY_COMMON_PROPERTYNAME(CASE_) +# undef CASE_ + +# define CASE_(name, _) case WellKnownAtomId::name: + JS_FOR_EACH_PROTOTYPE(CASE_) +# undef CASE_ + +# define CASE_(name) case WellKnownAtomId::name: + JS_FOR_EACH_WELL_KNOWN_SYMBOL(CASE_) +# undef CASE_ + + { + ParserAtomsTable::dumpCharsNoQuote(out, index); + break; + } + + default: + // This includes tiny WellKnownAtomId atoms, which is invalid. + out.printf("WellKnown#%zu", size_t(index)); + break; + } + return; + } + + if (taggedIndex.isLength1StaticParserString()) { + auto index = taggedIndex.toLength1StaticParserString(); + ParserAtomsTable::dumpCharsNoQuote(out, index); + return; + } + + if (taggedIndex.isLength2StaticParserString()) { + auto index = taggedIndex.toLength2StaticParserString(); + ParserAtomsTable::dumpCharsNoQuote(out, index); + return; + } + + if (taggedIndex.isLength3StaticParserString()) { + auto index = taggedIndex.toLength3StaticParserString(); + ParserAtomsTable::dumpCharsNoQuote(out, index); + return; + } + + MOZ_ASSERT(taggedIndex.isNull()); + out.put("#"); +} + +void RegExpStencil::dump() const { + js::Fprinter out(stderr); + js::JSONPrinter json(out); + dump(json, nullptr); +} + +void RegExpStencil::dump(js::JSONPrinter& json, + const CompilationStencil* stencil) const { + json.beginObject(); + dumpFields(json, stencil); + json.endObject(); +} + +void RegExpStencil::dumpFields(js::JSONPrinter& json, + const CompilationStencil* stencil) const { + json.beginObjectProperty("pattern"); + DumpTaggedParserAtomIndex(json, atom_, stencil); + json.endObject(); + + GenericPrinter& out = json.beginStringProperty("flags"); + + if (flags().global()) { + out.put("g"); + } + if (flags().ignoreCase()) { + out.put("i"); + } + if (flags().multiline()) { + out.put("m"); + } + if (flags().dotAll()) { + out.put("s"); + } + if (flags().unicode()) { + out.put("u"); + } + if (flags().sticky()) { + out.put("y"); + } + + json.endStringProperty(); +} + +void BigIntStencil::dump() const { + js::Fprinter out(stderr); + js::JSONPrinter json(out); + dump(json); +} + +void BigIntStencil::dump(js::JSONPrinter& json) const { + GenericPrinter& out = json.beginString(); + dumpCharsNoQuote(out); + json.endString(); +} + +void BigIntStencil::dumpCharsNoQuote(GenericPrinter& out) const { + for (char16_t c : source_) { + out.putChar(char(c)); + } +} + +void ScopeStencil::dump() const { + js::Fprinter out(stderr); + js::JSONPrinter json(out); + dump(json, nullptr, nullptr); +} + +void ScopeStencil::dump(js::JSONPrinter& json, + const BaseParserScopeData* baseScopeData, + const CompilationStencil* stencil) const { + json.beginObject(); + dumpFields(json, baseScopeData, stencil); + json.endObject(); +} + +void ScopeStencil::dumpFields(js::JSONPrinter& json, + const BaseParserScopeData* baseScopeData, + const CompilationStencil* stencil) const { + json.property("kind", ScopeKindString(kind_)); + + if (hasEnclosing()) { + json.formatProperty("enclosing", "ScopeIndex(%zu)", size_t(enclosing())); + } + + json.property("firstFrameSlot", firstFrameSlot_); + + if (hasEnvironmentShape()) { + json.formatProperty("numEnvironmentSlots", "%zu", + size_t(numEnvironmentSlots_)); + } + + if (isFunction()) { + json.formatProperty("functionIndex", "ScriptIndex(%zu)", + size_t(functionIndex_)); + } + + json.beginListProperty("flags"); + if (flags_ & HasEnclosing) { + json.value("HasEnclosing"); + } + if (flags_ & HasEnvironmentShape) { + json.value("HasEnvironmentShape"); + } + if (flags_ & IsArrow) { + json.value("IsArrow"); + } + json.endList(); + + if (!baseScopeData) { + return; + } + + json.beginObjectProperty("data"); + + mozilla::Span trailingNames; + switch (kind_) { + case ScopeKind::Function: { + const auto* data = + static_cast(baseScopeData); + json.property("nextFrameSlot", data->slotInfo.nextFrameSlot); + json.property("hasParameterExprs", data->slotInfo.hasParameterExprs()); + json.property("nonPositionalFormalStart", + data->slotInfo.nonPositionalFormalStart); + json.property("varStart", data->slotInfo.varStart); + + trailingNames = GetScopeDataTrailingNames(data); + break; + } + + case ScopeKind::FunctionBodyVar: { + const auto* data = + static_cast(baseScopeData); + json.property("nextFrameSlot", data->slotInfo.nextFrameSlot); + + trailingNames = GetScopeDataTrailingNames(data); + break; + } + + case ScopeKind::Lexical: + case ScopeKind::SimpleCatch: + case ScopeKind::Catch: + case ScopeKind::NamedLambda: + case ScopeKind::StrictNamedLambda: + case ScopeKind::FunctionLexical: { + const auto* data = + static_cast(baseScopeData); + json.property("nextFrameSlot", data->slotInfo.nextFrameSlot); + json.property("constStart", data->slotInfo.constStart); + + trailingNames = GetScopeDataTrailingNames(data); + break; + } + + case ScopeKind::ClassBody: { + const auto* data = + static_cast(baseScopeData); + json.property("nextFrameSlot", data->slotInfo.nextFrameSlot); + json.property("privateMethodStart", data->slotInfo.privateMethodStart); + + trailingNames = GetScopeDataTrailingNames(data); + break; + } + + case ScopeKind::With: { + break; + } + + case ScopeKind::Eval: + case ScopeKind::StrictEval: { + const auto* data = + static_cast(baseScopeData); + json.property("nextFrameSlot", data->slotInfo.nextFrameSlot); + + trailingNames = GetScopeDataTrailingNames(data); + break; + } + + case ScopeKind::Global: + case ScopeKind::NonSyntactic: { + const auto* data = + static_cast(baseScopeData); + json.property("letStart", data->slotInfo.letStart); + json.property("constStart", data->slotInfo.constStart); + + trailingNames = GetScopeDataTrailingNames(data); + break; + } + + case ScopeKind::Module: { + const auto* data = + static_cast(baseScopeData); + json.property("nextFrameSlot", data->slotInfo.nextFrameSlot); + json.property("varStart", data->slotInfo.varStart); + json.property("letStart", data->slotInfo.letStart); + json.property("constStart", data->slotInfo.constStart); + + trailingNames = GetScopeDataTrailingNames(data); + break; + } + + case ScopeKind::WasmInstance: { + const auto* data = + static_cast(baseScopeData); + json.property("nextFrameSlot", data->slotInfo.nextFrameSlot); + json.property("globalsStart", data->slotInfo.globalsStart); + + trailingNames = GetScopeDataTrailingNames(data); + break; + } + + case ScopeKind::WasmFunction: { + const auto* data = + static_cast(baseScopeData); + json.property("nextFrameSlot", data->slotInfo.nextFrameSlot); + + trailingNames = GetScopeDataTrailingNames(data); + break; + } + + default: { + MOZ_CRASH("Unexpected ScopeKind"); + break; + } + } + + if (!trailingNames.empty()) { + char index[64]; + json.beginObjectProperty("trailingNames"); + for (size_t i = 0; i < trailingNames.size(); i++) { + const auto& name = trailingNames[i]; + SprintfLiteral(index, "%zu", i); + json.beginObjectProperty(index); + + json.boolProperty("closedOver", name.closedOver()); + + json.boolProperty("isTopLevelFunction", name.isTopLevelFunction()); + + json.beginObjectProperty("name"); + DumpTaggedParserAtomIndex(json, name.name(), stencil); + json.endObject(); + + json.endObject(); + } + json.endObject(); + } + + json.endObject(); +} + +static void DumpModuleRequestVectorItems( + js::JSONPrinter& json, const StencilModuleMetadata::RequestVector& requests, + const CompilationStencil* stencil) { + for (const auto& request : requests) { + json.beginObject(); + if (request.specifier) { + json.beginObjectProperty("specifier"); + DumpTaggedParserAtomIndex(json, request.specifier, stencil); + json.endObject(); + } + json.endObject(); + } +} + +static void DumpModuleEntryVectorItems( + js::JSONPrinter& json, const StencilModuleMetadata::EntryVector& entries, + const CompilationStencil* stencil) { + for (const auto& entry : entries) { + json.beginObject(); + if (entry.moduleRequest) { + json.property("moduleRequest", entry.moduleRequest.value()); + } + if (entry.localName) { + json.beginObjectProperty("localName"); + DumpTaggedParserAtomIndex(json, entry.localName, stencil); + json.endObject(); + } + if (entry.importName) { + json.beginObjectProperty("importName"); + DumpTaggedParserAtomIndex(json, entry.importName, stencil); + json.endObject(); + } + if (entry.exportName) { + json.beginObjectProperty("exportName"); + DumpTaggedParserAtomIndex(json, entry.exportName, stencil); + json.endObject(); + } + // TODO: Dump assertions. + json.endObject(); + } +} + +void StencilModuleMetadata::dump() const { + js::Fprinter out(stderr); + js::JSONPrinter json(out); + dump(json, nullptr); +} + +void StencilModuleMetadata::dump(js::JSONPrinter& json, + const CompilationStencil* stencil) const { + json.beginObject(); + dumpFields(json, stencil); + json.endObject(); +} + +void StencilModuleMetadata::dumpFields( + js::JSONPrinter& json, const CompilationStencil* stencil) const { + json.beginListProperty("moduleRequests"); + DumpModuleRequestVectorItems(json, moduleRequests, stencil); + json.endList(); + + json.beginListProperty("requestedModules"); + DumpModuleEntryVectorItems(json, requestedModules, stencil); + json.endList(); + + json.beginListProperty("importEntries"); + DumpModuleEntryVectorItems(json, importEntries, stencil); + json.endList(); + + json.beginListProperty("localExportEntries"); + DumpModuleEntryVectorItems(json, localExportEntries, stencil); + json.endList(); + + json.beginListProperty("indirectExportEntries"); + DumpModuleEntryVectorItems(json, indirectExportEntries, stencil); + json.endList(); + + json.beginListProperty("starExportEntries"); + DumpModuleEntryVectorItems(json, starExportEntries, stencil); + json.endList(); + + json.beginListProperty("functionDecls"); + for (const auto& index : functionDecls) { + json.value("ScriptIndex(%zu)", size_t(index)); + } + json.endList(); + + json.boolProperty("isAsync", isAsync); +} + +void js::DumpImmutableScriptFlags(js::JSONPrinter& json, + ImmutableScriptFlags immutableFlags) { + for (uint32_t i = 1; i; i = i << 1) { + if (uint32_t(immutableFlags) & i) { + switch (ImmutableScriptFlagsEnum(i)) { + case ImmutableScriptFlagsEnum::IsForEval: + json.value("IsForEval"); + break; + case ImmutableScriptFlagsEnum::IsModule: + json.value("IsModule"); + break; + case ImmutableScriptFlagsEnum::IsFunction: + json.value("IsFunction"); + break; + case ImmutableScriptFlagsEnum::SelfHosted: + json.value("SelfHosted"); + break; + case ImmutableScriptFlagsEnum::ForceStrict: + json.value("ForceStrict"); + break; + case ImmutableScriptFlagsEnum::HasNonSyntacticScope: + json.value("HasNonSyntacticScope"); + break; + case ImmutableScriptFlagsEnum::NoScriptRval: + json.value("NoScriptRval"); + break; + case ImmutableScriptFlagsEnum::TreatAsRunOnce: + json.value("TreatAsRunOnce"); + break; + case ImmutableScriptFlagsEnum::Strict: + json.value("Strict"); + break; + case ImmutableScriptFlagsEnum::HasModuleGoal: + json.value("HasModuleGoal"); + break; + case ImmutableScriptFlagsEnum::HasInnerFunctions: + json.value("HasInnerFunctions"); + break; + case ImmutableScriptFlagsEnum::HasDirectEval: + json.value("HasDirectEval"); + break; + case ImmutableScriptFlagsEnum::BindingsAccessedDynamically: + json.value("BindingsAccessedDynamically"); + break; + case ImmutableScriptFlagsEnum::HasCallSiteObj: + json.value("HasCallSiteObj"); + break; + case ImmutableScriptFlagsEnum::IsAsync: + json.value("IsAsync"); + break; + case ImmutableScriptFlagsEnum::IsGenerator: + json.value("IsGenerator"); + break; + case ImmutableScriptFlagsEnum::FunHasExtensibleScope: + json.value("FunHasExtensibleScope"); + break; + case ImmutableScriptFlagsEnum::FunctionHasThisBinding: + json.value("FunctionHasThisBinding"); + break; + case ImmutableScriptFlagsEnum::NeedsHomeObject: + json.value("NeedsHomeObject"); + break; + case ImmutableScriptFlagsEnum::IsDerivedClassConstructor: + json.value("IsDerivedClassConstructor"); + break; + case ImmutableScriptFlagsEnum::IsSyntheticFunction: + json.value("IsSyntheticFunction"); + break; + case ImmutableScriptFlagsEnum::UseMemberInitializers: + json.value("UseMemberInitializers"); + break; + case ImmutableScriptFlagsEnum::HasRest: + json.value("HasRest"); + break; + case ImmutableScriptFlagsEnum::NeedsFunctionEnvironmentObjects: + json.value("NeedsFunctionEnvironmentObjects"); + break; + case ImmutableScriptFlagsEnum::FunctionHasExtraBodyVarScope: + json.value("FunctionHasExtraBodyVarScope"); + break; + case ImmutableScriptFlagsEnum::ShouldDeclareArguments: + json.value("ShouldDeclareArguments"); + break; + case ImmutableScriptFlagsEnum::NeedsArgsObj: + json.value("NeedsArgsObj"); + break; + case ImmutableScriptFlagsEnum::HasMappedArgsObj: + json.value("HasMappedArgsObj"); + break; + case ImmutableScriptFlagsEnum::IsInlinableLargeFunction: + json.value("IsInlinableLargeFunction"); + break; + case ImmutableScriptFlagsEnum::FunctionHasNewTargetBinding: + json.value("FunctionHasNewTargetBinding"); + break; + case ImmutableScriptFlagsEnum::UsesArgumentsIntrinsics: + json.value("UsesArgumentsIntrinsics"); + break; + default: + json.value("Unknown(%x)", i); + break; + } + } + } +} + +void js::DumpFunctionFlagsItems(js::JSONPrinter& json, + FunctionFlags functionFlags) { + switch (functionFlags.kind()) { + case FunctionFlags::FunctionKind::NormalFunction: + json.value("NORMAL_KIND"); + break; + case FunctionFlags::FunctionKind::AsmJS: + json.value("ASMJS_KIND"); + break; + case FunctionFlags::FunctionKind::Wasm: + json.value("WASM_KIND"); + break; + case FunctionFlags::FunctionKind::Arrow: + json.value("ARROW_KIND"); + break; + case FunctionFlags::FunctionKind::Method: + json.value("METHOD_KIND"); + break; + case FunctionFlags::FunctionKind::ClassConstructor: + json.value("CLASSCONSTRUCTOR_KIND"); + break; + case FunctionFlags::FunctionKind::Getter: + json.value("GETTER_KIND"); + break; + case FunctionFlags::FunctionKind::Setter: + json.value("SETTER_KIND"); + break; + default: + json.value("Unknown(%x)", uint8_t(functionFlags.kind())); + break; + } + + static_assert(FunctionFlags::FUNCTION_KIND_MASK == 0x0007, + "FunctionKind should use the lowest 3 bits"); + for (uint16_t i = 1 << 3; i; i = i << 1) { + if (functionFlags.toRaw() & i) { + switch (FunctionFlags::Flags(i)) { + case FunctionFlags::Flags::EXTENDED: + json.value("EXTENDED"); + break; + case FunctionFlags::Flags::SELF_HOSTED: + json.value("SELF_HOSTED"); + break; + case FunctionFlags::Flags::BASESCRIPT: + json.value("BASESCRIPT"); + break; + case FunctionFlags::Flags::SELFHOSTLAZY: + json.value("SELFHOSTLAZY"); + break; + case FunctionFlags::Flags::CONSTRUCTOR: + json.value("CONSTRUCTOR"); + break; + case FunctionFlags::Flags::LAMBDA: + json.value("LAMBDA"); + break; + case FunctionFlags::Flags::WASM_JIT_ENTRY: + json.value("WASM_JIT_ENTRY"); + break; + case FunctionFlags::Flags::HAS_INFERRED_NAME: + json.value("HAS_INFERRED_NAME"); + break; + case FunctionFlags::Flags::HAS_GUESSED_ATOM: + json.value("HAS_GUESSED_ATOM"); + break; + case FunctionFlags::Flags::RESOLVED_NAME: + json.value("RESOLVED_NAME"); + break; + case FunctionFlags::Flags::RESOLVED_LENGTH: + json.value("RESOLVED_LENGTH"); + break; + case FunctionFlags::Flags::GHOST_FUNCTION: + json.value("GHOST_FUNCTION"); + break; + default: + json.value("Unknown(%x)", i); + break; + } + } + } +} + +static void DumpScriptThing(js::JSONPrinter& json, + const CompilationStencil* stencil, + TaggedScriptThingIndex thing) { + switch (thing.tag()) { + case TaggedScriptThingIndex::Kind::ParserAtomIndex: + case TaggedScriptThingIndex::Kind::WellKnown: + json.beginObject(); + json.property("type", "Atom"); + DumpTaggedParserAtomIndex(json, thing.toAtom(), stencil); + json.endObject(); + break; + case TaggedScriptThingIndex::Kind::Null: + json.nullValue(); + break; + case TaggedScriptThingIndex::Kind::BigInt: + json.value("BigIntIndex(%zu)", size_t(thing.toBigInt())); + break; + case TaggedScriptThingIndex::Kind::ObjLiteral: + json.value("ObjLiteralIndex(%zu)", size_t(thing.toObjLiteral())); + break; + case TaggedScriptThingIndex::Kind::RegExp: + json.value("RegExpIndex(%zu)", size_t(thing.toRegExp())); + break; + case TaggedScriptThingIndex::Kind::Scope: + json.value("ScopeIndex(%zu)", size_t(thing.toScope())); + break; + case TaggedScriptThingIndex::Kind::Function: + json.value("ScriptIndex(%zu)", size_t(thing.toFunction())); + break; + case TaggedScriptThingIndex::Kind::EmptyGlobalScope: + json.value("EmptyGlobalScope"); + break; + } +} + +void ScriptStencil::dump() const { + js::Fprinter out(stderr); + js::JSONPrinter json(out); + dump(json, nullptr); +} + +void ScriptStencil::dump(js::JSONPrinter& json, + const CompilationStencil* stencil) const { + json.beginObject(); + dumpFields(json, stencil); + json.endObject(); +} + +void ScriptStencil::dumpFields(js::JSONPrinter& json, + const CompilationStencil* stencil) const { + json.formatProperty("gcThingsOffset", "CompilationGCThingIndex(%u)", + gcThingsOffset.index); + json.property("gcThingsLength", gcThingsLength); + + if (stencil) { + json.beginListProperty("gcThings"); + for (const auto& thing : gcthings(*stencil)) { + DumpScriptThing(json, stencil, thing); + } + json.endList(); + } + + json.beginListProperty("flags"); + if (flags_ & WasEmittedByEnclosingScriptFlag) { + json.value("WasEmittedByEnclosingScriptFlag"); + } + if (flags_ & AllowRelazifyFlag) { + json.value("AllowRelazifyFlag"); + } + if (flags_ & HasSharedDataFlag) { + json.value("HasSharedDataFlag"); + } + if (flags_ & HasLazyFunctionEnclosingScopeIndexFlag) { + json.value("HasLazyFunctionEnclosingScopeIndexFlag"); + } + json.endList(); + + if (isFunction()) { + json.beginObjectProperty("functionAtom"); + DumpTaggedParserAtomIndex(json, functionAtom, stencil); + json.endObject(); + + json.beginListProperty("functionFlags"); + DumpFunctionFlagsItems(json, functionFlags); + json.endList(); + + if (hasLazyFunctionEnclosingScopeIndex()) { + json.formatProperty("lazyFunctionEnclosingScopeIndex", "ScopeIndex(%zu)", + size_t(lazyFunctionEnclosingScopeIndex())); + } + + if (hasSelfHostedCanonicalName()) { + json.beginObjectProperty("selfHostCanonicalName"); + DumpTaggedParserAtomIndex(json, selfHostedCanonicalName(), stencil); + json.endObject(); + } + } +} + +void ScriptStencilExtra::dump() const { + js::Fprinter out(stderr); + js::JSONPrinter json(out); + dump(json); +} + +void ScriptStencilExtra::dump(js::JSONPrinter& json) const { + json.beginObject(); + dumpFields(json); + json.endObject(); +} + +void ScriptStencilExtra::dumpFields(js::JSONPrinter& json) const { + json.beginListProperty("immutableFlags"); + DumpImmutableScriptFlags(json, immutableFlags); + json.endList(); + + json.beginObjectProperty("extent"); + json.property("sourceStart", extent.sourceStart); + json.property("sourceEnd", extent.sourceEnd); + json.property("toStringStart", extent.toStringStart); + json.property("toStringEnd", extent.toStringEnd); + json.property("lineno", extent.lineno); + json.property("column", extent.column); + json.endObject(); + + json.property("memberInitializers", memberInitializers_); + + json.property("nargs", nargs); +} + +void SharedDataContainer::dump() const { + js::Fprinter out(stderr); + js::JSONPrinter json(out); + dump(json); +} + +void SharedDataContainer::dump(js::JSONPrinter& json) const { + json.beginObject(); + dumpFields(json); + json.endObject(); +} + +void SharedDataContainer::dumpFields(js::JSONPrinter& json) const { + if (isEmpty()) { + json.nullProperty("ScriptIndex(0)"); + return; + } + + if (isSingle()) { + json.formatProperty("ScriptIndex(0)", "u8[%zu]", + asSingle()->immutableDataLength()); + return; + } + + if (isVector()) { + auto& vec = *asVector(); + + char index[64]; + for (size_t i = 0; i < vec.length(); i++) { + SprintfLiteral(index, "ScriptIndex(%zu)", i); + if (vec[i]) { + json.formatProperty(index, "u8[%zu]", vec[i]->immutableDataLength()); + } else { + json.nullProperty(index); + } + } + return; + } + + if (isMap()) { + auto& map = *asMap(); + + char index[64]; + for (auto iter = map.iter(); !iter.done(); iter.next()) { + SprintfLiteral(index, "ScriptIndex(%u)", iter.get().key().index); + json.formatProperty(index, "u8[%zu]", + iter.get().value()->immutableDataLength()); + } + return; + } + + MOZ_ASSERT(isBorrow()); + asBorrow()->dumpFields(json); +} + +struct DumpOptionsFields { + js::JSONPrinter& json; + + void operator()(const char* name, JS::AsmJSOption value) { + const char* valueStr = nullptr; + switch (value) { + case JS::AsmJSOption::Enabled: + valueStr = "JS::AsmJSOption::Enabled"; + break; + case JS::AsmJSOption::DisabledByAsmJSPref: + valueStr = "JS::AsmJSOption::DisabledByAsmJSPref"; + break; + case JS::AsmJSOption::DisabledByLinker: + valueStr = "JS::AsmJSOption::DisabledByLinker"; + break; + case JS::AsmJSOption::DisabledByNoWasmCompiler: + valueStr = "JS::AsmJSOption::DisabledByNoWasmCompiler"; + break; + case JS::AsmJSOption::DisabledByDebugger: + valueStr = "JS::AsmJSOption::DisabledByDebugger"; + break; + } + json.property(name, valueStr); + } + + void operator()(const char* name, JS::DelazificationOption value) { + const char* valueStr = nullptr; + switch (value) { +# define SelectValueStr_(Strategy) \ + case JS::DelazificationOption::Strategy: \ + valueStr = "JS::DelazificationOption::" #Strategy; \ + break; + + FOREACH_DELAZIFICATION_STRATEGY(SelectValueStr_) +# undef SelectValueStr_ + } + json.property(name, valueStr); + } + + void operator()(const char* name, char16_t* value) {} + + void operator()(const char* name, bool value) { json.property(name, value); } + + void operator()(const char* name, uint32_t value) { + json.property(name, value); + } + + void operator()(const char* name, uint64_t value) { + json.property(name, value); + } + + void operator()(const char* name, const char* value) { + if (value) { + json.property(name, value); + return; + } + json.nullProperty(name); + } +}; + +static void DumpOptionsFields(js::JSONPrinter& json, + const JS::ReadOnlyCompileOptions& options) { + struct DumpOptionsFields printer { + json + }; + options.dumpWith(printer); +} + +static void DumpInputScopeFields(js::JSONPrinter& json, + const InputScope& scope) { + json.property("kind", ScopeKindString(scope.kind())); + + InputScope enclosing = scope.enclosing(); + if (enclosing.isNull()) { + json.nullProperty("enclosing"); + } else { + json.beginObjectProperty("enclosing"); + DumpInputScopeFields(json, enclosing); + json.endObject(); + } +} + +static void DumpInputScriptFields(js::JSONPrinter& json, + const InputScript& script) { + json.beginObjectProperty("extent"); + { + SourceExtent extent = script.extent(); + json.property("sourceStart", extent.sourceStart); + json.property("sourceEnd", extent.sourceEnd); + json.property("toStringStart", extent.toStringStart); + json.property("toStringEnd", extent.toStringEnd); + json.property("lineno", extent.lineno); + json.property("column", extent.column); + } + json.endObject(); + + json.beginListProperty("immutableFlags"); + DumpImmutableScriptFlags(json, script.immutableFlags()); + json.endList(); + + json.beginListProperty("functionFlags"); + DumpFunctionFlagsItems(json, script.functionFlags()); + json.endList(); + + json.property("hasPrivateScriptData", script.hasPrivateScriptData()); + + InputScope scope = script.enclosingScope(); + if (scope.isNull()) { + json.nullProperty("enclosingScope"); + } else { + json.beginObjectProperty("enclosingScope"); + DumpInputScopeFields(json, scope); + json.endObject(); + } + + if (script.useMemberInitializers()) { + json.property("memberInitializers", + script.getMemberInitializers().serialize()); + } +} + +void CompilationInput::dump() const { + js::Fprinter out(stderr); + js::JSONPrinter json(out); + dump(json); + out.put("\n"); +} + +void CompilationInput::dump(js::JSONPrinter& json) const { + json.beginObject(); + dumpFields(json); + json.endObject(); +} + +void CompilationInput::dumpFields(js::JSONPrinter& json) const { + const char* targetStr = nullptr; + switch (target) { + case CompilationTarget::Global: + targetStr = "CompilationTarget::Global"; + break; + case CompilationTarget::SelfHosting: + targetStr = "CompilationTarget::SelfHosting"; + break; + case CompilationTarget::StandaloneFunction: + targetStr = "CompilationTarget::StandaloneFunction"; + break; + case CompilationTarget::StandaloneFunctionInNonSyntacticScope: + targetStr = "CompilationTarget::StandaloneFunctionInNonSyntacticScope"; + break; + case CompilationTarget::Eval: + targetStr = "CompilationTarget::Eval"; + break; + case CompilationTarget::Module: + targetStr = "CompilationTarget::Module"; + break; + case CompilationTarget::Delazification: + targetStr = "CompilationTarget::Delazification"; + break; + } + json.property("target", targetStr); + + json.beginObjectProperty("options"); + DumpOptionsFields(json, options); + json.endObject(); + + if (lazy_.isNull()) { + json.nullProperty("lazy_"); + } else { + json.beginObjectProperty("lazy_"); + DumpInputScriptFields(json, lazy_); + json.endObject(); + } + + if (enclosingScope.isNull()) { + json.nullProperty("enclosingScope"); + } else { + json.beginObjectProperty("enclosingScope"); + DumpInputScopeFields(json, enclosingScope); + json.endObject(); + } + + // TODO: Support printing the atomCache and the source fields. +} + +void CompilationStencil::dump() const { + js::Fprinter out(stderr); + js::JSONPrinter json(out); + dump(json); + out.put("\n"); +} + +void CompilationStencil::dump(js::JSONPrinter& json) const { + json.beginObject(); + dumpFields(json); + json.endObject(); +} + +void CompilationStencil::dumpFields(js::JSONPrinter& json) const { + char index[64]; + + json.beginObjectProperty("scriptData"); + for (size_t i = 0; i < scriptData.size(); i++) { + SprintfLiteral(index, "ScriptIndex(%zu)", i); + json.beginObjectProperty(index); + scriptData[i].dumpFields(json, this); + json.endObject(); + } + json.endObject(); + + json.beginObjectProperty("scriptExtra"); + for (size_t i = 0; i < scriptExtra.size(); i++) { + SprintfLiteral(index, "ScriptIndex(%zu)", i); + json.beginObjectProperty(index); + scriptExtra[i].dumpFields(json); + json.endObject(); + } + json.endObject(); + + json.beginObjectProperty("scopeData"); + MOZ_ASSERT(scopeData.size() == scopeNames.size()); + for (size_t i = 0; i < scopeData.size(); i++) { + SprintfLiteral(index, "ScopeIndex(%zu)", i); + json.beginObjectProperty(index); + scopeData[i].dumpFields(json, scopeNames[i], this); + json.endObject(); + } + json.endObject(); + + json.beginObjectProperty("sharedData"); + sharedData.dumpFields(json); + json.endObject(); + + json.beginObjectProperty("regExpData"); + for (size_t i = 0; i < regExpData.size(); i++) { + SprintfLiteral(index, "RegExpIndex(%zu)", i); + json.beginObjectProperty(index); + regExpData[i].dumpFields(json, this); + json.endObject(); + } + json.endObject(); + + json.beginObjectProperty("bigIntData"); + for (size_t i = 0; i < bigIntData.size(); i++) { + SprintfLiteral(index, "BigIntIndex(%zu)", i); + GenericPrinter& out = json.beginStringProperty(index); + bigIntData[i].dumpCharsNoQuote(out); + json.endStringProperty(); + } + json.endObject(); + + json.beginObjectProperty("objLiteralData"); + for (size_t i = 0; i < objLiteralData.size(); i++) { + SprintfLiteral(index, "ObjLiteralIndex(%zu)", i); + json.beginObjectProperty(index); + objLiteralData[i].dumpFields(json, this); + json.endObject(); + } + json.endObject(); + + if (moduleMetadata) { + json.beginObjectProperty("moduleMetadata"); + moduleMetadata->dumpFields(json, this); + json.endObject(); + } + + json.beginObjectProperty("asmJS"); + if (asmJS) { + for (auto iter = asmJS->moduleMap.iter(); !iter.done(); iter.next()) { + SprintfLiteral(index, "ScriptIndex(%u)", iter.get().key().index); + json.formatProperty(index, "asm.js"); + } + } + json.endObject(); +} + +void CompilationStencil::dumpAtom(TaggedParserAtomIndex index) const { + js::Fprinter out(stderr); + js::JSONPrinter json(out); + json.beginObject(); + DumpTaggedParserAtomIndex(json, index, this); + json.endObject(); +} + +void ExtensibleCompilationStencil::dump() { + frontend::BorrowingCompilationStencil borrowingStencil(*this); + borrowingStencil.dump(); +} + +void ExtensibleCompilationStencil::dump(js::JSONPrinter& json) { + frontend::BorrowingCompilationStencil borrowingStencil(*this); + borrowingStencil.dump(json); +} + +void ExtensibleCompilationStencil::dumpFields(js::JSONPrinter& json) { + frontend::BorrowingCompilationStencil borrowingStencil(*this); + borrowingStencil.dumpFields(json); +} + +void ExtensibleCompilationStencil::dumpAtom(TaggedParserAtomIndex index) { + frontend::BorrowingCompilationStencil borrowingStencil(*this); + borrowingStencil.dumpAtom(index); +} + +#endif // defined(DEBUG) || defined(JS_JITSPEW) + +JSString* CompilationAtomCache::getExistingStringAt( + ParserAtomIndex index) const { + MOZ_RELEASE_ASSERT(atoms_.length() >= index); + return atoms_[index]; +} + +JSString* CompilationAtomCache::getExistingStringAt( + JSContext* cx, TaggedParserAtomIndex taggedIndex) const { + if (taggedIndex.isParserAtomIndex()) { + auto index = taggedIndex.toParserAtomIndex(); + return getExistingStringAt(index); + } + + if (taggedIndex.isWellKnownAtomId()) { + auto index = taggedIndex.toWellKnownAtomId(); + return GetWellKnownAtom(cx, index); + } + + if (taggedIndex.isLength1StaticParserString()) { + auto index = taggedIndex.toLength1StaticParserString(); + return cx->staticStrings().getUnit(char16_t(index)); + } + + if (taggedIndex.isLength2StaticParserString()) { + auto index = taggedIndex.toLength2StaticParserString(); + return cx->staticStrings().getLength2FromIndex(size_t(index)); + } + + MOZ_ASSERT(taggedIndex.isLength3StaticParserString()); + auto index = taggedIndex.toLength3StaticParserString(); + return cx->staticStrings().getUint(uint32_t(index)); +} + +JSString* CompilationAtomCache::getStringAt(ParserAtomIndex index) const { + if (size_t(index) >= atoms_.length()) { + return nullptr; + } + return atoms_[index]; +} + +JSAtom* CompilationAtomCache::getExistingAtomAt(ParserAtomIndex index) const { + return &getExistingStringAt(index)->asAtom(); +} + +JSAtom* CompilationAtomCache::getExistingAtomAt( + JSContext* cx, TaggedParserAtomIndex taggedIndex) const { + return &getExistingStringAt(cx, taggedIndex)->asAtom(); +} + +JSAtom* CompilationAtomCache::getAtomAt(ParserAtomIndex index) const { + if (size_t(index) >= atoms_.length()) { + return nullptr; + } + if (!atoms_[index]) { + return nullptr; + } + return &atoms_[index]->asAtom(); +} + +bool CompilationAtomCache::hasAtomAt(ParserAtomIndex index) const { + if (size_t(index) >= atoms_.length()) { + return false; + } + return !!atoms_[index]; +} + +bool CompilationAtomCache::setAtomAt(FrontendContext* fc, ParserAtomIndex index, + JSString* atom) { + if (size_t(index) < atoms_.length()) { + atoms_[index] = atom; + return true; + } + + if (!atoms_.resize(size_t(index) + 1)) { + ReportOutOfMemory(fc); + return false; + } + + atoms_[index] = atom; + return true; +} + +bool CompilationAtomCache::allocate(FrontendContext* fc, size_t length) { + MOZ_ASSERT(length >= atoms_.length()); + if (length == atoms_.length()) { + return true; + } + + if (!atoms_.resize(length)) { + ReportOutOfMemory(fc); + return false; + } + + return true; +} + +void CompilationAtomCache::stealBuffer(AtomCacheVector& atoms) { + atoms_ = std::move(atoms); + // Destroy elements, without unreserving. + atoms_.clear(); +} + +void CompilationAtomCache::releaseBuffer(AtomCacheVector& atoms) { + atoms = std::move(atoms_); +} + +bool CompilationState::allocateGCThingsUninitialized( + FrontendContext* fc, ScriptIndex scriptIndex, size_t length, + TaggedScriptThingIndex** cursor) { + MOZ_ASSERT(gcThingData.length() <= UINT32_MAX); + + auto gcThingsOffset = CompilationGCThingIndex(gcThingData.length()); + + if (length > INDEX_LIMIT) { + ReportAllocationOverflow(fc); + return false; + } + uint32_t gcThingsLength = length; + + if (!gcThingData.growByUninitialized(length)) { + js::ReportOutOfMemory(fc); + return false; + } + + if (gcThingData.length() > UINT32_MAX) { + ReportAllocationOverflow(fc); + return false; + } + + ScriptStencil& script = scriptData[scriptIndex]; + script.gcThingsOffset = gcThingsOffset; + script.gcThingsLength = gcThingsLength; + + *cursor = gcThingData.begin() + gcThingsOffset; + return true; +} + +bool CompilationState::appendScriptStencilAndData(FrontendContext* fc) { + if (!scriptData.emplaceBack()) { + js::ReportOutOfMemory(fc); + return false; + } + + if (isInitialStencil()) { + if (!scriptExtra.emplaceBack()) { + scriptData.popBack(); + MOZ_ASSERT(scriptData.length() == scriptExtra.length()); + + js::ReportOutOfMemory(fc); + return false; + } + } + + return true; +} + +bool CompilationState::appendGCThings( + FrontendContext* fc, ScriptIndex scriptIndex, + mozilla::Span things) { + MOZ_ASSERT(gcThingData.length() <= UINT32_MAX); + + auto gcThingsOffset = CompilationGCThingIndex(gcThingData.length()); + + if (things.size() > INDEX_LIMIT) { + ReportAllocationOverflow(fc); + return false; + } + uint32_t gcThingsLength = uint32_t(things.size()); + + if (!gcThingData.append(things.data(), things.size())) { + js::ReportOutOfMemory(fc); + return false; + } + + if (gcThingData.length() > UINT32_MAX) { + ReportAllocationOverflow(fc); + return false; + } + + ScriptStencil& script = scriptData[scriptIndex]; + script.gcThingsOffset = gcThingsOffset; + script.gcThingsLength = gcThingsLength; + return true; +} + +CompilationState::CompilationStatePosition CompilationState::getPosition() { + return CompilationStatePosition{scriptData.length(), + asmJS ? asmJS->moduleMap.count() : 0}; +} + +void CompilationState::rewind( + const CompilationState::CompilationStatePosition& pos) { + if (asmJS && asmJS->moduleMap.count() != pos.asmJSCount) { + for (size_t i = pos.scriptDataLength; i < scriptData.length(); i++) { + asmJS->moduleMap.remove(ScriptIndex(i)); + } + MOZ_ASSERT(asmJS->moduleMap.count() == pos.asmJSCount); + } + // scriptExtra is empty for delazification. + if (scriptExtra.length()) { + MOZ_ASSERT(scriptExtra.length() == scriptData.length()); + scriptExtra.shrinkTo(pos.scriptDataLength); + } + scriptData.shrinkTo(pos.scriptDataLength); +} + +void CompilationState::markGhost( + const CompilationState::CompilationStatePosition& pos) { + for (size_t i = pos.scriptDataLength; i < scriptData.length(); i++) { + scriptData[i].setIsGhost(); + } +} + +bool CompilationStencilMerger::buildFunctionKeyToIndex(FrontendContext* fc) { + if (!functionKeyToInitialScriptIndex_.reserve(initial_->scriptExtra.length() - + 1)) { + ReportOutOfMemory(fc); + return false; + } + + for (size_t i = 1; i < initial_->scriptExtra.length(); i++) { + const auto& extra = initial_->scriptExtra[i]; + auto key = extra.extent.toFunctionKey(); + + // There can be multiple ScriptStencilExtra with same extent if + // the function is parsed multiple times because of rewind for + // arrow function, and in that case the last one's index should be used. + // Overwrite with the last one. + // + // Already reserved above, but OOMTest can hit failure mode in + // HashTable::add. + if (!functionKeyToInitialScriptIndex_.put(key, ScriptIndex(i))) { + ReportOutOfMemory(fc); + return false; + } + } + + return true; +} + +ScriptIndex CompilationStencilMerger::getInitialScriptIndexFor( + const CompilationStencil& delazification) const { + auto p = functionKeyToInitialScriptIndex_.lookup(delazification.functionKey); + MOZ_ASSERT(p); + return p->value(); +} + +bool CompilationStencilMerger::buildAtomIndexMap( + FrontendContext* fc, const CompilationStencil& delazification, + AtomIndexMap& atomIndexMap) { + uint32_t atomCount = delazification.parserAtomData.size(); + if (!atomIndexMap.reserve(atomCount)) { + ReportOutOfMemory(fc); + return false; + } + for (const auto& atom : delazification.parserAtomData) { + auto mappedIndex = initial_->parserAtoms.internExternalParserAtom(fc, atom); + if (!mappedIndex) { + return false; + } + atomIndexMap.infallibleAppend(mappedIndex); + } + return true; +} + +bool CompilationStencilMerger::setInitial( + FrontendContext* fc, UniquePtr&& initial) { + MOZ_ASSERT(!initial_); + + initial_ = std::move(initial); + + return buildFunctionKeyToIndex(fc); +} + +template +static void MergeScriptStencil(ScriptStencil& dest, const ScriptStencil& src, + GCThingIndexMapFunc mapGCThingIndex, + AtomIndexMapFunc mapAtomIndex, + ScopeIndexMapFunc mapScopeIndex, + bool isTopLevel) { + // If this function was lazy, all inner functions should have been lazy. + MOZ_ASSERT(!dest.hasSharedData()); + + // If the inner lazy function is skipped, gcThingsLength is empty. + if (src.gcThingsLength) { + dest.gcThingsOffset = mapGCThingIndex(src.gcThingsOffset); + dest.gcThingsLength = src.gcThingsLength; + } + + if (src.functionAtom) { + dest.functionAtom = mapAtomIndex(src.functionAtom); + } + + if (!dest.hasLazyFunctionEnclosingScopeIndex() && + src.hasLazyFunctionEnclosingScopeIndex()) { + // Both enclosing function and this function were lazy, and + // now enclosing function is non-lazy and this function is still lazy. + dest.setLazyFunctionEnclosingScopeIndex( + mapScopeIndex(src.lazyFunctionEnclosingScopeIndex())); + } else if (dest.hasLazyFunctionEnclosingScopeIndex() && + !src.hasLazyFunctionEnclosingScopeIndex()) { + // The enclosing function was non-lazy and this function was lazy, and + // now this function is non-lazy. + dest.resetHasLazyFunctionEnclosingScopeIndexAfterStencilMerge(); + } else { + // The enclosing function is still lazy. + MOZ_ASSERT(!dest.hasLazyFunctionEnclosingScopeIndex()); + MOZ_ASSERT(!src.hasLazyFunctionEnclosingScopeIndex()); + } + +#ifdef DEBUG + uint16_t BASESCRIPT = uint16_t(FunctionFlags::Flags::BASESCRIPT); + uint16_t HAS_INFERRED_NAME = + uint16_t(FunctionFlags::Flags::HAS_INFERRED_NAME); + uint16_t HAS_GUESSED_ATOM = uint16_t(FunctionFlags::Flags::HAS_GUESSED_ATOM); + uint16_t acceptableDifferenceForLazy = HAS_INFERRED_NAME | HAS_GUESSED_ATOM; + uint16_t acceptableDifferenceForNonLazy = + BASESCRIPT | HAS_INFERRED_NAME | HAS_GUESSED_ATOM; + + MOZ_ASSERT_IF( + isTopLevel, + (dest.functionFlags.toRaw() | acceptableDifferenceForNonLazy) == + (src.functionFlags.toRaw() | acceptableDifferenceForNonLazy)); + + // NOTE: Currently we don't delazify inner functions. + MOZ_ASSERT_IF(!isTopLevel, + (dest.functionFlags.toRaw() | acceptableDifferenceForLazy) == + (src.functionFlags.toRaw() | acceptableDifferenceForLazy)); +#endif // DEBUG + dest.functionFlags = src.functionFlags; + + // Other flags. + + if (src.wasEmittedByEnclosingScript()) { + // NOTE: the top-level function of the delazification have + // src.wasEmittedByEnclosingScript() == false, and that shouldn't + // be copied. + dest.setWasEmittedByEnclosingScript(); + } + + if (src.allowRelazify()) { + dest.setAllowRelazify(); + } + + if (src.hasSharedData()) { + dest.setHasSharedData(); + } +} + +bool CompilationStencilMerger::addDelazification( + FrontendContext* fc, const CompilationStencil& delazification) { + MOZ_ASSERT(initial_); + + auto delazifiedFunctionIndex = getInitialScriptIndexFor(delazification); + auto& destFun = initial_->scriptData[delazifiedFunctionIndex]; + + if (destFun.hasSharedData()) { + // If the function was already non-lazy, it means the following happened: + // A. delazified twice within single incremental encoding + // 1. this function is lazily parsed + // 2. incremental encoding is started + // 3. this function is delazified, encoded, and merged + // 4. this function is relazified + // 5. this function is delazified, encoded, and merged + // + // B. delazified twice across decode + // 1. this function is lazily parsed + // 2. incremental encoding is started + // 3. this function is delazified, encoded, and merged + // 4. incremental encoding is finished + // 5. decoded + // 6. incremental encoding is started + // here, this function is non-lazy + // 7. this function is relazified + // 8. this function is delazified, encoded, and merged + // + // A can happen with public API. + // + // B cannot happen with public API, but can happen if incremental + // encoding at step B.6 is explicitly started by internal function. + // See Evaluate and StartIncrementalEncoding in js/src/shell/js.cpp. + return true; + } + + // If any failure happens, the initial stencil is left in the broken state. + // Immediately discard it. + auto failureCase = mozilla::MakeScopeExit([&] { initial_.reset(); }); + + mozilla::Maybe functionEnclosingScope; + if (destFun.hasLazyFunctionEnclosingScopeIndex()) { + // lazyFunctionEnclosingScopeIndex_ can be Nothing if this is + // top-level function. + functionEnclosingScope = + mozilla::Some(destFun.lazyFunctionEnclosingScopeIndex()); + } + + // A map from ParserAtomIndex in delazification to TaggedParserAtomIndex + // in initial_. + AtomIndexMap atomIndexMap; + if (!buildAtomIndexMap(fc, delazification, atomIndexMap)) { + return false; + } + auto mapAtomIndex = [&](TaggedParserAtomIndex index) { + if (index.isParserAtomIndex()) { + return atomIndexMap[index.toParserAtomIndex()]; + } + + return index; + }; + + size_t gcThingOffset = initial_->gcThingData.length(); + size_t regExpOffset = initial_->regExpData.length(); + size_t bigIntOffset = initial_->bigIntData.length(); + size_t objLiteralOffset = initial_->objLiteralData.length(); + size_t scopeOffset = initial_->scopeData.length(); + + // Map delazification's ScriptIndex to initial's ScriptIndex. + // + // The lazy function's gcthings list stores inner function's ScriptIndex. + // The n-th gcthing holds the ScriptIndex of the (n+1)-th script in + // delazification. + // + // NOTE: Currently we don't delazify inner functions. + auto lazyFunctionGCThingsOffset = destFun.gcThingsOffset; + auto mapScriptIndex = [&](ScriptIndex index) { + if (index == CompilationStencil::TopLevelIndex) { + return delazifiedFunctionIndex; + } + + return initial_->gcThingData[lazyFunctionGCThingsOffset + index.index - 1] + .toFunction(); + }; + + // Map other delazification's indices into initial's indices. + auto mapGCThingIndex = [&](CompilationGCThingIndex offset) { + return CompilationGCThingIndex(gcThingOffset + offset.index); + }; + auto mapRegExpIndex = [&](RegExpIndex index) { + return RegExpIndex(regExpOffset + index.index); + }; + auto mapBigIntIndex = [&](BigIntIndex index) { + return BigIntIndex(bigIntOffset + index.index); + }; + auto mapObjLiteralIndex = [&](ObjLiteralIndex index) { + return ObjLiteralIndex(objLiteralOffset + index.index); + }; + auto mapScopeIndex = [&](ScopeIndex index) { + return ScopeIndex(scopeOffset + index.index); + }; + + // Append gcThingData, with mapping TaggedScriptThingIndex. + if (!initial_->gcThingData.append(delazification.gcThingData.data(), + delazification.gcThingData.size())) { + js::ReportOutOfMemory(fc); + return false; + } + for (size_t i = gcThingOffset; i < initial_->gcThingData.length(); i++) { + auto& index = initial_->gcThingData[i]; + if (index.isNull()) { + // Nothing to do. + } else if (index.isAtom()) { + index = TaggedScriptThingIndex(mapAtomIndex(index.toAtom())); + } else if (index.isBigInt()) { + index = TaggedScriptThingIndex(mapBigIntIndex(index.toBigInt())); + } else if (index.isObjLiteral()) { + index = TaggedScriptThingIndex(mapObjLiteralIndex(index.toObjLiteral())); + } else if (index.isRegExp()) { + index = TaggedScriptThingIndex(mapRegExpIndex(index.toRegExp())); + } else if (index.isScope()) { + index = TaggedScriptThingIndex(mapScopeIndex(index.toScope())); + } else if (index.isFunction()) { + index = TaggedScriptThingIndex(mapScriptIndex(index.toFunction())); + } else { + MOZ_ASSERT(index.isEmptyGlobalScope()); + // Nothing to do + } + } + + // Append regExpData, with mapping RegExpStencil.atom_. + if (!initial_->regExpData.append(delazification.regExpData.data(), + delazification.regExpData.size())) { + js::ReportOutOfMemory(fc); + return false; + } + for (size_t i = regExpOffset; i < initial_->regExpData.length(); i++) { + auto& data = initial_->regExpData[i]; + data.atom_ = mapAtomIndex(data.atom_); + } + + // Append bigIntData, with copying BigIntStencil.source_. + if (!initial_->bigIntData.reserve(bigIntOffset + + delazification.bigIntData.size())) { + js::ReportOutOfMemory(fc); + return false; + } + for (const auto& data : delazification.bigIntData) { + initial_->bigIntData.infallibleEmplaceBack(); + if (!initial_->bigIntData.back().init(fc, initial_->alloc, data.source())) { + return false; + } + } + + // Append objLiteralData, with copying ObjLiteralStencil.code_, and mapping + // TaggedParserAtomIndex in it. + if (!initial_->objLiteralData.reserve(objLiteralOffset + + delazification.objLiteralData.size())) { + js::ReportOutOfMemory(fc); + return false; + } + for (const auto& data : delazification.objLiteralData) { + size_t length = data.code().size(); + auto* code = initial_->alloc.newArrayUninitialized(length); + if (!code) { + js::ReportOutOfMemory(fc); + return false; + } + memcpy(code, data.code().data(), length); + + ObjLiteralModifier modifier(mozilla::Span(code, length)); + modifier.mapAtom(mapAtomIndex); + + initial_->objLiteralData.infallibleEmplaceBack( + code, length, data.kind(), data.flags(), data.propertyCount()); + } + + // Append scopeData, with mapping indices in ScopeStencil fields. + // And append scopeNames, with copying the entire data, and mapping + // trailingNames. + if (!initial_->scopeData.reserve(scopeOffset + + delazification.scopeData.size())) { + js::ReportOutOfMemory(fc); + return false; + } + if (!initial_->scopeNames.reserve(scopeOffset + + delazification.scopeNames.size())) { + js::ReportOutOfMemory(fc); + return false; + } + for (size_t i = 0; i < delazification.scopeData.size(); i++) { + const auto& srcData = delazification.scopeData[i]; + const auto* srcNames = delazification.scopeNames[i]; + + mozilla::Maybe functionIndex = mozilla::Nothing(); + if (srcData.isFunction()) { + // Inner functions should be in the same order as initial, beginning from + // the delazification's index. + functionIndex = mozilla::Some(mapScriptIndex(srcData.functionIndex())); + } + + BaseParserScopeData* destNames = nullptr; + if (srcNames) { + destNames = CopyScopeData(fc, initial_->alloc, srcData.kind(), srcNames); + if (!destNames) { + return false; + } + auto trailingNames = + GetParserScopeDataTrailingNames(srcData.kind(), destNames); + for (auto& name : trailingNames) { + if (name.name()) { + name.updateNameAfterStencilMerge(mapAtomIndex(name.name())); + } + } + } + + initial_->scopeData.infallibleEmplaceBack( + srcData.kind(), + srcData.hasEnclosing() + ? mozilla::Some(mapScopeIndex(srcData.enclosing())) + : functionEnclosingScope, + srcData.firstFrameSlot(), + srcData.hasEnvironmentShape() + ? mozilla::Some(srcData.numEnvironmentSlots()) + : mozilla::Nothing(), + functionIndex, srcData.isArrow()); + + initial_->scopeNames.infallibleEmplaceBack(destNames); + } + + // Add delazified function's shared data. + // + // NOTE: Currently we don't delazify inner functions. + if (!initial_->sharedData.addExtraWithoutShare( + fc, delazifiedFunctionIndex, + delazification.sharedData.get(CompilationStencil::TopLevelIndex))) { + return false; + } + + // Update scriptData, with mapping indices in ScriptStencil fields. + for (uint32_t i = 0; i < delazification.scriptData.size(); i++) { + auto destIndex = mapScriptIndex(ScriptIndex(i)); + MergeScriptStencil(initial_->scriptData[destIndex], + delazification.scriptData[i], mapGCThingIndex, + mapAtomIndex, mapScopeIndex, + i == CompilationStencil::TopLevelIndex); + } + + // WARNING: moduleMetadata and asmJS fields are known at script/module + // top-level parsing, any mutation made in this function should be reflected + // to ExtensibleCompilationStencil::steal and CompilationStencil::clone. + + // Function shouldn't be a module. + MOZ_ASSERT(!delazification.moduleMetadata); + + // asm.js shouldn't appear inside delazification, given asm.js forces + // full-parse. + MOZ_ASSERT(!delazification.asmJS); + + failureCase.release(); + return true; +} + +void JS::StencilAddRef(JS::Stencil* stencil) { stencil->refCount++; } +void JS::StencilRelease(JS::Stencil* stencil) { + MOZ_RELEASE_ASSERT(stencil->refCount > 0); + if (--stencil->refCount == 0) { + js_delete(stencil); + } +} + +template +static already_AddRefed CompileGlobalScriptToStencilImpl( + JSContext* cx, const JS::ReadOnlyCompileOptions& options, + JS::SourceText& srcBuf) { + ScopeKind scopeKind = + options.nonSyntacticScope ? ScopeKind::NonSyntactic : ScopeKind::Global; + + AutoReportFrontendContext fc(cx); + NoScopeBindingCache scopeCache; + Rooted input(cx, CompilationInput(options)); + RefPtr stencil = js::frontend::CompileGlobalScriptToStencil( + cx, &fc, cx->tempLifoAlloc(), input.get(), &scopeCache, srcBuf, + scopeKind); + if (!stencil) { + return nullptr; + } + + // Convert the UniquePtr to a RefPtr and increment the count (to 1). + return stencil.forget(); +} + +already_AddRefed JS::CompileGlobalScriptToStencil( + JSContext* cx, const JS::ReadOnlyCompileOptions& options, + JS::SourceText& srcBuf) { + return CompileGlobalScriptToStencilImpl(cx, options, srcBuf); +} + +already_AddRefed JS::CompileGlobalScriptToStencil( + JSContext* cx, const JS::ReadOnlyCompileOptions& options, + JS::SourceText& srcBuf) { + return CompileGlobalScriptToStencilImpl(cx, options, srcBuf); +} + +template +static already_AddRefed CompileModuleScriptToStencilImpl( + JSContext* cx, const JS::ReadOnlyCompileOptions& optionsInput, + JS::SourceText& srcBuf) { + JS::CompileOptions options(cx, optionsInput); + options.setModule(); + + AutoReportFrontendContext fc(cx); + NoScopeBindingCache scopeCache; + Rooted input(cx, CompilationInput(options)); + RefPtr stencil = js::frontend::ParseModuleToStencil( + cx, &fc, cx->tempLifoAlloc(), input.get(), &scopeCache, srcBuf); + if (!stencil) { + return nullptr; + } + + // Convert the UniquePtr to a RefPtr and increment the count (to 1). + return stencil.forget(); +} + +already_AddRefed JS::CompileModuleScriptToStencil( + JSContext* cx, const JS::ReadOnlyCompileOptions& options, + JS::SourceText& srcBuf) { + return CompileModuleScriptToStencilImpl(cx, options, srcBuf); +} + +already_AddRefed JS::CompileModuleScriptToStencil( + JSContext* cx, const JS::ReadOnlyCompileOptions& options, + JS::SourceText& srcBuf) { + return CompileModuleScriptToStencilImpl(cx, options, srcBuf); +} + +JS_PUBLIC_API JSScript* JS::InstantiateGlobalStencil( + JSContext* cx, const JS::InstantiateOptions& options, JS::Stencil* stencil, + JS::InstantiationStorage* storage) { + MOZ_ASSERT_IF(storage, storage->isValid()); + + CompileOptions compileOptions(cx); + options.copyTo(compileOptions); + Rooted input(cx, CompilationInput(compileOptions)); + Rooted gcOutput(cx); + CompilationGCOutput& output = storage ? *storage->gcOutput_ : gcOutput.get(); + + if (!InstantiateStencils(cx, input.get(), *stencil, output)) { + return nullptr; + } + return output.script; +} + +JS_PUBLIC_API bool JS::StencilIsBorrowed(Stencil* stencil) { + return stencil->storageType == CompilationStencil::StorageType::Borrowed; +} + +JS_PUBLIC_API JSObject* JS::InstantiateModuleStencil( + JSContext* cx, const JS::InstantiateOptions& options, JS::Stencil* stencil, + JS::InstantiationStorage* storage) { + MOZ_ASSERT_IF(storage, storage->isValid()); + + CompileOptions compileOptions(cx); + options.copyTo(compileOptions); + compileOptions.setModule(); + Rooted input(cx, CompilationInput(compileOptions)); + Rooted gcOutput(cx); + CompilationGCOutput& output = storage ? *storage->gcOutput_ : gcOutput.get(); + + if (!InstantiateStencils(cx, input.get(), *stencil, output)) { + return nullptr; + } + return output.module; +} + +JS::TranscodeResult JS::EncodeStencil(JSContext* cx, JS::Stencil* stencil, + TranscodeBuffer& buffer) { + AutoReportFrontendContext fc(cx); + XDRStencilEncoder encoder(&fc, buffer); + XDRResult res = encoder.codeStencil(*stencil); + if (res.isErr()) { + return res.unwrapErr(); + } + return TranscodeResult::Ok; +} + +JS::TranscodeResult JS::DecodeStencil(JSContext* cx, + const JS::DecodeOptions& options, + const JS::TranscodeRange& range, + JS::Stencil** stencilOut) { + AutoReportFrontendContext fc(cx); + return JS::DecodeStencil(&fc, options, range, stencilOut); +} + +JS::TranscodeResult JS::DecodeStencil(JS::FrontendContext* fc, + const JS::DecodeOptions& options, + const JS::TranscodeRange& range, + JS::Stencil** stencilOut) { + RefPtr source = fc->getAllocator()->new_(); + if (!source) { + return TranscodeResult::Throw; + } + RefPtr stencil( + fc->getAllocator()->new_(source)); + if (!stencil) { + return TranscodeResult::Throw; + } + XDRStencilDecoder decoder(fc, range); + XDRResult res = decoder.codeStencil(options, *stencil); + if (res.isErr()) { + return res.unwrapErr(); + } + *stencilOut = stencil.forget().take(); + return TranscodeResult::Ok; +} + +JS_PUBLIC_API size_t JS::SizeOfStencil(Stencil* stencil, + mozilla::MallocSizeOf mallocSizeOf) { + return stencil->sizeOfIncludingThis(mallocSizeOf); +} + +JS::InstantiationStorage::~InstantiationStorage() { + if (gcOutput_) { + js_delete(gcOutput_); + gcOutput_ = nullptr; + } +} diff --git a/js/src/frontend/Stencil.h b/js/src/frontend/Stencil.h new file mode 100644 index 0000000000..a5aa84e787 --- /dev/null +++ b/js/src/frontend/Stencil.h @@ -0,0 +1,1141 @@ +/* -*- 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/. */ + +#ifndef frontend_Stencil_h +#define frontend_Stencil_h + +#include "mozilla/Assertions.h" // MOZ_ASSERT +#include "mozilla/Maybe.h" // mozilla::{Maybe, Nothing} +#include "mozilla/MemoryReporting.h" // mozilla::MallocSizeOf +#include "mozilla/Span.h" // mozilla::Span + +#include // size_t +#include // char16_t, uint8_t, uint16_t, uint32_t + +#include "frontend/AbstractScopePtr.h" // AbstractScopePtr, ScopeIndex +#include "frontend/ObjLiteral.h" // ObjLiteralStencil +#include "frontend/ParserAtom.h" // TaggedParserAtomIndex +#include "frontend/ScriptIndex.h" // ScriptIndex +#include "frontend/TypedIndex.h" // TypedIndex +#include "js/AllocPolicy.h" // SystemAllocPolicy +#include "js/RefCounted.h" // AtomicRefCounted +#include "js/RegExpFlags.h" // JS::RegExpFlags +#include "js/RootingAPI.h" // Handle +#include "js/TypeDecls.h" // JSContext +#include "js/UniquePtr.h" // js::UniquePtr +#include "js/Utility.h" // UniqueTwoByteChars +#include "js/Vector.h" // js::Vector +#include "vm/FunctionFlags.h" // FunctionFlags +#include "vm/Scope.h" // Scope, BaseScopeData, FunctionScope, LexicalScope, VarScope, GlobalScope, EvalScope, ModuleScope +#include "vm/ScopeKind.h" // ScopeKind +#include "vm/SharedStencil.h" // ImmutableScriptFlags, GCThingIndex, js::SharedImmutableScriptData, MemberInitializers, SourceExtent + +namespace js { + +class LifoAlloc; +class JSONPrinter; +class RegExpObject; + +namespace frontend { + +struct CompilationInput; +struct CompilationStencil; +struct CompilationAtomCache; +struct CompilationGCOutput; +struct CompilationStencilMerger; +class RegExpStencil; +class BigIntStencil; +class StencilXDR; + +using BaseParserScopeData = AbstractBaseScopeData; +using ParserBindingName = AbstractBindingName; + +template +using ParserScopeSlotInfo = typename Scope::SlotInfo; +using ParserGlobalScopeSlotInfo = ParserScopeSlotInfo; +using ParserEvalScopeSlotInfo = ParserScopeSlotInfo; +using ParserLexicalScopeSlotInfo = ParserScopeSlotInfo; +using ParserClassBodyScopeSlotInfo = ParserScopeSlotInfo; +using ParserFunctionScopeSlotInfo = ParserScopeSlotInfo; +using ParserModuleScopeSlotInfo = ParserScopeSlotInfo; +using ParserVarScopeSlotInfo = ParserScopeSlotInfo; + +using ParserBindingIter = AbstractBindingIter; +using ParserPositionalFormalParameterIter = + AbstractPositionalFormalParameterIter; + +// [SMDOC] Script Stencil (Frontend Representation) +// +// Stencils are the set of data structures capturing the result of parsing and +// bytecode emission. The Stencil format is a precursor format that is then used +// to allocate the corresponding scripts on the GC heap that will be used for +// execution. By decoupling from the GC and other runtime systems, robust +// caching and speculation systems can be built that are more thread-agnostic +// and flexible. +// +// See: https://bugzil.la/stencil +// +// There are numerous data structures that make up the Stencil format. The +// structures are designed for fast serialization to and from disk by preferring +// indices over pointers and vectors instead of graphs to allow bulk operations. +// +// +// ParserAtom +// ---------- +// Our parser relies on atomized strings as part of its normal operations and so +// a `ParserAtom` type exists that mirrors the `JSAtom` type but does not +// involve the garbage collector. This is possible because the lifetime of these +// ParserAtoms is the lifetime of the Stencil that makes use of them and we do +// not need finer grained collection. +// +// +// ScriptStencil +// ------------- +// The key structures generated by parsing are instances of `ScriptStencil`. +// There is a `ScriptStencil` for the top-level script and for each inner +// function. It contains information needed to create the `JSFunction` (if it is +// a function) and the `BaseScript` (if not asm.js) and may or may not have +// bytecode. Each `ScriptStencil` may also reference the following Stencil types +// (similar to the `BaseScript::gcthings()` list): +// +// * ParserAtom +// * ScopeStencil +// * RegExpStencil +// * BigIntStencil +// * ObjLiteralStencil +// * StencilModuleMetadata +// +// +// CompilationStencil / ExtensibleCompilationStencil +// ------------------------------------------------- +// Parsing a single JavaScript file may generate a tree of `ScriptStencil` that +// we then package up into the `ExtensibleCompilationStencil` type or +// `CompilationStencil`. They contain a series of vectors/spans segregated by +// data type for fast processing (a.k.a Data Oriented Design). +// +// `ExtensibleCompilationStencil` is mutable type used during parsing, and +// can be allocated either on stack or heap. +// `ExtensibleCompilationStencil` holds vectors of stencils. +// +// `CompilationStencil` is immutable type used for caching the compilation +// result, and is allocated on heap with refcount. +// `CompilationStencil` holds spans of stencils, and it can point either +// owned data or borrowed data. +// The borrowed data can be from other `ExtensibleCompilationStencil` or +// from serialized stencil (XDR) on memory or mmap. +// +// Delazifying a function will generate its bytecode but some fields remain +// unchanged from the initial lazy parse. +// When we delazify a function that was lazily parsed, we generate a new +// Stencil at the point too. These delazifications can be merged into the +// Stencil of the initial parse by using `CompilationStencilMerger`. +// +// Conversion from ExtensibleCompilationStencil to CompilationStencil +// ------------------------------------------------------------------ +// There are multiple ways to convert from `ExtensibleCompilationStencil` to +// `CompilationStencil`: +// +// 1. Temporarily borrow `ExtensibleCompilationStencil` content and call +// function that takes `CompilationStencil` reference, and keep using the +// `ExtensibleCompilationStencil` after that: +// +// ExtensibleCompilationStencil extensible = ...; +// { +// BorrowingCompilationStencil stencil(extensible); +// // Use `stencil reference. +// } +// +// 2. Take the ownership of an on-heap ExtensibleCompilationStencil. This makes +// the `CompilationStencil` self-contained and it's useful for caching: +// +// UniquePtr extensible = ...; +// CompilationStencil stencil(std::move(extensible)); +// +// Conversion from CompilationStencil to ExtensibleCompilationStencil +// ------------------------------------------------------------------ +// In the same way, there are multiple ways to convert from +// `CompilationStencil` to `ExtensibleCompilationStencil`: +// +// 1. Take the ownership of `CompilationStencil`'s underlying data, Only when +// stencil owns the data and the refcount is 1: +// +// RefPtr stencil = ...; +// ExtensibleCompilationStencil extensible(...); +// // NOTE: This is equivalent to cloneFrom below if `stencil` has refcount +// // more than 2, or it doesn't own the data. +// extensible.steal(fc, std::move(stencil)); +// +// 2. Clone the underlying data. This is slow but safe operation: +// +// CompilationStencil stencil = ...; +// ExtensibleCompilationStencil extensible(...); +// extensible.cloneFrom(fc, stencil); +// +// 3. Take the ownership back from the `CompilationStencil` which is created by +// taking the ownership of an on-heap `ExtensibleCompilationStencil`: +// +// CompilationStencil stencil = ...; +// ExtensibleCompilationStencil* extensible = stencil.takeOwnedBorrow(); +// +// CompilationGCOutput +// ------------------- +// When a Stencil is instantiated the equivalent script objects are allocated on +// the GC heap and their pointers are collected into the `CompilationGCOutput` +// structure. This is only used temporarily during instantiation. +// +// +// CompilationState +// ---------------- +// This is another temporary structure used by the parser while the Stencil is +// being generated. Once the `CompilationStencil` is complete, this can be +// released. + +// Typed indices for the different stencil elements in the compilation result. +using RegExpIndex = TypedIndex; +using BigIntIndex = TypedIndex; +using ObjLiteralIndex = TypedIndex; + +// Index into {ExtensibleCompilationStencil,CompilationStencil}.gcThingData. +class CompilationGCThingType {}; +using CompilationGCThingIndex = TypedIndex; + +// A syntax-checked regular expression string. +class RegExpStencil { + friend class StencilXDR; + + TaggedParserAtomIndex atom_; + // Use uint32_t to make this struct fully-packed. + uint32_t flags_; + + friend struct CompilationStencilMerger; + + public: + RegExpStencil() = default; + + RegExpStencil(TaggedParserAtomIndex atom, JS::RegExpFlags flags) + : atom_(atom), flags_(flags.value()) {} + + JS::RegExpFlags flags() const { return JS::RegExpFlags(flags_); } + + RegExpObject* createRegExp(JSContext* cx, + const CompilationAtomCache& atomCache) const; + + // This is used by `Reflect.parse` when we need the RegExpObject but are not + // doing a complete instantiation of the CompilationStencil. + RegExpObject* createRegExpAndEnsureAtom( + JSContext* cx, FrontendContext* fc, ParserAtomsTable& parserAtoms, + CompilationAtomCache& atomCache) const; + +#if defined(DEBUG) || defined(JS_JITSPEW) + void dump() const; + void dump(JSONPrinter& json, const CompilationStencil* stencil) const; + void dumpFields(JSONPrinter& json, const CompilationStencil* stencil) const; +#endif +}; + +// This owns a set of characters guaranteed to parse into a BigInt via +// ParseBigIntLiteral. Used to avoid allocating the BigInt on the +// GC heap during parsing. +class BigIntStencil { + friend class StencilXDR; + + // Source of the BigInt literal. + // It's not null-terminated, and also trailing 'n' suffix is not included. + mozilla::Span source_; + + public: + BigIntStencil() = default; + + [[nodiscard]] bool init(FrontendContext* fc, LifoAlloc& alloc, + const mozilla::Span buf); + + BigInt* createBigInt(JSContext* cx) const; + + bool isZero() const; + + mozilla::Span source() const { return source_; } + +#ifdef DEBUG + bool isContainedIn(const LifoAlloc& alloc) const; +#endif + +#if defined(DEBUG) || defined(JS_JITSPEW) + void dump() const; + void dump(JSONPrinter& json) const; + void dumpCharsNoQuote(GenericPrinter& out) const; +#endif +}; + +class ScopeStencil { + friend class StencilXDR; + friend class InputScope; + friend class AbstractBindingIter; + friend struct CompilationStencil; + friend struct CompilationStencilMerger; + + // The enclosing scope. Valid only if HasEnclosing flag is set. + // compilation applies. + ScopeIndex enclosing_; + + // First frame slot to use, or LOCALNO_LIMIT if none are allowed. + uint32_t firstFrameSlot_ = UINT32_MAX; + + // The number of environment shape's slots. Valid only if + // HasEnvironmentShape flag is set. + uint32_t numEnvironmentSlots_; + + // Canonical function if this is a FunctionScope. Valid only if + // kind_ is ScopeKind::Function. + ScriptIndex functionIndex_; + + // The kind determines the corresponding BaseParserScopeData. + ScopeKind kind_{UINT8_MAX}; + + // True if this scope has enclosing scope stencil. Otherwise, the enclosing + // scope will be read from CompilationInput while instantiating. Self-hosting + // is a special case and will use `emptyGlobalScope` when there is no + // enclosing scope stencil. + static constexpr uint8_t HasEnclosing = 1 << 0; + + // If true, an environment Shape must be created. The shape itself may + // have no slots if the environment may be extensible later. + static constexpr uint8_t HasEnvironmentShape = 1 << 1; + + // True if this is a FunctionScope for an arrow function. + static constexpr uint8_t IsArrow = 1 << 2; + + uint8_t flags_ = 0; + + // To make this struct packed, add explicit field for padding. + uint16_t padding_ = 0; + + public: + // For XDR only. + ScopeStencil() = default; + + ScopeStencil(ScopeKind kind, mozilla::Maybe enclosing, + uint32_t firstFrameSlot, + mozilla::Maybe numEnvironmentSlots, + mozilla::Maybe functionIndex = mozilla::Nothing(), + bool isArrow = false) + : enclosing_(enclosing.valueOr(ScopeIndex(0))), + firstFrameSlot_(firstFrameSlot), + numEnvironmentSlots_(numEnvironmentSlots.valueOr(0)), + functionIndex_(functionIndex.valueOr(ScriptIndex(0))), + kind_(kind), + flags_((enclosing.isSome() ? HasEnclosing : 0) | + (numEnvironmentSlots.isSome() ? HasEnvironmentShape : 0) | + (isArrow ? IsArrow : 0)) { + MOZ_ASSERT((kind == ScopeKind::Function) == functionIndex.isSome()); + // Silence -Wunused-private-field warnings. + (void)padding_; + } + + private: + // Create ScopeStencil with `args`, and append ScopeStencil and `data` to + // `compilationState`, and return the index of them as `indexOut`. + template + static bool appendScopeStencilAndData(FrontendContext* fc, + CompilationState& compilationState, + BaseParserScopeData* data, + ScopeIndex* indexOut, Args&&... args); + + public: + static bool createForFunctionScope( + FrontendContext* fc, CompilationState& compilationState, + FunctionScope::ParserData* dataArg, bool hasParameterExprs, + bool needsEnvironment, ScriptIndex functionIndex, bool isArrow, + mozilla::Maybe enclosing, ScopeIndex* index); + + static bool createForLexicalScope( + FrontendContext* fc, CompilationState& compilationState, ScopeKind kind, + LexicalScope::ParserData* dataArg, uint32_t firstFrameSlot, + mozilla::Maybe enclosing, ScopeIndex* index); + + static bool createForClassBodyScope( + FrontendContext* fc, CompilationState& compilationState, ScopeKind kind, + ClassBodyScope::ParserData* dataArg, uint32_t firstFrameSlot, + mozilla::Maybe enclosing, ScopeIndex* index); + + static bool createForVarScope(FrontendContext* fc, + CompilationState& compilationState, + ScopeKind kind, VarScope::ParserData* dataArg, + uint32_t firstFrameSlot, bool needsEnvironment, + mozilla::Maybe enclosing, + ScopeIndex* index); + + static bool createForGlobalScope(FrontendContext* fc, + CompilationState& compilationState, + ScopeKind kind, + GlobalScope::ParserData* dataArg, + ScopeIndex* index); + + static bool createForEvalScope(FrontendContext* fc, + CompilationState& compilationState, + ScopeKind kind, EvalScope::ParserData* dataArg, + mozilla::Maybe enclosing, + ScopeIndex* index); + + static bool createForModuleScope(FrontendContext* fc, + CompilationState& compilationState, + ModuleScope::ParserData* dataArg, + mozilla::Maybe enclosing, + ScopeIndex* index); + + static bool createForWithScope(FrontendContext* fc, + CompilationState& compilationState, + mozilla::Maybe enclosing, + ScopeIndex* index); + + AbstractScopePtr enclosing(CompilationState& compilationState) const; + js::Scope* enclosingExistingScope(const CompilationInput& input, + const CompilationGCOutput& gcOutput) const; + + private: + bool hasEnclosing() const { return flags_ & HasEnclosing; } + + ScopeIndex enclosing() const { + MOZ_ASSERT(hasEnclosing()); + return enclosing_; + } + + uint32_t firstFrameSlot() const { return firstFrameSlot_; } + + bool hasEnvironmentShape() const { return flags_ & HasEnvironmentShape; } + + uint32_t numEnvironmentSlots() const { + MOZ_ASSERT(hasEnvironmentShape()); + return numEnvironmentSlots_; + } + + bool isFunction() const { return kind_ == ScopeKind::Function; } + + ScriptIndex functionIndex() const { return functionIndex_; } + + public: + ScopeKind kind() const { return kind_; } + + bool hasEnvironment() const { + // Check if scope kind alone means we have an env shape, and + // otherwise check if we have one created. + return Scope::hasEnvironment(kind(), hasEnvironmentShape()); + } + + bool isArrow() const { return flags_ & IsArrow; } + + Scope* createScope(JSContext* cx, CompilationInput& input, + CompilationGCOutput& gcOutput, + BaseParserScopeData* baseScopeData) const; + Scope* createScope(JSContext* cx, CompilationAtomCache& atomCache, + Handle enclosingScope, + BaseParserScopeData* baseScopeData) const; + +#if defined(DEBUG) || defined(JS_JITSPEW) + void dump() const; + void dump(JSONPrinter& json, const BaseParserScopeData* baseScopeData, + const CompilationStencil* stencil) const; + void dumpFields(JSONPrinter& json, const BaseParserScopeData* baseScopeData, + const CompilationStencil* stencil) const; +#endif + + private: + // Transfer ownership into a new UniquePtr. + template + UniquePtr createSpecificScopeData( + JSContext* cx, CompilationAtomCache& atomCache, + BaseParserScopeData* baseData) const; + + template + [[nodiscard]] bool createSpecificShape( + JSContext* cx, ScopeKind kind, BaseScopeData* scopeData, + MutableHandle shape) const; + + template + Scope* createSpecificScope(JSContext* cx, CompilationAtomCache& atomCache, + Handle enclosingScope, + BaseParserScopeData* baseData) const; + + template + static constexpr bool matchScopeKind(ScopeKind kind) { + switch (kind) { + case ScopeKind::Function: { + return std::is_same_v; + } + case ScopeKind::Lexical: + case ScopeKind::SimpleCatch: + case ScopeKind::Catch: + case ScopeKind::NamedLambda: + case ScopeKind::StrictNamedLambda: + case ScopeKind::FunctionLexical: { + return std::is_same_v; + } + case ScopeKind::ClassBody: { + return std::is_same_v; + } + case ScopeKind::FunctionBodyVar: { + return std::is_same_v; + } + case ScopeKind::Global: + case ScopeKind::NonSyntactic: { + return std::is_same_v; + } + case ScopeKind::Eval: + case ScopeKind::StrictEval: { + return std::is_same_v; + } + case ScopeKind::Module: { + return std::is_same_v; + } + case ScopeKind::With: { + return std::is_same_v; + } + case ScopeKind::WasmFunction: + case ScopeKind::WasmInstance: { + return false; + } + } + return false; + } +}; + +class StencilModuleAssertion { + public: + TaggedParserAtomIndex key; + TaggedParserAtomIndex value; + + StencilModuleAssertion() = default; + StencilModuleAssertion(TaggedParserAtomIndex key, TaggedParserAtomIndex value) + : key(key), value(value) {} +}; + +class StencilModuleRequest { + public: + TaggedParserAtomIndex specifier; + + using AssertionVector = + Vector; + AssertionVector assertions; + + // For XDR only. + StencilModuleRequest() = default; + + explicit StencilModuleRequest(TaggedParserAtomIndex specifier) + : specifier(specifier) { + MOZ_ASSERT(specifier); + } + + StencilModuleRequest(const StencilModuleRequest& other) + : specifier(other.specifier) { + AutoEnterOOMUnsafeRegion oomUnsafe; + if (!assertions.appendAll(other.assertions)) { + oomUnsafe.crash("StencilModuleRequest::StencilModuleRequest"); + } + } + + StencilModuleRequest(StencilModuleRequest&& other) noexcept + : specifier(other.specifier), assertions(std::move(other.assertions)) {} + + StencilModuleRequest& operator=(StencilModuleRequest& other) { + specifier = other.specifier; + assertions = std::move(other.assertions); + return *this; + } + + StencilModuleRequest& operator=(StencilModuleRequest&& other) noexcept { + specifier = other.specifier; + assertions = std::move(other.assertions); + return *this; + } +}; + +class MaybeModuleRequestIndex { + static constexpr uint32_t NOTHING = UINT32_MAX; + + uint32_t bits = NOTHING; + + public: + MaybeModuleRequestIndex() = default; + explicit MaybeModuleRequestIndex(uint32_t index) : bits(index) { + MOZ_ASSERT(isSome()); + } + + MaybeModuleRequestIndex(const MaybeModuleRequestIndex& other) = default; + MaybeModuleRequestIndex& operator=(const MaybeModuleRequestIndex& other) = + default; + + bool isNothing() const { return bits == NOTHING; } + bool isSome() const { return !isNothing(); } + explicit operator bool() const { return isSome(); } + + uint32_t value() const { + MOZ_ASSERT(isSome()); + return bits; + } + + uint32_t* operator&() { return &bits; } +}; + +// Common type for ImportEntry / ExportEntry / ModuleRequest within frontend. We +// use a shared stencil class type to simplify serialization. +// +// https://tc39.es/ecma262/#importentry-record +// https://tc39.es/ecma262/#exportentry-record +// +// Note: We subdivide the spec's ExportEntry into ExportAs / ExportFrom forms +// for readability. +class StencilModuleEntry { + public: + // clang-format off + // + // | RequestedModule | ImportEntry | ImportNamespaceEntry | ExportAs | ExportFrom | ExportNamespaceFrom | ExportBatchFrom | + // |----------------------------------------------------------------------------------------------------------------------| + // moduleRequest | required | required | required | null | required | required | required | + // localName | null | required | required | required | null | null | null | + // importName | null | required | null | null | required | null | null | + // exportName | null | null | null | required | required | required | null | + // + // clang-format on + MaybeModuleRequestIndex moduleRequest; + TaggedParserAtomIndex localName; + TaggedParserAtomIndex importName; + TaggedParserAtomIndex exportName; + + // Location used for error messages. If this is for a module request entry + // then it is the module specifier string, otherwise the import/export spec + // that failed. Exports may not fill these fields if an error cannot be + // generated such as `export let x;`. + uint32_t lineno = 0; + uint32_t column = 0; + + private: + StencilModuleEntry(uint32_t lineno, uint32_t column) + : lineno(lineno), column(column) {} + + public: + // For XDR only. + StencilModuleEntry() = default; + + StencilModuleEntry(const StencilModuleEntry& other) + : moduleRequest(other.moduleRequest), + localName(other.localName), + importName(other.importName), + exportName(other.exportName), + lineno(other.lineno), + column(other.column) {} + + StencilModuleEntry(StencilModuleEntry&& other) noexcept + : moduleRequest(other.moduleRequest), + localName(other.localName), + importName(other.importName), + exportName(other.exportName), + lineno(other.lineno), + column(other.column) {} + + StencilModuleEntry& operator=(StencilModuleEntry& other) { + moduleRequest = other.moduleRequest; + localName = other.localName; + importName = other.importName; + exportName = other.exportName; + lineno = other.lineno; + column = other.column; + return *this; + } + + StencilModuleEntry& operator=(StencilModuleEntry&& other) noexcept { + moduleRequest = other.moduleRequest; + localName = other.localName; + importName = other.importName; + exportName = other.exportName; + lineno = other.lineno; + column = other.column; + return *this; + } + + static StencilModuleEntry requestedModule( + MaybeModuleRequestIndex moduleRequest, uint32_t lineno, uint32_t column) { + MOZ_ASSERT(moduleRequest.isSome()); + StencilModuleEntry entry(lineno, column); + entry.moduleRequest = moduleRequest; + return entry; + } + + static StencilModuleEntry importEntry(MaybeModuleRequestIndex moduleRequest, + TaggedParserAtomIndex localName, + TaggedParserAtomIndex importName, + uint32_t lineno, uint32_t column) { + MOZ_ASSERT(moduleRequest.isSome()); + MOZ_ASSERT(localName && importName); + StencilModuleEntry entry(lineno, column); + entry.moduleRequest = moduleRequest; + entry.localName = localName; + entry.importName = importName; + return entry; + } + + static StencilModuleEntry importNamespaceEntry( + MaybeModuleRequestIndex moduleRequest, TaggedParserAtomIndex localName, + uint32_t lineno, uint32_t column) { + MOZ_ASSERT(moduleRequest.isSome()); + MOZ_ASSERT(localName); + StencilModuleEntry entry(lineno, column); + entry.moduleRequest = moduleRequest; + entry.localName = localName; + return entry; + } + + static StencilModuleEntry exportAsEntry(TaggedParserAtomIndex localName, + TaggedParserAtomIndex exportName, + uint32_t lineno, uint32_t column) { + MOZ_ASSERT(localName && exportName); + StencilModuleEntry entry(lineno, column); + entry.localName = localName; + entry.exportName = exportName; + return entry; + } + + static StencilModuleEntry exportFromEntry( + MaybeModuleRequestIndex moduleRequest, TaggedParserAtomIndex importName, + TaggedParserAtomIndex exportName, uint32_t lineno, uint32_t column) { + MOZ_ASSERT(moduleRequest.isSome()); + MOZ_ASSERT(importName && exportName); + StencilModuleEntry entry(lineno, column); + entry.moduleRequest = moduleRequest; + entry.importName = importName; + entry.exportName = exportName; + return entry; + } + + static StencilModuleEntry exportNamespaceFromEntry( + MaybeModuleRequestIndex moduleRequest, TaggedParserAtomIndex exportName, + uint32_t lineno, uint32_t column) { + MOZ_ASSERT(moduleRequest.isSome()); + MOZ_ASSERT(exportName); + StencilModuleEntry entry(lineno, column); + entry.moduleRequest = MaybeModuleRequestIndex(moduleRequest); + entry.exportName = exportName; + return entry; + } + + static StencilModuleEntry exportBatchFromEntry( + MaybeModuleRequestIndex moduleRequest, uint32_t lineno, uint32_t column) { + MOZ_ASSERT(moduleRequest.isSome()); + StencilModuleEntry entry(lineno, column); + entry.moduleRequest = MaybeModuleRequestIndex(moduleRequest); + return entry; + } +}; + +// Metadata generated by parsing module scripts, including import/export tables. +class StencilModuleMetadata + : public js::AtomicRefCounted { + public: + using RequestVector = Vector; + using EntryVector = Vector; + + RequestVector moduleRequests; + EntryVector requestedModules; + EntryVector importEntries; + EntryVector localExportEntries; + EntryVector indirectExportEntries; + EntryVector starExportEntries; + FunctionDeclarationVector functionDecls; + // Set to true if the module has a top-level await keyword. + bool isAsync = false; + + StencilModuleMetadata() = default; + + bool initModule(JSContext* cx, FrontendContext* fc, + CompilationAtomCache& atomCache, + JS::Handle module) const; + + size_t sizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const { + return mallocSizeOf(this) + + requestedModules.sizeOfExcludingThis(mallocSizeOf) + + importEntries.sizeOfExcludingThis(mallocSizeOf) + + localExportEntries.sizeOfExcludingThis(mallocSizeOf) + + indirectExportEntries.sizeOfExcludingThis(mallocSizeOf) + + starExportEntries.sizeOfExcludingThis(mallocSizeOf) + + functionDecls.sizeOfExcludingThis(mallocSizeOf); + } + +#if defined(DEBUG) || defined(JS_JITSPEW) + void dump() const; + void dump(JSONPrinter& json, const CompilationStencil* stencil) const; + void dumpFields(JSONPrinter& json, const CompilationStencil* stencil) const; +#endif + + private: + bool createModuleRequestObjects( + JSContext* cx, CompilationAtomCache& atomCache, + MutableHandle output) const; + bool createRequestedModules( + JSContext* cx, CompilationAtomCache& atomCache, + Handle moduleRequests, + MutableHandle output) const; + bool createImportEntries(JSContext* cx, CompilationAtomCache& atomCache, + Handle moduleRequests, + MutableHandle output) const; + bool createExportEntries(JSContext* cx, CompilationAtomCache& atomCache, + Handle moduleRequests, + const EntryVector& input, + MutableHandle output) const; + ModuleRequestObject* createModuleRequestObject( + JSContext* cx, CompilationAtomCache& atomCache, + const StencilModuleRequest& request) const; +}; + +// As an alternative to a ScopeIndex (which references a ScopeStencil), we may +// instead refer to an existing scope from GlobalObject::emptyGlobalScope(). +// +// NOTE: This is only used for the self-hosting global. +class EmptyGlobalScopeType {}; + +// Things pointed by this index all end up being baked into GC things as part +// of stencil instantiation. +// +// 0x0000_0000 Null +// 0x1YYY_YYYY 28-bit ParserAtom +// 0x2YYY_YYYY Well-known/static atom (See TaggedParserAtomIndex) +// 0x3YYY_YYYY 28-bit BigInt +// 0x4YYY_YYYY 28-bit ObjLiteral +// 0x5YYY_YYYY 28-bit RegExp +// 0x6YYY_YYYY 28-bit Scope +// 0x7YYY_YYYY 28-bit Function +// 0x8000_0000 EmptyGlobalScope +class TaggedScriptThingIndex { + uint32_t data_; + + static constexpr size_t IndexBit = TaggedParserAtomIndex::IndexBit; + static constexpr size_t IndexMask = TaggedParserAtomIndex::IndexMask; + + static constexpr size_t TagShift = TaggedParserAtomIndex::TagShift; + static constexpr size_t TagBit = TaggedParserAtomIndex::TagBit; + static constexpr size_t TagMask = TaggedParserAtomIndex::TagMask; + + public: + enum class Kind : uint32_t { + Null = uint32_t(TaggedParserAtomIndex::Kind::Null), + ParserAtomIndex = uint32_t(TaggedParserAtomIndex::Kind::ParserAtomIndex), + WellKnown = uint32_t(TaggedParserAtomIndex::Kind::WellKnown), + BigInt, + ObjLiteral, + RegExp, + Scope, + Function, + EmptyGlobalScope, + }; + + private: + static constexpr uint32_t NullTag = uint32_t(Kind::Null) << TagShift; + static_assert(NullTag == TaggedParserAtomIndex::NullTag); + static constexpr uint32_t ParserAtomIndexTag = uint32_t(Kind::ParserAtomIndex) + << TagShift; + static_assert(ParserAtomIndexTag == + TaggedParserAtomIndex::ParserAtomIndexTag); + static constexpr uint32_t WellKnownTag = uint32_t(Kind::WellKnown) + << TagShift; + static_assert(WellKnownTag == TaggedParserAtomIndex::WellKnownTag); + + static constexpr uint32_t BigIntTag = uint32_t(Kind::BigInt) << TagShift; + static constexpr uint32_t ObjLiteralTag = uint32_t(Kind::ObjLiteral) + << TagShift; + static constexpr uint32_t RegExpTag = uint32_t(Kind::RegExp) << TagShift; + static constexpr uint32_t ScopeTag = uint32_t(Kind::Scope) << TagShift; + static constexpr uint32_t FunctionTag = uint32_t(Kind::Function) << TagShift; + static constexpr uint32_t EmptyGlobalScopeTag = + uint32_t(Kind::EmptyGlobalScope) << TagShift; + + public: + static constexpr uint32_t IndexLimit = Bit(IndexBit); + + TaggedScriptThingIndex() : data_(NullTag) {} + + explicit TaggedScriptThingIndex(TaggedParserAtomIndex index) + : data_(index.rawData()) {} + explicit TaggedScriptThingIndex(BigIntIndex index) + : data_(uint32_t(index) | BigIntTag) { + MOZ_ASSERT(uint32_t(index) < IndexLimit); + } + explicit TaggedScriptThingIndex(ObjLiteralIndex index) + : data_(uint32_t(index) | ObjLiteralTag) { + MOZ_ASSERT(uint32_t(index) < IndexLimit); + } + explicit TaggedScriptThingIndex(RegExpIndex index) + : data_(uint32_t(index) | RegExpTag) { + MOZ_ASSERT(uint32_t(index) < IndexLimit); + } + explicit TaggedScriptThingIndex(ScopeIndex index) + : data_(uint32_t(index) | ScopeTag) { + MOZ_ASSERT(uint32_t(index) < IndexLimit); + } + explicit TaggedScriptThingIndex(ScriptIndex index) + : data_(uint32_t(index) | FunctionTag) { + MOZ_ASSERT(uint32_t(index) < IndexLimit); + } + explicit TaggedScriptThingIndex(EmptyGlobalScopeType t) + : data_(EmptyGlobalScopeTag) {} + + bool isAtom() const { + return (data_ & TagMask) == ParserAtomIndexTag || + (data_ & TagMask) == WellKnownTag; + } + bool isNull() const { + bool result = !data_; + MOZ_ASSERT_IF(result, (data_ & TagMask) == NullTag); + return result; + } + bool isBigInt() const { return (data_ & TagMask) == BigIntTag; } + bool isObjLiteral() const { return (data_ & TagMask) == ObjLiteralTag; } + bool isRegExp() const { return (data_ & TagMask) == RegExpTag; } + bool isScope() const { return (data_ & TagMask) == ScopeTag; } + bool isFunction() const { return (data_ & TagMask) == FunctionTag; } + bool isEmptyGlobalScope() const { + return (data_ & TagMask) == EmptyGlobalScopeTag; + } + + TaggedParserAtomIndex toAtom() const { + MOZ_ASSERT(isAtom()); + return TaggedParserAtomIndex::fromRaw(data_); + } + BigIntIndex toBigInt() const { return BigIntIndex(data_ & IndexMask); } + ObjLiteralIndex toObjLiteral() const { + return ObjLiteralIndex(data_ & IndexMask); + } + RegExpIndex toRegExp() const { return RegExpIndex(data_ & IndexMask); } + ScopeIndex toScope() const { return ScopeIndex(data_ & IndexMask); } + ScriptIndex toFunction() const { return ScriptIndex(data_ & IndexMask); } + + TaggedParserAtomIndex toAtomOrNull() const { + MOZ_ASSERT(isAtom() || isNull()); + return TaggedParserAtomIndex::fromRaw(data_); + } + + uint32_t* rawDataRef() { return &data_; } + uint32_t rawData() const { return data_; } + + Kind tag() const { return Kind((data_ & TagMask) >> TagShift); } + + bool operator==(const TaggedScriptThingIndex& rhs) const { + return data_ == rhs.data_; + } +}; + +// Data generated by frontend that will be used to create a js::BaseScript. +class ScriptStencil { + friend struct CompilationStencilMerger; + + public: + // Fields for BaseScript. + // Used by: + // * Global script + // * Eval + // * Module + // * non-lazy Function (except asm.js module) + // * lazy Function (cannot be asm.js module) + + // GCThings are stored into + // {ExtensibleCompilationStencil,CompilationStencil}.gcThingData, + // in [gcThingsOffset, gcThingsOffset + gcThingsLength) range. + CompilationGCThingIndex gcThingsOffset; + uint32_t gcThingsLength = 0; + + // Fields for JSFunction. + // Used by: + // * non-lazy Function + // * lazy Function + // * asm.js module + + // The explicit or implicit name of the function. The FunctionFlags indicate + // the kind of name. + TaggedParserAtomIndex functionAtom; + + // If this ScriptStencil refers to a lazy child of the function being + // compiled, this field holds the child's immediately enclosing scope's index. + // Once compilation succeeds, we will store the scope pointed by this in the + // child's BaseScript. (Debugger may become confused if lazy scripts refer to + // partially initialized enclosing scopes, so we must avoid storing the + // scope in the BaseScript until compilation has completed + // successfully.) + // + // OR + // + // This may be used for self-hosting canonical name (TaggedParserAtomIndex). + TaggedScriptThingIndex enclosingScopeOrCanonicalName; + + // See: `FunctionFlags`. + FunctionFlags functionFlags = {}; + + // This is set by the BytecodeEmitter of the enclosing script when a reference + // to this function is generated. + static constexpr uint16_t WasEmittedByEnclosingScriptFlag = 1 << 0; + + // If this is for the root of delazification, this represents + // MutableScriptFlagsEnum::AllowRelazify value of the script *after* + // delazification. + // False otherwise. + static constexpr uint16_t AllowRelazifyFlag = 1 << 1; + + // Set if this is non-lazy script and shared data is created. + // The shared data is stored into CompilationStencil.sharedData. + static constexpr uint16_t HasSharedDataFlag = 1 << 2; + + // True if this script is lazy function and has enclosing scope. In that + // case, `enclosingScopeOrCanonicalName` will hold the ScopeIndex. + static constexpr uint16_t HasLazyFunctionEnclosingScopeIndexFlag = 1 << 3; + + // True if this script is a self-hosted function with a canonical name + // explicitly set. In that case, `enclosingScopeOrCanonicalName` will hold the + // TaggedParserAtomIndex. + static constexpr uint16_t HasSelfHostedCanonicalName = 1 << 4; + + uint16_t flags_ = 0; + + // End of fields. + + ScriptStencil() = default; + + bool isFunction() const { + bool result = functionFlags.toRaw() != 0x0000; + MOZ_ASSERT_IF( + result, functionFlags.isAsmJSNative() || functionFlags.hasBaseScript()); + return result; + } + + bool hasGCThings() const { return gcThingsLength; } + + mozilla::Span gcthings( + const CompilationStencil& stencil) const; + + bool wasEmittedByEnclosingScript() const { + return flags_ & WasEmittedByEnclosingScriptFlag; + } + + void setWasEmittedByEnclosingScript() { + flags_ |= WasEmittedByEnclosingScriptFlag; + } + + bool allowRelazify() const { return flags_ & AllowRelazifyFlag; } + + void setAllowRelazify() { flags_ |= AllowRelazifyFlag; } + + bool isGhost() const { return functionFlags.isGhost(); } + void setIsGhost() { functionFlags.setIsGhost(); } + + bool hasSharedData() const { return flags_ & HasSharedDataFlag; } + + void setHasSharedData() { flags_ |= HasSharedDataFlag; } + + bool hasLazyFunctionEnclosingScopeIndex() const { + return flags_ & HasLazyFunctionEnclosingScopeIndexFlag; + } + + bool hasSelfHostedCanonicalName() const { + return flags_ & HasSelfHostedCanonicalName; + } + + private: + void setHasLazyFunctionEnclosingScopeIndex() { + flags_ |= HasLazyFunctionEnclosingScopeIndexFlag; + } + + void setHasSelfHostedCanonicalName() { flags_ |= HasSelfHostedCanonicalName; } + + public: + void setLazyFunctionEnclosingScopeIndex(ScopeIndex index) { + MOZ_ASSERT(enclosingScopeOrCanonicalName.isNull()); + enclosingScopeOrCanonicalName = TaggedScriptThingIndex(index); + setHasLazyFunctionEnclosingScopeIndex(); + } + + void resetHasLazyFunctionEnclosingScopeIndexAfterStencilMerge() { + flags_ &= ~HasLazyFunctionEnclosingScopeIndexFlag; + enclosingScopeOrCanonicalName = TaggedScriptThingIndex(); + } + + ScopeIndex lazyFunctionEnclosingScopeIndex() const { + MOZ_ASSERT(hasLazyFunctionEnclosingScopeIndex()); + return enclosingScopeOrCanonicalName.toScope(); + } + + void setSelfHostedCanonicalName(TaggedParserAtomIndex name) { + MOZ_ASSERT(enclosingScopeOrCanonicalName.isNull()); + enclosingScopeOrCanonicalName = TaggedScriptThingIndex(name); + setHasSelfHostedCanonicalName(); + } + + TaggedParserAtomIndex selfHostedCanonicalName() const { + MOZ_ASSERT(hasSelfHostedCanonicalName()); + return enclosingScopeOrCanonicalName.toAtom(); + } + +#if defined(DEBUG) || defined(JS_JITSPEW) + void dump() const; + void dump(JSONPrinter& json, const CompilationStencil* stencil) const; + void dumpFields(JSONPrinter& json, const CompilationStencil* stencil) const; +#endif +}; + +// In addition to ScriptStencil, data generated only while initial-parsing. +class ScriptStencilExtra { + public: + // See `BaseScript::immutableFlags_`. + ImmutableScriptFlags immutableFlags; + + // The location of this script in the source. + SourceExtent extent; + + // See `PrivateScriptData::memberInitializers_`. + // This data only valid when `UseMemberInitializers` script flag is true. + uint32_t memberInitializers_ = 0; + + // See `JSFunction::nargs_`. + uint16_t nargs = 0; + + // To make this struct packed, add explicit field for padding. + uint16_t padding_ = 0; + + ScriptStencilExtra() = default; + + RO_IMMUTABLE_SCRIPT_FLAGS(immutableFlags) + + void setMemberInitializers(MemberInitializers member) { + MOZ_ASSERT(useMemberInitializers()); + memberInitializers_ = member.serialize(); + } + + MemberInitializers memberInitializers() const { + MOZ_ASSERT(useMemberInitializers()); + return MemberInitializers::deserialize(memberInitializers_); + } + +#if defined(DEBUG) || defined(JS_JITSPEW) + void dump() const; + void dump(JSONPrinter& json) const; + void dumpFields(JSONPrinter& json) const; +#endif +}; + +#if defined(DEBUG) || defined(JS_JITSPEW) +void DumpTaggedParserAtomIndex(js::JSONPrinter& json, + TaggedParserAtomIndex taggedIndex, + const CompilationStencil* stencil); + +void DumpTaggedParserAtomIndexNoQuote(GenericPrinter& out, + TaggedParserAtomIndex taggedIndex, + const CompilationStencil* stencil); +#endif + +} /* namespace frontend */ + +#if defined(DEBUG) || defined(JS_JITSPEW) +void DumpImmutableScriptFlags(js::JSONPrinter& json, + ImmutableScriptFlags immutableFlags); +void DumpFunctionFlagsItems(js::JSONPrinter& json, FunctionFlags functionFlags); +#endif + +} /* namespace js */ + +#endif /* frontend_Stencil_h */ diff --git a/js/src/frontend/StencilXdr.cpp b/js/src/frontend/StencilXdr.cpp new file mode 100644 index 0000000000..2c6d7ec49c --- /dev/null +++ b/js/src/frontend/StencilXdr.cpp @@ -0,0 +1,1536 @@ +/* -*- 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/StencilXdr.h" // StencilXDR + +#include "mozilla/ArrayUtils.h" // mozilla::ArrayEqual +#include "mozilla/OperatorNewExtensions.h" // mozilla::KnownNotNull +#include "mozilla/ScopeExit.h" // mozilla::MakeScopeExit + +#include // size_t +#include // uint8_t, uint16_t, uint32_t +#include // std::has_unique_object_representations +#include // std::forward + +#include "ds/LifoAlloc.h" // LifoAlloc +#include "frontend/CompilationStencil.h" // CompilationStencil, ExtensibleCompilationStencil +#include "frontend/ScriptIndex.h" // ScriptIndex +#include "vm/Scope.h" // SizeOfParserScopeData +#include "vm/StencilEnums.h" // js::ImmutableScriptFlagsEnum + +using namespace js; +using namespace js::frontend; + +using mozilla::Utf8Unit; + +template +struct CanEncodeNameType { + static constexpr bool value = false; +}; + +template <> +struct CanEncodeNameType { + static constexpr bool value = true; +}; + +template +static XDRResult XDRVectorUninitialized(XDRState* xdr, + Vector& vec, + uint32_t& length) { + if (mode == XDR_ENCODE) { + MOZ_ASSERT(vec.length() <= UINT32_MAX); + length = vec.length(); + } + + MOZ_TRY(xdr->codeUint32(&length)); + + if (mode == XDR_DECODE) { + MOZ_ASSERT(vec.empty()); + if (!vec.resizeUninitialized(length)) { + js::ReportOutOfMemory(xdr->fc()); + return xdr->fail(JS::TranscodeResult::Throw); + } + } + + return Ok(); +} + +template +static XDRResult XDRVectorInitialized(XDRState* xdr, + Vector& vec, uint32_t length) { + MOZ_ASSERT_IF(mode == XDR_ENCODE, length == vec.length()); + + if (mode == XDR_DECODE) { + MOZ_ASSERT(vec.empty()); + if (!vec.resize(length)) { + js::ReportOutOfMemory(xdr->fc()); + return xdr->fail(JS::TranscodeResult::Throw); + } + } + + return Ok(); +} + +template +static XDRResult XDRVectorInitialized(XDRState* xdr, + Vector& vec) { + uint32_t length; + if (mode == XDR_ENCODE) { + MOZ_ASSERT(vec.length() <= UINT32_MAX); + length = vec.length(); + } + + MOZ_TRY(xdr->codeUint32(&length)); + + return XDRVectorInitialized(xdr, vec, length); +} + +template +static XDRResult XDRVectorContent(XDRState* xdr, Vector& vec) { + static_assert(CanCopyDataToDisk::value, + "Vector content cannot be bulk-copied to disk."); + + uint32_t length; + MOZ_TRY(XDRVectorUninitialized(xdr, vec, length)); + MOZ_TRY(xdr->codeBytes(vec.begin(), sizeof(T) * length)); + + return Ok(); +} + +template +static XDRResult XDRSpanInitialized(XDRState* xdr, LifoAlloc& alloc, + mozilla::Span& span, uint32_t size) { + MOZ_ASSERT_IF(mode == XDR_ENCODE, size == span.size()); + + if (mode == XDR_DECODE) { + MOZ_ASSERT(span.empty()); + if (size > 0) { + auto* p = alloc.template newArrayUninitialized(size); + if (!p) { + js::ReportOutOfMemory(xdr->fc()); + return xdr->fail(JS::TranscodeResult::Throw); + } + span = mozilla::Span(p, size); + + for (size_t i = 0; i < size; i++) { + new (mozilla::KnownNotNull, &span[i]) T(); + } + } + } + + return Ok(); +} + +template +static XDRResult XDRSpanContent(XDRState* xdr, LifoAlloc& alloc, + mozilla::Span& span, uint32_t size) { + static_assert(CanCopyDataToDisk::value, + "Span cannot be bulk-copied to disk."); + MOZ_ASSERT_IF(mode == XDR_ENCODE, size == span.size()); + + if (size) { + MOZ_TRY(xdr->align32()); + + T* data; + if constexpr (mode == XDR_ENCODE) { + data = span.data(); + MOZ_TRY(xdr->codeBytes(data, sizeof(T) * size)); + } else { + const auto& options = static_cast(xdr)->options(); + if (options.borrowBuffer) { + MOZ_TRY(xdr->borrowedData(&data, sizeof(T) * size)); + } else { + data = alloc.template newArrayUninitialized(size); + if (!data) { + js::ReportOutOfMemory(xdr->fc()); + return xdr->fail(JS::TranscodeResult::Throw); + } + MOZ_TRY(xdr->codeBytes(data, sizeof(T) * size)); + } + } + if (mode == XDR_DECODE) { + span = mozilla::Span(data, size); + } + } + + return Ok(); +} + +template +static XDRResult XDRSpanContent(XDRState* xdr, LifoAlloc& alloc, + mozilla::Span& span) { + uint32_t size; + if (mode == XDR_ENCODE) { + MOZ_ASSERT(span.size() <= UINT32_MAX); + size = span.size(); + } + + MOZ_TRY(xdr->codeUint32(&size)); + + return XDRSpanContent(xdr, alloc, span, size); +} + +template +/* static */ XDRResult StencilXDR::codeBigInt(XDRState* xdr, + LifoAlloc& alloc, + BigIntStencil& stencil) { + uint32_t size; + if (mode == XDR_ENCODE) { + size = stencil.source_.size(); + } + MOZ_TRY(xdr->codeUint32(&size)); + + return XDRSpanContent(xdr, alloc, stencil.source_, size); +} + +template +/* static */ XDRResult StencilXDR::codeObjLiteral(XDRState* xdr, + LifoAlloc& alloc, + ObjLiteralStencil& stencil) { + uint8_t kindAndFlags = 0; + + if (mode == XDR_ENCODE) { + static_assert(sizeof(ObjLiteralKindAndFlags) == sizeof(uint8_t)); + kindAndFlags = stencil.kindAndFlags_.toRaw(); + } + MOZ_TRY(xdr->codeUint8(&kindAndFlags)); + if (mode == XDR_DECODE) { + stencil.kindAndFlags_.setRaw(kindAndFlags); + } + + MOZ_TRY(xdr->codeUint32(&stencil.propertyCount_)); + + MOZ_TRY(XDRSpanContent(xdr, alloc, stencil.code_)); + + return Ok(); +} + +template +/* static */ void AssertScopeSpecificDataIsEncodable() { + using ScopeDataT = typename ScopeT::ParserData; + + static_assert(CanEncodeNameType::value); + static_assert(CanCopyDataToDisk::value, + "ScopeData cannot be bulk-copied to disk"); +} + +template +/* static */ XDRResult StencilXDR::codeScopeData( + XDRState* xdr, LifoAlloc& alloc, ScopeStencil& stencil, + BaseParserScopeData*& baseScopeData) { + // WasmInstanceScope & WasmFunctionScope should not appear in stencils. + MOZ_ASSERT(stencil.kind_ != ScopeKind::WasmInstance); + MOZ_ASSERT(stencil.kind_ != ScopeKind::WasmFunction); + if (stencil.kind_ == ScopeKind::With) { + return Ok(); + } + + MOZ_TRY(xdr->align32()); + + static_assert(offsetof(BaseParserScopeData, length) == 0, + "length should be the first field"); + uint32_t length; + if (mode == XDR_ENCODE) { + length = baseScopeData->length; + } else { + MOZ_TRY(xdr->peekUint32(&length)); + } + + AssertScopeSpecificDataIsEncodable(); + AssertScopeSpecificDataIsEncodable(); + AssertScopeSpecificDataIsEncodable(); + AssertScopeSpecificDataIsEncodable(); + AssertScopeSpecificDataIsEncodable(); + AssertScopeSpecificDataIsEncodable(); + AssertScopeSpecificDataIsEncodable(); + + // In both decoding and encoding, stencil.kind_ is now known, and + // can be assumed. This allows the encoding to write out the bytes + // for the specialized scope-data type without needing to encode + // a distinguishing prefix. + uint32_t totalLength = SizeOfParserScopeData(stencil.kind_, length); + if constexpr (mode == XDR_ENCODE) { + MOZ_TRY(xdr->codeBytes(baseScopeData, totalLength)); + } else { + const auto& options = static_cast(xdr)->options(); + if (options.borrowBuffer) { + MOZ_TRY(xdr->borrowedData(&baseScopeData, totalLength)); + } else { + baseScopeData = + reinterpret_cast(alloc.alloc(totalLength)); + if (!baseScopeData) { + js::ReportOutOfMemory(xdr->fc()); + return xdr->fail(JS::TranscodeResult::Throw); + } + MOZ_TRY(xdr->codeBytes(baseScopeData, totalLength)); + } + } + + return Ok(); +} + +template +/* static */ +XDRResult StencilXDR::codeSharedData(XDRState* xdr, + RefPtr& sisd) { + static_assert(frontend::CanCopyDataToDisk::value, + "ImmutableScriptData cannot be bulk-copied to disk"); + static_assert(frontend::CanCopyDataToDisk::value, + "jsbytecode cannot be bulk-copied to disk"); + static_assert(frontend::CanCopyDataToDisk::value, + "SrcNote cannot be bulk-copied to disk"); + static_assert(frontend::CanCopyDataToDisk::value, + "ScopeNote cannot be bulk-copied to disk"); + static_assert(frontend::CanCopyDataToDisk::value, + "TryNote cannot be bulk-copied to disk"); + + uint32_t size; + uint32_t hash; + if (mode == XDR_ENCODE) { + if (sisd) { + size = sisd->immutableDataLength(); + hash = sisd->hash(); + } else { + size = 0; + hash = 0; + } + } + MOZ_TRY(xdr->codeUint32(&size)); + + // A size of zero is used when the `sisd` is nullptr. This can occur for + // certain outer container modes. In this case, there is no further + // transcoding to do. + if (!size) { + MOZ_ASSERT(!sisd); + return Ok(); + } + + MOZ_TRY(xdr->align32()); + static_assert(alignof(ImmutableScriptData) <= alignof(uint32_t)); + + MOZ_TRY(xdr->codeUint32(&hash)); + + if constexpr (mode == XDR_ENCODE) { + uint8_t* data = const_cast(sisd->get()->immutableData().data()); + MOZ_ASSERT(data == reinterpret_cast(sisd->get()), + "Decode below relies on the data placement"); + MOZ_TRY(xdr->codeBytes(data, size)); + } else { + sisd = SharedImmutableScriptData::create(xdr->fc()); + if (!sisd) { + return xdr->fail(JS::TranscodeResult::Throw); + } + + const auto& options = static_cast(xdr)->options(); + if (options.usePinnedBytecode) { + MOZ_ASSERT(options.borrowBuffer); + ImmutableScriptData* isd; + MOZ_TRY(xdr->borrowedData(&isd, size)); + sisd->setExternal(isd, hash); + } else { + auto isd = ImmutableScriptData::new_(xdr->fc(), size); + if (!isd) { + return xdr->fail(JS::TranscodeResult::Throw); + } + uint8_t* data = reinterpret_cast(isd.get()); + MOZ_TRY(xdr->codeBytes(data, size)); + sisd->setOwn(std::move(isd), hash); + } + + if (!sisd->get()->validateLayout(size)) { + MOZ_ASSERT(false, "Bad ImmutableScriptData"); + return xdr->fail(JS::TranscodeResult::Failure_BadDecode); + } + } + + if (mode == XDR_DECODE) { + if (!SharedImmutableScriptData::shareScriptData(xdr->fc(), sisd)) { + return xdr->fail(JS::TranscodeResult::Throw); + } + } + + return Ok(); +} + +// Called from js::XDRScript. +template /* static */ XDRResult StencilXDR::codeSharedData( + XDRState* xdr, RefPtr& sisd); +template /* static */ XDRResult StencilXDR::codeSharedData( + XDRState* xdr, RefPtr& sisd); + +template +/* static */ XDRResult StencilXDR::codeSharedDataContainer( + XDRState* xdr, SharedDataContainer& sharedData) { + if (mode == XDR_ENCODE) { + if (sharedData.isBorrow()) { + return codeSharedDataContainer(xdr, *sharedData.asBorrow()); + } + } + + enum Kind { + Single, + Vector, + Map, + }; + + Kind kind; + if (mode == XDR_ENCODE) { + if (sharedData.isSingle()) { + kind = Kind::Single; + } else if (sharedData.isVector()) { + kind = Kind::Vector; + } else { + MOZ_ASSERT(sharedData.isMap()); + kind = Kind::Map; + } + } + MOZ_TRY(xdr->codeEnum32(&kind)); + + switch (kind) { + case Kind::Single: { + RefPtr ref; + if (mode == XDR_ENCODE) { + ref = sharedData.asSingle(); + } + MOZ_TRY(codeSharedData(xdr, ref)); + if (mode == XDR_DECODE) { + sharedData.setSingle(ref.forget()); + } + break; + } + + case Kind::Vector: { + if (mode == XDR_DECODE) { + if (!sharedData.initVector(xdr->fc())) { + return xdr->fail(JS::TranscodeResult::Throw); + } + } + auto& vec = *sharedData.asVector(); + MOZ_TRY(XDRVectorInitialized(xdr, vec)); + for (auto& entry : vec) { + // NOTE: There can be nullptr, even if we don't perform syntax parsing, + // because of constant folding. + MOZ_TRY(codeSharedData(xdr, entry)); + } + break; + } + + case Kind::Map: { + if (mode == XDR_DECODE) { + if (!sharedData.initMap(xdr->fc())) { + return xdr->fail(JS::TranscodeResult::Throw); + } + } + auto& map = *sharedData.asMap(); + uint32_t count; + if (mode == XDR_ENCODE) { + count = map.count(); + } + MOZ_TRY(xdr->codeUint32(&count)); + if (mode == XDR_DECODE) { + if (!map.reserve(count)) { + js::ReportOutOfMemory(xdr->fc()); + return xdr->fail(JS::TranscodeResult::Throw); + } + } + + if (mode == XDR_ENCODE) { + for (auto iter = map.iter(); !iter.done(); iter.next()) { + uint32_t index = iter.get().key().index; + auto& data = iter.get().value(); + MOZ_TRY(xdr->codeUint32(&index)); + MOZ_TRY(codeSharedData(xdr, data)); + } + } else { + for (uint32_t i = 0; i < count; i++) { + ScriptIndex index; + MOZ_TRY(xdr->codeUint32(&index.index)); + + RefPtr data; + MOZ_TRY(codeSharedData(xdr, data)); + + if (!map.putNew(index, data)) { + js::ReportOutOfMemory(xdr->fc()); + return xdr->fail(JS::TranscodeResult::Throw); + } + } + } + + break; + } + + default: + return xdr->fail(JS::TranscodeResult::Failure_BadDecode); + } + + return Ok(); +} + +template +/* static */ XDRResult StencilXDR::codeParserAtom(XDRState* xdr, + LifoAlloc& alloc, + ParserAtom** atomp) { + static_assert(CanCopyDataToDisk::value, + "ParserAtom cannot be bulk-copied to disk."); + + MOZ_TRY(xdr->align32()); + + const ParserAtom* header; + if (mode == XDR_ENCODE) { + header = *atomp; + } else { + MOZ_TRY(xdr->peekData(&header)); + } + + const uint32_t CharSize = + header->hasLatin1Chars() ? sizeof(JS::Latin1Char) : sizeof(char16_t); + uint32_t totalLength = sizeof(ParserAtom) + (CharSize * header->length()); + + if constexpr (mode == XDR_ENCODE) { + MOZ_TRY(xdr->codeBytes(*atomp, totalLength)); + } else { + const auto& options = static_cast(xdr)->options(); + if (options.borrowBuffer) { + MOZ_TRY(xdr->borrowedData(atomp, totalLength)); + } else { + *atomp = reinterpret_cast(alloc.alloc(totalLength)); + if (!*atomp) { + js::ReportOutOfMemory(xdr->fc()); + return xdr->fail(JS::TranscodeResult::Throw); + } + MOZ_TRY(xdr->codeBytes(*atomp, totalLength)); + } + } + + return Ok(); +} + +template +static XDRResult XDRAtomCount(XDRState* xdr, uint32_t* atomCount) { + return xdr->codeUint32(atomCount); +} + +template +/* static */ XDRResult StencilXDR::codeParserAtomSpan( + XDRState* xdr, LifoAlloc& alloc, ParserAtomSpan& parserAtomData) { + if (mode == XDR_ENCODE) { + uint32_t atomVectorLength = parserAtomData.size(); + MOZ_TRY(XDRAtomCount(xdr, &atomVectorLength)); + + uint32_t atomCount = 0; + for (const auto& entry : parserAtomData) { + if (!entry) { + continue; + } + if (entry->isUsedByStencil()) { + atomCount++; + } + } + MOZ_TRY(XDRAtomCount(xdr, &atomCount)); + + for (uint32_t i = 0; i < atomVectorLength; i++) { + auto& entry = parserAtomData[i]; + if (!entry) { + continue; + } + if (entry->isUsedByStencil()) { + MOZ_TRY(xdr->codeUint32(&i)); + MOZ_TRY(codeParserAtom(xdr, alloc, &entry)); + } + } + + return Ok(); + } + + uint32_t atomVectorLength; + MOZ_TRY(XDRAtomCount(xdr, &atomVectorLength)); + + frontend::ParserAtomSpanBuilder builder(parserAtomData); + if (!builder.allocate(xdr->fc(), alloc, atomVectorLength)) { + return xdr->fail(JS::TranscodeResult::Throw); + } + + uint32_t atomCount; + MOZ_TRY(XDRAtomCount(xdr, &atomCount)); + + for (uint32_t i = 0; i < atomCount; i++) { + frontend::ParserAtom* entry = nullptr; + uint32_t index; + MOZ_TRY(xdr->codeUint32(&index)); + MOZ_TRY(codeParserAtom(xdr, alloc, &entry)); + if (mode == XDR_DECODE) { + if (index >= atomVectorLength) { + return xdr->fail(JS::TranscodeResult::Failure_BadDecode); + } + } + builder.set(frontend::ParserAtomIndex(index), entry); + } + + return Ok(); +} + +template +/* static */ XDRResult StencilXDR::codeModuleRequest( + XDRState* xdr, StencilModuleRequest& stencil) { + MOZ_TRY(xdr->codeUint32(stencil.specifier.rawDataRef())); + MOZ_TRY(XDRVectorContent(xdr, stencil.assertions)); + + return Ok(); +} + +template +/* static */ XDRResult StencilXDR::codeModuleRequestVector( + XDRState* xdr, StencilModuleMetadata::RequestVector& vector) { + MOZ_TRY(XDRVectorInitialized(xdr, vector)); + + for (auto& entry : vector) { + MOZ_TRY(codeModuleRequest(xdr, entry)); + } + + return Ok(); +} + +template +/* static */ XDRResult StencilXDR::codeModuleEntry( + XDRState* xdr, StencilModuleEntry& stencil) { + MOZ_TRY(xdr->codeUint32(&stencil.moduleRequest)); + MOZ_TRY(xdr->codeUint32(stencil.localName.rawDataRef())); + MOZ_TRY(xdr->codeUint32(stencil.importName.rawDataRef())); + MOZ_TRY(xdr->codeUint32(stencil.exportName.rawDataRef())); + MOZ_TRY(xdr->codeUint32(&stencil.lineno)); + MOZ_TRY(xdr->codeUint32(&stencil.column)); + + return Ok(); +} + +template +/* static */ XDRResult StencilXDR::codeModuleEntryVector( + XDRState* xdr, StencilModuleMetadata::EntryVector& vector) { + MOZ_TRY(XDRVectorInitialized(xdr, vector)); + + for (auto& entry : vector) { + MOZ_TRY(codeModuleEntry(xdr, entry)); + } + + return Ok(); +} + +template +/* static */ XDRResult StencilXDR::codeModuleMetadata( + XDRState* xdr, StencilModuleMetadata& stencil) { + MOZ_TRY(codeModuleRequestVector(xdr, stencil.moduleRequests)); + MOZ_TRY(codeModuleEntryVector(xdr, stencil.requestedModules)); + MOZ_TRY(codeModuleEntryVector(xdr, stencil.importEntries)); + MOZ_TRY(codeModuleEntryVector(xdr, stencil.localExportEntries)); + MOZ_TRY(codeModuleEntryVector(xdr, stencil.indirectExportEntries)); + MOZ_TRY(codeModuleEntryVector(xdr, stencil.starExportEntries)); + MOZ_TRY(XDRVectorContent(xdr, stencil.functionDecls)); + + uint8_t isAsync = 0; + if (mode == XDR_ENCODE) { + if (stencil.isAsync) { + isAsync = stencil.isAsync ? 1 : 0; + } + } + + MOZ_TRY(xdr->codeUint8(&isAsync)); + + if (mode == XDR_DECODE) { + stencil.isAsync = isAsync == 1; + } + + return Ok(); +} + +template +XDRResult XDRCompilationStencilSpanSize( + XDRState* xdr, uint32_t* scriptSize, uint32_t* gcThingSize, + uint32_t* scopeSize, uint32_t* scriptExtraSize, uint32_t* regExpSize, + uint32_t* bigIntSize, uint32_t* objLiteralSize) { + // Compress the series of span sizes, to avoid consuming extra space for + // unused/small span sizes. + // There will be align32 shortly after this section, so try to make the + // padding smaller. + + enum XDRSpanSizeKind { + // All of the size values fit in 1 byte each. The entire section takes 7 + // bytes, and expect no padding. + All8Kind, + + // Other cases. All of the size values fit in 4 bytes each. Expect 3 bytes + // padding for `sizeKind`. + All32Kind, + }; + + uint8_t sizeKind = All32Kind; + if (mode == XDR_ENCODE) { + uint32_t mask = (*scriptSize) | (*gcThingSize) | (*scopeSize) | + (*scriptExtraSize) | (*regExpSize) | (*bigIntSize) | + (*objLiteralSize); + + if (mask <= 0xff) { + sizeKind = All8Kind; + } + } + MOZ_TRY(xdr->codeUint8(&sizeKind)); + + if (sizeKind == All32Kind) { + MOZ_TRY(xdr->codeUint32(scriptSize)); + MOZ_TRY(xdr->codeUint32(gcThingSize)); + MOZ_TRY(xdr->codeUint32(scopeSize)); + MOZ_TRY(xdr->codeUint32(scriptExtraSize)); + MOZ_TRY(xdr->codeUint32(regExpSize)); + MOZ_TRY(xdr->codeUint32(bigIntSize)); + MOZ_TRY(xdr->codeUint32(objLiteralSize)); + } else { + uint8_t scriptSize8 = 0; + uint8_t gcThingSize8 = 0; + uint8_t scopeSize8 = 0; + uint8_t scriptExtraSize8 = 0; + uint8_t regExpSize8 = 0; + uint8_t bigIntSize8 = 0; + uint8_t objLiteralSize8 = 0; + + if (mode == XDR_ENCODE) { + scriptSize8 = uint8_t(*scriptSize); + gcThingSize8 = uint8_t(*gcThingSize); + scopeSize8 = uint8_t(*scopeSize); + scriptExtraSize8 = uint8_t(*scriptExtraSize); + regExpSize8 = uint8_t(*regExpSize); + bigIntSize8 = uint8_t(*bigIntSize); + objLiteralSize8 = uint8_t(*objLiteralSize); + } + + MOZ_TRY(xdr->codeUint8(&scriptSize8)); + MOZ_TRY(xdr->codeUint8(&gcThingSize8)); + MOZ_TRY(xdr->codeUint8(&scopeSize8)); + MOZ_TRY(xdr->codeUint8(&scriptExtraSize8)); + MOZ_TRY(xdr->codeUint8(®ExpSize8)); + MOZ_TRY(xdr->codeUint8(&bigIntSize8)); + MOZ_TRY(xdr->codeUint8(&objLiteralSize8)); + + if (mode == XDR_DECODE) { + *scriptSize = scriptSize8; + *gcThingSize = gcThingSize8; + *scopeSize = scopeSize8; + *scriptExtraSize = scriptExtraSize8; + *regExpSize = regExpSize8; + *bigIntSize = bigIntSize8; + *objLiteralSize = objLiteralSize8; + } + } + + return Ok(); +} + +// Marker between each section inside CompilationStencil. +// +// These values should meet the following requirement: +// * No same value (differ more than single bit flip) +// * Bit pattern that won't frequently appear inside other XDR data +// +// Currently they're randomly chosen prime numbers that doesn't have same +// byte pattern. +enum class SectionMarker : uint32_t { + ParserAtomData = 0xD9C098D3, + ScopeData = 0x892C25EF, + ScopeNames = 0x638C4FB3, + RegExpData = 0xB030C2AF, + BigIntData = 0x4B24F449, + ObjLiteralData = 0x9AFAAE45, + SharedData = 0xAAD52687, + GCThingData = 0x1BD8F533, + ScriptData = 0x840458FF, + ScriptExtra = 0xA90E489D, + ModuleMetadata = 0x94FDCE6D, + End = 0x16DDA135, +}; + +template +static XDRResult CodeMarker(XDRState* xdr, SectionMarker marker) { + return xdr->codeMarker(uint32_t(marker)); +} + +template +/* static */ XDRResult StencilXDR::codeCompilationStencil( + XDRState* xdr, CompilationStencil& stencil) { + MOZ_ASSERT(!stencil.asmJS); + + if constexpr (mode == XDR_DECODE) { + const auto& options = static_cast(xdr)->options(); + if (options.borrowBuffer) { + stencil.storageType = CompilationStencil::StorageType::Borrowed; + } else { + stencil.storageType = CompilationStencil::StorageType::Owned; + } + } + + MOZ_TRY(CodeMarker(xdr, SectionMarker::ParserAtomData)); + MOZ_TRY(codeParserAtomSpan(xdr, stencil.alloc, stencil.parserAtomData)); + + uint8_t canLazilyParse = 0; + + if (mode == XDR_ENCODE) { + canLazilyParse = stencil.canLazilyParse; + } + MOZ_TRY(xdr->codeUint8(&canLazilyParse)); + if (mode == XDR_DECODE) { + stencil.canLazilyParse = canLazilyParse; + // NOTE: stencil.canLazilyParse can be different than + // CanLazilyParse(static_cast(xdr)->options()). + // See bug 1726498 for removing the redundancy. + } + + MOZ_TRY(xdr->codeUint32(&stencil.functionKey)); + + uint32_t scriptSize, gcThingSize, scopeSize, scriptExtraSize; + uint32_t regExpSize, bigIntSize, objLiteralSize; + if (mode == XDR_ENCODE) { + scriptSize = stencil.scriptData.size(); + gcThingSize = stencil.gcThingData.size(); + scopeSize = stencil.scopeData.size(); + MOZ_ASSERT(scopeSize == stencil.scopeNames.size()); + + scriptExtraSize = stencil.scriptExtra.size(); + + regExpSize = stencil.regExpData.size(); + bigIntSize = stencil.bigIntData.size(); + objLiteralSize = stencil.objLiteralData.size(); + } + MOZ_TRY(XDRCompilationStencilSpanSize( + xdr, &scriptSize, &gcThingSize, &scopeSize, &scriptExtraSize, ®ExpSize, + &bigIntSize, &objLiteralSize)); + + // All of the vector-indexed data elements referenced by the + // main script tree must be materialized first. + + MOZ_TRY(CodeMarker(xdr, SectionMarker::ScopeData)); + MOZ_TRY(XDRSpanContent(xdr, stencil.alloc, stencil.scopeData, scopeSize)); + + MOZ_TRY(CodeMarker(xdr, SectionMarker::ScopeNames)); + MOZ_TRY( + XDRSpanInitialized(xdr, stencil.alloc, stencil.scopeNames, scopeSize)); + MOZ_ASSERT(stencil.scopeData.size() == stencil.scopeNames.size()); + for (uint32_t i = 0; i < scopeSize; i++) { + MOZ_TRY(codeScopeData(xdr, stencil.alloc, stencil.scopeData[i], + stencil.scopeNames[i])); + } + + MOZ_TRY(CodeMarker(xdr, SectionMarker::RegExpData)); + MOZ_TRY(XDRSpanContent(xdr, stencil.alloc, stencil.regExpData, regExpSize)); + + MOZ_TRY(CodeMarker(xdr, SectionMarker::BigIntData)); + MOZ_TRY( + XDRSpanInitialized(xdr, stencil.alloc, stencil.bigIntData, bigIntSize)); + for (auto& entry : stencil.bigIntData) { + MOZ_TRY(codeBigInt(xdr, stencil.alloc, entry)); + } + + MOZ_TRY(CodeMarker(xdr, SectionMarker::ObjLiteralData)); + MOZ_TRY(XDRSpanInitialized(xdr, stencil.alloc, stencil.objLiteralData, + objLiteralSize)); + for (auto& entry : stencil.objLiteralData) { + MOZ_TRY(codeObjLiteral(xdr, stencil.alloc, entry)); + } + + MOZ_TRY(CodeMarker(xdr, SectionMarker::SharedData)); + MOZ_TRY(codeSharedDataContainer(xdr, stencil.sharedData)); + + MOZ_TRY(CodeMarker(xdr, SectionMarker::GCThingData)); + MOZ_TRY(XDRSpanContent(xdr, stencil.alloc, stencil.gcThingData, gcThingSize)); + + // Now serialize the vector of ScriptStencils. + MOZ_TRY(CodeMarker(xdr, SectionMarker::ScriptData)); + MOZ_TRY(XDRSpanContent(xdr, stencil.alloc, stencil.scriptData, scriptSize)); + + MOZ_TRY(CodeMarker(xdr, SectionMarker::ScriptExtra)); + MOZ_TRY( + XDRSpanContent(xdr, stencil.alloc, stencil.scriptExtra, scriptExtraSize)); + + // We don't support coding non-initial CompilationStencil. + MOZ_ASSERT(stencil.isInitialStencil()); + + if (stencil.scriptExtra[CompilationStencil::TopLevelIndex].isModule()) { + if (mode == XDR_DECODE) { + stencil.moduleMetadata = + xdr->fc()->getAllocator()->template new_(); + if (!stencil.moduleMetadata) { + return xdr->fail(JS::TranscodeResult::Throw); + } + } + + MOZ_TRY(CodeMarker(xdr, SectionMarker::ModuleMetadata)); + MOZ_TRY(codeModuleMetadata(xdr, *stencil.moduleMetadata)); + + // codeModuleMetadata doesn't guarantee alignment. + MOZ_TRY(xdr->align32()); + } + + MOZ_TRY(CodeMarker(xdr, SectionMarker::End)); + + // The result should be aligned. + // + // NOTE: + // If the top-level isn't a module, ScriptData/ScriptExtra sections + // guarantee the alignment because there should be at least 1 item, + // and XDRSpanContent adds alignment before span content, and the struct size + // should also be aligned. + static_assert(sizeof(ScriptStencil) % 4 == 0, + "size of ScriptStencil should be aligned"); + static_assert(sizeof(ScriptStencilExtra) % 4 == 0, + "size of ScriptStencilExtra should be aligned"); + MOZ_RELEASE_ASSERT(xdr->isAligned32()); + + return Ok(); +} + +template +struct UnretrievableSourceDecoder { + XDRState* const xdr_; + ScriptSource* const scriptSource_; + const uint32_t uncompressedLength_; + + public: + UnretrievableSourceDecoder(XDRState* xdr, + ScriptSource* scriptSource, + uint32_t uncompressedLength) + : xdr_(xdr), + scriptSource_(scriptSource), + uncompressedLength_(uncompressedLength) {} + + XDRResult decode() { + auto sourceUnits = xdr_->fc()->getAllocator()->make_pod_array( + std::max(uncompressedLength_, 1)); + if (!sourceUnits) { + return xdr_->fail(JS::TranscodeResult::Throw); + } + + MOZ_TRY(xdr_->codeChars(sourceUnits.get(), uncompressedLength_)); + + if (!scriptSource_->initializeUnretrievableUncompressedSource( + xdr_->fc(), std::move(sourceUnits), uncompressedLength_)) { + return xdr_->fail(JS::TranscodeResult::Throw); + } + + return Ok(); + } +}; + +template <> +XDRResult StencilXDR::codeSourceUnretrievableUncompressed( + XDRState* xdr, ScriptSource* ss, uint8_t sourceCharSize, + uint32_t uncompressedLength) { + MOZ_ASSERT(sourceCharSize == 1 || sourceCharSize == 2); + + if (sourceCharSize == 1) { + UnretrievableSourceDecoder decoder(xdr, ss, uncompressedLength); + return decoder.decode(); + } + + UnretrievableSourceDecoder decoder(xdr, ss, uncompressedLength); + return decoder.decode(); +} + +template +struct UnretrievableSourceEncoder { + XDRState* const xdr_; + ScriptSource* const source_; + const uint32_t uncompressedLength_; + + UnretrievableSourceEncoder(XDRState* xdr, ScriptSource* source, + uint32_t uncompressedLength) + : xdr_(xdr), source_(source), uncompressedLength_(uncompressedLength) {} + + XDRResult encode() { + Unit* sourceUnits = + const_cast(source_->uncompressedData()->units()); + + return xdr_->codeChars(sourceUnits, uncompressedLength_); + } +}; + +template <> +/* static */ +XDRResult StencilXDR::codeSourceUnretrievableUncompressed( + XDRState* xdr, ScriptSource* ss, uint8_t sourceCharSize, + uint32_t uncompressedLength) { + MOZ_ASSERT(sourceCharSize == 1 || sourceCharSize == 2); + + if (sourceCharSize == 1) { + UnretrievableSourceEncoder encoder(xdr, ss, uncompressedLength); + return encoder.encode(); + } + + UnretrievableSourceEncoder encoder(xdr, ss, uncompressedLength); + return encoder.encode(); +} + +template +/* static */ +XDRResult StencilXDR::codeSourceUncompressedData(XDRState* const xdr, + ScriptSource* const ss) { + static_assert( + std::is_same_v || std::is_same_v, + "should handle UTF-8 and UTF-16"); + + if (mode == XDR_ENCODE) { + MOZ_ASSERT(ss->isUncompressed()); + } else { + MOZ_ASSERT(ss->data.is()); + } + + uint32_t uncompressedLength; + if (mode == XDR_ENCODE) { + uncompressedLength = ss->uncompressedData()->length(); + } + MOZ_TRY(xdr->codeUint32(&uncompressedLength)); + + return codeSourceUnretrievableUncompressed(xdr, ss, sizeof(Unit), + uncompressedLength); +} + +template +/* static */ +XDRResult StencilXDR::codeSourceCompressedData(XDRState* const xdr, + ScriptSource* const ss) { + static_assert( + std::is_same_v || std::is_same_v, + "should handle UTF-8 and UTF-16"); + + if (mode == XDR_ENCODE) { + MOZ_ASSERT(ss->isCompressed()); + } else { + MOZ_ASSERT(ss->data.is()); + } + + uint32_t uncompressedLength; + if (mode == XDR_ENCODE) { + uncompressedLength = + ss->data.as>() + .uncompressedLength; + } + MOZ_TRY(xdr->codeUint32(&uncompressedLength)); + + uint32_t compressedLength; + if (mode == XDR_ENCODE) { + compressedLength = + ss->data.as>() + .raw.length(); + } + MOZ_TRY(xdr->codeUint32(&compressedLength)); + + if (mode == XDR_DECODE) { + // Compressed data is always single-byte chars. + auto bytes = xdr->fc()->getAllocator()->template make_pod_array( + compressedLength); + if (!bytes) { + return xdr->fail(JS::TranscodeResult::Throw); + } + MOZ_TRY(xdr->codeBytes(bytes.get(), compressedLength)); + + if (!ss->initializeWithUnretrievableCompressedSource( + xdr->fc(), std::move(bytes), compressedLength, + uncompressedLength)) { + return xdr->fail(JS::TranscodeResult::Throw); + } + } else { + void* bytes = const_cast(ss->compressedData()->raw.chars()); + MOZ_TRY(xdr->codeBytes(bytes, compressedLength)); + } + + return Ok(); +} + +template class Data, + XDRMode mode> +/* static */ +void StencilXDR::codeSourceRetrievable(ScriptSource* const ss) { + static_assert( + std::is_same_v || std::is_same_v, + "should handle UTF-8 and UTF-16"); + + if (mode == XDR_ENCODE) { + MOZ_ASSERT((ss->data.is>())); + } else { + MOZ_ASSERT(ss->data.is()); + ss->data = ScriptSource::SourceType(ScriptSource::Retrievable()); + } +} + +template +/* static */ +void StencilXDR::codeSourceRetrievableData(ScriptSource* ss) { + // There's nothing to code for retrievable data. Just be sure to set + // retrievable data when decoding. + if (mode == XDR_ENCODE) { + MOZ_ASSERT(ss->data.is>()); + } else { + MOZ_ASSERT(ss->data.is()); + ss->data = ScriptSource::SourceType(ScriptSource::Retrievable()); + } +} + +template +/* static */ +XDRResult StencilXDR::codeSourceData(XDRState* const xdr, + ScriptSource* const ss) { + // The order here corresponds to the type order in |ScriptSource::SourceType| + // so number->internal Variant tag is a no-op. + enum class DataType { + CompressedUtf8Retrievable, + UncompressedUtf8Retrievable, + CompressedUtf8NotRetrievable, + UncompressedUtf8NotRetrievable, + CompressedUtf16Retrievable, + UncompressedUtf16Retrievable, + CompressedUtf16NotRetrievable, + UncompressedUtf16NotRetrievable, + RetrievableUtf8, + RetrievableUtf16, + Missing, + }; + + DataType tag; + { + // This is terrible, but we can't do better. When |mode == XDR_DECODE| we + // don't have a |ScriptSource::data| |Variant| to match -- the entire XDR + // idiom for tagged unions depends on coding a tag-number, then the + // corresponding tagged data. So we must manually define a tag-enum, code + // it, then switch on it (and ignore the |Variant::match| API). + class XDRDataTag { + public: + DataType operator()( + const ScriptSource::Compressed&) { + return DataType::CompressedUtf8Retrievable; + } + DataType operator()( + const ScriptSource::Uncompressed&) { + return DataType::UncompressedUtf8Retrievable; + } + DataType operator()( + const ScriptSource::Compressed&) { + return DataType::CompressedUtf8NotRetrievable; + } + DataType operator()( + const ScriptSource::Uncompressed&) { + return DataType::UncompressedUtf8NotRetrievable; + } + DataType operator()( + const ScriptSource::Compressed&) { + return DataType::CompressedUtf16Retrievable; + } + DataType operator()( + const ScriptSource::Uncompressed&) { + return DataType::UncompressedUtf16Retrievable; + } + DataType operator()( + const ScriptSource::Compressed&) { + return DataType::CompressedUtf16NotRetrievable; + } + DataType operator()( + const ScriptSource::Uncompressed&) { + return DataType::UncompressedUtf16NotRetrievable; + } + DataType operator()(const ScriptSource::Retrievable&) { + return DataType::RetrievableUtf8; + } + DataType operator()(const ScriptSource::Retrievable&) { + return DataType::RetrievableUtf16; + } + DataType operator()(const ScriptSource::Missing&) { + return DataType::Missing; + } + }; + + uint8_t type; + if (mode == XDR_ENCODE) { + type = static_cast(ss->data.match(XDRDataTag())); + } + MOZ_TRY(xdr->codeUint8(&type)); + + if (type > static_cast(DataType::Missing)) { + // Fail in debug, but only soft-fail in release, if the type is invalid. + MOZ_ASSERT_UNREACHABLE("bad tag"); + return xdr->fail(JS::TranscodeResult::Failure_BadDecode); + } + + tag = static_cast(type); + } + + switch (tag) { + case DataType::CompressedUtf8Retrievable: + codeSourceRetrievable(ss); + return Ok(); + + case DataType::CompressedUtf8NotRetrievable: + return codeSourceCompressedData(xdr, ss); + + case DataType::UncompressedUtf8Retrievable: + codeSourceRetrievable(ss); + return Ok(); + + case DataType::UncompressedUtf8NotRetrievable: + return codeSourceUncompressedData(xdr, ss); + + case DataType::CompressedUtf16Retrievable: + codeSourceRetrievable(ss); + return Ok(); + + case DataType::CompressedUtf16NotRetrievable: + return codeSourceCompressedData(xdr, ss); + + case DataType::UncompressedUtf16Retrievable: + codeSourceRetrievable(ss); + return Ok(); + + case DataType::UncompressedUtf16NotRetrievable: + return codeSourceUncompressedData(xdr, ss); + + case DataType::Missing: { + MOZ_ASSERT(ss->data.is(), + "ScriptSource::data is initialized as missing, so neither " + "encoding nor decoding has to change anything"); + + // There's no data to XDR for missing source. + break; + } + + case DataType::RetrievableUtf8: + codeSourceRetrievableData(ss); + return Ok(); + + case DataType::RetrievableUtf16: + codeSourceRetrievableData(ss); + return Ok(); + } + + // The range-check on |type| far above ought ensure the above |switch| is + // exhaustive and all cases will return, but not all compilers understand + // this. Make the Missing case break to here so control obviously never flows + // off the end. + MOZ_ASSERT(tag == DataType::Missing); + return Ok(); +} + +template +/* static */ +XDRResult StencilXDR::codeSource(XDRState* xdr, + const JS::DecodeOptions* maybeOptions, + RefPtr& source) { + FrontendContext* fc = xdr->fc(); + + if (mode == XDR_DECODE) { + // Allocate a new ScriptSource and root it with the holder. + source = do_AddRef(fc->getAllocator()->new_()); + if (!source) { + return xdr->fail(JS::TranscodeResult::Throw); + } + } + + static constexpr uint8_t HasFilename = 1 << 0; + static constexpr uint8_t HasDisplayURL = 1 << 1; + static constexpr uint8_t HasSourceMapURL = 1 << 2; + static constexpr uint8_t MutedErrors = 1 << 3; + + uint8_t flags = 0; + if (mode == XDR_ENCODE) { + if (source->filename_) { + flags |= HasFilename; + } + if (source->hasDisplayURL()) { + flags |= HasDisplayURL; + } + if (source->hasSourceMapURL()) { + flags |= HasSourceMapURL; + } + if (source->mutedErrors()) { + flags |= MutedErrors; + } + } + + MOZ_TRY(xdr->codeUint8(&flags)); + + if (flags & HasFilename) { + XDRTranscodeString chars; + + if (mode == XDR_ENCODE) { + chars.construct(source->filename()); + } + MOZ_TRY(xdr->codeCharsZ(chars)); + if (mode == XDR_DECODE) { + if (!source->setFilename(fc, std::move(chars.ref()))) { + return xdr->fail(JS::TranscodeResult::Throw); + } + } + } + + if (flags & HasDisplayURL) { + XDRTranscodeString chars; + + if (mode == XDR_ENCODE) { + chars.construct(source->displayURL()); + } + MOZ_TRY(xdr->codeCharsZ(chars)); + if (mode == XDR_DECODE) { + if (!source->setDisplayURL(fc, + std::move(chars.ref()))) { + return xdr->fail(JS::TranscodeResult::Throw); + } + } + } + + if (flags & HasSourceMapURL) { + XDRTranscodeString chars; + + if (mode == XDR_ENCODE) { + chars.construct(source->sourceMapURL()); + } + MOZ_TRY(xdr->codeCharsZ(chars)); + if (mode == XDR_DECODE) { + if (!source->setSourceMapURL( + fc, std::move(chars.ref()))) { + return xdr->fail(JS::TranscodeResult::Throw); + } + } + } + + MOZ_ASSERT(source->parameterListEnd_ == 0); + + if (flags & MutedErrors) { + if (mode == XDR_DECODE) { + source->mutedErrors_ = true; + } + } + + MOZ_TRY(xdr->codeUint32(&source->startLine_)); + MOZ_TRY(xdr->codeUint32(&source->startColumn_)); + + // The introduction info doesn't persist across encode/decode. + if (mode == XDR_DECODE) { + source->introductionType_ = maybeOptions->introductionType; + source->setIntroductionOffset(maybeOptions->introductionOffset); + if (maybeOptions->introducerFilename) { + if (!source->setIntroducerFilename(fc, + maybeOptions->introducerFilename)) { + return xdr->fail(JS::TranscodeResult::Throw); + } + } + } + + MOZ_TRY(codeSourceData(xdr, source.get())); + + return Ok(); +} + +template /* static */ + XDRResult + StencilXDR::codeSource(XDRState* xdr, + const JS::DecodeOptions* maybeOptions, + RefPtr& holder); +template /* static */ + XDRResult + StencilXDR::codeSource(XDRState* xdr, + const JS::DecodeOptions* maybeOptions, + RefPtr& holder); + +JS_PUBLIC_API bool JS::GetScriptTranscodingBuildId( + JS::BuildIdCharVector* buildId) { + MOZ_ASSERT(buildId->empty()); + MOZ_ASSERT(GetBuildId); + + if (!GetBuildId(buildId)) { + return false; + } + + // Note: the buildId returned here is also used for the bytecode cache MIME + // type so use plain ASCII characters. + + if (!buildId->reserve(buildId->length() + 4)) { + return false; + } + + buildId->infallibleAppend('-'); + + // XDR depends on pointer size and endianness. + static_assert(sizeof(uintptr_t) == 4 || sizeof(uintptr_t) == 8); + buildId->infallibleAppend(sizeof(uintptr_t) == 4 ? '4' : '8'); + buildId->infallibleAppend(MOZ_LITTLE_ENDIAN() ? 'l' : 'b'); + + return true; +} + +template +static XDRResult VersionCheck(XDRState* xdr) { + JS::BuildIdCharVector buildId; + if (!JS::GetScriptTranscodingBuildId(&buildId)) { + ReportOutOfMemory(xdr->fc()); + return xdr->fail(JS::TranscodeResult::Throw); + } + MOZ_ASSERT(!buildId.empty()); + + uint32_t buildIdLength; + if (mode == XDR_ENCODE) { + buildIdLength = buildId.length(); + } + + MOZ_TRY(xdr->codeUint32(&buildIdLength)); + + if (mode == XDR_DECODE && buildIdLength != buildId.length()) { + return xdr->fail(JS::TranscodeResult::Failure_BadBuildId); + } + + if (mode == XDR_ENCODE) { + MOZ_TRY(xdr->codeBytes(buildId.begin(), buildIdLength)); + } else { + JS::BuildIdCharVector decodedBuildId; + + // buildIdLength is already checked against the length of current + // buildId. + if (!decodedBuildId.resize(buildIdLength)) { + ReportOutOfMemory(xdr->fc()); + return xdr->fail(JS::TranscodeResult::Throw); + } + + MOZ_TRY(xdr->codeBytes(decodedBuildId.begin(), buildIdLength)); + + // We do not provide binary compatibility with older scripts. + if (!mozilla::ArrayEqual(decodedBuildId.begin(), buildId.begin(), + buildIdLength)) { + return xdr->fail(JS::TranscodeResult::Failure_BadBuildId); + } + } + + return Ok(); +} + +XDRResult XDRStencilEncoder::codeStencil( + const RefPtr& source, + const frontend::CompilationStencil& stencil) { +#ifdef DEBUG + auto sanityCheck = mozilla::MakeScopeExit( + [&] { MOZ_ASSERT(validateResultCode(fc(), resultCode())); }); +#endif + + MOZ_TRY(frontend::StencilXDR::checkCompilationStencil(this, stencil)); + + MOZ_TRY(VersionCheck(this)); + + uint32_t dummy = 0; + size_t lengthOffset = buf->cursor(); + MOZ_TRY(codeUint32(&dummy)); + size_t hashOffset = buf->cursor(); + MOZ_TRY(codeUint32(&dummy)); + + size_t contentOffset = buf->cursor(); + MOZ_TRY(frontend::StencilXDR::codeSource( + this, nullptr, const_cast&>(source))); + MOZ_TRY(frontend::StencilXDR::codeCompilationStencil( + this, const_cast(stencil))); + size_t endOffset = buf->cursor(); + + if (endOffset > UINT32_MAX) { + ReportOutOfMemory(fc()); + return fail(JS::TranscodeResult::Throw); + } + + uint32_t length = endOffset - contentOffset; + codeUint32At(&length, lengthOffset); + + const uint8_t* contentBegin = buf->bufferAt(contentOffset); + uint32_t hash = mozilla::HashBytes(contentBegin, length); + codeUint32At(&hash, hashOffset); + + return Ok(); +} + +XDRResult XDRStencilEncoder::codeStencil( + const frontend::CompilationStencil& stencil) { + return codeStencil(stencil.source, stencil); +} + +void StencilIncrementalEncoderPtr::reset() { + if (merger_) { + js_delete(merger_); + } + merger_ = nullptr; +} + +bool StencilIncrementalEncoderPtr::setInitial( + JSContext* cx, + UniquePtr&& initial) { + AutoReportFrontendContext fc(cx); + merger_ = fc.getAllocator()->new_(); + if (!merger_) { + return false; + } + + return merger_->setInitial( + &fc, + std::forward>(initial)); +} + +bool StencilIncrementalEncoderPtr::addDelazification( + JSContext* cx, const frontend::CompilationStencil& delazification) { + AutoReportFrontendContext fc(cx); + return merger_->addDelazification(&fc, delazification); +} + +XDRResult XDRStencilDecoder::codeStencil( + const JS::DecodeOptions& options, frontend::CompilationStencil& stencil) { +#ifdef DEBUG + auto sanityCheck = mozilla::MakeScopeExit( + [&] { MOZ_ASSERT(validateResultCode(fc(), resultCode())); }); +#endif + + auto resetOptions = mozilla::MakeScopeExit([&] { options_ = nullptr; }); + options_ = &options; + + MOZ_TRY(VersionCheck(this)); + + uint32_t length; + MOZ_TRY(codeUint32(&length)); + + uint32_t hash; + MOZ_TRY(codeUint32(&hash)); + + const uint8_t* contentBegin; + MOZ_TRY(peekArray(length, &contentBegin)); + uint32_t actualHash = mozilla::HashBytes(contentBegin, length); + + if (MOZ_UNLIKELY(actualHash != hash)) { + return fail(JS::TranscodeResult::Failure_BadDecode); + } + + MOZ_TRY(frontend::StencilXDR::codeSource(this, &options, stencil.source)); + MOZ_TRY(frontend::StencilXDR::codeCompilationStencil(this, stencil)); + + return Ok(); +} + +template /* static */ XDRResult StencilXDR::codeCompilationStencil( + XDRState* xdr, CompilationStencil& stencil); + +template /* static */ XDRResult StencilXDR::codeCompilationStencil( + XDRState* xdr, CompilationStencil& stencil); + +/* static */ XDRResult StencilXDR::checkCompilationStencil( + XDRStencilEncoder* encoder, const CompilationStencil& stencil) { + if (stencil.asmJS) { + return encoder->fail(JS::TranscodeResult::Failure_AsmJSNotSupported); + } + + return Ok(); +} + +/* static */ XDRResult StencilXDR::checkCompilationStencil( + const ExtensibleCompilationStencil& stencil) { + if (stencil.asmJS) { + return mozilla::Err(JS::TranscodeResult::Failure_AsmJSNotSupported); + } + + return Ok(); +} diff --git a/js/src/frontend/StencilXdr.h b/js/src/frontend/StencilXdr.h new file mode 100644 index 0000000000..a07e7787fa --- /dev/null +++ b/js/src/frontend/StencilXdr.h @@ -0,0 +1,217 @@ +/* -*- 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/. */ + +#ifndef frontend_StencilXdr_h +#define frontend_StencilXdr_h + +#include "mozilla/RefPtr.h" // RefPtr + +#include "frontend/ParserAtom.h" // ParserAtom, ParserAtomSpan +#include "frontend/Stencil.h" // BitIntStencil, ScopeStencil, BaseParserScopeData +#include "vm/Xdr.h" // XDRMode, XDRResult, XDRState + +namespace JS { + +class DecodeOptions; + +} // namespace JS + +namespace js { + +class LifoAlloc; +class ObjLiteralStencil; +class ScriptSource; +class SharedImmutableScriptData; + +class XDRStencilDecoder; +class XDRStencilEncoder; + +namespace frontend { + +struct CompilationStencil; +struct ExtensibleCompilationStencil; +struct SharedDataContainer; + +// Check that we can copy data to disk and restore it in another instance of +// the program in a different address space. +template +struct CanCopyDataToDisk { + // Check that the object is fully packed, to save disk space. +#ifdef __cpp_lib_has_unique_object_representations + static constexpr bool unique_repr = + std::has_unique_object_representations(); +#else + static constexpr bool unique_repr = true; +#endif + + // Approximation which assumes that 32bits variant of the class would not + // have pointers if the 64bits variant does not have pointer. + static constexpr bool no_pointer = + alignof(DataT) < alignof(void*) || sizeof(void*) == sizeof(uint32_t); + + static constexpr bool value = unique_repr && no_pointer; +}; + +// This is just a namespace class that can be used in friend declarations, +// so that the statically declared XDR methods within have access to the +// relevant struct internals. +class StencilXDR { + private: + template + [[nodiscard]] static XDRResult codeSourceUnretrievableUncompressed( + XDRState* xdr, ScriptSource* ss, uint8_t sourceCharSize, + uint32_t uncompressedLength); + + template class Data, + XDRMode mode> + static void codeSourceRetrievable(ScriptSource* ss); + + template + [[nodiscard]] static XDRResult codeSourceUncompressedData( + XDRState* const xdr, ScriptSource* const ss); + + template + [[nodiscard]] static XDRResult codeSourceCompressedData( + XDRState* const xdr, ScriptSource* const ss); + + template + static void codeSourceRetrievableData(ScriptSource* ss); + + template + [[nodiscard]] static XDRResult codeSourceData(XDRState* const xdr, + ScriptSource* const ss); + + public: + template + static XDRResult codeSource(XDRState* xdr, + const JS::DecodeOptions* maybeOptions, + RefPtr& source); + + template + static XDRResult codeBigInt(XDRState* xdr, LifoAlloc& alloc, + BigIntStencil& stencil); + + template + static XDRResult codeObjLiteral(XDRState* xdr, LifoAlloc& alloc, + ObjLiteralStencil& stencil); + + template + static XDRResult codeScopeData(XDRState* xdr, LifoAlloc& alloc, + ScopeStencil& stencil, + BaseParserScopeData*& baseScopeData); + + template + static XDRResult codeSharedData(XDRState* xdr, + RefPtr& sisd); + + template + static XDRResult codeSharedDataContainer(XDRState* xdr, + SharedDataContainer& sharedData); + + template + static XDRResult codeParserAtom(XDRState* xdr, LifoAlloc& alloc, + ParserAtom** atomp); + + template + static XDRResult codeParserAtomSpan(XDRState* xdr, LifoAlloc& alloc, + ParserAtomSpan& parserAtomData); + + template + static XDRResult codeModuleRequest(XDRState* xdr, + StencilModuleRequest& stencil); + + template + static XDRResult codeModuleRequestVector( + XDRState* xdr, StencilModuleMetadata::RequestVector& vector); + + template + static XDRResult codeModuleEntry(XDRState* xdr, + StencilModuleEntry& stencil); + + template + static XDRResult codeModuleEntryVector( + XDRState* xdr, StencilModuleMetadata::EntryVector& vector); + + template + static XDRResult codeModuleMetadata(XDRState* xdr, + StencilModuleMetadata& stencil); + + static XDRResult checkCompilationStencil(XDRStencilEncoder* encoder, + const CompilationStencil& stencil); + + static XDRResult checkCompilationStencil( + const ExtensibleCompilationStencil& stencil); + + template + static XDRResult codeCompilationStencil(XDRState* xdr, + CompilationStencil& stencil); +}; + +} /* namespace frontend */ + +/* + * The structure of the Stencil XDR buffer is: + * + * 1. Version + * 2. length of content + * 3. checksum of content + * 4. content + * a. ScriptSource + * b. CompilationStencil + */ + +/* + * The stencil decoder accepts `range` as input. + * + * The decoded stencils are outputted to the default-initialized + * `stencil` parameter of `codeStencil` method. + * + * The decoded stencils borrow the input `buffer`/`range`, and the consumer + * has to keep the buffer alive while the decoded stencils are alive. + */ +class XDRStencilDecoder : public XDRState { + using Base = XDRState; + + public: + XDRStencilDecoder(FrontendContext* fc, const JS::TranscodeRange& range) + : Base(fc, range) { + MOZ_ASSERT(JS::IsTranscodingBytecodeAligned(range.begin().get())); + } + + XDRResult codeStencil(const JS::DecodeOptions& options, + frontend::CompilationStencil& stencil); + + const JS::DecodeOptions& options() { + MOZ_ASSERT(options_); + return *options_; + } + + private: + const JS::DecodeOptions* options_ = nullptr; +}; + +class XDRStencilEncoder : public XDRState { + using Base = XDRState; + + public: + XDRStencilEncoder(FrontendContext* fc, JS::TranscodeBuffer& buffer) + : Base(fc, buffer, buffer.length()) { + // NOTE: If buffer is empty, buffer.begin() doesn't point valid buffer. + MOZ_ASSERT_IF(!buffer.empty(), + JS::IsTranscodingBytecodeAligned(buffer.begin())); + MOZ_ASSERT(JS::IsTranscodingBytecodeOffsetAligned(buffer.length())); + } + + XDRResult codeStencil(const RefPtr& source, + const frontend::CompilationStencil& stencil); + + XDRResult codeStencil(const frontend::CompilationStencil& stencil); +}; + +} /* namespace js */ + +#endif /* frontend_StencilXdr_h */ diff --git a/js/src/frontend/SwitchEmitter.cpp b/js/src/frontend/SwitchEmitter.cpp new file mode 100644 index 0000000000..9c3ad7aa7d --- /dev/null +++ b/js/src/frontend/SwitchEmitter.cpp @@ -0,0 +1,414 @@ +/* -*- 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/SwitchEmitter.h" + +#include "mozilla/Assertions.h" // MOZ_ASSERT +#include "mozilla/Span.h" // mozilla::Span + +#include // std::min, std::max + +#include "jstypes.h" // JS_BIT + +#include "frontend/BytecodeEmitter.h" // BytecodeEmitter +#include "frontend/SharedContext.h" // StatementKind +#include "js/friend/ErrorMessages.h" // JSMSG_* +#include "js/TypeDecls.h" // jsbytecode +#include "util/BitArray.h" +#include "vm/BytecodeUtil.h" // SET_JUMP_OFFSET, JUMP_OFFSET_LEN, SET_RESUMEINDEX +#include "vm/Opcodes.h" // JSOp, JSOpLength_TableSwitch +#include "vm/Runtime.h" // ReportOutOfMemory + +using namespace js; +using namespace js::frontend; + +bool SwitchEmitter::TableGenerator::addNumber(int32_t caseValue) { + if (isInvalid()) { + return true; + } + + if (unsigned(caseValue + int(Bit(15))) >= unsigned(Bit(16))) { + setInvalid(); + return true; + } + + if (intmap_.isNothing()) { + intmap_.emplace(); + } + + low_ = std::min(low_, caseValue); + high_ = std::max(high_, caseValue); + + // Check for duplicates, which are not supported in a table switch. + // We bias caseValue by 65536 if it's negative, and hope that's a rare case + // (because it requires a malloc'd bitmap). + if (caseValue < 0) { + caseValue += Bit(16); + } + if (caseValue >= intmapBitLength_) { + size_t newLength = NumWordsForBitArrayOfLength(caseValue + 1); + if (!intmap_->resize(newLength)) { + ReportOutOfMemory(bce_->fc); + return false; + } + intmapBitLength_ = newLength * BitArrayElementBits; + } + if (IsBitArrayElementSet(intmap_->begin(), intmap_->length(), caseValue)) { + // Duplicate entry is not supported in table switch. + setInvalid(); + return true; + } + SetBitArrayElement(intmap_->begin(), intmap_->length(), caseValue); + return true; +} + +void SwitchEmitter::TableGenerator::finish(uint32_t caseCount) { + intmap_.reset(); + +#ifdef DEBUG + finished_ = true; +#endif + + if (isInvalid()) { + return; + } + + if (caseCount == 0) { + low_ = 0; + high_ = -1; + return; + } + + // Compute table length. Don't use table switch if overlarge or more than + // half-sparse. + tableLength_ = uint32_t(high_ - low_ + 1); + if (tableLength_ >= Bit(16) || tableLength_ > 2 * caseCount) { + setInvalid(); + } +} + +uint32_t SwitchEmitter::TableGenerator::toCaseIndex(int32_t caseValue) const { + MOZ_ASSERT(finished_); + MOZ_ASSERT(isValid()); + uint32_t caseIndex = uint32_t(caseValue - low_); + MOZ_ASSERT(caseIndex < tableLength_); + return caseIndex; +} + +uint32_t SwitchEmitter::TableGenerator::tableLength() const { + MOZ_ASSERT(finished_); + MOZ_ASSERT(isValid()); + return tableLength_; +} + +SwitchEmitter::SwitchEmitter(BytecodeEmitter* bce) : bce_(bce) {} + +bool SwitchEmitter::emitDiscriminant(uint32_t switchPos) { + MOZ_ASSERT(state_ == State::Start); + switchPos_ = switchPos; + + // Ensure that the column of the switch statement is set properly. + if (!bce_->updateSourceCoordNotes(switchPos_)) { + return false; + } + + state_ = State::Discriminant; + return true; +} + +bool SwitchEmitter::emitLexical(LexicalScope::ParserData* bindings) { + MOZ_ASSERT(state_ == State::Discriminant); + MOZ_ASSERT(bindings); + + tdzCacheLexical_.emplace(bce_); + emitterScope_.emplace(bce_); + if (!emitterScope_->enterLexical(bce_, ScopeKind::Lexical, bindings)) { + return false; + } + + state_ = State::Lexical; + return true; +} + +bool SwitchEmitter::validateCaseCount(uint32_t caseCount) { + MOZ_ASSERT(state_ == State::Discriminant || state_ == State::Lexical); + if (caseCount > Bit(16)) { + bce_->reportError(switchPos_, JSMSG_TOO_MANY_CASES); + return false; + } + caseCount_ = caseCount; + + state_ = State::CaseCount; + return true; +} + +bool SwitchEmitter::emitCond() { + MOZ_ASSERT(state_ == State::CaseCount); + + kind_ = Kind::Cond; + + // After entering the scope if necessary, push the switch control. + controlInfo_.emplace(bce_, StatementKind::Switch); + top_ = bce_->bytecodeSection().offset(); + + if (!caseOffsets_.resize(caseCount_)) { + ReportOutOfMemory(bce_->fc); + return false; + } + + MOZ_ASSERT(top_ == bce_->bytecodeSection().offset()); + + tdzCacheCaseAndBody_.emplace(bce_); + + state_ = State::Cond; + return true; +} + +bool SwitchEmitter::emitTable(const TableGenerator& tableGen) { + MOZ_ASSERT(state_ == State::CaseCount); + kind_ = Kind::Table; + + // After entering the scope if necessary, push the switch control. + controlInfo_.emplace(bce_, StatementKind::Switch); + top_ = bce_->bytecodeSection().offset(); + + if (!caseOffsets_.resize(tableGen.tableLength())) { + ReportOutOfMemory(bce_->fc); + return false; + } + + MOZ_ASSERT(top_ == bce_->bytecodeSection().offset()); + if (!bce_->emitN(JSOp::TableSwitch, + JSOpLength_TableSwitch - sizeof(jsbytecode))) { + return false; + } + + // Skip default offset. + jsbytecode* pc = + bce_->bytecodeSection().code(top_ + BytecodeOffsetDiff(JUMP_OFFSET_LEN)); + + // Fill in switch bounds, which we know fit in 16-bit offsets. + SET_JUMP_OFFSET(pc, tableGen.low()); + SET_JUMP_OFFSET(pc + JUMP_OFFSET_LEN, tableGen.high()); + + state_ = State::Table; + return true; +} + +bool SwitchEmitter::emitCaseOrDefaultJump(uint32_t caseIndex, bool isDefault) { + MOZ_ASSERT(kind_ == Kind::Cond); + + if (isDefault) { + if (!bce_->emitJump(JSOp::Default, &condSwitchDefaultOffset_)) { + return false; + } + return true; + } + + JumpList caseJump; + if (!bce_->emitJump(JSOp::Case, &caseJump)) { + return false; + } + caseOffsets_[caseIndex] = caseJump.offset; + lastCaseOffset_ = caseJump.offset; + + return true; +} + +bool SwitchEmitter::prepareForCaseValue() { + MOZ_ASSERT(kind_ == Kind::Cond); + MOZ_ASSERT(state_ == State::Cond || state_ == State::Case); + + if (!bce_->emit1(JSOp::Dup)) { + return false; + } + + state_ = State::CaseValue; + return true; +} + +bool SwitchEmitter::emitCaseJump() { + MOZ_ASSERT(kind_ == Kind::Cond); + MOZ_ASSERT(state_ == State::CaseValue); + + if (!bce_->emit1(JSOp::StrictEq)) { + return false; + } + + if (!emitCaseOrDefaultJump(caseIndex_, false)) { + return false; + } + caseIndex_++; + + state_ = State::Case; + return true; +} + +bool SwitchEmitter::emitImplicitDefault() { + MOZ_ASSERT(kind_ == Kind::Cond); + MOZ_ASSERT(state_ == State::Cond || state_ == State::Case); + if (!emitCaseOrDefaultJump(0, true)) { + return false; + } + + caseIndex_ = 0; + + // No internal state after emitting default jump. + return true; +} + +bool SwitchEmitter::emitCaseBody() { + MOZ_ASSERT(kind_ == Kind::Cond); + MOZ_ASSERT(state_ == State::Cond || state_ == State::Case || + state_ == State::CaseBody || state_ == State::DefaultBody); + + tdzCacheCaseAndBody_.reset(); + + if (state_ == State::Cond || state_ == State::Case) { + // For cond switch, JSOp::Default is always emitted. + if (!emitImplicitDefault()) { + return false; + } + } + + JumpList caseJump; + caseJump.offset = caseOffsets_[caseIndex_]; + if (!bce_->emitJumpTargetAndPatch(caseJump)) { + return false; + } + + JumpTarget here; + if (!bce_->emitJumpTarget(&here)) { + return false; + } + caseIndex_++; + + tdzCacheCaseAndBody_.emplace(bce_); + + state_ = State::CaseBody; + return true; +} + +bool SwitchEmitter::emitCaseBody(int32_t caseValue, + const TableGenerator& tableGen) { + MOZ_ASSERT(kind_ == Kind::Table); + MOZ_ASSERT(state_ == State::Table || state_ == State::CaseBody || + state_ == State::DefaultBody); + + tdzCacheCaseAndBody_.reset(); + + JumpTarget here; + if (!bce_->emitJumpTarget(&here)) { + return false; + } + caseOffsets_[tableGen.toCaseIndex(caseValue)] = here.offset; + + tdzCacheCaseAndBody_.emplace(bce_); + + state_ = State::CaseBody; + return true; +} + +bool SwitchEmitter::emitDefaultBody() { + MOZ_ASSERT(state_ == State::Cond || state_ == State::Table || + state_ == State::Case || state_ == State::CaseBody); + MOZ_ASSERT(!hasDefault_); + + tdzCacheCaseAndBody_.reset(); + + if (state_ == State::Cond || state_ == State::Case) { + // For cond switch, JSOp::Default is always emitted. + if (!emitImplicitDefault()) { + return false; + } + } + JumpTarget here; + if (!bce_->emitJumpTarget(&here)) { + return false; + } + defaultJumpTargetOffset_ = here; + + tdzCacheCaseAndBody_.emplace(bce_); + + hasDefault_ = true; + state_ = State::DefaultBody; + return true; +} + +bool SwitchEmitter::emitEnd() { + MOZ_ASSERT(state_ == State::Cond || state_ == State::Table || + state_ == State::CaseBody || state_ == State::DefaultBody); + + tdzCacheCaseAndBody_.reset(); + + if (!hasDefault_) { + // If no default case, offset for default is to end of switch. + if (!bce_->emitJumpTarget(&defaultJumpTargetOffset_)) { + return false; + } + } + MOZ_ASSERT(defaultJumpTargetOffset_.offset.valid()); + + // Set the default offset (to end of switch if no default). + jsbytecode* pc; + if (kind_ == Kind::Cond) { + pc = nullptr; + bce_->patchJumpsToTarget(condSwitchDefaultOffset_, + defaultJumpTargetOffset_); + } else { + // Fill in the default jump target. + pc = bce_->bytecodeSection().code(top_); + SET_JUMP_OFFSET(pc, (defaultJumpTargetOffset_.offset - top_).value()); + pc += JUMP_OFFSET_LEN; + } + + if (kind_ == Kind::Table) { + // Skip over the already-initialized switch bounds. + pc += 2 * JUMP_OFFSET_LEN; + + // Use the 'default' offset for missing cases. + for (uint32_t i = 0, length = caseOffsets_.length(); i < length; i++) { + if (caseOffsets_[i].value() == 0) { + caseOffsets_[i] = defaultJumpTargetOffset_.offset; + } + } + + // Allocate resume index range. + uint32_t firstResumeIndex = 0; + mozilla::Span offsets = + mozilla::Span(caseOffsets_.begin(), caseOffsets_.end()); + if (!bce_->allocateResumeIndexRange(offsets, &firstResumeIndex)) { + return false; + } + SET_RESUMEINDEX(pc, firstResumeIndex); + } + + // Patch breaks before leaving the scope, as all breaks are under the + // lexical scope if it exists. + if (!controlInfo_->patchBreaks(bce_)) { + return false; + } + + if (emitterScope_ && !emitterScope_->leave(bce_)) { + return false; + } + + emitterScope_.reset(); + tdzCacheLexical_.reset(); + + controlInfo_.reset(); + + state_ = State::End; + return true; +} + +InternalSwitchEmitter::InternalSwitchEmitter(BytecodeEmitter* bce) + : SwitchEmitter(bce) { +#ifdef DEBUG + // Skip emitDiscriminant (see the comment above InternalSwitchEmitter) + state_ = State::Discriminant; +#endif +} diff --git a/js/src/frontend/SwitchEmitter.h b/js/src/frontend/SwitchEmitter.h new file mode 100644 index 0000000000..59d45fc71c --- /dev/null +++ b/js/src/frontend/SwitchEmitter.h @@ -0,0 +1,474 @@ +/* -*- 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/. */ + +#ifndef frontend_SwitchEmitter_h +#define frontend_SwitchEmitter_h + +#include "mozilla/Assertions.h" // MOZ_ASSERT +#include "mozilla/Attributes.h" // MOZ_STACK_CLASS +#include "mozilla/Maybe.h" // mozilla::Maybe + +#include // size_t +#include // int32_t, uint32_t + +#include "frontend/BytecodeControlStructures.h" // BreakableControl +#include "frontend/EmitterScope.h" // EmitterScope +#include "frontend/JumpList.h" // JumpList, JumpTarget +#include "frontend/TDZCheckCache.h" // TDZCheckCache +#include "js/AllocPolicy.h" // SystemAllocPolicy +#include "js/Value.h" // JSVAL_INT_MAX, JSVAL_INT_MIN +#include "js/Vector.h" // Vector +#include "vm/Scope.h" // LexicalScope + +namespace js { +namespace frontend { + +struct BytecodeEmitter; + +// Class for emitting bytecode for switch-case-default block. +// +// Usage: (check for the return value is omitted for simplicity) +// +// `switch (discriminant) { case c1_expr: c1_body; }` +// SwitchEmitter se(this); +// se.emitDiscriminant(offset_of_switch); +// emit(discriminant); +// +// se.validateCaseCount(1); +// se.emitCond(); +// +// se.prepareForCaseValue(); +// emit(c1_expr); +// se.emitCaseJump(); +// +// se.emitCaseBody(); +// emit(c1_body); +// +// se.emitEnd(); +// +// `switch (discriminant) { case c1_expr: c1_body; case c2_expr: c2_body; +// default: def_body; }` +// SwitchEmitter se(this); +// se.emitDiscriminant(offset_of_switch); +// emit(discriminant); +// +// se.validateCaseCount(2); +// se.emitCond(); +// +// se.prepareForCaseValue(); +// emit(c1_expr); +// se.emitCaseJump(); +// +// se.prepareForCaseValue(); +// emit(c2_expr); +// se.emitCaseJump(); +// +// se.emitCaseBody(); +// emit(c1_body); +// +// se.emitCaseBody(); +// emit(c2_body); +// +// se.emitDefaultBody(); +// emit(def_body); +// +// se.emitEnd(); +// +// `switch (discriminant) { case c1_expr: c1_body; case c2_expr: c2_body; }` +// with Table Switch +// SwitchEmitter::TableGenerator tableGen(this); +// tableGen.addNumber(c1_expr_value); +// tableGen.addNumber(c2_expr_value); +// tableGen.finish(2); +// +// // If `!tableGen.isValid()` here, `emitCond` should be used instead. +// +// SwitchEmitter se(this); +// se.emitDiscriminant(offset_of_switch); +// emit(discriminant); +// se.validateCaseCount(2); +// se.emitTable(tableGen); +// +// se.emitCaseBody(c1_expr_value, tableGen); +// emit(c1_body); +// +// se.emitCaseBody(c2_expr_value, tableGen); +// emit(c2_body); +// +// se.emitEnd(); +// +// `switch (discriminant) { case c1_expr: c1_body; case c2_expr: c2_body; +// default: def_body; }` +// with Table Switch +// SwitchEmitter::TableGenerator tableGen(bce); +// tableGen.addNumber(c1_expr_value); +// tableGen.addNumber(c2_expr_value); +// tableGen.finish(2); +// +// // If `!tableGen.isValid()` here, `emitCond` should be used instead. +// +// SwitchEmitter se(this); +// se.emitDiscriminant(offset_of_switch); +// emit(discriminant); +// se.validateCaseCount(2); +// se.emitTable(tableGen); +// +// se.emitCaseBody(c1_expr_value, tableGen); +// emit(c1_body); +// +// se.emitCaseBody(c2_expr_value, tableGen); +// emit(c2_body); +// +// se.emitDefaultBody(); +// emit(def_body); +// +// se.emitEnd(); +// +// `switch (discriminant) { case c1_expr: c1_body; }` +// in case c1_body contains lexical bindings +// SwitchEmitter se(this); +// se.emitDiscriminant(offset_of_switch); +// emit(discriminant); +// +// se.validateCaseCount(1); +// +// se.emitLexical(bindings); +// +// se.emitCond(); +// +// se.prepareForCaseValue(); +// emit(c1_expr); +// se.emitCaseJump(); +// +// se.emitCaseBody(); +// emit(c1_body); +// +// se.emitEnd(); +// +// `switch (discriminant) { case c1_expr: c1_body; }` +// in case c1_body contains hosted functions +// SwitchEmitter se(this); +// se.emitDiscriminant(offset_of_switch); +// emit(discriminant); +// +// se.validateCaseCount(1); +// +// se.emitLexical(bindings); +// emit(hosted functions); +// +// se.emitCond(); +// +// se.prepareForCaseValue(); +// emit(c1_expr); +// se.emitCaseJump(); +// +// se.emitCaseBody(); +// emit(c1_body); +// +// se.emitEnd(); +// +class MOZ_STACK_CLASS SwitchEmitter { + // Bytecode for each case. + // + // Cond Switch (uses an equality comparison for each case) + // {discriminant} + // + // {c1_expr} + // JSOp::Case c1 + // + // JSOp::JumpTarget + // {c2_expr} + // JSOp::Case c2 + // + // ... + // + // JSOp::JumpTarget + // JSOp::Default default + // + // c1: + // JSOp::JumpTarget + // {c1_body} + // JSOp::Goto end + // + // c2: + // JSOp::JumpTarget + // {c2_body} + // JSOp::Goto end + // + // default: + // end: + // JSOp::JumpTarget + // + // Table Switch + // {discriminant} + // JSOp::TableSwitch c1, c2, ... + // + // c1: + // JSOp::JumpTarget + // {c1_body} + // JSOp::Goto end + // + // c2: + // JSOp::JumpTarget + // {c2_body} + // JSOp::Goto end + // + // ... + // + // end: + // JSOp::JumpTarget + + public: + enum class Kind { Table, Cond }; + + // Class for generating optimized table switch data. + class MOZ_STACK_CLASS TableGenerator { + BytecodeEmitter* bce_; + + // Bit array for given numbers. + mozilla::Maybe> intmap_; + + // The length of the intmap_. + int32_t intmapBitLength_ = 0; + + // The length of the table. + uint32_t tableLength_ = 0; + + // The lower and higher bounds of the table. + int32_t low_ = JSVAL_INT_MAX, high_ = JSVAL_INT_MIN; + + // Whether the table is still valid. + bool valid_ = true; + +#ifdef DEBUG + bool finished_ = false; +#endif + + public: + explicit TableGenerator(BytecodeEmitter* bce) : bce_(bce) {} + + void setInvalid() { valid_ = false; } + [[nodiscard]] bool isValid() const { return valid_; } + [[nodiscard]] bool isInvalid() const { return !valid_; } + + // Add the given number to the table. The number is the value of + // `expr` for `case expr:` syntax. + [[nodiscard]] bool addNumber(int32_t caseValue); + + // Finish generating the table. + // `caseCount` should be the number of cases in the switch statement, + // excluding the default case. + void finish(uint32_t caseCount); + + private: + friend SwitchEmitter; + + // The following methods can be used only after calling `finish`. + + // Returns the lower bound of the added numbers. + int32_t low() const { + MOZ_ASSERT(finished_); + return low_; + } + + // Returns the higher bound of the numbers. + int32_t high() const { + MOZ_ASSERT(finished_); + return high_; + } + + // Returns the index in SwitchEmitter.caseOffsets_ for table switch. + uint32_t toCaseIndex(int32_t caseValue) const; + + // Returns the length of the table. + // This method can be called only if `isValid()` is true. + uint32_t tableLength() const; + }; + + private: + BytecodeEmitter* bce_; + + // `kind_` should be set to the correct value in emitCond/emitTable. + Kind kind_ = Kind::Cond; + + // True if there's explicit default case. + bool hasDefault_ = false; + + // The number of cases in the switch statement, excluding the default case. + uint32_t caseCount_ = 0; + + // Internal index for case jump and case body, used by cond switch. + uint32_t caseIndex_ = 0; + + // Bytecode offset after emitting `discriminant`. + BytecodeOffset top_; + + // Bytecode offset of the previous JSOp::Case. + BytecodeOffset lastCaseOffset_; + + // Bytecode offset of the JSOp::JumpTarget for default body. + JumpTarget defaultJumpTargetOffset_; + + // Bytecode offset of the JSOp::Default. + JumpList condSwitchDefaultOffset_; + + // Instantiated when there's lexical scope for entire switch. + mozilla::Maybe tdzCacheLexical_; + mozilla::Maybe emitterScope_; + + // Instantiated while emitting case expression and case/default body. + mozilla::Maybe tdzCacheCaseAndBody_; + + // Control for switch. + mozilla::Maybe controlInfo_; + + uint32_t switchPos_ = 0; + + // Cond Switch: + // Offset of each JSOp::Case. + // Table Switch: + // Offset of each JSOp::JumpTarget for case. + js::Vector caseOffsets_; + + // The state of this emitter. + // + // +-------+ emitDiscriminant +--------------+ + // | Start |----------------->| Discriminant |-+ + // +-------+ +--------------+ | + // | + // +-------------------------------------------+ + // | + // | validateCaseCount +-----------+ + // +->+------------------------>+------------------>| CaseCount |-+ + // | ^ +-----------+ | + // | emitLexical +---------+ | | + // +------------>| Lexical |-+ | + // +---------+ | + // | + // +--------------------------------------------------------------+ + // | + // | emitTable +-------+ + // +---------->| Table |----------------------------------->+-+ + // | +-------+ ^ | + // | | | + // | emitCond +------+ | | + // +---------->| Cond |-+------------------------------->+->+ | + // +------+ | ^ | + // | | | + // +------------------+ | | + // | | | + // |prepareForCaseValue +-----------+ | | + // +----------+--------->| CaseValue | | | + // ^ +-----------+ | | + // | | | | + // | | emitCaseJump +------+ | | + // | +------------->| Case |->+-+ | + // | +------+ | | + // | | | + // +--------------------------------------+ | + // | + // +----------------------------------------------------------+ + // | + // | emitEnd +-----+ + // +-+----------------------------------------->+-------->| End | + // | ^ +-----+ + // | emitCaseBody +----------+ | + // +->+-+---------------->| CaseBody |--->+-+-+ + // ^ | +----------+ ^ | + // | | | | + // | | emitDefaultBody +-------------+ | | + // | +---------------->| DefaultBody |-+ | + // | +-------------+ | + // | | + // +-------------------------------------+ + // + protected: + enum class State { + // The initial state. + Start, + + // After calling emitDiscriminant. + Discriminant, + + // After calling validateCaseCount. + CaseCount, + + // After calling emitLexical. + Lexical, + + // After calling emitCond. + Cond, + + // After calling emitTable. + Table, + + // After calling prepareForCaseValue. + CaseValue, + + // After calling emitCaseJump. + Case, + + // After calling emitCaseBody. + CaseBody, + + // After calling emitDefaultBody. + DefaultBody, + + // After calling emitEnd. + End + }; + State state_ = State::Start; + + public: + explicit SwitchEmitter(BytecodeEmitter* bce); + + // `switchPos` is the offset in the source code for the character below: + // + // switch ( cond ) { ... } + // ^ + // | + // switchPos + [[nodiscard]] bool emitDiscriminant(uint32_t switchPos); + + // `caseCount` should be the number of cases in the switch statement, + // excluding the default case. + [[nodiscard]] bool validateCaseCount(uint32_t caseCount); + + // `bindings` is a lexical scope for the entire switch, in case there's + // let/const effectively directly under case or default blocks. + [[nodiscard]] bool emitLexical(LexicalScope::ParserData* bindings); + + [[nodiscard]] bool emitCond(); + [[nodiscard]] bool emitTable(const TableGenerator& tableGen); + + [[nodiscard]] bool prepareForCaseValue(); + [[nodiscard]] bool emitCaseJump(); + + [[nodiscard]] bool emitCaseBody(); + [[nodiscard]] bool emitCaseBody(int32_t caseValue, + const TableGenerator& tableGen); + [[nodiscard]] bool emitDefaultBody(); + [[nodiscard]] bool emitEnd(); + + private: + [[nodiscard]] bool emitCaseOrDefaultJump(uint32_t caseIndex, bool isDefault); + [[nodiscard]] bool emitImplicitDefault(); +}; + +// Class for emitting bytecode for switch-case-default block that doesn't +// correspond to a syntactic `switch`. +// Compared to SwitchEmitter, this class doesn't require `emitDiscriminant`, +// and the discriminant can already be on the stack. Usage is otherwise +// the same as SwitchEmitter. +class MOZ_STACK_CLASS InternalSwitchEmitter : public SwitchEmitter { + public: + explicit InternalSwitchEmitter(BytecodeEmitter* bce); +}; + +} /* namespace frontend */ +} /* namespace js */ + +#endif /* frontend_SwitchEmitter_h */ diff --git a/js/src/frontend/SyntaxParseHandler.h b/js/src/frontend/SyntaxParseHandler.h new file mode 100644 index 0000000000..d56591e463 --- /dev/null +++ b/js/src/frontend/SyntaxParseHandler.h @@ -0,0 +1,797 @@ +/* -*- 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/. */ + +#ifndef frontend_SyntaxParseHandler_h +#define frontend_SyntaxParseHandler_h + +#include "mozilla/Assertions.h" +#include "mozilla/Maybe.h" // mozilla::Maybe + +#include + +#include "jstypes.h" + +#include "frontend/CompilationStencil.h" // CompilationState +#include "frontend/FunctionSyntaxKind.h" // FunctionSyntaxKind +#include "frontend/NameAnalysisTypes.h" // PrivateNameKind +#include "frontend/ParseNode.h" +#include "frontend/ParserAtom.h" // TaggedParserAtomIndex +#include "frontend/TokenStream.h" + +namespace js { + +namespace frontend { + +// Parse handler used when processing the syntax in a block of code, to generate +// the minimal information which is required to detect syntax errors and allow +// bytecode to be emitted for outer functions. +// +// When parsing, we start at the top level with a full parse, and when possible +// only check the syntax for inner functions, so that they can be lazily parsed +// into bytecode when/if they first run. Checking the syntax of a function is +// several times faster than doing a full parse/emit, and lazy parsing improves +// both performance and memory usage significantly when pages contain large +// amounts of code that never executes (which happens often). +class SyntaxParseHandler { + // Remember the last encountered name or string literal during syntax parses. + TaggedParserAtomIndex lastAtom; + TokenPos lastStringPos; + + public: + enum Node { + NodeFailure = 0, + NodeGeneric, + NodeGetProp, + NodeStringExprStatement, + NodeReturn, + NodeBreak, + NodeThrow, + NodeEmptyStatement, + + NodeVarDeclaration, + NodeLexicalDeclaration, + + // A non-arrow function expression with block body, from bog-standard + // ECMAScript. + NodeFunctionExpression, + + NodeFunctionArrow, + NodeFunctionStatement, + + // This is needed for proper assignment-target handling. ES6 formally + // requires function calls *not* pass IsValidSimpleAssignmentTarget, + // but at last check there were still sites with |f() = 5| and similar + // in code not actually executed (or at least not executed enough to be + // noticed). + NodeFunctionCall, + + NodeOptionalFunctionCall, + + // Node representing normal names which don't require any special + // casing. + NodeName, + + // Nodes representing the names "arguments" and "eval". + NodeArgumentsName, + NodeEvalName, + + // Node representing the "async" name, which may actually be a + // contextual keyword. + NodePotentialAsyncKeyword, + + // Node representing private names. + NodePrivateName, + + NodeDottedProperty, + NodeOptionalDottedProperty, + NodeElement, + NodeOptionalElement, + // A distinct node for [PrivateName], to make detecting delete this.#x + // detectable in syntax parse + NodePrivateMemberAccess, + NodeOptionalPrivateMemberAccess, + + // Destructuring target patterns can't be parenthesized: |([a]) = [3];| + // must be a syntax error. (We can't use NodeGeneric instead of these + // because that would trigger invalid-left-hand-side ReferenceError + // semantics when SyntaxError semantics are desired.) + NodeParenthesizedArray, + NodeParenthesizedObject, + + // In rare cases a parenthesized |node| doesn't have the same semantics + // as |node|. Each such node has a special Node value, and we use a + // different Node value to represent the parenthesized form. See also + // is{Unp,P}arenthesized*(Node), parenthesize(Node), and the various + // functions that deal in NodeUnparenthesized* below. + + // Valuable for recognizing potential destructuring patterns. + NodeUnparenthesizedArray, + NodeUnparenthesizedObject, + + // The directive prologue at the start of a FunctionBody or ScriptBody + // is the longest sequence (possibly empty) of string literal + // expression statements at the start of a function. Thus we need this + // to treat |"use strict";| as a possible Use Strict Directive and + // |("use strict");| as a useless statement. + NodeUnparenthesizedString, + + // For destructuring patterns an assignment element with + // an initializer expression is not allowed be parenthesized. + // i.e. |{x = 1} = obj| + NodeUnparenthesizedAssignment, + + // This node is necessary to determine if the base operand in an + // exponentiation operation is an unparenthesized unary expression. + // We want to reject |-2 ** 3|, but still need to allow |(-2) ** 3|. + NodeUnparenthesizedUnary, + + // This node is necessary to determine if the LHS of a property access is + // super related. + NodeSuperBase + }; + +#define DECLARE_TYPE(typeName, longTypeName, asMethodName) \ + using longTypeName = Node; + FOR_EACH_PARSENODE_SUBCLASS(DECLARE_TYPE) +#undef DECLARE_TYPE + + using NullNode = Node; + + bool isNonArrowFunctionExpression(Node node) const { + return node == NodeFunctionExpression; + } + + bool isPropertyOrPrivateMemberAccess(Node node) { + return node == NodeDottedProperty || node == NodeElement || + node == NodePrivateMemberAccess; + } + + bool isOptionalPropertyOrPrivateMemberAccess(Node node) { + return node == NodeOptionalDottedProperty || node == NodeOptionalElement || + node == NodeOptionalPrivateMemberAccess; + } + + bool isFunctionCall(Node node) { + // Note: super() is a special form, *not* a function call. + return node == NodeFunctionCall; + } + + static bool isUnparenthesizedDestructuringPattern(Node node) { + return node == NodeUnparenthesizedArray || + node == NodeUnparenthesizedObject; + } + + static bool isParenthesizedDestructuringPattern(Node node) { + // Technically this isn't a destructuring target 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 == NodeParenthesizedArray || node == NodeParenthesizedObject; + } + + public: + SyntaxParseHandler(FrontendContext* fc, CompilationState& compilationState) { + MOZ_ASSERT(!compilationState.input.isDelazifying()); + } + + static NullNode null() { return NodeFailure; } + +#define DECLARE_AS(typeName, longTypeName, asMethodName) \ + static longTypeName asMethodName(Node node) { return node; } + FOR_EACH_PARSENODE_SUBCLASS(DECLARE_AS) +#undef DECLARE_AS + + NameNodeType newName(TaggedParserAtomIndex name, const TokenPos& pos) { + lastAtom = name; + if (name == TaggedParserAtomIndex::WellKnown::arguments()) { + return NodeArgumentsName; + } + if (pos.begin + strlen("async") == pos.end && + name == TaggedParserAtomIndex::WellKnown::async()) { + return NodePotentialAsyncKeyword; + } + if (name == TaggedParserAtomIndex::WellKnown::eval()) { + return NodeEvalName; + } + return NodeName; + } + + UnaryNodeType newComputedName(Node expr, uint32_t start, uint32_t end) { + return NodeGeneric; + } + + UnaryNodeType newSyntheticComputedName(Node expr, uint32_t start, + uint32_t end) { + return NodeGeneric; + } + + NameNodeType newObjectLiteralPropertyName(TaggedParserAtomIndex atom, + const TokenPos& pos) { + return NodeName; + } + + NameNodeType newPrivateName(TaggedParserAtomIndex atom, const TokenPos& pos) { + return NodePrivateName; + } + + NumericLiteralType newNumber(double value, DecimalPoint decimalPoint, + const TokenPos& pos) { + return NodeGeneric; + } + + BigIntLiteralType newBigInt() { return NodeGeneric; } + + BooleanLiteralType newBooleanLiteral(bool cond, const TokenPos& pos) { + return NodeGeneric; + } + + NameNodeType newStringLiteral(TaggedParserAtomIndex atom, + const TokenPos& pos) { + lastAtom = atom; + lastStringPos = pos; + return NodeUnparenthesizedString; + } + + NameNodeType newTemplateStringLiteral(TaggedParserAtomIndex atom, + const TokenPos& pos) { + return NodeGeneric; + } + + CallSiteNodeType newCallSiteObject(uint32_t begin) { return NodeGeneric; } + + void addToCallSiteObject(CallSiteNodeType callSiteObj, Node rawNode, + Node cookedNode) {} + + ThisLiteralType newThisLiteral(const TokenPos& pos, Node thisName) { + return NodeGeneric; + } + NullLiteralType newNullLiteral(const TokenPos& pos) { return NodeGeneric; } + RawUndefinedLiteralType newRawUndefinedLiteral(const TokenPos& pos) { + return NodeGeneric; + } + + RegExpLiteralType newRegExp(Node reobj, const TokenPos& pos) { + return NodeGeneric; + } + + ConditionalExpressionType newConditional(Node cond, Node thenExpr, + Node elseExpr) { + return NodeGeneric; + } + + UnaryNodeType newDelete(uint32_t begin, Node expr) { + return NodeUnparenthesizedUnary; + } + + UnaryNodeType newTypeof(uint32_t begin, Node kid) { + return NodeUnparenthesizedUnary; + } + + UnaryNodeType newUnary(ParseNodeKind kind, uint32_t begin, Node kid) { + return NodeUnparenthesizedUnary; + } + + UnaryNodeType newUpdate(ParseNodeKind kind, uint32_t begin, Node kid) { + return NodeGeneric; + } + + UnaryNodeType newSpread(uint32_t begin, Node kid) { return NodeGeneric; } + + Node appendOrCreateList(ParseNodeKind kind, Node left, Node right, + ParseContext* pc) { + return NodeGeneric; + } + + // Expressions + + ListNodeType newArrayLiteral(uint32_t begin) { + return NodeUnparenthesizedArray; + } + [[nodiscard]] bool addElision(ListNodeType literal, const TokenPos& pos) { + return true; + } + [[nodiscard]] bool addSpreadElement(ListNodeType literal, uint32_t begin, + Node inner) { + return true; + } + void addArrayElement(ListNodeType literal, Node element) {} + + ListNodeType newArguments(const TokenPos& pos) { return NodeGeneric; } + CallNodeType newCall(Node callee, Node args, JSOp callOp) { + return NodeFunctionCall; + } + + CallNodeType newOptionalCall(Node callee, Node args, JSOp callOp) { + return NodeOptionalFunctionCall; + } + + CallNodeType newSuperCall(Node callee, Node args, bool isSpread) { + return NodeGeneric; + } + CallNodeType newTaggedTemplate(Node tag, Node args, JSOp callOp) { + return NodeGeneric; + } + + ListNodeType newObjectLiteral(uint32_t begin) { + return NodeUnparenthesizedObject; + } + +#ifdef ENABLE_RECORD_TUPLE + ListNodeType newRecordLiteral(uint32_t begin) { return NodeGeneric; } + + ListNodeType newTupleLiteral(uint32_t begin) { return NodeGeneric; } +#endif + + ListNodeType newClassMemberList(uint32_t begin) { return NodeGeneric; } + ClassNamesType newClassNames(Node outer, Node inner, const TokenPos& pos) { + return NodeGeneric; + } + ClassNodeType newClass(Node name, Node heritage, Node methodBlock, +#ifdef ENABLE_DECORATORS + ListNodeType decorators, +#endif + const TokenPos& pos) { + return NodeGeneric; + } + + LexicalScopeNodeType newLexicalScope(Node body) { + return NodeLexicalDeclaration; + } + + ClassBodyScopeNodeType newClassBodyScope(Node body) { + return NodeLexicalDeclaration; + } + + NewTargetNodeType newNewTarget(NullaryNodeType newHolder, + NullaryNodeType targetHolder, + NameNodeType newTargetName) { + return NodeGeneric; + } + NullaryNodeType newPosHolder(const TokenPos& pos) { return NodeGeneric; } + UnaryNodeType newSuperBase(Node thisName, const TokenPos& pos) { + return NodeSuperBase; + } + + [[nodiscard]] bool addPrototypeMutation(ListNodeType literal, uint32_t begin, + Node expr) { + return true; + } + BinaryNodeType newPropertyDefinition(Node key, Node val) { + return NodeGeneric; + } + void addPropertyDefinition(ListNodeType literal, BinaryNodeType propdef) {} + [[nodiscard]] bool addPropertyDefinition(ListNodeType literal, Node key, + Node expr) { + return true; + } + [[nodiscard]] bool addShorthand(ListNodeType literal, NameNodeType name, + NameNodeType expr) { + return true; + } + [[nodiscard]] bool addSpreadProperty(ListNodeType literal, uint32_t begin, + Node inner) { + return true; + } + [[nodiscard]] bool addObjectMethodDefinition(ListNodeType literal, Node key, + FunctionNodeType funNode, + AccessorType atype) { + return true; + } + [[nodiscard]] Node newDefaultClassConstructor(Node key, + FunctionNodeType funNode) { + return NodeGeneric; + } + [[nodiscard]] Node newClassMethodDefinition( + Node key, FunctionNodeType funNode, AccessorType atype, bool isStatic, + mozilla::Maybe initializerIfPrivate +#ifdef ENABLE_DECORATORS + , + ListNodeType decorators +#endif + ) { + return NodeGeneric; + } + [[nodiscard]] Node newClassFieldDefinition(Node name, + FunctionNodeType initializer, + bool isStatic +#ifdef ENABLE_DECORATORS + , + ListNodeType decorators, + bool hasAccessor +#endif + ) { + return NodeGeneric; + } + + [[nodiscard]] Node newStaticClassBlock(FunctionNodeType block) { + return NodeGeneric; + } + + [[nodiscard]] bool addClassMemberDefinition(ListNodeType memberList, + Node member) { + return true; + } + UnaryNodeType newYieldExpression(uint32_t begin, Node value) { + return NodeGeneric; + } + UnaryNodeType newYieldStarExpression(uint32_t begin, Node value) { + return NodeGeneric; + } + UnaryNodeType newAwaitExpression(uint32_t begin, Node value) { + return NodeUnparenthesizedUnary; + } + UnaryNodeType newOptionalChain(uint32_t begin, Node value) { + return NodeGeneric; + } + + // Statements + + ListNodeType newStatementList(const TokenPos& pos) { return NodeGeneric; } + void addStatementToList(ListNodeType list, Node stmt) {} + void setListEndPosition(ListNodeType list, const TokenPos& pos) {} + void addCaseStatementToList(ListNodeType list, CaseClauseType caseClause) {} + [[nodiscard]] bool prependInitialYield(ListNodeType stmtList, Node genName) { + return true; + } + NullaryNodeType newEmptyStatement(const TokenPos& pos) { + return NodeEmptyStatement; + } + + BinaryNodeType newImportAssertion(Node keyNode, Node valueNode) { + return NodeGeneric; + } + BinaryNodeType newModuleRequest(Node moduleSpec, Node importAssertionList, + const TokenPos& pos) { + return NodeGeneric; + } + BinaryNodeType newImportDeclaration(Node importSpecSet, Node moduleRequest, + const TokenPos& pos) { + return NodeGeneric; + } + BinaryNodeType newImportSpec(Node importNameNode, Node bindingName) { + return NodeGeneric; + } + UnaryNodeType newImportNamespaceSpec(uint32_t begin, Node bindingName) { + return NodeGeneric; + } + UnaryNodeType newExportDeclaration(Node kid, const TokenPos& pos) { + return NodeGeneric; + } + BinaryNodeType newExportFromDeclaration(uint32_t begin, Node exportSpecSet, + Node moduleRequest) { + return NodeGeneric; + } + BinaryNodeType newExportDefaultDeclaration(Node kid, Node maybeBinding, + const TokenPos& pos) { + return NodeGeneric; + } + BinaryNodeType newExportSpec(Node bindingName, Node exportName) { + return NodeGeneric; + } + UnaryNodeType newExportNamespaceSpec(uint32_t begin, Node exportName) { + return NodeGeneric; + } + NullaryNodeType newExportBatchSpec(const TokenPos& pos) { + return NodeGeneric; + } + BinaryNodeType newImportMeta(NullaryNodeType importHolder, + NullaryNodeType metaHolder) { + return NodeGeneric; + } + BinaryNodeType newCallImport(NullaryNodeType importHolder, Node singleArg) { + return NodeGeneric; + } + BinaryNodeType newCallImportSpec(Node specifierArg, Node optionalArg) { + return NodeGeneric; + } + + BinaryNodeType newSetThis(Node thisName, Node value) { return value; } + + UnaryNodeType newExprStatement(Node expr, uint32_t end) { + return expr == NodeUnparenthesizedString ? NodeStringExprStatement + : NodeGeneric; + } + + TernaryNodeType newIfStatement(uint32_t begin, Node cond, Node thenBranch, + Node elseBranch) { + return NodeGeneric; + } + BinaryNodeType newDoWhileStatement(Node body, Node cond, + const TokenPos& pos) { + return NodeGeneric; + } + BinaryNodeType newWhileStatement(uint32_t begin, Node cond, Node body) { + return NodeGeneric; + } + SwitchStatementType newSwitchStatement( + uint32_t begin, Node discriminant, + LexicalScopeNodeType lexicalForCaseList, bool hasDefault) { + return NodeGeneric; + } + CaseClauseType newCaseOrDefault(uint32_t begin, Node expr, Node body) { + return NodeGeneric; + } + ContinueStatementType newContinueStatement(TaggedParserAtomIndex label, + const TokenPos& pos) { + return NodeGeneric; + } + BreakStatementType newBreakStatement(TaggedParserAtomIndex label, + const TokenPos& pos) { + return NodeBreak; + } + UnaryNodeType newReturnStatement(Node expr, const TokenPos& pos) { + return NodeReturn; + } + UnaryNodeType newExpressionBody(Node expr) { return NodeReturn; } + BinaryNodeType newWithStatement(uint32_t begin, Node expr, Node body) { + return NodeGeneric; + } + + LabeledStatementType newLabeledStatement(TaggedParserAtomIndex label, + Node stmt, uint32_t begin) { + return NodeGeneric; + } + + UnaryNodeType newThrowStatement(Node expr, const TokenPos& pos) { + return NodeThrow; + } + TernaryNodeType newTryStatement(uint32_t begin, Node body, + LexicalScopeNodeType catchScope, + Node finallyBlock) { + return NodeGeneric; + } + DebuggerStatementType newDebuggerStatement(const TokenPos& pos) { + return NodeGeneric; + } + + NameNodeType newPropertyName(TaggedParserAtomIndex name, + const TokenPos& pos) { + lastAtom = name; + return NodeGeneric; + } + + PropertyAccessType newPropertyAccess(Node expr, NameNodeType key) { + return NodeDottedProperty; + } + + PropertyAccessType newOptionalPropertyAccess(Node expr, NameNodeType key) { + return NodeOptionalDottedProperty; + } + + PropertyByValueType newPropertyByValue(Node lhs, Node index, uint32_t end) { + MOZ_ASSERT(!isPrivateName(index)); + return NodeElement; + } + + PropertyByValueType newOptionalPropertyByValue(Node lhs, Node index, + uint32_t end) { + return NodeOptionalElement; + } + + PrivateMemberAccessType newPrivateMemberAccess(Node lhs, Node privateName, + uint32_t end) { + return NodePrivateMemberAccess; + } + + PrivateMemberAccessType newOptionalPrivateMemberAccess(Node lhs, + Node privateName, + uint32_t end) { + return NodeOptionalPrivateMemberAccess; + } + + [[nodiscard]] bool setupCatchScope(LexicalScopeNodeType lexicalScope, + Node catchName, Node catchBody) { + return true; + } + + [[nodiscard]] bool setLastFunctionFormalParameterDefault( + FunctionNodeType funNode, Node defaultValue) { + return true; + } + + void checkAndSetIsDirectRHSAnonFunction(Node pn) {} + + ParamsBodyNodeType newParamsBody(const TokenPos& pos) { return NodeGeneric; } + + FunctionNodeType newFunction(FunctionSyntaxKind syntaxKind, + const TokenPos& pos) { + switch (syntaxKind) { + case FunctionSyntaxKind::Statement: + return NodeFunctionStatement; + case FunctionSyntaxKind::Arrow: + return NodeFunctionArrow; + default: + // All non-arrow function expressions are initially presumed to have + // block body. This will be overridden later *if* the function + // expression permissibly has an AssignmentExpression body. + return NodeFunctionExpression; + } + } + + void setFunctionFormalParametersAndBody(FunctionNodeType funNode, + ParamsBodyNodeType paramsBody) {} + void setFunctionBody(FunctionNodeType funNode, LexicalScopeNodeType body) {} + void setFunctionBox(FunctionNodeType funNode, FunctionBox* funbox) {} + void addFunctionFormalParameter(FunctionNodeType funNode, Node argpn) {} + + ForNodeType newForStatement(uint32_t begin, TernaryNodeType forHead, + Node body, unsigned iflags) { + return NodeGeneric; + } + + TernaryNodeType newForHead(Node init, Node test, Node update, + const TokenPos& pos) { + return NodeGeneric; + } + + TernaryNodeType newForInOrOfHead(ParseNodeKind kind, Node target, + Node iteratedExpr, const TokenPos& pos) { + return NodeGeneric; + } + + AssignmentNodeType finishInitializerAssignment(NameNodeType nameNode, + Node init) { + return NodeUnparenthesizedAssignment; + } + + void setBeginPosition(Node pn, Node oth) {} + void setBeginPosition(Node pn, uint32_t begin) {} + + void setEndPosition(Node pn, Node oth) {} + void setEndPosition(Node pn, uint32_t end) {} + + uint32_t getFunctionNameOffset(Node func, TokenStreamAnyChars& ts) { + // XXX This offset isn't relevant to the offending function name. But + // we may not *have* that function name around, because of how lazy + // parsing works -- the actual name could be outside + // |tokenStream.userbuf|'s observed region. So the current offset + // is the best we can do. + return ts.currentToken().pos.begin; + } + + ListNodeType newList(ParseNodeKind kind, const TokenPos& pos) { + MOZ_ASSERT(kind != ParseNodeKind::VarStmt); + MOZ_ASSERT(kind != ParseNodeKind::LetDecl); + MOZ_ASSERT(kind != ParseNodeKind::ConstDecl); + MOZ_ASSERT(kind != ParseNodeKind::ParamsBody); + return NodeGeneric; + } + + ListNodeType newList(ParseNodeKind kind, Node kid) { + return newList(kind, TokenPos()); + } + + DeclarationListNodeType newDeclarationList(ParseNodeKind kind, + const TokenPos& pos) { + if (kind == ParseNodeKind::VarStmt) { + return NodeVarDeclaration; + } + MOZ_ASSERT(kind == ParseNodeKind::LetDecl || + kind == ParseNodeKind::ConstDecl); + return NodeLexicalDeclaration; + } + + ListNodeType newCommaExpressionList(Node kid) { return NodeGeneric; } + + void addList(ListNodeType list, Node kid) { + MOZ_ASSERT(list == NodeGeneric || list == NodeUnparenthesizedArray || + list == NodeUnparenthesizedObject || + list == NodeVarDeclaration || list == NodeLexicalDeclaration || + list == NodeFunctionCall); + } + + CallNodeType newNewExpression(uint32_t begin, Node ctor, Node args, + bool isSpread) { + return NodeGeneric; + } + + AssignmentNodeType newAssignment(ParseNodeKind kind, Node lhs, Node rhs) { + return kind == ParseNodeKind::AssignExpr ? NodeUnparenthesizedAssignment + : NodeGeneric; + } + + AssignmentNodeType newInitExpr(Node lhs, Node rhs) { return NodeGeneric; } + + bool isUnparenthesizedAssignment(Node node) { + return node == NodeUnparenthesizedAssignment; + } + + bool isUnparenthesizedUnaryExpression(Node node) { + return node == NodeUnparenthesizedUnary; + } + + bool isReturnStatement(Node node) { return node == NodeReturn; } + + bool isStatementPermittedAfterReturnStatement(Node pn) { + return pn == NodeFunctionStatement || isNonArrowFunctionExpression(pn) || + pn == NodeVarDeclaration || pn == NodeBreak || pn == NodeThrow || + pn == NodeEmptyStatement; + } + + bool isSuperBase(Node pn) { return pn == NodeSuperBase; } + + void setListHasNonConstInitializer(ListNodeType literal) {} + [[nodiscard]] Node parenthesize(Node node) { + // A number of nodes have different behavior upon parenthesization, but + // only in some circumstances. Convert these nodes to special + // parenthesized forms. + if (node == NodeUnparenthesizedArray) { + return NodeParenthesizedArray; + } + if (node == NodeUnparenthesizedObject) { + return NodeParenthesizedObject; + } + + // Other nodes need not be recognizable after parenthesization; convert + // them to a generic node. + if (node == NodeUnparenthesizedString || + node == NodeUnparenthesizedAssignment || + node == NodeUnparenthesizedUnary) { + return NodeGeneric; + } + + // Convert parenthesized |async| to a normal name node. + if (node == NodePotentialAsyncKeyword) { + return NodeName; + } + + // In all other cases, the parenthesized form of |node| is equivalent + // to the unparenthesized form: return |node| unchanged. + return node; + } + template + [[nodiscard]] NodeType setLikelyIIFE(NodeType node) { + return node; // Remain in syntax-parse mode. + } + + bool isName(Node node) { + return node == NodeName || node == NodeArgumentsName || + node == NodeEvalName || node == NodePotentialAsyncKeyword; + } + + bool isArgumentsName(Node node) { return node == NodeArgumentsName; } + bool isEvalName(Node node) { return node == NodeEvalName; } + bool isAsyncKeyword(Node node) { return node == NodePotentialAsyncKeyword; } + + bool isPrivateName(Node node) { return node == NodePrivateName; } + bool isPrivateMemberAccess(Node node) { + return node == NodePrivateMemberAccess; + } + + TaggedParserAtomIndex maybeDottedProperty(Node node) { + // Note: |super.apply(...)| is a special form that calls an "apply" + // method retrieved from one value, but using a *different* value as + // |this|. It's not really eligible for the funapply/funcall + // optimizations as they're currently implemented (assuming a single + // value is used for both retrieval and |this|). + if (node != NodeDottedProperty && node != NodeOptionalDottedProperty) { + return TaggedParserAtomIndex::null(); + } + return lastAtom; + } + + TaggedParserAtomIndex isStringExprStatement(Node pn, TokenPos* pos) { + if (pn == NodeStringExprStatement) { + *pos = lastStringPos; + return lastAtom; + } + return TaggedParserAtomIndex::null(); + } + + bool reuseLazyInnerFunctions() { return false; } + bool reuseClosedOverBindings() { return false; } + TaggedParserAtomIndex nextLazyClosedOverBinding() { + MOZ_CRASH( + "SyntaxParseHandler::canSkipLazyClosedOverBindings must return false"); + } + + void setPrivateNameKind(Node node, PrivateNameKind kind) {} +}; + +} // namespace frontend +} // namespace js + +#endif /* frontend_SyntaxParseHandler_h */ diff --git a/js/src/frontend/TDZCheckCache.cpp b/js/src/frontend/TDZCheckCache.cpp new file mode 100644 index 0000000000..779d335759 --- /dev/null +++ b/js/src/frontend/TDZCheckCache.cpp @@ -0,0 +1,76 @@ +/* -*- 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/TDZCheckCache.h" + +#include "frontend/BytecodeEmitter.h" + +using namespace js; +using namespace js::frontend; + +using mozilla::Maybe; +using mozilla::Nothing; +using mozilla::Some; + +TDZCheckCache::TDZCheckCache(BytecodeEmitter* bce) + : Nestable(&bce->innermostTDZCheckCache), + cache_(bce->fc->nameCollectionPool()) {} + +bool TDZCheckCache::ensureCache(BytecodeEmitter* bce) { + return cache_ || cache_.acquire(bce->fc); +} + +Maybe TDZCheckCache::needsTDZCheck(BytecodeEmitter* bce, + TaggedParserAtomIndex name) { + if (!ensureCache(bce)) { + return Nothing(); + } + + CheckTDZMap::AddPtr p = cache_->lookupForAdd(name); + if (p) { + return Some(p->value().wrapped); + } + + MaybeCheckTDZ rv = CheckTDZ; + for (TDZCheckCache* it = enclosing(); it; it = it->enclosing()) { + if (it->cache_) { + if (CheckTDZMap::Ptr p2 = it->cache_->lookup(name)) { + rv = p2->value(); + break; + } + } + } + + if (!cache_->add(p, name, rv)) { + ReportOutOfMemory(bce->fc); + return Nothing(); + } + + return Some(rv); +} + +bool TDZCheckCache::noteTDZCheck(BytecodeEmitter* bce, + TaggedParserAtomIndex name, + MaybeCheckTDZ check) { + if (!ensureCache(bce)) { + return false; + } + + CheckTDZMap::AddPtr p = cache_->lookupForAdd(name); + if (p) { + MOZ_ASSERT( + !check, + "TDZ only needs to be checked once per binding per basic block."); + p->value() = check; + } else { + if (!cache_->add(p, name, check)) { + ReportOutOfMemory(bce->fc); + return false; + } + } + + return true; +} diff --git a/js/src/frontend/TDZCheckCache.h b/js/src/frontend/TDZCheckCache.h new file mode 100644 index 0000000000..8b6f796e87 --- /dev/null +++ b/js/src/frontend/TDZCheckCache.h @@ -0,0 +1,59 @@ +/* -*- 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/. */ + +#ifndef frontend_TDZCheckCache_h +#define frontend_TDZCheckCache_h + +#include "mozilla/Maybe.h" + +#include "ds/Nestable.h" +#include "frontend/NameCollections.h" + +namespace js { +namespace frontend { + +struct BytecodeEmitter; +class TaggedParserAtomIndex; + +enum MaybeCheckTDZ { CheckTDZ = true, DontCheckTDZ = false }; + +using CheckTDZMap = RecyclableNameMap; + +// A cache that tracks Temporal Dead Zone (TDZ) checks, so that any use of a +// lexical variable that's dominated by an earlier use, or by evaluation of its +// declaration (which will initialize it, perhaps to |undefined|), doesn't have +// to redundantly check that the lexical variable has been initialized +// +// Each basic block should have a TDZCheckCache in scope. Some NestableControl +// subclasses contain a TDZCheckCache. +// +// When a scope containing lexical variables is entered, all such variables are +// marked as CheckTDZ. When a lexical variable is accessed, its entry is +// checked. If it's CheckTDZ, a JSOp::CheckLexical is emitted and then the +// entry is marked DontCheckTDZ. If it's DontCheckTDZ, no check is emitted +// because a prior check would have already failed. Finally, because +// evaluating a lexical variable declaration initializes it (after any +// initializer is evaluated), evaluating a lexical declaration marks its entry +// as DontCheckTDZ. +class TDZCheckCache : public Nestable { + PooledMapPtr cache_; + + [[nodiscard]] bool ensureCache(BytecodeEmitter* bce); + + public: + explicit TDZCheckCache(BytecodeEmitter* bce); + + mozilla::Maybe needsTDZCheck(BytecodeEmitter* bce, + TaggedParserAtomIndex name); + [[nodiscard]] bool noteTDZCheck(BytecodeEmitter* bce, + TaggedParserAtomIndex name, + MaybeCheckTDZ check); +}; + +} /* namespace frontend */ +} /* namespace js */ + +#endif /* frontend_TDZCheckCache_h */ diff --git a/js/src/frontend/TaggedParserAtomIndexHasher.h b/js/src/frontend/TaggedParserAtomIndexHasher.h new file mode 100644 index 0000000000..0dc3f0eeaa --- /dev/null +++ b/js/src/frontend/TaggedParserAtomIndexHasher.h @@ -0,0 +1,44 @@ +/* -*- 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/. */ + +#ifndef frontend_TaggedParserAtomIndexHasher_h +#define frontend_TaggedParserAtomIndexHasher_h + +#include "frontend/ParserAtom.h" // TaggedParserAtomIndex, TrivialTaggedParserAtomIndex +#include "js/HashTable.h" // HashNumber + +namespace js { +namespace frontend { + +class TaggedParserAtomIndexHasher { + public: + using Lookup = TaggedParserAtomIndex; + + static inline HashNumber hash(const Lookup& l) { + return HashNumber(l.rawData()); + } + static inline bool match(TaggedParserAtomIndex entry, const Lookup& l) { + return l == entry; + } +}; + +class TrivialTaggedParserAtomIndexHasher { + public: + using Lookup = TrivialTaggedParserAtomIndex; + + static inline HashNumber hash(const Lookup& l) { + return HashNumber(l.rawData()); + } + static inline bool match(TrivialTaggedParserAtomIndex entry, + const Lookup& l) { + return l == entry; + } +}; + +} // namespace frontend +} // namespace js + +#endif // frontend_TaggedParserAtomIndexHasher_h diff --git a/js/src/frontend/Token.h b/js/src/frontend/Token.h new file mode 100644 index 0000000000..da01169935 --- /dev/null +++ b/js/src/frontend/Token.h @@ -0,0 +1,218 @@ +/* -*- 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/. */ + +/* + * Token-affiliated data structures except for TokenKind (defined in its own + * header). + */ + +#ifndef frontend_Token_h +#define frontend_Token_h + +#include "mozilla/Assertions.h" // MOZ_ASSERT + +#include // uint32_t + +#include "frontend/ParserAtom.h" // TaggedParserAtomIndex, TrivialTaggedParserAtomIndex +#include "frontend/TokenKind.h" // js::frontend::TokenKind +#include "js/RegExpFlags.h" // JS::RegExpFlags + +namespace js { + +namespace frontend { + +struct TokenPos { + uint32_t begin = 0; // Offset of the token's first code unit. + uint32_t end = 0; // Offset of 1 past the token's last code unit. + + TokenPos() = default; + TokenPos(uint32_t begin, uint32_t end) : begin(begin), end(end) {} + + // Return a TokenPos that covers left, right, and anything in between. + static TokenPos box(const TokenPos& left, const TokenPos& right) { + MOZ_ASSERT(left.begin <= left.end); + MOZ_ASSERT(left.end <= right.begin); + MOZ_ASSERT(right.begin <= right.end); + return TokenPos(left.begin, right.end); + } + + bool operator==(const TokenPos& bpos) const { + return begin == bpos.begin && end == bpos.end; + } + + bool operator!=(const TokenPos& bpos) const { + return begin != bpos.begin || end != bpos.end; + } + + bool operator<(const TokenPos& bpos) const { return begin < bpos.begin; } + + bool operator<=(const TokenPos& bpos) const { return begin <= bpos.begin; } + + bool operator>(const TokenPos& bpos) const { return !(*this <= bpos); } + + bool operator>=(const TokenPos& bpos) const { return !(*this < bpos); } + + bool encloses(const TokenPos& pos) const { + return begin <= pos.begin && pos.end <= end; + } +}; + +enum DecimalPoint { NoDecimal = false, HasDecimal = true }; + +// The only escapes found in IdentifierName are of the Unicode flavor. +enum class IdentifierEscapes { None, SawUnicodeEscape }; + +enum class NameVisibility { Public, Private }; + +class TokenStreamShared; + +struct Token { + private: + // The lexical grammar of JavaScript has a quirk around the '/' character. + // As the spec puts it: + // + // > There are several situations where the identification of lexical input + // > elements is sensitive to the syntactic grammar context that is consuming + // > the input elements. This requires multiple goal symbols for the lexical + // > grammar. [...] The InputElementRegExp goal symbol is used in all + // > syntactic grammar contexts where a RegularExpressionLiteral is permitted + // > [...] In all other contexts, InputElementDiv is used as the lexical + // > goal symbol. + // + // https://tc39.github.io/ecma262/#sec-lexical-and-regexp-grammars + // + // What "sensitive to the syntactic grammar context" means is, the parser has + // to tell the TokenStream whether to interpret '/' as division or + // RegExp. Because only one or the other (or neither) will be legal at that + // point in the program, and only the parser knows which one. + // + // But there's a problem: the parser often gets a token, puts it back, then + // consumes it later; or (equivalently) peeks at a token, leaves it, peeks + // again later, then finally consumes it. Of course we don't actually re-scan + // the token every time; we cache it in the TokenStream. This leads to the + // following rule: + // + // The parser must not pass SlashIsRegExp when getting/peeking at a token + // previously scanned with SlashIsDiv; or vice versa. + // + // That way, code that asks for a SlashIsRegExp mode will never get a cached + // Div token. But this rule is easy to screw up, because tokens are so often + // peeked at on Parser.cpp line A and consumed on line B, where |A-B| is + // thousands of lines. We therefore enforce it with the frontend's most + // annoying assertion (in verifyConsistentModifier), and provide + // Modifier::SlashIsInvalid to help avoid tripping it. + // + // This enum belongs in TokenStream, but C++, so we define it here and + // typedef it there. + enum Modifier { + // Parse `/` and `/=` as the division operators. (That is, use + // InputElementDiv as the goal symbol.) + SlashIsDiv, + + // Parse `/` as the beginning of a RegExp literal. (That is, use + // InputElementRegExp.) + SlashIsRegExp, + + // Neither a Div token nor a RegExp token is syntactically valid here. When + // the parser calls `getToken(SlashIsInvalid)`, it must be prepared to see + // either one (and throw a SyntaxError either way). + // + // It's OK to use SlashIsInvalid to get a token that was originally scanned + // with SlashIsDiv or SlashIsRegExp. The reverse--peeking with + // SlashIsInvalid, then getting with another mode--is not OK. If either Div + // or RegExp is syntactically valid here, use the appropriate modifier. + SlashIsInvalid, + }; + friend class TokenStreamShared; + + public: + /** The type of this token. */ + TokenKind type; + + /** The token's position in the overall script. */ + TokenPos pos; + + union { + private: + friend struct Token; + + TrivialTaggedParserAtomIndex atom; + + struct { + /** Numeric literal's value. */ + double value; + + /** Does the numeric literal contain a '.'? */ + DecimalPoint decimalPoint; + } number; + + /** Regular expression flags; use charBuffer to access source chars. */ + JS::RegExpFlags reflags; + } u; + +#ifdef DEBUG + /** The modifier used to get this token. */ + Modifier modifier; +#endif + + // Mutators + + void setName(TaggedParserAtomIndex name) { + MOZ_ASSERT(type == TokenKind::Name || type == TokenKind::PrivateName); + u.atom = TrivialTaggedParserAtomIndex::from(name); + } + + void setAtom(TaggedParserAtomIndex atom) { + MOZ_ASSERT(type == TokenKind::String || type == TokenKind::TemplateHead || + type == TokenKind::NoSubsTemplate); + u.atom = TrivialTaggedParserAtomIndex::from(atom); + } + + void setRegExpFlags(JS::RegExpFlags flags) { + MOZ_ASSERT(type == TokenKind::RegExp); + u.reflags = flags; + } + + void setNumber(double n, DecimalPoint decimalPoint) { + MOZ_ASSERT(type == TokenKind::Number); + u.number.value = n; + u.number.decimalPoint = decimalPoint; + } + + // Type-safe accessors + + TaggedParserAtomIndex name() const { + MOZ_ASSERT(type == TokenKind::Name || type == TokenKind::PrivateName); + return u.atom; + } + + TaggedParserAtomIndex atom() const { + MOZ_ASSERT(type == TokenKind::String || type == TokenKind::TemplateHead || + type == TokenKind::NoSubsTemplate); + return u.atom; + } + + JS::RegExpFlags regExpFlags() const { + MOZ_ASSERT(type == TokenKind::RegExp); + return u.reflags; + } + + double number() const { + MOZ_ASSERT(type == TokenKind::Number); + return u.number.value; + } + + DecimalPoint decimalPoint() const { + MOZ_ASSERT(type == TokenKind::Number); + return u.number.decimalPoint; + } +}; + +} // namespace frontend + +} // namespace js + +#endif // frontend_Token_h diff --git a/js/src/frontend/TokenKind.h b/js/src/frontend/TokenKind.h new file mode 100644 index 0000000000..102b91c6c4 --- /dev/null +++ b/js/src/frontend/TokenKind.h @@ -0,0 +1,333 @@ +/* -*- 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/. */ + +#ifndef frontend_TokenKind_h +#define frontend_TokenKind_h + +#include + +#include "js/TypeDecls.h" // IF_RECORD_TUPLE + +/* + * List of token kinds and their ranges. + * + * The format for each line is: + * + * MACRO(, ) + * + * or + * + * RANGE(, ) + * + * where ; + * is a legal C identifier of the token, that will be used in + * the JS engine source. + * + * is a string that describe about the token, and will be used in + * error message. + * + * is a legal C identifier of the range that will be used to + * JS engine source. It should end with `First` or `Last`. This is used to check + * TokenKind by range-testing: + * BinOpFirst <= tt && tt <= BinOpLast + * + * Second argument of `RANGE` is the actual value of the , + * should be same as one of in other `MACRO`s. + * + * To use this macro, define two macros for `MACRO` and `RANGE`, and pass them + * as arguments. + * + * #define EMIT_TOKEN(name, desc) ... + * #define EMIT_RANGE(name, value) ... + * FOR_EACH_TOKEN_KIND_WITH_RANGE(EMIT_TOKEN, EMIT_RANGE) + * #undef EMIT_TOKEN + * #undef EMIT_RANGE + * + * If you don't need range data, use FOR_EACH_TOKEN_KIND instead. + * + * #define EMIT_TOKEN(name, desc) ... + * FOR_EACH_TOKEN_KIND(EMIT_TOKEN) + * #undef EMIT_TOKEN + * + * Note that this list does not contain ERROR and LIMIT. + */ +#define FOR_EACH_TOKEN_KIND_WITH_RANGE(MACRO, RANGE) \ + MACRO(Eof, "end of script") \ + \ + /* only returned by peekTokenSameLine() */ \ + MACRO(Eol, "line terminator") \ + \ + MACRO(Semi, "';'") \ + MACRO(Comma, "','") \ + MACRO(Hook, "'?'") /* conditional */ \ + MACRO(Colon, "':'") /* conditional */ \ + MACRO(Inc, "'++'") /* increment */ \ + MACRO(Dec, "'--'") /* decrement */ \ + MACRO(Dot, "'.'") /* member operator */ \ + MACRO(TripleDot, "'...'") /* rest arguments and spread operator */ \ + MACRO(OptionalChain, "'?.'") \ + MACRO(LeftBracket, "'['") \ + IF_RECORD_TUPLE(MACRO(HashBracket, "'#['")) \ + MACRO(RightBracket, "']'") \ + MACRO(LeftCurly, "'{'") \ + IF_RECORD_TUPLE(MACRO(HashCurly, "'#{'")) \ + MACRO(RightCurly, "'}'") \ + MACRO(LeftParen, "'('") \ + MACRO(RightParen, "')'") \ + MACRO(Name, "identifier") \ + MACRO(PrivateName, "private identifier") \ + MACRO(Number, "numeric literal") \ + MACRO(String, "string literal") \ + MACRO(BigInt, "bigint literal") \ + IF_DECORATORS(MACRO(At, "'@'")) \ + \ + /* start of template literal with substitutions */ \ + MACRO(TemplateHead, "'${'") \ + /* template literal without substitutions */ \ + MACRO(NoSubsTemplate, "template literal") \ + \ + MACRO(RegExp, "regular expression literal") \ + MACRO(True, "boolean literal 'true'") \ + RANGE(ReservedWordLiteralFirst, True) \ + MACRO(False, "boolean literal 'false'") \ + MACRO(Null, "null literal") \ + RANGE(ReservedWordLiteralLast, Null) \ + MACRO(This, "keyword 'this'") \ + RANGE(KeywordFirst, This) \ + MACRO(Function, "keyword 'function'") \ + MACRO(If, "keyword 'if'") \ + MACRO(Else, "keyword 'else'") \ + MACRO(Switch, "keyword 'switch'") \ + MACRO(Case, "keyword 'case'") \ + MACRO(Default, "keyword 'default'") \ + MACRO(While, "keyword 'while'") \ + MACRO(Do, "keyword 'do'") \ + MACRO(For, "keyword 'for'") \ + MACRO(Break, "keyword 'break'") \ + MACRO(Continue, "keyword 'continue'") \ + MACRO(Var, "keyword 'var'") \ + MACRO(Const, "keyword 'const'") \ + MACRO(With, "keyword 'with'") \ + MACRO(Return, "keyword 'return'") \ + MACRO(New, "keyword 'new'") \ + MACRO(Delete, "keyword 'delete'") \ + MACRO(Try, "keyword 'try'") \ + MACRO(Catch, "keyword 'catch'") \ + MACRO(Finally, "keyword 'finally'") \ + MACRO(Throw, "keyword 'throw'") \ + MACRO(Debugger, "keyword 'debugger'") \ + MACRO(Export, "keyword 'export'") \ + MACRO(Import, "keyword 'import'") \ + MACRO(Class, "keyword 'class'") \ + MACRO(Extends, "keyword 'extends'") \ + MACRO(Super, "keyword 'super'") \ + RANGE(KeywordLast, Super) \ + \ + /* contextual keywords */ \ + MACRO(As, "'as'") \ + RANGE(ContextualKeywordFirst, As) \ + /* TODO: Move to alphabetical order when IF_DECORATORS is removed */ \ + IF_DECORATORS(MACRO(Accessor, "'accessor'")) \ + MACRO(Assert, "'assert'") \ + MACRO(Async, "'async'") \ + MACRO(Await, "'await'") \ + MACRO(Each, "'each'") \ + MACRO(From, "'from'") \ + MACRO(Get, "'get'") \ + MACRO(Let, "'let'") \ + MACRO(Meta, "'meta'") \ + MACRO(Of, "'of'") \ + MACRO(Set, "'set'") \ + MACRO(Static, "'static'") \ + MACRO(Target, "'target'") \ + MACRO(Yield, "'yield'") \ + RANGE(ContextualKeywordLast, Yield) \ + \ + /* future reserved words */ \ + MACRO(Enum, "reserved word 'enum'") \ + RANGE(FutureReservedKeywordFirst, Enum) \ + RANGE(FutureReservedKeywordLast, Enum) \ + \ + /* reserved words in strict mode */ \ + MACRO(Implements, "reserved word 'implements'") \ + RANGE(StrictReservedKeywordFirst, Implements) \ + MACRO(Interface, "reserved word 'interface'") \ + MACRO(Package, "reserved word 'package'") \ + MACRO(Private, "reserved word 'private'") \ + MACRO(Protected, "reserved word 'protected'") \ + MACRO(Public, "reserved word 'public'") \ + RANGE(StrictReservedKeywordLast, Public) \ + \ + /* \ + * The following token types occupy contiguous ranges to enable easy \ + * range-testing. \ + */ \ + /* \ + * Binary operators. \ + * This list must be kept in the same order in several places: \ + * - the binary operators in ParseNode.h \ + * - the precedence list in Parser.cpp \ + * - the JSOp code list in BytecodeEmitter.cpp \ + */ \ + MACRO(Coalesce, "'\?\?'") /* escapes to avoid trigraphs warning */ \ + RANGE(BinOpFirst, Coalesce) \ + MACRO(Or, "'||'") /* logical or */ \ + MACRO(And, "'&&'") /* logical and */ \ + MACRO(BitOr, "'|'") /* bitwise-or */ \ + MACRO(BitXor, "'^'") /* bitwise-xor */ \ + MACRO(BitAnd, "'&'") /* bitwise-and */ \ + \ + /* Equality operation tokens, per TokenKindIsEquality. */ \ + MACRO(StrictEq, "'==='") \ + RANGE(EqualityStart, StrictEq) \ + MACRO(Eq, "'=='") \ + MACRO(StrictNe, "'!=='") \ + MACRO(Ne, "'!='") \ + RANGE(EqualityLast, Ne) \ + \ + /* Relational ops, per TokenKindIsRelational. */ \ + MACRO(Lt, "'<'") \ + RANGE(RelOpStart, Lt) \ + MACRO(Le, "'<='") \ + MACRO(Gt, "'>'") \ + MACRO(Ge, "'>='") \ + RANGE(RelOpLast, Ge) \ + \ + MACRO(InstanceOf, "keyword 'instanceof'") \ + RANGE(KeywordBinOpFirst, InstanceOf) \ + MACRO(In, "keyword 'in'") \ + MACRO(PrivateIn, "keyword 'in' (private)") \ + RANGE(KeywordBinOpLast, PrivateIn) \ + \ + /* Shift ops, per TokenKindIsShift. */ \ + MACRO(Lsh, "'<<'") \ + RANGE(ShiftOpStart, Lsh) \ + MACRO(Rsh, "'>>'") \ + MACRO(Ursh, "'>>>'") \ + RANGE(ShiftOpLast, Ursh) \ + \ + MACRO(Add, "'+'") \ + MACRO(Sub, "'-'") \ + MACRO(Mul, "'*'") \ + MACRO(Div, "'/'") \ + MACRO(Mod, "'%'") \ + MACRO(Pow, "'**'") \ + RANGE(BinOpLast, Pow) \ + \ + /* Unary operation tokens. */ \ + MACRO(TypeOf, "keyword 'typeof'") \ + RANGE(KeywordUnOpFirst, TypeOf) \ + MACRO(Void, "keyword 'void'") \ + RANGE(KeywordUnOpLast, Void) \ + MACRO(Not, "'!'") \ + MACRO(BitNot, "'~'") \ + \ + MACRO(Arrow, "'=>'") /* function arrow */ \ + \ + /* Assignment ops, per TokenKindIsAssignment */ \ + MACRO(Assign, "'='") \ + RANGE(AssignmentStart, Assign) \ + MACRO(AddAssign, "'+='") \ + MACRO(SubAssign, "'-='") \ + MACRO(CoalesceAssign, "'\?\?='") /* avoid trigraphs warning */ \ + MACRO(OrAssign, "'||='") \ + MACRO(AndAssign, "'&&='") \ + MACRO(BitOrAssign, "'|='") \ + MACRO(BitXorAssign, "'^='") \ + MACRO(BitAndAssign, "'&='") \ + MACRO(LshAssign, "'<<='") \ + MACRO(RshAssign, "'>>='") \ + MACRO(UrshAssign, "'>>>='") \ + MACRO(MulAssign, "'*='") \ + MACRO(DivAssign, "'/='") \ + MACRO(ModAssign, "'%='") \ + MACRO(PowAssign, "'**='") \ + RANGE(AssignmentLast, PowAssign) + +#define TOKEN_KIND_RANGE_EMIT_NONE(name, value) +#define FOR_EACH_TOKEN_KIND(MACRO) \ + FOR_EACH_TOKEN_KIND_WITH_RANGE(MACRO, TOKEN_KIND_RANGE_EMIT_NONE) + +namespace js { +namespace frontend { + +// Values of this type are used to index into arrays such as isExprEnding[], +// so the first value must be zero. +enum class TokenKind : uint8_t { +#define EMIT_ENUM(name, desc) name, +#define EMIT_ENUM_RANGE(name, value) name = value, + FOR_EACH_TOKEN_KIND_WITH_RANGE(EMIT_ENUM, EMIT_ENUM_RANGE) +#undef EMIT_ENUM +#undef EMIT_ENUM_RANGE + Limit // domain size +}; + +inline bool TokenKindIsBinaryOp(TokenKind tt) { + return TokenKind::BinOpFirst <= tt && tt <= TokenKind::BinOpLast; +} + +inline bool TokenKindIsEquality(TokenKind tt) { + return TokenKind::EqualityStart <= tt && tt <= TokenKind::EqualityLast; +} + +inline bool TokenKindIsRelational(TokenKind tt) { + return TokenKind::RelOpStart <= tt && tt <= TokenKind::RelOpLast; +} + +inline bool TokenKindIsShift(TokenKind tt) { + return TokenKind::ShiftOpStart <= tt && tt <= TokenKind::ShiftOpLast; +} + +inline bool TokenKindIsAssignment(TokenKind tt) { + return TokenKind::AssignmentStart <= tt && tt <= TokenKind::AssignmentLast; +} + +[[nodiscard]] inline bool TokenKindIsKeyword(TokenKind tt) { + return (TokenKind::KeywordFirst <= tt && tt <= TokenKind::KeywordLast) || + (TokenKind::KeywordBinOpFirst <= tt && + tt <= TokenKind::KeywordBinOpLast) || + (TokenKind::KeywordUnOpFirst <= tt && + tt <= TokenKind::KeywordUnOpLast); +} + +[[nodiscard]] inline bool TokenKindIsContextualKeyword(TokenKind tt) { + return TokenKind::ContextualKeywordFirst <= tt && + tt <= TokenKind::ContextualKeywordLast; +} + +[[nodiscard]] inline bool TokenKindIsFutureReservedWord(TokenKind tt) { + return TokenKind::FutureReservedKeywordFirst <= tt && + tt <= TokenKind::FutureReservedKeywordLast; +} + +[[nodiscard]] inline bool TokenKindIsStrictReservedWord(TokenKind tt) { + return TokenKind::StrictReservedKeywordFirst <= tt && + tt <= TokenKind::StrictReservedKeywordLast; +} + +[[nodiscard]] inline bool TokenKindIsReservedWordLiteral(TokenKind tt) { + return TokenKind::ReservedWordLiteralFirst <= tt && + tt <= TokenKind::ReservedWordLiteralLast; +} + +[[nodiscard]] inline bool TokenKindIsReservedWord(TokenKind tt) { + return TokenKindIsKeyword(tt) || TokenKindIsFutureReservedWord(tt) || + TokenKindIsReservedWordLiteral(tt); +} + +[[nodiscard]] inline bool TokenKindIsPossibleIdentifier(TokenKind tt) { + return tt == TokenKind::Name || TokenKindIsContextualKeyword(tt) || + TokenKindIsStrictReservedWord(tt); +} + +[[nodiscard]] inline bool TokenKindIsPossibleIdentifierName(TokenKind tt) { + return TokenKindIsPossibleIdentifier(tt) || TokenKindIsReservedWord(tt); +} + +} // namespace frontend +} // namespace js + +#endif /* frontend_TokenKind_h */ diff --git a/js/src/frontend/TokenStream.cpp b/js/src/frontend/TokenStream.cpp new file mode 100644 index 0000000000..e05340927a --- /dev/null +++ b/js/src/frontend/TokenStream.cpp @@ -0,0 +1,3829 @@ +/* -*- 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 lexical scanner. + +#include "frontend/TokenStream.h" + +#include "mozilla/ArrayUtils.h" +#include "mozilla/Attributes.h" +#include "mozilla/Likely.h" +#include "mozilla/Maybe.h" +#include "mozilla/MemoryChecking.h" +#include "mozilla/ScopeExit.h" +#include "mozilla/Span.h" +#include "mozilla/TemplateLib.h" +#include "mozilla/TextUtils.h" +#include "mozilla/Utf8.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "jsnum.h" + +#include "frontend/BytecodeCompiler.h" +#include "frontend/FrontendContext.h" +#include "frontend/Parser.h" +#include "frontend/ParserAtom.h" +#include "frontend/ReservedWords.h" +#include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_* +#include "js/Printf.h" // JS_smprintf +#include "js/RegExpFlags.h" // JS::RegExpFlags +#include "js/UniquePtr.h" +#include "util/Text.h" +#include "util/Unicode.h" +#include "vm/FrameIter.h" // js::{,NonBuiltin}FrameIter +#include "vm/JSContext.h" +#include "vm/Realm.h" +#include "vm/WellKnownAtom.h" // js_*_str + +using mozilla::AsciiAlphanumericToNumber; +using mozilla::AssertedCast; +using mozilla::DecodeOneUtf8CodePoint; +using mozilla::IsAscii; +using mozilla::IsAsciiAlpha; +using mozilla::IsAsciiDigit; +using mozilla::IsAsciiHexDigit; +using mozilla::IsTrailingUnit; +using mozilla::MakeScopeExit; +using mozilla::Maybe; +using mozilla::PointerRangeSize; +using mozilla::Span; +using mozilla::Utf8Unit; + +using JS::ReadOnlyCompileOptions; +using JS::RegExpFlag; +using JS::RegExpFlags; + +struct ReservedWordInfo { + const char* chars; // C string with reserved word text + js::frontend::TokenKind tokentype; +}; + +static const ReservedWordInfo reservedWords[] = { +#define RESERVED_WORD_INFO(word, name, type) \ + {js_##word##_str, js::frontend::type}, + FOR_EACH_JAVASCRIPT_RESERVED_WORD(RESERVED_WORD_INFO) +#undef RESERVED_WORD_INFO +}; + +enum class ReservedWordsIndex : size_t { +#define ENTRY_(_1, NAME, _3) NAME, + FOR_EACH_JAVASCRIPT_RESERVED_WORD(ENTRY_) +#undef ENTRY_ +}; + +// Returns a ReservedWordInfo for the specified characters, or nullptr if the +// string is not a reserved word. +template +static const ReservedWordInfo* FindReservedWord(const CharT* s, size_t length) { + MOZ_ASSERT(length != 0); + + size_t i; + const ReservedWordInfo* rw; + const char* chars; + +#define JSRW_LENGTH() length +#define JSRW_AT(column) s[column] +#define JSRW_GOT_MATCH(index) \ + i = (index); \ + goto got_match; +#define JSRW_TEST_GUESS(index) \ + i = (index); \ + goto test_guess; +#define JSRW_NO_MATCH() goto no_match; +#include "frontend/ReservedWordsGenerated.h" +#undef JSRW_NO_MATCH +#undef JSRW_TEST_GUESS +#undef JSRW_GOT_MATCH +#undef JSRW_AT +#undef JSRW_LENGTH + +got_match: + return &reservedWords[i]; + +test_guess: + rw = &reservedWords[i]; + chars = rw->chars; + do { + if (*s++ != static_cast(*chars++)) { + goto no_match; + } + } while (--length != 0); + return rw; + +no_match: + return nullptr; +} + +template <> +MOZ_ALWAYS_INLINE const ReservedWordInfo* FindReservedWord( + const Utf8Unit* units, size_t length) { + return FindReservedWord(Utf8AsUnsignedChars(units), length); +} + +static const ReservedWordInfo* FindReservedWord( + const js::frontend::TaggedParserAtomIndex atom) { + switch (atom.rawData()) { +#define CASE_(_1, NAME, _3) \ + case js::frontend::TaggedParserAtomIndex::WellKnownRawData::NAME(): \ + return &reservedWords[size_t(ReservedWordsIndex::NAME)]; + FOR_EACH_JAVASCRIPT_RESERVED_WORD(CASE_) +#undef CASE_ + } + + return nullptr; +} + +static char32_t GetSingleCodePoint(const char16_t** p, const char16_t* end) { + using namespace js; + + if (MOZ_UNLIKELY(unicode::IsLeadSurrogate(**p)) && *p + 1 < end) { + char16_t lead = **p; + char16_t maybeTrail = *(*p + 1); + if (unicode::IsTrailSurrogate(maybeTrail)) { + *p += 2; + return unicode::UTF16Decode(lead, maybeTrail); + } + } + + char32_t codePoint = **p; + (*p)++; + return codePoint; +} + +template +static constexpr bool IsAsciiBinary(CharT c) { + using UnsignedCharT = std::make_unsigned_t; + auto uc = static_cast(c); + return uc == '0' || uc == '1'; +} + +template +static constexpr bool IsAsciiOctal(CharT c) { + using UnsignedCharT = std::make_unsigned_t; + auto uc = static_cast(c); + return '0' <= uc && uc <= '7'; +} + +template +static constexpr uint8_t AsciiOctalToNumber(CharT c) { + using UnsignedCharT = std::make_unsigned_t; + auto uc = static_cast(c); + return uc - '0'; +} + +namespace js { + +namespace frontend { + +bool IsIdentifier(JSLinearString* str) { + JS::AutoCheckCannotGC nogc; + MOZ_ASSERT(str); + if (str->hasLatin1Chars()) { + return IsIdentifier(str->latin1Chars(nogc), str->length()); + } + return IsIdentifier(str->twoByteChars(nogc), str->length()); +} + +bool IsIdentifierNameOrPrivateName(JSLinearString* str) { + JS::AutoCheckCannotGC nogc; + MOZ_ASSERT(str); + if (str->hasLatin1Chars()) { + return IsIdentifierNameOrPrivateName(str->latin1Chars(nogc), str->length()); + } + return IsIdentifierNameOrPrivateName(str->twoByteChars(nogc), str->length()); +} + +bool IsIdentifier(const Latin1Char* chars, size_t length) { + if (length == 0) { + return false; + } + + if (!unicode::IsIdentifierStart(char16_t(*chars))) { + return false; + } + + const Latin1Char* end = chars + length; + while (++chars != end) { + if (!unicode::IsIdentifierPart(char16_t(*chars))) { + return false; + } + } + + return true; +} + +bool IsIdentifierASCII(char c) { return unicode::IsIdentifierStartASCII(c); } + +bool IsIdentifierASCII(char c1, char c2) { + return unicode::IsIdentifierStartASCII(c1) && + unicode::IsIdentifierPartASCII(c2); +} + +bool IsIdentifierNameOrPrivateName(const Latin1Char* chars, size_t length) { + if (length == 0) { + return false; + } + + // Skip over any private name marker. + if (*chars == '#') { + ++chars; + --length; + } + + return IsIdentifier(chars, length); +} + +bool IsIdentifier(const char16_t* chars, size_t length) { + if (length == 0) { + return false; + } + + const char16_t* p = chars; + const char16_t* end = chars + length; + char32_t codePoint; + + codePoint = GetSingleCodePoint(&p, end); + if (!unicode::IsIdentifierStart(codePoint)) { + return false; + } + + while (p < end) { + codePoint = GetSingleCodePoint(&p, end); + if (!unicode::IsIdentifierPart(codePoint)) { + return false; + } + } + + return true; +} + +bool IsIdentifierNameOrPrivateName(const char16_t* chars, size_t length) { + if (length == 0) { + return false; + } + + const char16_t* p = chars; + const char16_t* end = chars + length; + char32_t codePoint; + + codePoint = GetSingleCodePoint(&p, end); + + // Skip over any private name marker. + if (codePoint == '#') { + // The identifier part of a private name mustn't be empty. + if (length == 1) { + return false; + } + + codePoint = GetSingleCodePoint(&p, end); + } + + if (!unicode::IsIdentifierStart(codePoint)) { + return false; + } + + while (p < end) { + codePoint = GetSingleCodePoint(&p, end); + if (!unicode::IsIdentifierPart(codePoint)) { + return false; + } + } + + return true; +} + +bool IsKeyword(TaggedParserAtomIndex atom) { + if (const ReservedWordInfo* rw = FindReservedWord(atom)) { + return TokenKindIsKeyword(rw->tokentype); + } + + return false; +} + +TokenKind ReservedWordTokenKind(TaggedParserAtomIndex name) { + if (const ReservedWordInfo* rw = FindReservedWord(name)) { + return rw->tokentype; + } + + return TokenKind::Limit; +} + +const char* ReservedWordToCharZ(TaggedParserAtomIndex name) { + if (const ReservedWordInfo* rw = FindReservedWord(name)) { + return ReservedWordToCharZ(rw->tokentype); + } + + return nullptr; +} + +const char* ReservedWordToCharZ(TokenKind tt) { + MOZ_ASSERT(tt != TokenKind::Name); + switch (tt) { +#define EMIT_CASE(word, name, type) \ + case type: \ + return js_##word##_str; + FOR_EACH_JAVASCRIPT_RESERVED_WORD(EMIT_CASE) +#undef EMIT_CASE + default: + MOZ_ASSERT_UNREACHABLE("Not a reserved word PropertyName."); + } + return nullptr; +} + +TaggedParserAtomIndex TokenStreamAnyChars::reservedWordToPropertyName( + TokenKind tt) const { + MOZ_ASSERT(tt != TokenKind::Name); + switch (tt) { +#define EMIT_CASE(word, name, type) \ + case type: \ + return TaggedParserAtomIndex::WellKnown::name(); + FOR_EACH_JAVASCRIPT_RESERVED_WORD(EMIT_CASE) +#undef EMIT_CASE + default: + MOZ_ASSERT_UNREACHABLE("Not a reserved word TokenKind."); + } + return TaggedParserAtomIndex::null(); +} + +SourceCoords::SourceCoords(FrontendContext* fc, uint32_t initialLineNumber, + uint32_t initialOffset) + : lineStartOffsets_(fc), initialLineNum_(initialLineNumber), lastIndex_(0) { + // This is actually necessary! Removing it causes compile errors on + // GCC and clang. You could try declaring this: + // + // const uint32_t SourceCoords::MAX_PTR; + // + // which fixes the GCC/clang error, but causes bustage on Windows. Sigh. + // + uint32_t maxPtr = MAX_PTR; + + // The first line begins at buffer offset |initialOffset|. MAX_PTR is the + // sentinel. The appends cannot fail because |lineStartOffsets_| has + // statically-allocated elements. + MOZ_ASSERT(lineStartOffsets_.capacity() >= 2); + MOZ_ALWAYS_TRUE(lineStartOffsets_.reserve(2)); + lineStartOffsets_.infallibleAppend(initialOffset); + lineStartOffsets_.infallibleAppend(maxPtr); +} + +MOZ_ALWAYS_INLINE bool SourceCoords::add(uint32_t lineNum, + uint32_t lineStartOffset) { + uint32_t index = indexFromLineNumber(lineNum); + uint32_t sentinelIndex = lineStartOffsets_.length() - 1; + + MOZ_ASSERT(lineStartOffsets_[0] <= lineStartOffset); + MOZ_ASSERT(lineStartOffsets_[sentinelIndex] == MAX_PTR); + + if (index == sentinelIndex) { + // We haven't seen this newline before. Update lineStartOffsets_ + // only if lineStartOffsets_.append succeeds, to keep sentinel. + // Otherwise return false to tell TokenStream about OOM. + uint32_t maxPtr = MAX_PTR; + if (!lineStartOffsets_.append(maxPtr)) { + static_assert(std::is_same_v, + "this function's caller depends on it reporting an " + "error on failure, as TempAllocPolicy ensures"); + return false; + } + + lineStartOffsets_[index] = lineStartOffset; + } else { + // We have seen this newline before (and ungot it). Do nothing (other + // than checking it hasn't mysteriously changed). + // This path can be executed after hitting OOM, so check index. + MOZ_ASSERT_IF(index < sentinelIndex, + lineStartOffsets_[index] == lineStartOffset); + } + return true; +} + +MOZ_ALWAYS_INLINE bool SourceCoords::fill(const SourceCoords& other) { + MOZ_ASSERT(lineStartOffsets_[0] == other.lineStartOffsets_[0]); + MOZ_ASSERT(lineStartOffsets_.back() == MAX_PTR); + MOZ_ASSERT(other.lineStartOffsets_.back() == MAX_PTR); + + if (lineStartOffsets_.length() >= other.lineStartOffsets_.length()) { + return true; + } + + uint32_t sentinelIndex = lineStartOffsets_.length() - 1; + lineStartOffsets_[sentinelIndex] = other.lineStartOffsets_[sentinelIndex]; + + for (size_t i = sentinelIndex + 1; i < other.lineStartOffsets_.length(); + i++) { + if (!lineStartOffsets_.append(other.lineStartOffsets_[i])) { + return false; + } + } + return true; +} + +MOZ_ALWAYS_INLINE uint32_t +SourceCoords::indexFromOffset(uint32_t offset) const { + uint32_t iMin, iMax, iMid; + + if (lineStartOffsets_[lastIndex_] <= offset) { + // If we reach here, offset is on a line the same as or higher than + // last time. Check first for the +0, +1, +2 cases, because they + // typically cover 85--98% of cases. + if (offset < lineStartOffsets_[lastIndex_ + 1]) { + return lastIndex_; // index is same as last time + } + + // If we reach here, there must be at least one more entry (plus the + // sentinel). Try it. + lastIndex_++; + if (offset < lineStartOffsets_[lastIndex_ + 1]) { + return lastIndex_; // index is one higher than last time + } + + // The same logic applies here. + lastIndex_++; + if (offset < lineStartOffsets_[lastIndex_ + 1]) { + return lastIndex_; // index is two higher than last time + } + + // No luck. Oh well, we have a better-than-default starting point for + // the binary search. + iMin = lastIndex_ + 1; + MOZ_ASSERT(iMin < + lineStartOffsets_.length() - 1); // -1 due to the sentinel + + } else { + iMin = 0; + } + + // This is a binary search with deferred detection of equality, which was + // marginally faster in this case than a standard binary search. + // The -2 is because |lineStartOffsets_.length() - 1| is the sentinel, and we + // want one before that. + iMax = lineStartOffsets_.length() - 2; + while (iMax > iMin) { + iMid = iMin + (iMax - iMin) / 2; + if (offset >= lineStartOffsets_[iMid + 1]) { + iMin = iMid + 1; // offset is above lineStartOffsets_[iMid] + } else { + iMax = iMid; // offset is below or within lineStartOffsets_[iMid] + } + } + + MOZ_ASSERT(iMax == iMin); + MOZ_ASSERT(lineStartOffsets_[iMin] <= offset); + MOZ_ASSERT(offset < lineStartOffsets_[iMin + 1]); + + lastIndex_ = iMin; + return iMin; +} + +SourceCoords::LineToken SourceCoords::lineToken(uint32_t offset) const { + return LineToken(indexFromOffset(offset), offset); +} + +TokenStreamAnyChars::TokenStreamAnyChars(FrontendContext* fc, + const ReadOnlyCompileOptions& options, + StrictModeGetter* smg) + : fc(fc), + options_(options), + strictModeGetter_(smg), + filename_(options.filename()), + longLineColumnInfo_(fc), + srcCoords(fc, options.lineno, options.scriptSourceOffset), + lineno(options.lineno), + mutedErrors(options.mutedErrors()) { + // |isExprEnding| was initially zeroed: overwrite the true entries here. + isExprEnding[size_t(TokenKind::Comma)] = true; + isExprEnding[size_t(TokenKind::Semi)] = true; + isExprEnding[size_t(TokenKind::Colon)] = true; + isExprEnding[size_t(TokenKind::RightParen)] = true; + isExprEnding[size_t(TokenKind::RightBracket)] = true; + isExprEnding[size_t(TokenKind::RightCurly)] = true; +} + +template +TokenStreamCharsBase::TokenStreamCharsBase(FrontendContext* fc, + ParserAtomsTable* parserAtoms, + const Unit* units, + size_t length, + size_t startOffset) + : TokenStreamCharsShared(fc, parserAtoms), + sourceUnits(units, length, startOffset) {} + +bool FillCharBufferFromSourceNormalizingAsciiLineBreaks(CharBuffer& charBuffer, + const char16_t* cur, + const char16_t* end) { + MOZ_ASSERT(charBuffer.length() == 0); + + while (cur < end) { + char16_t ch = *cur++; + if (ch == '\r') { + ch = '\n'; + if (cur < end && *cur == '\n') { + cur++; + } + } + + if (!charBuffer.append(ch)) { + return false; + } + } + + MOZ_ASSERT(cur == end); + return true; +} + +bool FillCharBufferFromSourceNormalizingAsciiLineBreaks(CharBuffer& charBuffer, + const Utf8Unit* cur, + const Utf8Unit* end) { + MOZ_ASSERT(charBuffer.length() == 0); + + while (cur < end) { + Utf8Unit unit = *cur++; + if (MOZ_LIKELY(IsAscii(unit))) { + char16_t ch = unit.toUint8(); + if (ch == '\r') { + ch = '\n'; + if (cur < end && *cur == Utf8Unit('\n')) { + cur++; + } + } + + if (!charBuffer.append(ch)) { + return false; + } + + continue; + } + + Maybe ch = DecodeOneUtf8CodePoint(unit, &cur, end); + MOZ_ASSERT(ch.isSome(), + "provided source text should already have been validated"); + + if (!AppendCodePointToCharBuffer(charBuffer, ch.value())) { + return false; + } + } + + MOZ_ASSERT(cur == end); + return true; +} + +template +TokenStreamSpecific::TokenStreamSpecific( + FrontendContext* fc, ParserAtomsTable* parserAtoms, + const ReadOnlyCompileOptions& options, const Unit* units, size_t length) + : TokenStreamChars(fc, parserAtoms, units, length, + options.scriptSourceOffset) {} + +bool TokenStreamAnyChars::checkOptions() { + // Constrain starting columns to where they will saturate. + if (options().column > ColumnLimit) { + reportErrorNoOffset(JSMSG_BAD_COLUMN_NUMBER); + return false; + } + + return true; +} + +void TokenStreamAnyChars::reportErrorNoOffset(unsigned errorNumber, ...) const { + va_list args; + va_start(args, errorNumber); + + reportErrorNoOffsetVA(errorNumber, &args); + + va_end(args); +} + +void TokenStreamAnyChars::reportErrorNoOffsetVA(unsigned errorNumber, + va_list* args) const { + ErrorMetadata metadata; + computeErrorMetadataNoOffset(&metadata); + + ReportCompileErrorLatin1(fc, std::move(metadata), nullptr, errorNumber, args); +} + +[[nodiscard]] MOZ_ALWAYS_INLINE bool +TokenStreamAnyChars::internalUpdateLineInfoForEOL(uint32_t lineStartOffset) { + prevLinebase = linebase; + linebase = lineStartOffset; + lineno++; + + // On overflow, report error. + if (MOZ_UNLIKELY(!lineno)) { + reportErrorNoOffset(JSMSG_BAD_LINE_NUMBER); + return false; + } + + return srcCoords.add(lineno, linebase); +} + +#ifdef DEBUG + +template <> +inline void SourceUnits::assertNextCodePoint( + const PeekedCodePoint& peeked) { + char32_t c = peeked.codePoint(); + if (c < unicode::NonBMPMin) { + MOZ_ASSERT(peeked.lengthInUnits() == 1); + MOZ_ASSERT(ptr[0] == c); + } else { + MOZ_ASSERT(peeked.lengthInUnits() == 2); + char16_t lead, trail; + unicode::UTF16Encode(c, &lead, &trail); + MOZ_ASSERT(ptr[0] == lead); + MOZ_ASSERT(ptr[1] == trail); + } +} + +template <> +inline void SourceUnits::assertNextCodePoint( + const PeekedCodePoint& peeked) { + char32_t c = peeked.codePoint(); + + // This is all roughly indulgence of paranoia only for assertions, so the + // reimplementation of UTF-8 encoding a code point is (we think) a virtue. + uint8_t expectedUnits[4] = {}; + if (c < 0x80) { + expectedUnits[0] = AssertedCast(c); + } else if (c < 0x800) { + expectedUnits[0] = 0b1100'0000 | (c >> 6); + expectedUnits[1] = 0b1000'0000 | (c & 0b11'1111); + } else if (c < 0x10000) { + expectedUnits[0] = 0b1110'0000 | (c >> 12); + expectedUnits[1] = 0b1000'0000 | ((c >> 6) & 0b11'1111); + expectedUnits[2] = 0b1000'0000 | (c & 0b11'1111); + } else { + expectedUnits[0] = 0b1111'0000 | (c >> 18); + expectedUnits[1] = 0b1000'0000 | ((c >> 12) & 0b11'1111); + expectedUnits[2] = 0b1000'0000 | ((c >> 6) & 0b11'1111); + expectedUnits[3] = 0b1000'0000 | (c & 0b11'1111); + } + + MOZ_ASSERT(peeked.lengthInUnits() <= 4); + for (uint8_t i = 0; i < peeked.lengthInUnits(); i++) { + MOZ_ASSERT(expectedUnits[i] == ptr[i].toUint8()); + } +} + +#endif // DEBUG + +static MOZ_ALWAYS_INLINE void RetractPointerToCodePointBoundary( + const Utf8Unit** ptr, const Utf8Unit* limit) { + MOZ_ASSERT(*ptr <= limit); + + // |limit| is a code point boundary. + if (MOZ_UNLIKELY(*ptr == limit)) { + return; + } + + // Otherwise rewind past trailing units to the start of the code point. +#ifdef DEBUG + size_t retracted = 0; +#endif + while (MOZ_UNLIKELY(IsTrailingUnit((*ptr)[0]))) { + --*ptr; +#ifdef DEBUG + retracted++; +#endif + } + + MOZ_ASSERT(retracted < 4, + "the longest UTF-8 code point is four units, so this should never " + "retract more than three units"); +} + +static MOZ_ALWAYS_INLINE void RetractPointerToCodePointBoundary( + const char16_t** ptr, const char16_t* limit) { + MOZ_ASSERT(*ptr <= limit); + + // |limit| is a code point boundary. + if (MOZ_UNLIKELY(*ptr == limit)) { + return; + } + + // Otherwise the pointer must be retracted by one iff it splits a two-unit + // code point. + if (MOZ_UNLIKELY(unicode::IsTrailSurrogate((*ptr)[0]))) { + // Outside test suites testing garbage WTF-16, it's basically guaranteed + // here that |(*ptr)[-1] (*ptr)[0]| is a surrogate pair. + if (MOZ_LIKELY(unicode::IsLeadSurrogate((*ptr)[-1]))) { + --*ptr; + } + } +} + +template +uint32_t TokenStreamAnyChars::computePartialColumn( + const LineToken lineToken, const uint32_t offset, + const SourceUnits& sourceUnits) const { + lineToken.assertConsistentOffset(offset); + + const uint32_t line = lineNumber(lineToken); + const uint32_t start = srcCoords.lineStart(lineToken); + + // Reset the previous offset/column cache for this line, if the previous + // lookup wasn't on this line. + if (line != lineOfLastColumnComputation_) { + lineOfLastColumnComputation_ = line; + lastChunkVectorForLine_ = nullptr; + lastOffsetOfComputedColumn_ = start; + lastComputedColumn_ = 0; + } + + // Compute and return the final column number from a partial offset/column, + // using the last-cached offset/column if they're more optimal. + auto ColumnFromPartial = [this, offset, &sourceUnits](uint32_t partialOffset, + uint32_t partialCols, + UnitsType unitsType) { + MOZ_ASSERT(partialOffset <= offset); + + // If the last lookup on this line was closer to |offset|, use it. + if (partialOffset < this->lastOffsetOfComputedColumn_ && + this->lastOffsetOfComputedColumn_ <= offset) { + partialOffset = this->lastOffsetOfComputedColumn_; + partialCols = this->lastComputedColumn_; + } + + const Unit* begin = sourceUnits.codeUnitPtrAt(partialOffset); + const Unit* end = sourceUnits.codeUnitPtrAt(offset); + + size_t offsetDelta = AssertedCast(PointerRangeSize(begin, end)); + partialOffset += offsetDelta; + + if (unitsType == UnitsType::GuaranteedSingleUnit) { + MOZ_ASSERT(unicode::CountCodePoints(begin, end) == offsetDelta, + "guaranteed-single-units also guarantee pointer distance " + "equals code point count"); + partialCols += offsetDelta; + } else { + partialCols += + AssertedCast(unicode::CountCodePoints(begin, end)); + } + + this->lastOffsetOfComputedColumn_ = partialOffset; + this->lastComputedColumn_ = partialCols; + return partialCols; + }; + + const uint32_t offsetInLine = offset - start; + + // We won't add an entry to |longLineColumnInfo_| for lines where the maximum + // column has offset less than this value. The most common (non-minified) + // long line length is likely 80ch, maybe 100ch, so we use that, rounded up to + // the next power of two for efficient division/multiplication below. + constexpr uint32_t ColumnChunkLength = mozilla::tl::RoundUpPow2<100>::value; + + // The index within any associated |Vector| of |offset|'s chunk. + const uint32_t chunkIndex = offsetInLine / ColumnChunkLength; + if (chunkIndex == 0) { + // We don't know from an |offset| in the zeroth chunk that this line is even + // long. First-chunk info is mostly useless, anyway -- we have |start| + // already. So if we have *easy* access to that zeroth chunk, use it -- + // otherwise just count pessimally. (This will still benefit from caching + // the last column/offset for computations for successive offsets, so it's + // not *always* worst-case.) + UnitsType unitsType; + if (lastChunkVectorForLine_ && lastChunkVectorForLine_->length() > 0) { + MOZ_ASSERT((*lastChunkVectorForLine_)[0].column() == 0); + unitsType = (*lastChunkVectorForLine_)[0].unitsType(); + } else { + unitsType = UnitsType::PossiblyMultiUnit; + } + + return ColumnFromPartial(start, 0, unitsType); + } + + // If this line has no chunk vector yet, insert one in the hash map. (The + // required index is allocated and filled further down.) + if (!lastChunkVectorForLine_) { + auto ptr = longLineColumnInfo_.lookupForAdd(line); + if (!ptr) { + // This could rehash and invalidate a cached vector pointer, but the outer + // condition means we don't have a cached pointer. + if (!longLineColumnInfo_.add(ptr, line, Vector(fc))) { + // In case of OOM, just count columns from the start of the line. + fc->recoverFromOutOfMemory(); + return ColumnFromPartial(start, 0, UnitsType::PossiblyMultiUnit); + } + } + + // Note that adding elements to this vector won't invalidate this pointer. + lastChunkVectorForLine_ = &ptr->value(); + } + + const Unit* const limit = sourceUnits.codeUnitPtrAt(offset); + + auto RetractedOffsetOfChunk = [ +#ifdef DEBUG + this, +#endif + start, limit, + &sourceUnits](uint32_t index) { + MOZ_ASSERT(index < this->lastChunkVectorForLine_->length()); + + uint32_t naiveOffset = start + index * ColumnChunkLength; + const Unit* naivePtr = sourceUnits.codeUnitPtrAt(naiveOffset); + + const Unit* actualPtr = naivePtr; + RetractPointerToCodePointBoundary(&actualPtr, limit); + +#ifdef DEBUG + if ((*this->lastChunkVectorForLine_)[index].unitsType() == + UnitsType::GuaranteedSingleUnit) { + MOZ_ASSERT(naivePtr == actualPtr, "miscomputed unitsType value"); + } +#endif + + return naiveOffset - PointerRangeSize(actualPtr, naivePtr); + }; + + uint32_t partialOffset; + uint32_t partialColumn; + UnitsType unitsType; + + auto entriesLen = AssertedCast(lastChunkVectorForLine_->length()); + if (chunkIndex < entriesLen) { + // We've computed the chunk |offset| resides in. Compute the column number + // from the chunk. + partialOffset = RetractedOffsetOfChunk(chunkIndex); + partialColumn = (*lastChunkVectorForLine_)[chunkIndex].column(); + + // This is exact if |chunkIndex| isn't the last chunk. + unitsType = (*lastChunkVectorForLine_)[chunkIndex].unitsType(); + + // Otherwise the last chunk is pessimistically assumed to contain multi-unit + // code points because we haven't fully examined its contents yet -- they + // may not have been tokenized yet, they could contain encoding errors, or + // they might not even exist. + MOZ_ASSERT_IF(chunkIndex == entriesLen - 1, + (*lastChunkVectorForLine_)[chunkIndex].unitsType() == + UnitsType::PossiblyMultiUnit); + } else { + // Extend the vector from its last entry or the start of the line. (This is + // also a suitable partial start point if we must recover from OOM.) + if (entriesLen > 0) { + partialOffset = RetractedOffsetOfChunk(entriesLen - 1); + partialColumn = (*lastChunkVectorForLine_)[entriesLen - 1].column(); + } else { + partialOffset = start; + partialColumn = 0; + } + + if (!lastChunkVectorForLine_->reserve(chunkIndex + 1)) { + // As earlier, just start from the greatest offset/column in case of OOM. + fc->recoverFromOutOfMemory(); + return ColumnFromPartial(partialOffset, partialColumn, + UnitsType::PossiblyMultiUnit); + } + + // OOM is no longer possible now. \o/ + + // The vector always begins with the column of the line start, i.e. zero, + // with chunk units pessimally assumed not single-unit. + if (entriesLen == 0) { + lastChunkVectorForLine_->infallibleAppend( + ChunkInfo(0, UnitsType::PossiblyMultiUnit)); + entriesLen++; + } + + do { + const Unit* const begin = sourceUnits.codeUnitPtrAt(partialOffset); + const Unit* chunkLimit = sourceUnits.codeUnitPtrAt( + start + std::min(entriesLen++ * ColumnChunkLength, offsetInLine)); + + MOZ_ASSERT(begin < chunkLimit); + MOZ_ASSERT(chunkLimit <= limit); + + static_assert( + ColumnChunkLength > SourceUnitTraits::maxUnitsLength - 1, + "any retraction below is assumed to never underflow to the " + "preceding chunk, even for the longest code point"); + + // Prior tokenizing ensured that [begin, limit) is validly encoded, and + // |begin < chunkLimit|, so any retraction here can't underflow. + RetractPointerToCodePointBoundary(&chunkLimit, limit); + + MOZ_ASSERT(begin < chunkLimit); + MOZ_ASSERT(chunkLimit <= limit); + + size_t numUnits = PointerRangeSize(begin, chunkLimit); + size_t numCodePoints = unicode::CountCodePoints(begin, chunkLimit); + + // If this chunk (which will become non-final at the end of the loop) is + // all single-unit code points, annotate the chunk accordingly. + if (numUnits == numCodePoints) { + lastChunkVectorForLine_->back().guaranteeSingleUnits(); + } + + partialOffset += numUnits; + partialColumn += numCodePoints; + + lastChunkVectorForLine_->infallibleEmplaceBack( + partialColumn, UnitsType::PossiblyMultiUnit); + } while (entriesLen < chunkIndex + 1); + + // We're at a spot in the current final chunk, and final chunks never have + // complete units information, so be pessimistic. + unitsType = UnitsType::PossiblyMultiUnit; + } + + return ColumnFromPartial(partialOffset, partialColumn, unitsType); +} + +template +uint32_t GeneralTokenStreamChars::computeColumn( + LineToken lineToken, uint32_t offset) const { + lineToken.assertConsistentOffset(offset); + + const TokenStreamAnyChars& anyChars = anyCharsAccess(); + + uint32_t column = + anyChars.computePartialColumn(lineToken, offset, this->sourceUnits); + + if (lineToken.isFirstLine()) { + if (column > ColumnLimit) { + return ColumnLimit; + } + + static_assert(uint32_t(ColumnLimit + ColumnLimit) > ColumnLimit, + "Adding ColumnLimit should not overflow"); + + uint32_t firstLineOffset = anyChars.options_.column; + column += firstLineOffset; + } + + if (column > ColumnLimit) { + return ColumnLimit; + } + + return column; +} + +template +void GeneralTokenStreamChars::computeLineAndColumn( + uint32_t offset, uint32_t* line, uint32_t* column) const { + const TokenStreamAnyChars& anyChars = anyCharsAccess(); + + auto lineToken = anyChars.lineToken(offset); + *line = anyChars.lineNumber(lineToken); + *column = computeColumn(lineToken, offset); +} + +template +MOZ_COLD void TokenStreamChars::internalEncodingError( + uint8_t relevantUnits, unsigned errorNumber, ...) { + va_list args; + va_start(args, errorNumber); + + do { + size_t offset = this->sourceUnits.offset(); + + ErrorMetadata err; + + TokenStreamAnyChars& anyChars = anyCharsAccess(); + + bool canAddLineOfContext = fillExceptingContext(&err, offset); + if (canAddLineOfContext) { + if (!internalComputeLineOfContext(&err, offset)) { + break; + } + + // As this is an encoding error, the computed window-end must be + // identical to the location of the error -- any further on and the + // window would contain invalid Unicode. + MOZ_ASSERT_IF(err.lineOfContext != nullptr, + err.lineLength == err.tokenOffset); + } + + auto notes = MakeUnique(); + if (!notes) { + ReportOutOfMemory(anyChars.fc); + break; + } + + // The largest encoding of a UTF-8 code point is 4 units. (Encoding an + // obsolete 5- or 6-byte code point will complain only about a bad lead + // code unit.) + constexpr size_t MaxWidth = sizeof("0xHH 0xHH 0xHH 0xHH"); + + MOZ_ASSERT(relevantUnits > 0); + + char badUnitsStr[MaxWidth]; + char* ptr = badUnitsStr; + while (relevantUnits > 0) { + byteToString(this->sourceUnits.getCodeUnit().toUint8(), ptr); + ptr[4] = ' '; + + ptr += 5; + relevantUnits--; + } + + ptr[-1] = '\0'; + + uint32_t line, column; + computeLineAndColumn(offset, &line, &column); + + if (!notes->addNoteASCII(anyChars.fc, anyChars.getFilename(), 0, line, + column, GetErrorMessage, nullptr, + JSMSG_BAD_CODE_UNITS, badUnitsStr)) { + break; + } + + ReportCompileErrorLatin1(anyChars.fc, std::move(err), std::move(notes), + errorNumber, &args); + } while (false); + + va_end(args); +} + +template +MOZ_COLD void TokenStreamChars::badLeadUnit( + Utf8Unit lead) { + uint8_t leadValue = lead.toUint8(); + + char leadByteStr[5]; + byteToTerminatedString(leadValue, leadByteStr); + + internalEncodingError(1, JSMSG_BAD_LEADING_UTF8_UNIT, leadByteStr); +} + +template +MOZ_COLD void TokenStreamChars::notEnoughUnits( + Utf8Unit lead, uint8_t remaining, uint8_t required) { + uint8_t leadValue = lead.toUint8(); + + MOZ_ASSERT(required == 2 || required == 3 || required == 4); + MOZ_ASSERT(remaining < 4); + MOZ_ASSERT(remaining < required); + + char leadByteStr[5]; + byteToTerminatedString(leadValue, leadByteStr); + + // |toHexChar| produces the desired decimal numbers for values < 4. + const char expectedStr[] = {toHexChar(required - 1), '\0'}; + const char actualStr[] = {toHexChar(remaining - 1), '\0'}; + + internalEncodingError(remaining, JSMSG_NOT_ENOUGH_CODE_UNITS, leadByteStr, + expectedStr, required == 2 ? "" : "s", actualStr, + remaining == 2 ? " was" : "s were"); +} + +template +MOZ_COLD void TokenStreamChars::badTrailingUnit( + uint8_t unitsObserved) { + Utf8Unit badUnit = + this->sourceUnits.addressOfNextCodeUnit()[unitsObserved - 1]; + + char badByteStr[5]; + byteToTerminatedString(badUnit.toUint8(), badByteStr); + + internalEncodingError(unitsObserved, JSMSG_BAD_TRAILING_UTF8_UNIT, + badByteStr); +} + +template +MOZ_COLD void +TokenStreamChars::badStructurallyValidCodePoint( + char32_t codePoint, uint8_t codePointLength, const char* reason) { + // Construct a string like "0x203D" (including null terminator) to include + // in the error message. Write the string end-to-start from end to start + // of an adequately sized |char| array, shifting least significant nibbles + // off the number and writing the corresponding hex digits until done, then + // prefixing with "0x". |codePointStr| points at the incrementally + // computed string, within |codePointCharsArray|'s bounds. + + // 0x1F'FFFF is the maximum value that can fit in 3+6+6+6 unconstrained + // bits in a four-byte UTF-8 code unit sequence. + constexpr size_t MaxHexSize = sizeof( + "0x1F" + "FFFF"); // including '\0' + char codePointCharsArray[MaxHexSize]; + + char* codePointStr = std::end(codePointCharsArray); + *--codePointStr = '\0'; + + // Note that by do-while looping here rather than while-looping, this + // writes a '0' when |codePoint == 0|. + do { + MOZ_ASSERT(codePointCharsArray < codePointStr); + *--codePointStr = toHexChar(codePoint & 0xF); + codePoint >>= 4; + } while (codePoint); + + MOZ_ASSERT(codePointCharsArray + 2 <= codePointStr); + *--codePointStr = 'x'; + *--codePointStr = '0'; + + internalEncodingError(codePointLength, JSMSG_FORBIDDEN_UTF8_CODE_POINT, + codePointStr, reason); +} + +template +[[nodiscard]] bool +TokenStreamChars::getNonAsciiCodePointDontNormalize( + Utf8Unit lead, char32_t* codePoint) { + auto onBadLeadUnit = [this, &lead]() { this->badLeadUnit(lead); }; + + auto onNotEnoughUnits = [this, &lead](uint8_t remaining, uint8_t required) { + this->notEnoughUnits(lead, remaining, required); + }; + + auto onBadTrailingUnit = [this](uint8_t unitsObserved) { + this->badTrailingUnit(unitsObserved); + }; + + auto onBadCodePoint = [this](char32_t badCodePoint, uint8_t unitsObserved) { + this->badCodePoint(badCodePoint, unitsObserved); + }; + + auto onNotShortestForm = [this](char32_t badCodePoint, + uint8_t unitsObserved) { + this->notShortestForm(badCodePoint, unitsObserved); + }; + + // If a valid code point is decoded, this function call consumes its code + // units. If not, it ungets the lead code unit and invokes the right error + // handler, so on failure we must immediately return false. + SourceUnitsIterator iter(this->sourceUnits); + Maybe maybeCodePoint = DecodeOneUtf8CodePointInline( + lead, &iter, SourceUnitsEnd(), onBadLeadUnit, onNotEnoughUnits, + onBadTrailingUnit, onBadCodePoint, onNotShortestForm); + if (maybeCodePoint.isNothing()) { + return false; + } + + *codePoint = maybeCodePoint.value(); + return true; +} + +template +bool TokenStreamChars::getNonAsciiCodePoint( + int32_t lead, char32_t* codePoint) { + MOZ_ASSERT(lead != EOF); + MOZ_ASSERT(!isAsciiCodePoint(lead), + "ASCII code unit/point must be handled separately"); + MOZ_ASSERT(lead == this->sourceUnits.previousCodeUnit(), + "getNonAsciiCodePoint called incorrectly"); + + // The code point is usually |lead|: overwrite later if needed. + *codePoint = AssertedCast(lead); + + // ECMAScript specifically requires that unpaired UTF-16 surrogates be + // treated as the corresponding code point and not as an error. See + // . + // Thus this function does not consider any sequence of 16-bit numbers to + // be intrinsically in error. + + // Dispense with single-unit code points and lone trailing surrogates. + if (MOZ_LIKELY(!unicode::IsLeadSurrogate(lead))) { + if (MOZ_UNLIKELY(lead == unicode::LINE_SEPARATOR || + lead == unicode::PARA_SEPARATOR)) { + if (!updateLineInfoForEOL()) { +#ifdef DEBUG + // Assign to a sentinel value to hopefully cause errors. + *codePoint = std::numeric_limits::max(); +#endif + MOZ_MAKE_MEM_UNDEFINED(codePoint, sizeof(*codePoint)); + return false; + } + + *codePoint = '\n'; + } else { + MOZ_ASSERT(!IsLineTerminator(*codePoint)); + } + + return true; + } + + // Also handle a lead surrogate not paired with a trailing surrogate. + if (MOZ_UNLIKELY( + this->sourceUnits.atEnd() || + !unicode::IsTrailSurrogate(this->sourceUnits.peekCodeUnit()))) { + MOZ_ASSERT(!IsLineTerminator(*codePoint)); + return true; + } + + // Otherwise we have a multi-unit code point. + *codePoint = unicode::UTF16Decode(lead, this->sourceUnits.getCodeUnit()); + MOZ_ASSERT(!IsLineTerminator(*codePoint)); + return true; +} + +template +bool TokenStreamChars::getNonAsciiCodePoint( + int32_t unit, char32_t* codePoint) { + MOZ_ASSERT(unit != EOF); + MOZ_ASSERT(!isAsciiCodePoint(unit), + "ASCII code unit/point must be handled separately"); + + Utf8Unit lead = Utf8Unit(static_cast(unit)); + MOZ_ASSERT(lead == this->sourceUnits.previousCodeUnit(), + "getNonAsciiCodePoint called incorrectly"); + + auto onBadLeadUnit = [this, &lead]() { this->badLeadUnit(lead); }; + + auto onNotEnoughUnits = [this, &lead](uint_fast8_t remaining, + uint_fast8_t required) { + this->notEnoughUnits(lead, remaining, required); + }; + + auto onBadTrailingUnit = [this](uint_fast8_t unitsObserved) { + this->badTrailingUnit(unitsObserved); + }; + + auto onBadCodePoint = [this](char32_t badCodePoint, + uint_fast8_t unitsObserved) { + this->badCodePoint(badCodePoint, unitsObserved); + }; + + auto onNotShortestForm = [this](char32_t badCodePoint, + uint_fast8_t unitsObserved) { + this->notShortestForm(badCodePoint, unitsObserved); + }; + + // This consumes the full, valid code point or ungets |lead| and calls the + // appropriate error functor on failure. + SourceUnitsIterator iter(this->sourceUnits); + Maybe maybeCodePoint = DecodeOneUtf8CodePoint( + lead, &iter, SourceUnitsEnd(), onBadLeadUnit, onNotEnoughUnits, + onBadTrailingUnit, onBadCodePoint, onNotShortestForm); + if (maybeCodePoint.isNothing()) { + return false; + } + + char32_t cp = maybeCodePoint.value(); + if (MOZ_UNLIKELY(cp == unicode::LINE_SEPARATOR || + cp == unicode::PARA_SEPARATOR)) { + if (!updateLineInfoForEOL()) { +#ifdef DEBUG + // Assign to a sentinel value to hopefully cause errors. + *codePoint = std::numeric_limits::max(); +#endif + MOZ_MAKE_MEM_UNDEFINED(codePoint, sizeof(*codePoint)); + return false; + } + + *codePoint = '\n'; + } else { + MOZ_ASSERT(!IsLineTerminator(cp)); + *codePoint = cp; + } + + return true; +} + +template <> +size_t SourceUnits::findWindowStart(size_t offset) const { + // This is JS's understanding of UTF-16 that allows lone surrogates, so + // we have to exclude lone surrogates from [windowStart, offset) ourselves. + + const char16_t* const earliestPossibleStart = codeUnitPtrAt(startOffset_); + + const char16_t* const initial = codeUnitPtrAt(offset); + const char16_t* p = initial; + + auto HalfWindowSize = [&p, &initial]() { + return PointerRangeSize(p, initial); + }; + + while (true) { + MOZ_ASSERT(earliestPossibleStart <= p); + MOZ_ASSERT(HalfWindowSize() <= WindowRadius); + if (p <= earliestPossibleStart || HalfWindowSize() >= WindowRadius) { + break; + } + + char16_t c = p[-1]; + + // This stops at U+2028 LINE SEPARATOR or U+2029 PARAGRAPH SEPARATOR in + // string and template literals. These code points do affect line and + // column coordinates, even as they encode their literal values. + if (IsLineTerminator(c)) { + break; + } + + // Don't allow invalid UTF-16 in pre-context. (Current users don't + // require this, and this behavior isn't currently imposed on + // pre-context, but these facts might change someday.) + + if (MOZ_UNLIKELY(unicode::IsLeadSurrogate(c))) { + break; + } + + // Optimistically include the code unit, reverting below if needed. + p--; + + // If it's not a surrogate at all, keep going. + if (MOZ_LIKELY(!unicode::IsTrailSurrogate(c))) { + continue; + } + + // Stop if we don't have a usable surrogate pair. + if (HalfWindowSize() >= WindowRadius || + p <= earliestPossibleStart || // trail surrogate at low end + !unicode::IsLeadSurrogate(p[-1])) // no paired lead surrogate + { + p++; + break; + } + + p--; + } + + MOZ_ASSERT(HalfWindowSize() <= WindowRadius); + return offset - HalfWindowSize(); +} + +template <> +size_t SourceUnits::findWindowStart(size_t offset) const { + // |offset| must be the location of the error or somewhere before it, so we + // know preceding data is valid UTF-8. + + const Utf8Unit* const earliestPossibleStart = codeUnitPtrAt(startOffset_); + + const Utf8Unit* const initial = codeUnitPtrAt(offset); + const Utf8Unit* p = initial; + + auto HalfWindowSize = [&p, &initial]() { + return PointerRangeSize(p, initial); + }; + + while (true) { + MOZ_ASSERT(earliestPossibleStart <= p); + MOZ_ASSERT(HalfWindowSize() <= WindowRadius); + if (p <= earliestPossibleStart || HalfWindowSize() >= WindowRadius) { + break; + } + + // Peek backward for a line break, and only decrement if there is none. + uint8_t prev = p[-1].toUint8(); + + // First check for the ASCII LineTerminators. + if (prev == '\r' || prev == '\n') { + break; + } + + // Now check for the non-ASCII LineTerminators U+2028 LINE SEPARATOR + // (0xE2 0x80 0xA8) and U+2029 PARAGRAPH (0xE2 0x80 0xA9). If there + // aren't three code units available, some comparison here will fail + // before we'd underflow. + if (MOZ_UNLIKELY((prev == 0xA8 || prev == 0xA9) && + p[-2].toUint8() == 0x80 && p[-3].toUint8() == 0xE2)) { + break; + } + + // Rewind over the non-LineTerminator. This can't underflow + // |earliestPossibleStart| because it begins a code point. + while (IsTrailingUnit(*--p)) { + continue; + } + + MOZ_ASSERT(earliestPossibleStart <= p); + + // But if we underflowed |WindowRadius|, adjust forward and stop. + if (HalfWindowSize() > WindowRadius) { + static_assert(WindowRadius > 3, + "skipping over non-lead code units below must not " + "advance past |offset|"); + + while (IsTrailingUnit(*++p)) { + continue; + } + + MOZ_ASSERT(HalfWindowSize() < WindowRadius); + break; + } + } + + MOZ_ASSERT(HalfWindowSize() <= WindowRadius); + return offset - HalfWindowSize(); +} + +template <> +size_t SourceUnits::findWindowEnd(size_t offset) const { + const char16_t* const initial = codeUnitPtrAt(offset); + const char16_t* p = initial; + + auto HalfWindowSize = [&initial, &p]() { + return PointerRangeSize(initial, p); + }; + + while (true) { + MOZ_ASSERT(p <= limit_); + MOZ_ASSERT(HalfWindowSize() <= WindowRadius); + if (p >= limit_ || HalfWindowSize() >= WindowRadius) { + break; + } + + char16_t c = *p; + + // This stops at U+2028 LINE SEPARATOR or U+2029 PARAGRAPH SEPARATOR in + // string and template literals. These code points do affect line and + // column coordinates, even as they encode their literal values. + if (IsLineTerminator(c)) { + break; + } + + // Don't allow invalid UTF-16 in post-context. (Current users don't + // require this, and this behavior isn't currently imposed on + // pre-context, but these facts might change someday.) + + if (MOZ_UNLIKELY(unicode::IsTrailSurrogate(c))) { + break; + } + + // Optimistically consume the code unit, ungetting it below if needed. + p++; + + // If it's not a surrogate at all, keep going. + if (MOZ_LIKELY(!unicode::IsLeadSurrogate(c))) { + continue; + } + + // Retract if the lead surrogate would stand alone at the end of the + // window. + if (HalfWindowSize() >= WindowRadius || // split pair + p >= limit_ || // half-pair at end of source + !unicode::IsTrailSurrogate(*p)) // no paired trail surrogate + { + p--; + break; + } + + p++; + } + + return offset + HalfWindowSize(); +} + +template <> +size_t SourceUnits::findWindowEnd(size_t offset) const { + const Utf8Unit* const initial = codeUnitPtrAt(offset); + const Utf8Unit* p = initial; + + auto HalfWindowSize = [&initial, &p]() { + return PointerRangeSize(initial, p); + }; + + while (true) { + MOZ_ASSERT(p <= limit_); + MOZ_ASSERT(HalfWindowSize() <= WindowRadius); + if (p >= limit_ || HalfWindowSize() >= WindowRadius) { + break; + } + + // A non-encoding error might be followed by an encoding error within + // |maxEnd|, so we must validate as we go to not include invalid UTF-8 + // in the computed window. What joy! + + Utf8Unit lead = *p; + if (mozilla::IsAscii(lead)) { + if (IsSingleUnitLineTerminator(lead)) { + break; + } + + p++; + continue; + } + + PeekedCodePoint peeked = PeekCodePoint(p, limit_); + if (peeked.isNone()) { + break; // encoding error + } + + char32_t c = peeked.codePoint(); + if (MOZ_UNLIKELY(c == unicode::LINE_SEPARATOR || + c == unicode::PARA_SEPARATOR)) { + break; + } + + MOZ_ASSERT(!IsLineTerminator(c)); + + uint8_t len = peeked.lengthInUnits(); + if (HalfWindowSize() + len > WindowRadius) { + break; + } + + p += len; + } + + MOZ_ASSERT(HalfWindowSize() <= WindowRadius); + return offset + HalfWindowSize(); +} + +template +bool TokenStreamSpecific::advance(size_t position) { + const Unit* end = this->sourceUnits.codeUnitPtrAt(position); + while (this->sourceUnits.addressOfNextCodeUnit() < end) { + if (!getCodePoint()) { + return false; + } + } + + TokenStreamAnyChars& anyChars = anyCharsAccess(); + Token* cur = const_cast(&anyChars.currentToken()); + cur->pos.begin = this->sourceUnits.offset(); + cur->pos.end = cur->pos.begin; +#ifdef DEBUG + cur->type = TokenKind::Limit; +#endif + MOZ_MAKE_MEM_UNDEFINED(&cur->type, sizeof(cur->type)); + anyChars.lookahead = 0; + return true; +} + +template +void TokenStreamSpecific::seekTo(const Position& pos) { + TokenStreamAnyChars& anyChars = anyCharsAccess(); + + this->sourceUnits.setAddressOfNextCodeUnit(pos.buf, + /* allowPoisoned = */ true); + anyChars.flags = pos.flags; + anyChars.lineno = pos.lineno; + anyChars.linebase = pos.linebase; + anyChars.prevLinebase = pos.prevLinebase; + anyChars.lookahead = pos.lookahead; + + anyChars.tokens[anyChars.cursor()] = pos.currentToken; + for (unsigned i = 0; i < anyChars.lookahead; i++) { + anyChars.tokens[anyChars.aheadCursor(1 + i)] = pos.lookaheadTokens[i]; + } +} + +template +bool TokenStreamSpecific::seekTo( + const Position& pos, const TokenStreamAnyChars& other) { + if (!anyCharsAccess().srcCoords.fill(other.srcCoords)) { + return false; + } + + seekTo(pos); + return true; +} + +void TokenStreamAnyChars::computeErrorMetadataNoOffset( + ErrorMetadata* err) const { + err->isMuted = mutedErrors; + err->filename = filename_; + err->lineNumber = 0; + err->columnNumber = 0; + + MOZ_ASSERT(err->lineOfContext == nullptr); +} + +bool TokenStreamAnyChars::fillExceptingContext(ErrorMetadata* err, + uint32_t offset) const { + err->isMuted = mutedErrors; + + // If this TokenStreamAnyChars doesn't have location information, try to + // get it from the caller. + if (!filename_) { + JSContext* maybeCx = context()->maybeCurrentJSContext(); + if (maybeCx && !maybeCx->isHelperThreadContext()) { + NonBuiltinFrameIter iter(maybeCx, + FrameIter::FOLLOW_DEBUGGER_EVAL_PREV_LINK, + maybeCx->realm()->principals()); + if (!iter.done() && iter.filename()) { + err->filename = iter.filename(); + err->lineNumber = iter.computeLine(&err->columnNumber); + return false; + } + } + } + + // Otherwise use this TokenStreamAnyChars's location information. + err->filename = filename_; + return true; +} + +template <> +inline void SourceUnits::computeWindowOffsetAndLength( + const char16_t* encodedWindow, size_t encodedTokenOffset, + size_t* utf16TokenOffset, size_t encodedWindowLength, + size_t* utf16WindowLength) const { + MOZ_ASSERT_UNREACHABLE("shouldn't need to recompute for UTF-16"); +} + +template <> +inline void SourceUnits::computeWindowOffsetAndLength( + const Utf8Unit* encodedWindow, size_t encodedTokenOffset, + size_t* utf16TokenOffset, size_t encodedWindowLength, + size_t* utf16WindowLength) const { + MOZ_ASSERT(encodedTokenOffset <= encodedWindowLength, + "token offset must be within the window, and the two lambda " + "calls below presume this ordering of values"); + + const Utf8Unit* const encodedWindowEnd = encodedWindow + encodedWindowLength; + + size_t i = 0; + auto ComputeUtf16Count = [&i, &encodedWindow](const Utf8Unit* limit) { + while (encodedWindow < limit) { + Utf8Unit lead = *encodedWindow++; + if (MOZ_LIKELY(IsAscii(lead))) { + // ASCII contributes a single UTF-16 code unit. + i++; + continue; + } + + Maybe cp = DecodeOneUtf8CodePoint(lead, &encodedWindow, limit); + MOZ_ASSERT(cp.isSome(), + "computed window should only contain valid UTF-8"); + + i += unicode::IsSupplementary(cp.value()) ? 2 : 1; + } + + return i; + }; + + // Compute the token offset from |i == 0| and the initial |encodedWindow|. + const Utf8Unit* token = encodedWindow + encodedTokenOffset; + MOZ_ASSERT(token <= encodedWindowEnd); + *utf16TokenOffset = ComputeUtf16Count(token); + + // Compute the window length, picking up from |i| and |encodedWindow| that, + // in general, were modified just above. + *utf16WindowLength = ComputeUtf16Count(encodedWindowEnd); +} + +template +bool TokenStreamCharsBase::addLineOfContext(ErrorMetadata* err, + uint32_t offset) const { + // Rename the variable to make meaning clearer: an offset into source units + // in Unit encoding. + size_t encodedOffset = offset; + + // These are also offsets into source units in Unit encoding. + size_t encodedWindowStart = sourceUnits.findWindowStart(encodedOffset); + size_t encodedWindowEnd = sourceUnits.findWindowEnd(encodedOffset); + + size_t encodedWindowLength = encodedWindowEnd - encodedWindowStart; + MOZ_ASSERT(encodedWindowLength <= SourceUnits::WindowRadius * 2); + + // Don't add a useless "line" of context when the window ends up empty + // because of an invalid encoding at the start of a line. + if (encodedWindowLength == 0) { + MOZ_ASSERT(err->lineOfContext == nullptr, + "ErrorMetadata::lineOfContext must be null so we don't " + "have to set the lineLength/tokenOffset fields"); + return true; + } + + CharBuffer lineOfContext(fc); + + const Unit* encodedWindow = sourceUnits.codeUnitPtrAt(encodedWindowStart); + if (!FillCharBufferFromSourceNormalizingAsciiLineBreaks( + lineOfContext, encodedWindow, encodedWindow + encodedWindowLength)) { + return false; + } + + size_t utf16WindowLength = lineOfContext.length(); + + // The windowed string is null-terminated. + if (!lineOfContext.append('\0')) { + return false; + } + + err->lineOfContext.reset(lineOfContext.extractOrCopyRawBuffer()); + if (!err->lineOfContext) { + return false; + } + + size_t encodedTokenOffset = encodedOffset - encodedWindowStart; + + MOZ_ASSERT(encodedTokenOffset <= encodedWindowLength, + "token offset must be inside the window"); + + // The length in UTF-8 code units of a code point is always greater than or + // equal to the same code point's length in UTF-16 code points. ASCII code + // points are 1 unit in either encoding. Code points in [U+0080, U+10000) + // are 2-3 UTF-8 code units to 1 UTF-16 code unit. And code points in + // [U+10000, U+10FFFF] are 4 UTF-8 code units to 2 UTF-16 code units. + // + // Therefore, if encoded window length equals the length in UTF-16 (this is + // always the case for Unit=char16_t), the UTF-16 offsets are exactly the + // encoded offsets. Otherwise we must convert offset/length from UTF-8 to + // UTF-16. + if constexpr (std::is_same_v) { + MOZ_ASSERT(utf16WindowLength == encodedWindowLength, + "UTF-16 to UTF-16 shouldn't change window length"); + err->tokenOffset = encodedTokenOffset; + err->lineLength = encodedWindowLength; + } else { + static_assert(std::is_same_v, "should only see UTF-8 here"); + + bool simple = utf16WindowLength == encodedWindowLength; +#ifdef DEBUG + auto isAscii = [](Unit u) { return IsAscii(u); }; + MOZ_ASSERT(std::all_of(encodedWindow, encodedWindow + encodedWindowLength, + isAscii) == simple, + "equal window lengths in UTF-8 should correspond only to " + "wholly-ASCII text"); +#endif + if (simple) { + err->tokenOffset = encodedTokenOffset; + err->lineLength = encodedWindowLength; + } else { + sourceUnits.computeWindowOffsetAndLength( + encodedWindow, encodedTokenOffset, &err->tokenOffset, + encodedWindowLength, &err->lineLength); + } + } + + return true; +} + +template +bool TokenStreamSpecific::computeErrorMetadata( + ErrorMetadata* err, const ErrorOffset& errorOffset) const { + if (errorOffset.is()) { + anyCharsAccess().computeErrorMetadataNoOffset(err); + return true; + } + + uint32_t offset; + if (errorOffset.is()) { + offset = errorOffset.as(); + } else { + offset = this->sourceUnits.offset(); + } + + // This function's return value isn't a success/failure indication: it + // returns true if this TokenStream can be used to provide a line of + // context. + if (fillExceptingContext(err, offset)) { + // Add a line of context from this TokenStream to help with debugging. + return internalComputeLineOfContext(err, offset); + } + + // We can't fill in any more here. + return true; +} + +template +void TokenStreamSpecific::reportIllegalCharacter( + int32_t cp) { + UniqueChars display = JS_smprintf("U+%04X", cp); + if (!display) { + ReportOutOfMemory(anyCharsAccess().fc); + return; + } + error(JSMSG_ILLEGAL_CHARACTER, display.get()); +} + +// We have encountered a '\': check for a Unicode escape sequence after it. +// Return the length of the escape sequence and the encoded code point (by +// value) if we found a Unicode escape sequence, and skip all code units +// involed. Otherwise, return 0 and don't advance along the buffer. +template +uint32_t GeneralTokenStreamChars::matchUnicodeEscape( + char32_t* codePoint) { + MOZ_ASSERT(this->sourceUnits.previousCodeUnit() == Unit('\\')); + + int32_t unit = getCodeUnit(); + if (unit != 'u') { + // NOTE: |unit| may be EOF here. + ungetCodeUnit(unit); + MOZ_ASSERT(this->sourceUnits.previousCodeUnit() == Unit('\\')); + return 0; + } + + char16_t v; + unit = getCodeUnit(); + if (IsAsciiHexDigit(unit) && this->sourceUnits.matchHexDigits(3, &v)) { + *codePoint = (AsciiAlphanumericToNumber(unit) << 12) | v; + return 5; + } + + if (unit == '{') { + return matchExtendedUnicodeEscape(codePoint); + } + + // NOTE: |unit| may be EOF here, so this ungets either one or two units. + ungetCodeUnit(unit); + ungetCodeUnit('u'); + MOZ_ASSERT(this->sourceUnits.previousCodeUnit() == Unit('\\')); + return 0; +} + +template +uint32_t +GeneralTokenStreamChars::matchExtendedUnicodeEscape( + char32_t* codePoint) { + MOZ_ASSERT(this->sourceUnits.previousCodeUnit() == Unit('{')); + + int32_t unit = getCodeUnit(); + + // Skip leading zeroes. + uint32_t leadingZeroes = 0; + while (unit == '0') { + leadingZeroes++; + unit = getCodeUnit(); + } + + size_t i = 0; + uint32_t code = 0; + while (IsAsciiHexDigit(unit) && i < 6) { + code = (code << 4) | AsciiAlphanumericToNumber(unit); + unit = getCodeUnit(); + i++; + } + + uint32_t gotten = + 2 + // 'u{' + leadingZeroes + i + // significant hexdigits + (unit != EOF); // subtract a get if it didn't contribute to length + + if (unit == '}' && (leadingZeroes > 0 || i > 0) && + code <= unicode::NonBMPMax) { + *codePoint = code; + return gotten; + } + + this->sourceUnits.unskipCodeUnits(gotten); + MOZ_ASSERT(this->sourceUnits.previousCodeUnit() == Unit('\\')); + return 0; +} + +template +uint32_t +GeneralTokenStreamChars::matchUnicodeEscapeIdStart( + char32_t* codePoint) { + uint32_t length = matchUnicodeEscape(codePoint); + if (MOZ_LIKELY(length > 0)) { + if (MOZ_LIKELY(unicode::IsIdentifierStart(*codePoint))) { + return length; + } + + this->sourceUnits.unskipCodeUnits(length); + } + + MOZ_ASSERT(this->sourceUnits.previousCodeUnit() == Unit('\\')); + return 0; +} + +template +bool GeneralTokenStreamChars::matchUnicodeEscapeIdent( + char32_t* codePoint) { + uint32_t length = matchUnicodeEscape(codePoint); + if (MOZ_LIKELY(length > 0)) { + if (MOZ_LIKELY(unicode::IsIdentifierPart(*codePoint))) { + return true; + } + + this->sourceUnits.unskipCodeUnits(length); + } + + MOZ_ASSERT(this->sourceUnits.previousCodeUnit() == Unit('\\')); + return false; +} + +template +[[nodiscard]] bool +TokenStreamSpecific::matchIdentifierStart( + IdentifierEscapes* sawEscape) { + int32_t unit = getCodeUnit(); + if (unit == EOF) { + error(JSMSG_MISSING_PRIVATE_NAME); + return false; + } + + if (MOZ_LIKELY(isAsciiCodePoint(unit))) { + if (unicode::IsIdentifierStart(char16_t(unit))) { + *sawEscape = IdentifierEscapes::None; + return true; + } + + if (unit == '\\') { + char32_t codePoint; + uint32_t escapeLength = matchUnicodeEscapeIdStart(&codePoint); + if (escapeLength != 0) { + *sawEscape = IdentifierEscapes::SawUnicodeEscape; + return true; + } + + // We could point "into" a mistyped escape, e.g. for "\u{41H}" we + // could point at the 'H'. But we don't do that now, so the code + // unit after the '\' isn't necessarily bad, so just point at the + // start of the actually-invalid escape. + ungetCodeUnit('\\'); + error(JSMSG_BAD_ESCAPE); + return false; + } + } + + // Unget the lead code unit before peeking at the full code point. + ungetCodeUnit(unit); + + PeekedCodePoint peeked = this->sourceUnits.peekCodePoint(); + if (!peeked.isNone() && unicode::IsIdentifierStart(peeked.codePoint())) { + this->sourceUnits.consumeKnownCodePoint(peeked); + + *sawEscape = IdentifierEscapes::None; + return true; + } + + error(JSMSG_MISSING_PRIVATE_NAME); + return false; +} + +template +bool TokenStreamSpecific::getDirectives( + bool isMultiline, bool shouldWarnDeprecated) { + // Match directive comments used in debugging, such as "//# sourceURL" and + // "//# sourceMappingURL". Use of "//@" instead of "//#" is deprecated. + // + // To avoid a crashing bug in IE, several JavaScript transpilers wrap single + // line comments containing a source mapping URL inside a multiline + // comment. To avoid potentially expensive lookahead and backtracking, we + // only check for this case if we encounter a '#' code unit. + + bool res = getDisplayURL(isMultiline, shouldWarnDeprecated) && + getSourceMappingURL(isMultiline, shouldWarnDeprecated); + if (!res) { + badToken(); + } + + return res; +} + +[[nodiscard]] bool TokenStreamCharsShared::copyCharBufferTo( + UniquePtr* destination) { + size_t length = charBuffer.length(); + + *destination = fc->getAllocator()->make_pod_array(length + 1); + if (!*destination) { + return false; + } + + std::copy(charBuffer.begin(), charBuffer.end(), destination->get()); + (*destination)[length] = '\0'; + return true; +} + +template +[[nodiscard]] bool TokenStreamSpecific::getDirective( + bool isMultiline, bool shouldWarnDeprecated, const char* directive, + uint8_t directiveLength, const char* errorMsgPragma, + UniquePtr* destination) { + // Stop if we don't find |directive|. (Note that |directive| must be + // ASCII, so there are no tricky encoding issues to consider in matching + // UTF-8/16-agnostically.) + if (!this->sourceUnits.matchCodeUnits(directive, directiveLength)) { + return true; + } + + if (shouldWarnDeprecated) { + if (!warning(JSMSG_DEPRECATED_PRAGMA, errorMsgPragma)) { + return false; + } + } + + this->charBuffer.clear(); + + do { + int32_t unit = peekCodeUnit(); + if (unit == EOF) { + break; + } + + if (MOZ_LIKELY(isAsciiCodePoint(unit))) { + if (unicode::IsSpace(AssertedCast(unit))) { + break; + } + + consumeKnownCodeUnit(unit); + + // Debugging directives can occur in both single- and multi-line + // comments. If we're currently inside a multi-line comment, we + // also must recognize multi-line comment terminators. + if (isMultiline && unit == '*' && peekCodeUnit() == '/') { + ungetCodeUnit('*'); + break; + } + + if (!this->charBuffer.append(unit)) { + return false; + } + + continue; + } + + // This ignores encoding errors: subsequent caller-side code to + // handle the remaining source text in the comment will do so. + PeekedCodePoint peeked = this->sourceUnits.peekCodePoint(); + if (peeked.isNone() || unicode::IsSpace(peeked.codePoint())) { + break; + } + + MOZ_ASSERT(!IsLineTerminator(peeked.codePoint()), + "!IsSpace must imply !IsLineTerminator or else we'll fail to " + "maintain line-info/flags for EOL"); + this->sourceUnits.consumeKnownCodePoint(peeked); + + if (!AppendCodePointToCharBuffer(this->charBuffer, peeked.codePoint())) { + return false; + } + } while (true); + + if (this->charBuffer.empty()) { + // The directive's URL was missing, but comments can contain anything, + // so it isn't an error. + return true; + } + + return copyCharBufferTo(destination); +} + +template +bool TokenStreamSpecific::getDisplayURL( + bool isMultiline, bool shouldWarnDeprecated) { + // Match comments of the form "//# sourceURL=" or + // "/\* //# sourceURL= *\/" + // + // Note that while these are labeled "sourceURL" in the source text, + // internally we refer to it as a "displayURL" to distinguish what the + // developer would like to refer to the source as from the source's actual + // URL. + + static constexpr char sourceURLDirective[] = " sourceURL="; + constexpr uint8_t sourceURLDirectiveLength = js_strlen(sourceURLDirective); + return getDirective(isMultiline, shouldWarnDeprecated, sourceURLDirective, + sourceURLDirectiveLength, "sourceURL", + &anyCharsAccess().displayURL_); +} + +template +bool TokenStreamSpecific::getSourceMappingURL( + bool isMultiline, bool shouldWarnDeprecated) { + // Match comments of the form "//# sourceMappingURL=" or + // "/\* //# sourceMappingURL= *\/" + + static constexpr char sourceMappingURLDirective[] = " sourceMappingURL="; + constexpr uint8_t sourceMappingURLDirectiveLength = + js_strlen(sourceMappingURLDirective); + return getDirective(isMultiline, shouldWarnDeprecated, + sourceMappingURLDirective, + sourceMappingURLDirectiveLength, "sourceMappingURL", + &anyCharsAccess().sourceMapURL_); +} + +template +MOZ_ALWAYS_INLINE Token* +GeneralTokenStreamChars::newTokenInternal( + TokenKind kind, TokenStart start, TokenKind* out) { + MOZ_ASSERT(kind < TokenKind::Limit); + MOZ_ASSERT(kind != TokenKind::Eol, + "TokenKind::Eol should never be used in an actual Token, only " + "returned by peekTokenSameLine()"); + + TokenStreamAnyChars& anyChars = anyCharsAccess(); + anyChars.flags.isDirtyLine = true; + + Token* token = anyChars.allocateToken(); + + *out = token->type = kind; + token->pos = TokenPos(start.offset(), this->sourceUnits.offset()); + MOZ_ASSERT(token->pos.begin <= token->pos.end); + + // NOTE: |token->modifier| is set in |newToken()| so that optimized, + // non-debug code won't do any work to pass a modifier-argument that will + // never be used. + + return token; +} + +template +MOZ_COLD bool GeneralTokenStreamChars::badToken() { + // We didn't get a token, so don't set |flags.isDirtyLine|. + anyCharsAccess().flags.hadError = true; + + // Poisoning sourceUnits on error establishes an invariant: once an + // erroneous token has been seen, sourceUnits will not be consulted again. + // This is true because the parser will deal with the illegal token by + // aborting parsing immediately. + this->sourceUnits.poisonInDebug(); + + return false; +}; + +bool AppendCodePointToCharBuffer(CharBuffer& charBuffer, char32_t codePoint) { + MOZ_ASSERT(codePoint <= unicode::NonBMPMax, + "should only be processing code points validly decoded from UTF-8 " + "or WTF-16 source text (surrogate code points permitted)"); + + char16_t units[2]; + unsigned numUnits = 0; + unicode::UTF16Encode(codePoint, units, &numUnits); + + MOZ_ASSERT(numUnits == 1 || numUnits == 2, + "UTF-16 code points are only encoded in one or two units"); + + if (!charBuffer.append(units[0])) { + return false; + } + + if (numUnits == 1) { + return true; + } + + return charBuffer.append(units[1]); +} + +template +bool TokenStreamSpecific::putIdentInCharBuffer( + const Unit* identStart) { + const Unit* const originalAddress = this->sourceUnits.addressOfNextCodeUnit(); + this->sourceUnits.setAddressOfNextCodeUnit(identStart); + + auto restoreNextRawCharAddress = MakeScopeExit([this, originalAddress]() { + this->sourceUnits.setAddressOfNextCodeUnit(originalAddress); + }); + + this->charBuffer.clear(); + do { + int32_t unit = getCodeUnit(); + if (unit == EOF) { + break; + } + + char32_t codePoint; + if (MOZ_LIKELY(isAsciiCodePoint(unit))) { + if (unicode::IsIdentifierPart(char16_t(unit)) || unit == '#') { + if (!this->charBuffer.append(unit)) { + return false; + } + + continue; + } + + if (unit != '\\' || !matchUnicodeEscapeIdent(&codePoint)) { + break; + } + } else { + // |restoreNextRawCharAddress| undoes all gets, and this function + // doesn't update line/column info. + char32_t cp; + if (!getNonAsciiCodePointDontNormalize(toUnit(unit), &cp)) { + return false; + } + + codePoint = cp; + if (!unicode::IsIdentifierPart(codePoint)) { + break; + } + } + + if (!AppendCodePointToCharBuffer(this->charBuffer, codePoint)) { + return false; + } + } while (true); + + return true; +} + +template +[[nodiscard]] bool TokenStreamSpecific::identifierName( + TokenStart start, const Unit* identStart, IdentifierEscapes escaping, + Modifier modifier, NameVisibility visibility, TokenKind* out) { + // Run the bad-token code for every path out of this function except the + // two success-cases. + auto noteBadToken = MakeScopeExit([this]() { this->badToken(); }); + + // We've already consumed an initial code point in the identifer, to *know* + // that this is an identifier. So no need to worry about not consuming any + // code points in the loop below. + int32_t unit; + while (true) { + unit = peekCodeUnit(); + if (unit == EOF) { + break; + } + + if (MOZ_LIKELY(isAsciiCodePoint(unit))) { + consumeKnownCodeUnit(unit); + + if (MOZ_UNLIKELY( + !unicode::IsIdentifierPart(static_cast(unit)))) { + // Handle a Unicode escape -- otherwise it's not part of the + // identifier. + char32_t codePoint; + if (unit != '\\' || !matchUnicodeEscapeIdent(&codePoint)) { + ungetCodeUnit(unit); + break; + } + + escaping = IdentifierEscapes::SawUnicodeEscape; + } + } else { + // This ignores encoding errors: subsequent caller-side code to + // handle source text after the IdentifierName will do so. + PeekedCodePoint peeked = this->sourceUnits.peekCodePoint(); + if (peeked.isNone() || !unicode::IsIdentifierPart(peeked.codePoint())) { + break; + } + + MOZ_ASSERT(!IsLineTerminator(peeked.codePoint()), + "IdentifierPart must guarantee !IsLineTerminator or " + "else we'll fail to maintain line-info/flags for EOL"); + + this->sourceUnits.consumeKnownCodePoint(peeked); + } + } + + TaggedParserAtomIndex atom; + if (MOZ_UNLIKELY(escaping == IdentifierEscapes::SawUnicodeEscape)) { + // Identifiers containing Unicode escapes have to be converted into + // tokenbuf before atomizing. + if (!putIdentInCharBuffer(identStart)) { + return false; + } + + atom = drainCharBufferIntoAtom(); + } else { + // Escape-free identifiers can be created directly from sourceUnits. + const Unit* chars = identStart; + size_t length = this->sourceUnits.addressOfNextCodeUnit() - identStart; + + // Private identifiers start with a '#', and so cannot be reserved words. + if (visibility == NameVisibility::Public) { + // Represent reserved words lacking escapes as reserved word tokens. + if (const ReservedWordInfo* rw = FindReservedWord(chars, length)) { + noteBadToken.release(); + newSimpleToken(rw->tokentype, start, modifier, out); + return true; + } + } + + atom = atomizeSourceChars(Span(chars, length)); + } + if (!atom) { + return false; + } + + noteBadToken.release(); + if (visibility == NameVisibility::Private) { + newPrivateNameToken(atom, start, modifier, out); + return true; + } + newNameToken(atom, start, modifier, out); + return true; +} + +enum FirstCharKind { + // A char16_t has the 'OneChar' kind if it, by itself, constitutes a valid + // token that cannot also be a prefix of a longer token. E.g. ';' has the + // OneChar kind, but '+' does not, because '++' and '+=' are valid longer + // tokens + // that begin with '+'. + // + // The few token kinds satisfying these properties cover roughly 35--45% + // of the tokens seen in practice. + // + // We represent the 'OneChar' kind with any positive value less than + // TokenKind::Limit. This representation lets us associate + // each one-char token char16_t with a TokenKind and thus avoid + // a subsequent char16_t-to-TokenKind conversion. + OneChar_Min = 0, + OneChar_Max = size_t(TokenKind::Limit) - 1, + + Space = size_t(TokenKind::Limit), + Ident, + Dec, + String, + EOL, + ZeroDigit, + Other, + + LastCharKind = Other +}; + +// OneChar: 40, 41, 44, 58, 59, 91, 93, 123, 125, 126: +// '(', ')', ',', ':', ';', '[', ']', '{', '}', '~' +// Ident: 36, 65..90, 95, 97..122: '$', 'A'..'Z', '_', 'a'..'z' +// Dot: 46: '.' +// Equals: 61: '=' +// String: 34, 39, 96: '"', '\'', '`' +// Dec: 49..57: '1'..'9' +// Plus: 43: '+' +// ZeroDigit: 48: '0' +// Space: 9, 11, 12, 32: '\t', '\v', '\f', ' ' +// EOL: 10, 13: '\n', '\r' +// +#define T_COMMA size_t(TokenKind::Comma) +#define T_COLON size_t(TokenKind::Colon) +#define T_BITNOT size_t(TokenKind::BitNot) +#define T_LP size_t(TokenKind::LeftParen) +#define T_RP size_t(TokenKind::RightParen) +#define T_SEMI size_t(TokenKind::Semi) +#define T_LB size_t(TokenKind::LeftBracket) +#define T_RB size_t(TokenKind::RightBracket) +#define T_LC size_t(TokenKind::LeftCurly) +#define T_RC size_t(TokenKind::RightCurly) +#define _______ Other +static const uint8_t firstCharKinds[] = { + // clang-format off +/* 0 1 2 3 4 5 6 7 8 9 */ +/* 0+ */ _______, _______, _______, _______, _______, _______, _______, _______, _______, Space, +/* 10+ */ EOL, Space, Space, EOL, _______, _______, _______, _______, _______, _______, +/* 20+ */ _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, +/* 30+ */ _______, _______, Space, _______, String, _______, Ident, _______, _______, String, +/* 40+ */ T_LP, T_RP, _______, _______, T_COMMA, _______, _______, _______,ZeroDigit, Dec, +/* 50+ */ Dec, Dec, Dec, Dec, Dec, Dec, Dec, Dec, T_COLON, T_SEMI, +/* 60+ */ _______, _______, _______, _______, _______, Ident, Ident, Ident, Ident, Ident, +/* 70+ */ Ident, Ident, Ident, Ident, Ident, Ident, Ident, Ident, Ident, Ident, +/* 80+ */ Ident, Ident, Ident, Ident, Ident, Ident, Ident, Ident, Ident, Ident, +/* 90+ */ Ident, T_LB, _______, T_RB, _______, Ident, String, Ident, Ident, Ident, +/* 100+ */ Ident, Ident, Ident, Ident, Ident, Ident, Ident, Ident, Ident, Ident, +/* 110+ */ Ident, Ident, Ident, Ident, Ident, Ident, Ident, Ident, Ident, Ident, +/* 120+ */ Ident, Ident, Ident, T_LC, _______, T_RC,T_BITNOT, _______ + // clang-format on +}; +#undef T_COMMA +#undef T_COLON +#undef T_BITNOT +#undef T_LP +#undef T_RP +#undef T_SEMI +#undef T_LB +#undef T_RB +#undef T_LC +#undef T_RC +#undef _______ + +static_assert(LastCharKind < (1 << (sizeof(firstCharKinds[0]) * 8)), + "Elements of firstCharKinds[] are too small"); + +template <> +void SourceUnits::consumeRestOfSingleLineComment() { + while (MOZ_LIKELY(!atEnd())) { + char16_t unit = peekCodeUnit(); + if (IsLineTerminator(unit)) { + return; + } + + consumeKnownCodeUnit(unit); + } +} + +template <> +void SourceUnits::consumeRestOfSingleLineComment() { + while (MOZ_LIKELY(!atEnd())) { + const Utf8Unit unit = peekCodeUnit(); + if (IsSingleUnitLineTerminator(unit)) { + return; + } + + if (MOZ_LIKELY(IsAscii(unit))) { + consumeKnownCodeUnit(unit); + continue; + } + + PeekedCodePoint peeked = peekCodePoint(); + if (peeked.isNone()) { + return; + } + + char32_t c = peeked.codePoint(); + if (MOZ_UNLIKELY(c == unicode::LINE_SEPARATOR || + c == unicode::PARA_SEPARATOR)) { + return; + } + + consumeKnownCodePoint(peeked); + } +} + +template +[[nodiscard]] MOZ_ALWAYS_INLINE bool +TokenStreamSpecific::matchInteger( + IsIntegerUnit isIntegerUnit, int32_t* nextUnit) { + int32_t unit = getCodeUnit(); + if (!isIntegerUnit(unit)) { + *nextUnit = unit; + return true; + } + return matchIntegerAfterFirstDigit(isIntegerUnit, nextUnit); +} + +template +[[nodiscard]] MOZ_ALWAYS_INLINE bool +TokenStreamSpecific::matchIntegerAfterFirstDigit( + IsIntegerUnit isIntegerUnit, int32_t* nextUnit) { + int32_t unit; + while (true) { + unit = getCodeUnit(); + if (isIntegerUnit(unit)) { + continue; + } + if (unit != '_') { + break; + } + unit = getCodeUnit(); + if (!isIntegerUnit(unit)) { + if (unit == '_') { + ungetCodeUnit(unit); + error(JSMSG_NUMBER_MULTIPLE_ADJACENT_UNDERSCORES); + } else { + ungetCodeUnit(unit); + ungetCodeUnit('_'); + error(JSMSG_NUMBER_END_WITH_UNDERSCORE); + } + return false; + } + } + + *nextUnit = unit; + return true; +} + +template +[[nodiscard]] bool TokenStreamSpecific::decimalNumber( + int32_t unit, TokenStart start, const Unit* numStart, Modifier modifier, + TokenKind* out) { + // Run the bad-token code for every path out of this function except the + // one success-case. + auto noteBadToken = MakeScopeExit([this]() { this->badToken(); }); + + // Consume integral component digits. + if (IsAsciiDigit(unit)) { + if (!matchIntegerAfterFirstDigit(IsAsciiDigit, &unit)) { + return false; + } + } + + // Numbers contain no escapes, so we can read directly from |sourceUnits|. + double dval; + bool isBigInt = false; + DecimalPoint decimalPoint = NoDecimal; + if (unit != '.' && unit != 'e' && unit != 'E' && unit != 'n') { + // NOTE: |unit| may be EOF here. + ungetCodeUnit(unit); + + // Most numbers are pure decimal integers without fractional component + // or exponential notation. Handle that with optimized code. + if (!GetDecimalInteger(numStart, this->sourceUnits.addressOfNextCodeUnit(), + &dval)) { + ReportOutOfMemory(this->fc); + return false; + } + } else if (unit == 'n') { + isBigInt = true; + unit = peekCodeUnit(); + } else { + // Consume any decimal dot and fractional component. + if (unit == '.') { + decimalPoint = HasDecimal; + if (!matchInteger(IsAsciiDigit, &unit)) { + return false; + } + } + + // Consume any exponential notation. + if (unit == 'e' || unit == 'E') { + unit = getCodeUnit(); + if (unit == '+' || unit == '-') { + unit = getCodeUnit(); + } + + // Exponential notation must contain at least one digit. + if (!IsAsciiDigit(unit)) { + ungetCodeUnit(unit); + error(JSMSG_MISSING_EXPONENT); + return false; + } + + // Consume exponential digits. + if (!matchIntegerAfterFirstDigit(IsAsciiDigit, &unit)) { + return false; + } + } + + ungetCodeUnit(unit); + + if (!GetDecimal(numStart, this->sourceUnits.addressOfNextCodeUnit(), + &dval)) { + ReportOutOfMemory(this->fc); + return false; + } + } + + // Number followed by IdentifierStart is an error. (This is the only place + // in ECMAScript where token boundary is inadequate to properly separate + // two tokens, necessitating this unaesthetic lookahead.) + if (unit != EOF) { + if (MOZ_LIKELY(isAsciiCodePoint(unit))) { + if (unicode::IsIdentifierStart(char16_t(unit))) { + error(JSMSG_IDSTART_AFTER_NUMBER); + return false; + } + } else { + // This ignores encoding errors: subsequent caller-side code to + // handle source text after the number will do so. + PeekedCodePoint peeked = this->sourceUnits.peekCodePoint(); + if (!peeked.isNone() && unicode::IsIdentifierStart(peeked.codePoint())) { + error(JSMSG_IDSTART_AFTER_NUMBER); + return false; + } + } + } + + noteBadToken.release(); + + if (isBigInt) { + return bigIntLiteral(start, modifier, out); + } + + newNumberToken(dval, decimalPoint, start, modifier, out); + return true; +} + +template +[[nodiscard]] bool TokenStreamSpecific::regexpLiteral( + TokenStart start, TokenKind* out) { + MOZ_ASSERT(this->sourceUnits.previousCodeUnit() == Unit('/')); + this->charBuffer.clear(); + + auto ProcessNonAsciiCodePoint = [this](int32_t lead) { + MOZ_ASSERT(lead != EOF); + MOZ_ASSERT(!this->isAsciiCodePoint(lead)); + + char32_t codePoint; + if (!this->getNonAsciiCodePointDontNormalize(this->toUnit(lead), + &codePoint)) { + return false; + } + + if (MOZ_UNLIKELY(codePoint == unicode::LINE_SEPARATOR || + codePoint == unicode::PARA_SEPARATOR)) { + this->sourceUnits.ungetLineOrParagraphSeparator(); + this->error(JSMSG_UNTERMINATED_REGEXP); + return false; + } + + return AppendCodePointToCharBuffer(this->charBuffer, codePoint); + }; + + auto ReportUnterminatedRegExp = [this](int32_t unit) { + this->ungetCodeUnit(unit); + this->error(JSMSG_UNTERMINATED_REGEXP); + }; + + bool inCharClass = false; + do { + int32_t unit = getCodeUnit(); + if (unit == EOF) { + ReportUnterminatedRegExp(unit); + return badToken(); + } + + if (MOZ_UNLIKELY(!isAsciiCodePoint(unit))) { + if (!ProcessNonAsciiCodePoint(unit)) { + return badToken(); + } + + continue; + } + + if (unit == '\\') { + if (!this->charBuffer.append(unit)) { + return badToken(); + } + + unit = getCodeUnit(); + if (unit == EOF) { + ReportUnterminatedRegExp(unit); + return badToken(); + } + + // Fallthrough only handles ASCII code points, so + // deal with non-ASCII and skip everything else. + if (MOZ_UNLIKELY(!isAsciiCodePoint(unit))) { + if (!ProcessNonAsciiCodePoint(unit)) { + return badToken(); + } + + continue; + } + } else if (unit == '[') { + inCharClass = true; + } else if (unit == ']') { + inCharClass = false; + } else if (unit == '/' && !inCharClass) { + // For IE compat, allow unescaped / in char classes. + break; + } + + // NOTE: Non-ASCII LineTerminators were handled by + // ProcessNonAsciiCodePoint calls above. + if (unit == '\r' || unit == '\n') { + ReportUnterminatedRegExp(unit); + return badToken(); + } + + MOZ_ASSERT(!IsLineTerminator(AssertedCast(unit))); + if (!this->charBuffer.append(unit)) { + return badToken(); + } + } while (true); + + int32_t unit; + RegExpFlags reflags = RegExpFlag::NoFlags; + while (true) { + uint8_t flag; + unit = getCodeUnit(); + if (unit == 'd') { + flag = RegExpFlag::HasIndices; + } else if (unit == 'g') { + flag = RegExpFlag::Global; + } else if (unit == 'i') { + flag = RegExpFlag::IgnoreCase; + } else if (unit == 'm') { + flag = RegExpFlag::Multiline; + } else if (unit == 's') { + flag = RegExpFlag::DotAll; + } else if (unit == 'u') { + flag = RegExpFlag::Unicode; + } else if (unit == 'y') { + flag = RegExpFlag::Sticky; + } else if (IsAsciiAlpha(unit)) { + flag = RegExpFlag::NoFlags; + } else { + break; + } + + if ((reflags & flag) || flag == RegExpFlag::NoFlags) { + ungetCodeUnit(unit); + char buf[2] = {char(unit), '\0'}; + error(JSMSG_BAD_REGEXP_FLAG, buf); + return badToken(); + } + + reflags |= flag; + } + ungetCodeUnit(unit); + + newRegExpToken(reflags, start, out); + return true; +} + +template +[[nodiscard]] bool TokenStreamSpecific::bigIntLiteral( + TokenStart start, Modifier modifier, TokenKind* out) { + MOZ_ASSERT(this->sourceUnits.previousCodeUnit() == toUnit('n')); + MOZ_ASSERT(this->sourceUnits.offset() > start.offset()); + uint32_t length = this->sourceUnits.offset() - start.offset(); + MOZ_ASSERT(length >= 2); + this->charBuffer.clear(); + mozilla::Range chars( + this->sourceUnits.codeUnitPtrAt(start.offset()), length); + for (uint32_t idx = 0; idx < length - 1; idx++) { + int32_t unit = CodeUnitValue(chars[idx]); + // Char buffer may start with a 0[bBoOxX] prefix, then follows with + // binary, octal, decimal, or hex digits. Already checked by caller, as + // the "n" indicating bigint comes at the end. + MOZ_ASSERT(isAsciiCodePoint(unit)); + // Skip over any separators. + if (unit == '_') { + continue; + } + if (!AppendCodePointToCharBuffer(this->charBuffer, unit)) { + return false; + } + } + newBigIntToken(start, modifier, out); + return true; +} + +template +void GeneralTokenStreamChars::consumeOptionalHashbangComment() { + MOZ_ASSERT(this->sourceUnits.atStart(), + "HashBangComment can only appear immediately at the start of a " + "Script or Module"); + + // HashbangComment :: + // #! SingleLineCommentChars_opt + + if (!matchCodeUnit('#')) { + // HashbangComment is optional at start of Script or Module. + return; + } + + if (!matchCodeUnit('!')) { + // # not followed by ! at start of Script or Module is an error, but normal + // parsing code will handle that error just fine if we let it. + ungetCodeUnit('#'); + return; + } + + // This doesn't consume a concluding LineTerminator, and it stops consuming + // just before any encoding error. The subsequent |getToken| call will call + // |getTokenInternal| below which will handle these possibilities. + this->sourceUnits.consumeRestOfSingleLineComment(); +} + +template +[[nodiscard]] bool TokenStreamSpecific::getTokenInternal( + TokenKind* const ttp, const Modifier modifier) { + // Assume we'll fail: success cases will overwrite this. +#ifdef DEBUG + *ttp = TokenKind::Limit; +#endif + MOZ_MAKE_MEM_UNDEFINED(ttp, sizeof(*ttp)); + + // This loop runs more than once only when whitespace or comments are + // encountered. + do { + int32_t unit = peekCodeUnit(); + if (MOZ_UNLIKELY(unit == EOF)) { + MOZ_ASSERT(this->sourceUnits.atEnd()); + anyCharsAccess().flags.isEOF = true; + TokenStart start(this->sourceUnits, 0); + newSimpleToken(TokenKind::Eof, start, modifier, ttp); + return true; + } + + if (MOZ_UNLIKELY(!isAsciiCodePoint(unit))) { + // Non-ASCII code points can only be identifiers or whitespace. It would + // be nice to compute these *after* discarding whitespace, but IN A WORLD + // where |unicode::IsSpace| requires consuming a variable number of code + // units, it's easier to assume it's an identifier and maybe do a little + // wasted work, than to unget and compute and reget if whitespace. + TokenStart start(this->sourceUnits, 0); + const Unit* identStart = this->sourceUnits.addressOfNextCodeUnit(); + + PeekedCodePoint peeked = this->sourceUnits.peekCodePoint(); + if (peeked.isNone()) { + MOZ_ALWAYS_FALSE(getCodePoint()); + return badToken(); + } + + char32_t cp = peeked.codePoint(); + if (unicode::IsSpace(cp)) { + this->sourceUnits.consumeKnownCodePoint(peeked); + if (IsLineTerminator(cp)) { + if (!updateLineInfoForEOL()) { + return badToken(); + } + + anyCharsAccess().updateFlagsForEOL(); + } + + continue; + } + + static_assert(isAsciiCodePoint('$'), + "IdentifierStart contains '$', but as " + "!IsUnicodeIDStart('$'), ensure that '$' is never " + "handled here"); + static_assert(isAsciiCodePoint('_'), + "IdentifierStart contains '_', but as " + "!IsUnicodeIDStart('_'), ensure that '_' is never " + "handled here"); + + if (MOZ_LIKELY(unicode::IsUnicodeIDStart(cp))) { + this->sourceUnits.consumeKnownCodePoint(peeked); + MOZ_ASSERT(!IsLineTerminator(cp), + "IdentifierStart must guarantee !IsLineTerminator " + "or else we'll fail to maintain line-info/flags " + "for EOL here"); + + return identifierName(start, identStart, IdentifierEscapes::None, + modifier, NameVisibility::Public, ttp); + } + + reportIllegalCharacter(cp); + return badToken(); + } // !isAsciiCodePoint(unit) + + consumeKnownCodeUnit(unit); + + // Get the token kind, based on the first char. The ordering of c1kind + // comparison is based on the frequency of tokens in real code: + // Parsemark (which represents typical JS code on the web) and the + // Unreal demo (which represents asm.js code). + // + // Parsemark Unreal + // OneChar 32.9% 39.7% + // Space 25.0% 0.6% + // Ident 19.2% 36.4% + // Dec 7.2% 5.1% + // String 7.9% 0.0% + // EOL 1.7% 0.0% + // ZeroDigit 0.4% 4.9% + // Other 5.7% 13.3% + // + // The ordering is based mostly only Parsemark frequencies, with Unreal + // frequencies used to break close categories (e.g. |Dec| and + // |String|). |Other| is biggish, but no other token kind is common + // enough for it to be worth adding extra values to FirstCharKind. + FirstCharKind c1kind = FirstCharKind(firstCharKinds[unit]); + + // Look for an unambiguous single-char token. + // + if (c1kind <= OneChar_Max) { + TokenStart start(this->sourceUnits, -1); + newSimpleToken(TokenKind(c1kind), start, modifier, ttp); + return true; + } + + // Skip over non-EOL whitespace chars. + // + if (c1kind == Space) { + continue; + } + + // Look for an identifier. + // + if (c1kind == Ident) { + TokenStart start(this->sourceUnits, -1); + return identifierName( + start, this->sourceUnits.addressOfNextCodeUnit() - 1, + IdentifierEscapes::None, modifier, NameVisibility::Public, ttp); + } + + // Look for a decimal number. + // + if (c1kind == Dec) { + TokenStart start(this->sourceUnits, -1); + const Unit* numStart = this->sourceUnits.addressOfNextCodeUnit() - 1; + return decimalNumber(unit, start, numStart, modifier, ttp); + } + + // Look for a string or a template string. + // + if (c1kind == String) { + return getStringOrTemplateToken(static_cast(unit), modifier, ttp); + } + + // Skip over EOL chars, updating line state along the way. + // + if (c1kind == EOL) { + if (unit == '\r') { + matchLineTerminator('\n'); + } + + if (!updateLineInfoForEOL()) { + return badToken(); + } + + anyCharsAccess().updateFlagsForEOL(); + continue; + } + + // From a '0', look for a hexadecimal, binary, octal, or "noctal" (a + // number starting with '0' that contains '8' or '9' and is treated as + // decimal) number. + // + if (c1kind == ZeroDigit) { + TokenStart start(this->sourceUnits, -1); + int radix; + bool isBigInt = false; + const Unit* numStart; + unit = getCodeUnit(); + if (unit == 'x' || unit == 'X') { + radix = 16; + unit = getCodeUnit(); + if (!IsAsciiHexDigit(unit)) { + // NOTE: |unit| may be EOF here. + ungetCodeUnit(unit); + error(JSMSG_MISSING_HEXDIGITS); + return badToken(); + } + + // one past the '0x' + numStart = this->sourceUnits.addressOfNextCodeUnit() - 1; + + if (!matchIntegerAfterFirstDigit(IsAsciiHexDigit, &unit)) { + return badToken(); + } + } else if (unit == 'b' || unit == 'B') { + radix = 2; + unit = getCodeUnit(); + if (!IsAsciiBinary(unit)) { + // NOTE: |unit| may be EOF here. + ungetCodeUnit(unit); + error(JSMSG_MISSING_BINARY_DIGITS); + return badToken(); + } + + // one past the '0b' + numStart = this->sourceUnits.addressOfNextCodeUnit() - 1; + + if (!matchIntegerAfterFirstDigit(IsAsciiBinary, &unit)) { + return badToken(); + } + } else if (unit == 'o' || unit == 'O') { + radix = 8; + unit = getCodeUnit(); + if (!IsAsciiOctal(unit)) { + // NOTE: |unit| may be EOF here. + ungetCodeUnit(unit); + error(JSMSG_MISSING_OCTAL_DIGITS); + return badToken(); + } + + // one past the '0o' + numStart = this->sourceUnits.addressOfNextCodeUnit() - 1; + + if (!matchIntegerAfterFirstDigit(IsAsciiOctal, &unit)) { + return badToken(); + } + } else if (IsAsciiDigit(unit)) { + // Reject octal literals that appear in strict mode code. + if (!strictModeError(JSMSG_DEPRECATED_OCTAL_LITERAL)) { + return badToken(); + } + + // The above test doesn't catch a few edge cases; see + // |GeneralParser::maybeParseDirective|. Record the violation so that + // that function can handle them. + anyCharsAccess().setSawDeprecatedOctalLiteral(); + + radix = 8; + // one past the '0' + numStart = this->sourceUnits.addressOfNextCodeUnit() - 1; + + bool nonOctalDecimalIntegerLiteral = false; + do { + if (unit >= '8') { + nonOctalDecimalIntegerLiteral = true; + } + unit = getCodeUnit(); + } while (IsAsciiDigit(unit)); + + if (unit == '_') { + ungetCodeUnit(unit); + error(JSMSG_SEPARATOR_IN_ZERO_PREFIXED_NUMBER); + return badToken(); + } + + if (unit == 'n') { + ungetCodeUnit(unit); + error(JSMSG_BIGINT_INVALID_SYNTAX); + return badToken(); + } + + if (nonOctalDecimalIntegerLiteral) { + // Use the decimal scanner for the rest of the number. + return decimalNumber(unit, start, numStart, modifier, ttp); + } + } else if (unit == '_') { + // Give a more explicit error message when '_' is used after '0'. + ungetCodeUnit(unit); + error(JSMSG_SEPARATOR_IN_ZERO_PREFIXED_NUMBER); + return badToken(); + } else { + // '0' not followed by [XxBbOo0-9_]; scan as a decimal number. + ungetCodeUnit(unit); + numStart = this->sourceUnits.addressOfNextCodeUnit() - 1; // The '0'. + return decimalNumber('0', start, numStart, modifier, ttp); + } + + if (unit == 'n') { + isBigInt = true; + unit = peekCodeUnit(); + } else { + ungetCodeUnit(unit); + } + + // Error if an identifier-start code point appears immediately + // after the number. Somewhat surprisingly, if we don't check + // here, we'll never check at all. + if (MOZ_LIKELY(isAsciiCodePoint(unit))) { + if (unicode::IsIdentifierStart(char16_t(unit))) { + error(JSMSG_IDSTART_AFTER_NUMBER); + return badToken(); + } + } else if (MOZ_LIKELY(unit != EOF)) { + // This ignores encoding errors: subsequent caller-side code to + // handle source text after the number will do so. + PeekedCodePoint peeked = this->sourceUnits.peekCodePoint(); + if (!peeked.isNone() && + unicode::IsIdentifierStart(peeked.codePoint())) { + error(JSMSG_IDSTART_AFTER_NUMBER); + return badToken(); + } + } + + if (isBigInt) { + return bigIntLiteral(start, modifier, ttp); + } + + double dval; + if (!GetFullInteger(numStart, this->sourceUnits.addressOfNextCodeUnit(), + radix, IntegerSeparatorHandling::SkipUnderscore, + &dval)) { + ReportOutOfMemory(this->fc); + return badToken(); + } + newNumberToken(dval, NoDecimal, start, modifier, ttp); + return true; + } + + MOZ_ASSERT(c1kind == Other); + + // This handles everything else. Simple tokens distinguished solely by + // TokenKind should set |simpleKind| and break, to share simple-token + // creation code for all such tokens. All other tokens must be handled + // by returning (or by continuing from the loop enclosing this). + // + TokenStart start(this->sourceUnits, -1); + TokenKind simpleKind; +#ifdef DEBUG + simpleKind = TokenKind::Limit; // sentinel value for code after switch +#endif + + // The block a ways above eliminated all non-ASCII, so cast to the + // smallest type possible to assist the C++ compiler. + switch (AssertedCast(CodeUnitValue(toUnit(unit)))) { + case '.': + if (IsAsciiDigit(peekCodeUnit())) { + return decimalNumber('.', start, + this->sourceUnits.addressOfNextCodeUnit() - 1, + modifier, ttp); + } + + unit = getCodeUnit(); + if (unit == '.') { + if (matchCodeUnit('.')) { + simpleKind = TokenKind::TripleDot; + break; + } + } + + // NOTE: |unit| may be EOF here. A stray '.' at EOF would be an + // error, but subsequent code will handle it. + ungetCodeUnit(unit); + + simpleKind = TokenKind::Dot; + break; + + case '#': { +#ifdef ENABLE_RECORD_TUPLE + if (matchCodeUnit('{')) { + simpleKind = TokenKind::HashCurly; + break; + } + if (matchCodeUnit('[')) { + simpleKind = TokenKind::HashBracket; + break; + } +#endif + + TokenStart start(this->sourceUnits, -1); + const Unit* identStart = this->sourceUnits.addressOfNextCodeUnit() - 1; + IdentifierEscapes sawEscape; + if (!matchIdentifierStart(&sawEscape)) { + return badToken(); + } + return identifierName(start, identStart, sawEscape, modifier, + NameVisibility::Private, ttp); + } + + case '=': + if (matchCodeUnit('=')) { + simpleKind = matchCodeUnit('=') ? TokenKind::StrictEq : TokenKind::Eq; + } else if (matchCodeUnit('>')) { + simpleKind = TokenKind::Arrow; + } else { + simpleKind = TokenKind::Assign; + } + break; + + case '+': + if (matchCodeUnit('+')) { + simpleKind = TokenKind::Inc; + } else { + simpleKind = + matchCodeUnit('=') ? TokenKind::AddAssign : TokenKind::Add; + } + break; + + case '\\': { + char32_t codePoint; + if (uint32_t escapeLength = matchUnicodeEscapeIdStart(&codePoint)) { + return identifierName( + start, + this->sourceUnits.addressOfNextCodeUnit() - escapeLength - 1, + IdentifierEscapes::SawUnicodeEscape, modifier, + NameVisibility::Public, ttp); + } + + // We could point "into" a mistyped escape, e.g. for "\u{41H}" we + // could point at the 'H'. But we don't do that now, so the code + // unit after the '\' isn't necessarily bad, so just point at the + // start of the actually-invalid escape. + ungetCodeUnit('\\'); + error(JSMSG_BAD_ESCAPE); + return badToken(); + } + + case '|': + if (matchCodeUnit('|')) { + simpleKind = matchCodeUnit('=') ? TokenKind::OrAssign : TokenKind::Or; + } else { + simpleKind = + matchCodeUnit('=') ? TokenKind::BitOrAssign : TokenKind::BitOr; + } + break; + + case '^': + simpleKind = + matchCodeUnit('=') ? TokenKind::BitXorAssign : TokenKind::BitXor; + break; + + case '&': + if (matchCodeUnit('&')) { + simpleKind = + matchCodeUnit('=') ? TokenKind::AndAssign : TokenKind::And; + } else { + simpleKind = + matchCodeUnit('=') ? TokenKind::BitAndAssign : TokenKind::BitAnd; + } + break; + + case '?': + if (matchCodeUnit('.')) { + unit = getCodeUnit(); + if (IsAsciiDigit(unit)) { + // if the code unit is followed by a number, for example it has the + // following form `<...> ?.5 <..> then it should be treated as a + // ternary rather than as an optional chain + simpleKind = TokenKind::Hook; + ungetCodeUnit(unit); + ungetCodeUnit('.'); + } else { + ungetCodeUnit(unit); + simpleKind = TokenKind::OptionalChain; + } + } else if (matchCodeUnit('?')) { + simpleKind = matchCodeUnit('=') ? TokenKind::CoalesceAssign + : TokenKind::Coalesce; + } else { + simpleKind = TokenKind::Hook; + } + break; + + case '!': + if (matchCodeUnit('=')) { + simpleKind = matchCodeUnit('=') ? TokenKind::StrictNe : TokenKind::Ne; + } else { + simpleKind = TokenKind::Not; + } + break; + + case '<': + if (anyCharsAccess().options().allowHTMLComments) { + // Treat HTML begin-comment as comment-till-end-of-line. + if (matchCodeUnit('!')) { + if (matchCodeUnit('-')) { + if (matchCodeUnit('-')) { + this->sourceUnits.consumeRestOfSingleLineComment(); + continue; + } + ungetCodeUnit('-'); + } + ungetCodeUnit('!'); + } + } + if (matchCodeUnit('<')) { + simpleKind = + matchCodeUnit('=') ? TokenKind::LshAssign : TokenKind::Lsh; + } else { + simpleKind = matchCodeUnit('=') ? TokenKind::Le : TokenKind::Lt; + } + break; + + case '>': + if (matchCodeUnit('>')) { + if (matchCodeUnit('>')) { + simpleKind = + matchCodeUnit('=') ? TokenKind::UrshAssign : TokenKind::Ursh; + } else { + simpleKind = + matchCodeUnit('=') ? TokenKind::RshAssign : TokenKind::Rsh; + } + } else { + simpleKind = matchCodeUnit('=') ? TokenKind::Ge : TokenKind::Gt; + } + break; + + case '*': + if (matchCodeUnit('*')) { + simpleKind = + matchCodeUnit('=') ? TokenKind::PowAssign : TokenKind::Pow; + } else { + simpleKind = + matchCodeUnit('=') ? TokenKind::MulAssign : TokenKind::Mul; + } + break; + + case '/': + // Look for a single-line comment. + if (matchCodeUnit('/')) { + unit = getCodeUnit(); + if (unit == '@' || unit == '#') { + bool shouldWarn = unit == '@'; + if (!getDirectives(false, shouldWarn)) { + return false; + } + } else { + // NOTE: |unit| may be EOF here. + ungetCodeUnit(unit); + } + + this->sourceUnits.consumeRestOfSingleLineComment(); + continue; + } + + // Look for a multi-line comment. + if (matchCodeUnit('*')) { + TokenStreamAnyChars& anyChars = anyCharsAccess(); + unsigned linenoBefore = anyChars.lineno; + + do { + int32_t unit = getCodeUnit(); + if (unit == EOF) { + error(JSMSG_UNTERMINATED_COMMENT); + return badToken(); + } + + if (unit == '*' && matchCodeUnit('/')) { + break; + } + + if (unit == '@' || unit == '#') { + bool shouldWarn = unit == '@'; + if (!getDirectives(true, shouldWarn)) { + return badToken(); + } + } else if (MOZ_LIKELY(isAsciiCodePoint(unit))) { + if (!getFullAsciiCodePoint(unit)) { + return badToken(); + } + } else { + char32_t codePoint; + if (!getNonAsciiCodePoint(unit, &codePoint)) { + return badToken(); + } + } + } while (true); + + if (linenoBefore != anyChars.lineno) { + anyChars.updateFlagsForEOL(); + } + + continue; + } + + // Look for a regexp. + if (modifier == SlashIsRegExp) { + return regexpLiteral(start, ttp); + } + + simpleKind = matchCodeUnit('=') ? TokenKind::DivAssign : TokenKind::Div; + break; + + case '%': + simpleKind = matchCodeUnit('=') ? TokenKind::ModAssign : TokenKind::Mod; + break; + + case '-': + if (matchCodeUnit('-')) { + if (anyCharsAccess().options().allowHTMLComments && + !anyCharsAccess().flags.isDirtyLine) { + if (matchCodeUnit('>')) { + this->sourceUnits.consumeRestOfSingleLineComment(); + continue; + } + } + + simpleKind = TokenKind::Dec; + } else { + simpleKind = + matchCodeUnit('=') ? TokenKind::SubAssign : TokenKind::Sub; + } + break; + +#ifdef ENABLE_DECORATORS + case '@': + simpleKind = TokenKind::At; + break; +#endif + + default: + // We consumed a bad ASCII code point/unit. Put it back so the + // error location is the bad code point. + ungetCodeUnit(unit); + reportIllegalCharacter(unit); + return badToken(); + } // switch (AssertedCast(CodeUnitValue(toUnit(unit)))) + + MOZ_ASSERT(simpleKind != TokenKind::Limit, + "switch-statement should have set |simpleKind| before " + "breaking"); + + newSimpleToken(simpleKind, start, modifier, ttp); + return true; + } while (true); +} + +template +bool TokenStreamSpecific::getStringOrTemplateToken( + char untilChar, Modifier modifier, TokenKind* out) { + MOZ_ASSERT(untilChar == '\'' || untilChar == '"' || untilChar == '`', + "unexpected string/template literal delimiter"); + + bool parsingTemplate = (untilChar == '`'); + bool templateHead = false; + + TokenStart start(this->sourceUnits, -1); + this->charBuffer.clear(); + + // Run the bad-token code for every path out of this function except the + // one success-case. + auto noteBadToken = MakeScopeExit([this]() { this->badToken(); }); + + auto ReportPrematureEndOfLiteral = [this, untilChar](unsigned errnum) { + // Unicode separators aren't end-of-line in template or (as of + // recently) string literals, so this assertion doesn't allow them. + MOZ_ASSERT(this->sourceUnits.atEnd() || + this->sourceUnits.peekCodeUnit() == Unit('\r') || + this->sourceUnits.peekCodeUnit() == Unit('\n'), + "must be parked at EOF or EOL to call this function"); + + // The various errors reported here include language like "in a '' + // literal" or similar, with '' being '', "", or `` as appropriate. + const char delimiters[] = {untilChar, untilChar, '\0'}; + + this->error(errnum, delimiters); + return; + }; + + // We need to detect any of these chars: " or ', \n (or its + // equivalents), \\, EOF. Because we detect EOL sequences here and + // put them back immediately, we can use getCodeUnit(). + int32_t unit; + while ((unit = getCodeUnit()) != untilChar) { + if (unit == EOF) { + ReportPrematureEndOfLiteral(JSMSG_EOF_BEFORE_END_OF_LITERAL); + return false; + } + + // Non-ASCII code points are always directly appended -- even + // U+2028 LINE SEPARATOR and U+2029 PARAGRAPH SEPARATOR that are + // ordinarily LineTerminatorSequences. (They contribute their literal + // values to template and [as of recently] string literals, but they're + // line terminators when computing line/column coordinates.) Handle + // the non-ASCII case early for readability. + if (MOZ_UNLIKELY(!isAsciiCodePoint(unit))) { + char32_t cp; + if (!getNonAsciiCodePointDontNormalize(toUnit(unit), &cp)) { + return false; + } + + if (MOZ_UNLIKELY(cp == unicode::LINE_SEPARATOR || + cp == unicode::PARA_SEPARATOR)) { + if (!updateLineInfoForEOL()) { + return false; + } + + anyCharsAccess().updateFlagsForEOL(); + } else { + MOZ_ASSERT(!IsLineTerminator(cp)); + } + + if (!AppendCodePointToCharBuffer(this->charBuffer, cp)) { + return false; + } + + continue; + } + + if (unit == '\\') { + // When parsing templates, we don't immediately report errors for + // invalid escapes; these are handled by the parser. We don't + // append to charBuffer in those cases because it won't be read. + unit = getCodeUnit(); + if (unit == EOF) { + ReportPrematureEndOfLiteral(JSMSG_EOF_IN_ESCAPE_IN_LITERAL); + return false; + } + + // Non-ASCII |unit| isn't handled by code after this, so dedicate + // an unlikely special-case to it and then continue. + if (MOZ_UNLIKELY(!isAsciiCodePoint(unit))) { + char32_t codePoint; + if (!getNonAsciiCodePoint(unit, &codePoint)) { + return false; + } + + // If we consumed U+2028 LINE SEPARATOR or U+2029 PARAGRAPH + // SEPARATOR, they'll be normalized to '\n'. '\' followed by + // LineContinuation represents no code points, so don't append + // in this case. + if (codePoint != '\n') { + if (!AppendCodePointToCharBuffer(this->charBuffer, codePoint)) { + return false; + } + } + + continue; + } + + // The block above eliminated all non-ASCII, so cast to the + // smallest type possible to assist the C++ compiler. + switch (AssertedCast(CodeUnitValue(toUnit(unit)))) { + case 'b': + unit = '\b'; + break; + case 'f': + unit = '\f'; + break; + case 'n': + unit = '\n'; + break; + case 'r': + unit = '\r'; + break; + case 't': + unit = '\t'; + break; + case 'v': + unit = '\v'; + break; + + case '\r': + matchLineTerminator('\n'); + [[fallthrough]]; + case '\n': { + // LineContinuation represents no code points. We're manually + // consuming a LineTerminatorSequence, so we must manually + // update line/column info. + if (!updateLineInfoForEOL()) { + return false; + } + + continue; + } + + // Unicode character specification. + case 'u': { + int32_t c2 = getCodeUnit(); + if (c2 == EOF) { + ReportPrematureEndOfLiteral(JSMSG_EOF_IN_ESCAPE_IN_LITERAL); + return false; + } + + // First handle a delimited Unicode escape, e.g. \u{1F4A9}. + if (c2 == '{') { + uint32_t start = this->sourceUnits.offset() - 3; + uint32_t code = 0; + bool first = true; + bool valid = true; + do { + int32_t u3 = getCodeUnit(); + if (u3 == EOF) { + if (parsingTemplate) { + TokenStreamAnyChars& anyChars = anyCharsAccess(); + anyChars.setInvalidTemplateEscape(start, + InvalidEscapeType::Unicode); + valid = false; + break; + } + reportInvalidEscapeError(start, InvalidEscapeType::Unicode); + return false; + } + if (u3 == '}') { + if (first) { + if (parsingTemplate) { + TokenStreamAnyChars& anyChars = anyCharsAccess(); + anyChars.setInvalidTemplateEscape( + start, InvalidEscapeType::Unicode); + valid = false; + break; + } + reportInvalidEscapeError(start, InvalidEscapeType::Unicode); + return false; + } + break; + } + + // Beware: |u3| may be a non-ASCII code point here; if + // so it'll pass into this |if|-block. + if (!IsAsciiHexDigit(u3)) { + if (parsingTemplate) { + // We put the code unit back so that we read it + // on the next pass, which matters if it was + // '`' or '\'. + ungetCodeUnit(u3); + + TokenStreamAnyChars& anyChars = anyCharsAccess(); + anyChars.setInvalidTemplateEscape(start, + InvalidEscapeType::Unicode); + valid = false; + break; + } + reportInvalidEscapeError(start, InvalidEscapeType::Unicode); + return false; + } + + code = (code << 4) | AsciiAlphanumericToNumber(u3); + if (code > unicode::NonBMPMax) { + if (parsingTemplate) { + TokenStreamAnyChars& anyChars = anyCharsAccess(); + anyChars.setInvalidTemplateEscape( + start + 3, InvalidEscapeType::UnicodeOverflow); + valid = false; + break; + } + reportInvalidEscapeError(start + 3, + InvalidEscapeType::UnicodeOverflow); + return false; + } + + first = false; + } while (true); + + if (!valid) { + continue; + } + + MOZ_ASSERT(code <= unicode::NonBMPMax); + if (!AppendCodePointToCharBuffer(this->charBuffer, code)) { + return false; + } + + continue; + } // end of delimited Unicode escape handling + + // Otherwise it must be a fixed-length \uXXXX Unicode escape. + // If it isn't, this is usually an error -- but if this is a + // template literal, we must defer error reporting because + // malformed escapes are okay in *tagged* template literals. + char16_t v; + if (IsAsciiHexDigit(c2) && this->sourceUnits.matchHexDigits(3, &v)) { + unit = (AsciiAlphanumericToNumber(c2) << 12) | v; + } else { + // Beware: |c2| may not be an ASCII code point here! + ungetCodeUnit(c2); + uint32_t start = this->sourceUnits.offset() - 2; + if (parsingTemplate) { + TokenStreamAnyChars& anyChars = anyCharsAccess(); + anyChars.setInvalidTemplateEscape(start, + InvalidEscapeType::Unicode); + continue; + } + reportInvalidEscapeError(start, InvalidEscapeType::Unicode); + return false; + } + break; + } // case 'u' + + // Hexadecimal character specification. + case 'x': { + char16_t v; + if (this->sourceUnits.matchHexDigits(2, &v)) { + unit = v; + } else { + uint32_t start = this->sourceUnits.offset() - 2; + if (parsingTemplate) { + TokenStreamAnyChars& anyChars = anyCharsAccess(); + anyChars.setInvalidTemplateEscape(start, + InvalidEscapeType::Hexadecimal); + continue; + } + reportInvalidEscapeError(start, InvalidEscapeType::Hexadecimal); + return false; + } + break; + } + + default: { + if (!IsAsciiOctal(unit)) { + // \8 or \9 in an untagged template literal is a syntax error, + // reported in GeneralParser::noSubstitutionUntaggedTemplate. + // + // Tagged template literals, however, may contain \8 and \9. The + // "cooked" representation of such a part will be |undefined|, and + // the "raw" representation will contain the literal characters. + // + // function f(parts) { + // assertEq(parts[0], undefined); + // assertEq(parts.raw[0], "\\8"); + // return "composed"; + // } + // assertEq(f`\8`, "composed"); + if (unit == '8' || unit == '9') { + TokenStreamAnyChars& anyChars = anyCharsAccess(); + if (parsingTemplate) { + anyChars.setInvalidTemplateEscape( + this->sourceUnits.offset() - 2, + InvalidEscapeType::EightOrNine); + continue; + } + + // \8 and \9 are forbidden in string literals in strict mode code. + if (!strictModeError(JSMSG_DEPRECATED_EIGHT_OR_NINE_ESCAPE)) { + return false; + } + + // The above test doesn't catch a few edge cases; see + // |GeneralParser::maybeParseDirective|. Record the violation so + // that that function can handle them. + anyChars.setSawDeprecatedEightOrNineEscape(); + } + break; + } + + // Octal character specification. + int32_t val = AsciiOctalToNumber(unit); + + unit = peekCodeUnit(); + if (MOZ_UNLIKELY(unit == EOF)) { + ReportPrematureEndOfLiteral(JSMSG_EOF_IN_ESCAPE_IN_LITERAL); + return false; + } + + // Strict mode code allows only \0 followed by a non-digit. + if (val != 0 || IsAsciiDigit(unit)) { + TokenStreamAnyChars& anyChars = anyCharsAccess(); + if (parsingTemplate) { + anyChars.setInvalidTemplateEscape(this->sourceUnits.offset() - 2, + InvalidEscapeType::Octal); + continue; + } + + if (!strictModeError(JSMSG_DEPRECATED_OCTAL_ESCAPE)) { + return false; + } + + // The above test doesn't catch a few edge cases; see + // |GeneralParser::maybeParseDirective|. Record the violation so + // that that function can handle them. + anyChars.setSawDeprecatedOctalEscape(); + } + + if (IsAsciiOctal(unit)) { + val = 8 * val + AsciiOctalToNumber(unit); + consumeKnownCodeUnit(unit); + + unit = peekCodeUnit(); + if (MOZ_UNLIKELY(unit == EOF)) { + ReportPrematureEndOfLiteral(JSMSG_EOF_IN_ESCAPE_IN_LITERAL); + return false; + } + + if (IsAsciiOctal(unit)) { + int32_t save = val; + val = 8 * val + AsciiOctalToNumber(unit); + if (val <= 0xFF) { + consumeKnownCodeUnit(unit); + } else { + val = save; + } + } + } + + unit = char16_t(val); + break; + } // default + } // switch (AssertedCast(CodeUnitValue(toUnit(unit)))) + + if (!this->charBuffer.append(unit)) { + return false; + } + + continue; + } // (unit == '\\') + + if (unit == '\r' || unit == '\n') { + if (!parsingTemplate) { + // String literals don't allow ASCII line breaks. + ungetCodeUnit(unit); + ReportPrematureEndOfLiteral(JSMSG_EOL_BEFORE_END_OF_STRING); + return false; + } + + if (unit == '\r') { + unit = '\n'; + matchLineTerminator('\n'); + } + + if (!updateLineInfoForEOL()) { + return false; + } + + anyCharsAccess().updateFlagsForEOL(); + } else if (parsingTemplate && unit == '$' && matchCodeUnit('{')) { + templateHead = true; + break; + } + + if (!this->charBuffer.append(unit)) { + return false; + } + } + + TaggedParserAtomIndex atom = drainCharBufferIntoAtom(); + if (!atom) { + return false; + } + + noteBadToken.release(); + + MOZ_ASSERT_IF(!parsingTemplate, !templateHead); + + TokenKind kind = !parsingTemplate ? TokenKind::String + : templateHead ? TokenKind::TemplateHead + : TokenKind::NoSubsTemplate; + newAtomToken(kind, atom, start, modifier, out); + return true; +} + +const char* TokenKindToDesc(TokenKind tt) { + switch (tt) { +#define EMIT_CASE(name, desc) \ + case TokenKind::name: \ + return desc; + FOR_EACH_TOKEN_KIND(EMIT_CASE) +#undef EMIT_CASE + case TokenKind::Limit: + MOZ_ASSERT_UNREACHABLE("TokenKind::Limit should not be passed."); + break; + } + + return ""; +} + +#ifdef DEBUG +const char* TokenKindToString(TokenKind tt) { + switch (tt) { +# define EMIT_CASE(name, desc) \ + case TokenKind::name: \ + return "TokenKind::" #name; + FOR_EACH_TOKEN_KIND(EMIT_CASE) +# undef EMIT_CASE + case TokenKind::Limit: + break; + } + + return ""; +} +#endif + +template class TokenStreamCharsBase; +template class TokenStreamCharsBase; + +template class GeneralTokenStreamChars; +template class TokenStreamChars; +template class TokenStreamSpecific; + +template class GeneralTokenStreamChars< + Utf8Unit, ParserAnyCharsAccess>>; +template class GeneralTokenStreamChars< + Utf8Unit, + ParserAnyCharsAccess>>; +template class GeneralTokenStreamChars< + char16_t, ParserAnyCharsAccess>>; +template class GeneralTokenStreamChars< + char16_t, + ParserAnyCharsAccess>>; + +template class TokenStreamChars< + Utf8Unit, ParserAnyCharsAccess>>; +template class TokenStreamChars< + Utf8Unit, + ParserAnyCharsAccess>>; +template class TokenStreamChars< + char16_t, ParserAnyCharsAccess>>; +template class TokenStreamChars< + char16_t, + ParserAnyCharsAccess>>; + +template class TokenStreamSpecific< + Utf8Unit, ParserAnyCharsAccess>>; +template class TokenStreamSpecific< + Utf8Unit, + ParserAnyCharsAccess>>; +template class TokenStreamSpecific< + char16_t, ParserAnyCharsAccess>>; +template class TokenStreamSpecific< + char16_t, + ParserAnyCharsAccess>>; + +} // namespace frontend + +} // namespace js diff --git a/js/src/frontend/TokenStream.h b/js/src/frontend/TokenStream.h new file mode 100644 index 0000000000..95a8651e05 --- /dev/null +++ b/js/src/frontend/TokenStream.h @@ -0,0 +1,2962 @@ +/* -*- 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/. */ + +/* + * Streaming access to the raw tokens of JavaScript source. + * + * Because JS tokenization is context-sensitive -- a '/' could be either a + * regular expression *or* a division operator depending on context -- the + * various token stream classes are mostly not useful outside of the Parser + * where they reside. We should probably eventually merge the two concepts. + */ +#ifndef frontend_TokenStream_h +#define frontend_TokenStream_h + +/* + * [SMDOC] Parser Token Stream + * + * A token stream exposes the raw tokens -- operators, names, numbers, + * keywords, and so on -- of JavaScript source code. + * + * These are the components of the overall token stream concept: + * TokenStreamShared, TokenStreamAnyChars, TokenStreamCharsBase, + * TokenStreamChars, and TokenStreamSpecific. + * + * == TokenStreamShared → ∅ == + * + * Certain aspects of tokenizing are used everywhere: + * + * * modifiers (used to select which context-sensitive interpretation of a + * character should be used to decide what token it is) and modifier + * assertion handling; + * * flags on the overall stream (have we encountered any characters on this + * line? have we hit a syntax error? and so on); + * * and certain token-count constants. + * + * These are all defined in TokenStreamShared. (They could be namespace- + * scoped, but it seems tentatively better not to clutter the namespace.) + * + * == TokenStreamAnyChars → TokenStreamShared == + * + * Certain aspects of tokenizing have meaning independent of the character type + * of the source text being tokenized: line/column number information, tokens + * in lookahead from determining the meaning of a prior token, compilation + * options, the filename, flags, source map URL, access to details of the + * current and next tokens (is the token of the given type? what name or + * number is contained in the token? and other queries), and others. + * + * All this data/functionality *could* be duplicated for both single-byte and + * double-byte tokenizing, but there are two problems. First, it's potentially + * wasteful if the compiler doesnt recognize it can unify the concepts. (And + * if any-character concepts are intermixed with character-specific concepts, + * potentially the compiler *can't* unify them because offsets into the + * hypothetical TokenStreams would differ.) Second, some of this stuff + * needs to be accessible in ParserBase, the aspects of JS language parsing + * that have meaning independent of the character type of the source text being + * parsed. So we need a separate data structure that ParserBase can hold on to + * for it. (ParserBase isn't the only instance of this, but it's certainly the + * biggest case of it.) Ergo, TokenStreamAnyChars. + * + * == TokenStreamCharsShared → ∅ == + * + * Some functionality has meaning independent of character type, yet has no use + * *unless* you know the character type in actual use. It *could* live in + * TokenStreamAnyChars, but it makes more sense to live in a separate class + * that character-aware token information can simply inherit. + * + * This class currently exists only to contain a char16_t buffer, transiently + * used to accumulate strings in tricky cases that can't just be read directly + * from source text. It's not used outside character-aware tokenizing, so it + * doesn't make sense in TokenStreamAnyChars. + * + * == TokenStreamCharsBase → TokenStreamCharsShared == + * + * Certain data structures in tokenizing are character-type-specific: namely, + * the various pointers identifying the source text (including current offset + * and end). + * + * Additionally, some functions operating on this data are defined the same way + * no matter what character type you have (e.g. current offset in code units + * into the source text) or share a common interface regardless of character + * type (e.g. consume the next code unit if it has a given value). + * + * All such functionality lives in TokenStreamCharsBase. + * + * == SpecializedTokenStreamCharsBase → TokenStreamCharsBase == + * + * Certain tokenizing functionality is specific to a single character type. + * For example, JS's UTF-16 encoding recognizes no coding errors, because lone + * surrogates are not an error; but a UTF-8 encoding must recognize a variety + * of validation errors. Such functionality is defined only in the appropriate + * SpecializedTokenStreamCharsBase specialization. + * + * == GeneralTokenStreamChars → + * SpecializedTokenStreamCharsBase == + * + * Some functionality operates differently on different character types, just + * as for TokenStreamCharsBase, but additionally requires access to character- + * type-agnostic information in TokenStreamAnyChars. For example, getting the + * next character performs different steps for different character types and + * must access TokenStreamAnyChars to update line break information. + * + * Such functionality, if it can be defined using the same algorithm for all + * character types, lives in GeneralTokenStreamChars. + * The AnyCharsAccess parameter provides a way for a GeneralTokenStreamChars + * instance to access its corresponding TokenStreamAnyChars, without inheriting + * from it. + * + * GeneralTokenStreamChars is just functionality, no + * actual member data. + * + * Such functionality all lives in TokenStreamChars, a + * declared-but-not-defined template class whose specializations have a common + * public interface (plus whatever private helper functions are desirable). + * + * == TokenStreamChars → + * GeneralTokenStreamChars == + * + * Some functionality is like that in GeneralTokenStreamChars, *but* it's + * defined entirely differently for different character types. + * + * For example, consider "match a multi-code unit code point" (hypothetically: + * we've only implemented two-byte tokenizing right now): + * + * * For two-byte text, there must be two code units to get, the leading code + * unit must be a UTF-16 lead surrogate, and the trailing code unit must be + * a UTF-16 trailing surrogate. (If any of these fail to hold, a next code + * unit encodes that code point and is not multi-code unit.) + * * For single-byte Latin-1 text, there are no multi-code unit code points. + * * For single-byte UTF-8 text, the first code unit must have N > 1 of its + * highest bits set (and the next unset), and |N - 1| successive code units + * must have their high bit set and next-highest bit unset, *and* + * concatenating all unconstrained bits together must not produce a code + * point value that could have been encoded in fewer code units. + * + * This functionality can't be implemented as member functions in + * GeneralTokenStreamChars because we'd need to *partially specialize* those + * functions -- hold Unit constant while letting AnyCharsAccess vary. But + * C++ forbids function template partial specialization like this: either you + * fix *all* parameters or you fix none of them. + * + * Fortunately, C++ *does* allow *class* template partial specialization. So + * TokenStreamChars is a template class with one specialization per Unit. + * Functions can be defined differently in the different specializations, + * because AnyCharsAccess as the only template parameter on member functions + * *can* vary. + * + * All TokenStreamChars specializations, one per Unit, + * are just functionality, no actual member data. + * + * == TokenStreamSpecific → + * TokenStreamChars, TokenStreamShared, + * ErrorReporter == + * + * TokenStreamSpecific is operations that are parametrized on character type + * but implement the *general* idea of tokenizing, without being intrinsically + * tied to character type. Notably, this includes all operations that can + * report warnings or errors at particular offsets, because we include a line + * of context with such errors -- and that necessarily accesses the raw + * characters of their specific type. + * + * Much TokenStreamSpecific operation depends on functionality in + * TokenStreamAnyChars. The obvious solution is to inherit it -- but this + * doesn't work in Parser: its ParserBase base class needs some + * TokenStreamAnyChars functionality without knowing character type. + * + * The AnyCharsAccess type parameter is a class that statically converts from a + * TokenStreamSpecific* to its corresponding TokenStreamAnyChars. The + * TokenStreamSpecific in Parser can then specify a class + * that properly converts from TokenStreamSpecific Parser::tokenStream to + * TokenStreamAnyChars ParserBase::anyChars. + * + * Could we hardcode one set of offset calculations for this and eliminate + * AnyCharsAccess? No. Offset calculations possibly could be hardcoded if + * TokenStreamSpecific were present in Parser before Parser::handler, assuring + * the same offsets in all Parser-related cases. But there's still a separate + * TokenStream class, that requires different offset calculations. So even if + * we wanted to hardcode this (it's not clear we would, because forcing the + * TokenStreamSpecific declarer to specify this is more explicit), we couldn't. + */ + +#include "mozilla/ArrayUtils.h" +#include "mozilla/Assertions.h" +#include "mozilla/Attributes.h" +#include "mozilla/Casting.h" +#include "mozilla/Maybe.h" +#include "mozilla/MemoryChecking.h" +#include "mozilla/Span.h" +#include "mozilla/TextUtils.h" +#include "mozilla/Utf8.h" + +#include +#include +#include +#include +#include +#include + +#include "jspubtd.h" + +#include "frontend/ErrorReporter.h" +#include "frontend/ParserAtom.h" // ParserAtom, ParserAtomsTable, TaggedParserAtomIndex +#include "frontend/Token.h" +#include "frontend/TokenKind.h" +#include "js/CompileOptions.h" +#include "js/friend/ErrorMessages.h" // JSMSG_* +#include "js/HashTable.h" // js::HashMap +#include "js/RegExpFlags.h" // JS::RegExpFlags +#include "js/UniquePtr.h" +#include "js/Vector.h" +#include "util/Unicode.h" +#include "vm/ErrorReporting.h" + +struct KeywordInfo; + +namespace js { + +class FrontendContext; + +namespace frontend { + +// Saturate column number at a limit that can be represented in various parts of +// the engine. Source locations beyond this point will report at the limit +// column instead. +// +// See: +// - TokenStreamAnyChars::checkOptions +// - ColSpan::isRepresentable +// - WasmFrameIter::computeLine +static constexpr uint32_t ColumnLimit = std::numeric_limits::max() / 2; + +// If `name` is reserved word, returns the TokenKind of it. +// TokenKind::Limit otherwise. +extern TokenKind ReservedWordTokenKind(TaggedParserAtomIndex name); + +// If `name` is reserved word, returns string representation of it. +// nullptr otherwise. +extern const char* ReservedWordToCharZ(TaggedParserAtomIndex name); + +// If `tt` is reserved word, returns string representation of it. +// nullptr otherwise. +extern const char* ReservedWordToCharZ(TokenKind tt); + +enum class DeprecatedContent : uint8_t { + // No deprecated content was present. + None = 0, + // Octal literal not prefixed by "0o" but rather by just "0", e.g. 0755. + OctalLiteral, + // Octal character escape, e.g. "hell\157 world". + OctalEscape, + // NonOctalDecimalEscape, i.e. "\8" or "\9". + EightOrNineEscape, +}; + +struct TokenStreamFlags { + // Hit end of file. + bool isEOF : 1; + // Non-whitespace since start of line. + bool isDirtyLine : 1; + // Hit a syntax error, at start or during a token. + bool hadError : 1; + + // The nature of any deprecated content seen since last reset. + // We have to uint8_t instead DeprecatedContent to work around a GCC 7 bug. + // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=61414 + uint8_t sawDeprecatedContent : 2; + + TokenStreamFlags() + : isEOF(false), + isDirtyLine(false), + hadError(false), + sawDeprecatedContent(uint8_t(DeprecatedContent::None)) {} +}; + +template +class TokenStreamPosition; + +/** + * TokenStream types and constants that are used in both TokenStreamAnyChars + * and TokenStreamSpecific. Do not add any non-static data members to this + * class! + */ +class TokenStreamShared { + protected: + static constexpr size_t ntokens = 4; // 1 current + 2 lookahead, rounded + // to power of 2 to avoid divmod by 3 + + static constexpr unsigned ntokensMask = ntokens - 1; + + template + friend class TokenStreamPosition; + + public: + static constexpr unsigned maxLookahead = 2; + + using Modifier = Token::Modifier; + static constexpr Modifier SlashIsDiv = Token::SlashIsDiv; + static constexpr Modifier SlashIsRegExp = Token::SlashIsRegExp; + static constexpr Modifier SlashIsInvalid = Token::SlashIsInvalid; + + static void verifyConsistentModifier(Modifier modifier, + const Token& nextToken) { + MOZ_ASSERT( + modifier == nextToken.modifier || modifier == SlashIsInvalid, + "This token was scanned with both SlashIsRegExp and SlashIsDiv, " + "indicating the parser is confused about how to handle a slash here. " + "See comment at Token::Modifier."); + } +}; + +static_assert(std::is_empty_v, + "TokenStreamShared shouldn't bloat classes that inherit from it"); + +template +class TokenStreamSpecific; + +template +class MOZ_STACK_CLASS TokenStreamPosition final { + public: + template + inline explicit TokenStreamPosition( + TokenStreamSpecific& tokenStream); + + private: + TokenStreamPosition(const TokenStreamPosition&) = delete; + + // Technically only TokenStreamSpecific::seek with + // Unit constant and AnyCharsAccess varying must be friended, but 1) it's + // hard to friend one function in template classes, and 2) C++ doesn't + // allow partial friend specialization to target just that single class. + template + friend class TokenStreamSpecific; + + const Unit* buf; + TokenStreamFlags flags; + unsigned lineno; + size_t linebase; + size_t prevLinebase; + Token currentToken; + unsigned lookahead; + Token lookaheadTokens[TokenStreamShared::maxLookahead]; +}; + +template +class SourceUnits; + +/** + * This class maps: + * + * * a sourceUnits offset (a 0-indexed count of code units) + * + * to + * + * * a (1-indexed) line number and + * * a (0-indexed) offset in code *units* (not code points, not bytes) into + * that line, + * + * for either |Unit = Utf8Unit| or |Unit = char16_t|. + * + * Note that the latter quantity is *not* the same as a column number, which is + * a count of code *points*. Computing a column number requires the offset + * within the line and the source units of that line (including what type |Unit| + * is, to know how to decode them). If you need a column number, functions in + * |GeneralTokenStreamChars| will consult this and source units to compute + * it. + */ +class SourceCoords { + // For a given buffer holding source code, |lineStartOffsets_| has one + // element per line of source code, plus one sentinel element. Each + // non-sentinel element holds the buffer offset for the start of the + // corresponding line of source code. For this example script, + // assuming an initialLineOffset of 0: + // + // 1 // xyz [line starts at offset 0] + // 2 var x; [line starts at offset 7] + // 3 [line starts at offset 14] + // 4 var y; [line starts at offset 15] + // + // |lineStartOffsets_| is: + // + // [0, 7, 14, 15, MAX_PTR] + // + // To convert a "line number" to an "index" into |lineStartOffsets_|, + // subtract |initialLineNum_|. E.g. line 3's index is + // (3 - initialLineNum_), which is 2. Therefore lineStartOffsets_[2] + // holds the buffer offset for the start of line 3, which is 14. (Note + // that |initialLineNum_| is often 1, but not always. + // + // The first element is always initialLineOffset, passed to the + // constructor, and the last element is always the MAX_PTR sentinel. + // + // Offset-to-{line,offset-into-line} lookups are O(log n) in the worst + // case (binary search), but in practice they're heavily clustered and + // we do better than that by using the previous lookup's result + // (lastIndex_) as a starting point. + // + // Checking if an offset lies within a particular line number + // (isOnThisLine()) is O(1). + // + Vector lineStartOffsets_; + + /** The line number on which the source text begins. */ + uint32_t initialLineNum_; + + /** + * The index corresponding to the last offset lookup -- used so that if + * offset lookups proceed in increasing order, and and the offset appears + * in the next couple lines from the last offset, we can avoid a full + * binary-search. + * + * This is mutable because it's modified on every search, but that fact + * isn't visible outside this class. + */ + mutable uint32_t lastIndex_; + + uint32_t indexFromOffset(uint32_t offset) const; + + static const uint32_t MAX_PTR = UINT32_MAX; + + uint32_t lineNumberFromIndex(uint32_t index) const { + return index + initialLineNum_; + } + + uint32_t indexFromLineNumber(uint32_t lineNum) const { + return lineNum - initialLineNum_; + } + + public: + SourceCoords(FrontendContext* fc, uint32_t initialLineNumber, + uint32_t initialOffset); + + [[nodiscard]] bool add(uint32_t lineNum, uint32_t lineStartOffset); + [[nodiscard]] bool fill(const SourceCoords& other); + + bool isOnThisLine(uint32_t offset, uint32_t lineNum, bool* onThisLine) const { + uint32_t index = indexFromLineNumber(lineNum); + if (index + 1 >= lineStartOffsets_.length()) { // +1 due to sentinel + return false; + } + *onThisLine = lineStartOffsets_[index] <= offset && + offset < lineStartOffsets_[index + 1]; + return true; + } + + /** + * A token, computed for an offset in source text, that can be used to + * access line number and line-offset information for that offset. + * + * LineToken *alone* exposes whether the corresponding offset is in the + * the first line of source (which may not be 1, depending on + * |initialLineNumber|), and whether it's in the same line as + * another LineToken. + */ + class LineToken { + uint32_t index; +#ifdef DEBUG + uint32_t offset_; // stored for consistency-of-use assertions +#endif + + friend class SourceCoords; + + public: + LineToken(uint32_t index, uint32_t offset) + : index(index) +#ifdef DEBUG + , + offset_(offset) +#endif + { + } + + bool isFirstLine() const { return index == 0; } + + bool isSameLine(LineToken other) const { return index == other.index; } + + void assertConsistentOffset(uint32_t offset) const { + MOZ_ASSERT(offset_ == offset); + } + }; + + /** + * Compute a token usable to access information about the line at the + * given offset. + * + * The only information directly accessible in a token is whether it + * corresponds to the first line of source text (which may not be line + * 1, depending on the |initialLineNumber| value used to construct + * this). Use |lineNumber(LineToken)| to compute the actual line + * number (incorporating the contribution of |initialLineNumber|). + */ + LineToken lineToken(uint32_t offset) const; + + /** Compute the line number for the given token. */ + uint32_t lineNumber(LineToken lineToken) const { + return lineNumberFromIndex(lineToken.index); + } + + /** Return the offset of the start of the line for |lineToken|. */ + uint32_t lineStart(LineToken lineToken) const { + MOZ_ASSERT(lineToken.index + 1 < lineStartOffsets_.length(), + "recorded line-start information must be available"); + return lineStartOffsets_[lineToken.index]; + } +}; + +enum class UnitsType : unsigned char { + PossiblyMultiUnit = 0, + GuaranteedSingleUnit = 1, +}; + +class ChunkInfo { + private: + // Store everything in |unsigned char|s so everything packs. + unsigned char column_[sizeof(uint32_t)]; + unsigned char unitsType_; + + public: + ChunkInfo(uint32_t col, UnitsType type) + : unitsType_(static_cast(type)) { + memcpy(column_, &col, sizeof(col)); + } + + uint32_t column() const { + uint32_t col; + memcpy(&col, column_, sizeof(uint32_t)); + return col; + } + + UnitsType unitsType() const { + MOZ_ASSERT(unitsType_ <= 1, "unitsType_ must be 0 or 1"); + return static_cast(unitsType_); + } + + void guaranteeSingleUnits() { + MOZ_ASSERT(unitsType() == UnitsType::PossiblyMultiUnit, + "should only be setting to possibly optimize from the " + "pessimistic case"); + unitsType_ = static_cast(UnitsType::GuaranteedSingleUnit); + } +}; + +enum class InvalidEscapeType { + // No invalid character escapes. + None, + // A malformed \x escape. + Hexadecimal, + // A malformed \u escape. + Unicode, + // An otherwise well-formed \u escape which represents a + // codepoint > 10FFFF. + UnicodeOverflow, + // An octal escape in a template token. + Octal, + // NonOctalDecimalEscape - \8 or \9. + EightOrNine +}; + +class TokenStreamAnyChars : public TokenStreamShared { + private: + // Constant-at-construction fields. + + FrontendContext* const fc; + + /** Options used for parsing/tokenizing. */ + const JS::ReadOnlyCompileOptions& options_; + + /** + * Pointer used internally to test whether in strict mode. Use |strictMode()| + * instead of this field. + */ + StrictModeGetter* const strictModeGetter_; + + /** Input filename or null. */ + const char* const filename_; + + // Column number computation fields. + + /** + * A map of (line number => sequence of the column numbers at + * |ColumnChunkLength|-unit boundaries rewound [if needed] to the nearest code + * point boundary). (|TokenStreamAnyChars::computePartialColumn| is the sole + * user of |ColumnChunkLength| and therefore contains its definition.) + * + * Entries appear in this map only when a column computation of sufficient + * distance is performed on a line -- and only when the column is beyond the + * first |ColumnChunkLength| units. Each line's vector is lazily filled as + * greater offsets require column computations. + */ + mutable HashMap> longLineColumnInfo_; + + // Computing accurate column numbers requires at *some* point linearly + // iterating through prior source units in the line, to properly account for + // multi-unit code points. This is quadratic if counting happens repeatedly. + // + // But usually we need columns for advancing offsets through scripts. By + // caching the last ((line number, offset) => relative column) mapping (in + // similar manner to how |SourceCoords::lastIndex_| is used to cache + // (offset => line number) mappings) we can usually avoid re-iterating through + // the common line prefix. + // + // Additionally, we avoid hash table lookup costs by caching the + // |Vector*| for the line of the last lookup. (|nullptr| means we + // must look it up -- or it hasn't been created yet.) This pointer is nulled + // when a lookup on a new line occurs, but as it's not a pointer at literal, + // reallocatable element data, it's *not* invalidated when new entries are + // added to such a vector. + + /** + * The line in which the last column computation occurred, or UINT32_MAX if + * no prior computation has yet happened. + */ + mutable uint32_t lineOfLastColumnComputation_ = UINT32_MAX; + + /** + * The chunk vector of the line for that last column computation. This is + * null if the chunk vector needs to be recalculated or initially created. + */ + mutable Vector* lastChunkVectorForLine_ = nullptr; + + /** + * The offset (in code units) of the last column computation performed, + * relative to source start. + */ + mutable uint32_t lastOffsetOfComputedColumn_ = UINT32_MAX; + + /** + * The column number for the offset (in code units) of the last column + * computation performed, relative to source start. + */ + mutable uint32_t lastComputedColumn_ = 0; + + // Intra-token fields. + + /** + * The offset of the first invalid escape in a template literal. (If there is + * one -- if not, the value of this field is meaningless.) + * + * See also |invalidTemplateEscapeType|. + */ + uint32_t invalidTemplateEscapeOffset = 0; + + /** + * The type of the first invalid escape in a template literal. (If there + * isn't one, this will be |None|.) + * + * See also |invalidTemplateEscapeOffset|. + */ + InvalidEscapeType invalidTemplateEscapeType = InvalidEscapeType::None; + + // Fields with values relevant across tokens (and therefore potentially across + // function boundaries, such that lazy function parsing and stream-seeking + // must take care in saving and restoring them). + + /** Line number and offset-to-line mapping information. */ + SourceCoords srcCoords; + + /** Circular token buffer of gotten tokens that have been ungotten. */ + Token tokens[ntokens] = {}; + + /** The index in |tokens| of the last parsed token. */ + unsigned cursor_ = 0; + + /** The number of tokens in |tokens| available to be gotten. */ + unsigned lookahead = 0; + + /** The current line number. */ + unsigned lineno; + + /** Various flag bits (see above). */ + TokenStreamFlags flags = {}; + + /** The offset of the start of the current line. */ + size_t linebase = 0; + + /** The start of the previous line, or |size_t(-1)| on the first line. */ + size_t prevLinebase = size_t(-1); + + /** The user's requested source URL. Null if none has been set. */ + UniqueTwoByteChars displayURL_ = nullptr; + + /** The URL of the source map for this script. Null if none has been set. */ + UniqueTwoByteChars sourceMapURL_ = nullptr; + + // Assorted boolean fields, none of which require maintenance across tokens, + // stored at class end to minimize padding. + + /** + * Whether syntax errors should or should not contain details about the + * precise nature of the error. (This is intended for use in suppressing + * content-revealing details about syntax errors in cross-origin scripts on + * the web.) + */ + const bool mutedErrors; + + /** + * An array storing whether a TokenKind observed while attempting to extend + * a valid AssignmentExpression into an even longer AssignmentExpression + * (e.g., extending '3' to '3 + 5') will terminate it without error. + * + * For example, ';' always ends an AssignmentExpression because it ends a + * Statement or declaration. '}' always ends an AssignmentExpression + * because it terminates BlockStatement, FunctionBody, and embedded + * expressions in TemplateLiterals. Therefore both entries are set to true + * in TokenStreamAnyChars construction. + * + * But e.g. '+' *could* extend an AssignmentExpression, so its entry here + * is false. Meanwhile 'this' can't extend an AssignmentExpression, but + * it's only valid after a line break, so its entry here must be false. + * + * NOTE: This array could be static, but without C99's designated + * initializers it's easier zeroing here and setting the true entries + * in the constructor body. (Having this per-instance might also aid + * locality.) Don't worry! Initialization time for each TokenStream + * is trivial. See bug 639420. + */ + bool isExprEnding[size_t(TokenKind::Limit)] = {}; // all-false initially + + // End of fields. + + public: + TokenStreamAnyChars(FrontendContext* fc, + const JS::ReadOnlyCompileOptions& options, + StrictModeGetter* smg); + + template + friend class GeneralTokenStreamChars; + template + friend class TokenStreamChars; + template + friend class TokenStreamSpecific; + + template + friend class TokenStreamPosition; + + // Accessors. + unsigned cursor() const { return cursor_; } + unsigned nextCursor() const { return (cursor_ + 1) & ntokensMask; } + unsigned aheadCursor(unsigned steps) const { + return (cursor_ + steps) & ntokensMask; + } + + const Token& currentToken() const { return tokens[cursor()]; } + bool isCurrentTokenType(TokenKind type) const { + return currentToken().type == type; + } + + [[nodiscard]] bool checkOptions(); + + private: + TaggedParserAtomIndex reservedWordToPropertyName(TokenKind tt) const; + + public: + TaggedParserAtomIndex currentName() const { + if (isCurrentTokenType(TokenKind::Name) || + isCurrentTokenType(TokenKind::PrivateName)) { + return currentToken().name(); + } + + MOZ_ASSERT(TokenKindIsPossibleIdentifierName(currentToken().type)); + return reservedWordToPropertyName(currentToken().type); + } + + bool currentNameHasEscapes(ParserAtomsTable& parserAtoms) const { + if (isCurrentTokenType(TokenKind::Name) || + isCurrentTokenType(TokenKind::PrivateName)) { + TokenPos pos = currentToken().pos; + return (pos.end - pos.begin) != parserAtoms.length(currentToken().name()); + } + + MOZ_ASSERT(TokenKindIsPossibleIdentifierName(currentToken().type)); + return false; + } + + bool isCurrentTokenAssignment() const { + return TokenKindIsAssignment(currentToken().type); + } + + // Flag methods. + bool isEOF() const { return flags.isEOF; } + bool hadError() const { return flags.hadError; } + + DeprecatedContent sawDeprecatedContent() const { + return static_cast(flags.sawDeprecatedContent); + } + + private: + // Workaround GCC 7 sadness. + void setSawDeprecatedContent(DeprecatedContent content) { + flags.sawDeprecatedContent = static_cast(content); + } + + public: + void clearSawDeprecatedContent() { + setSawDeprecatedContent(DeprecatedContent::None); + } + void setSawDeprecatedOctalLiteral() { + setSawDeprecatedContent(DeprecatedContent::OctalLiteral); + } + void setSawDeprecatedOctalEscape() { + setSawDeprecatedContent(DeprecatedContent::OctalEscape); + } + void setSawDeprecatedEightOrNineEscape() { + setSawDeprecatedContent(DeprecatedContent::EightOrNineEscape); + } + + bool hasInvalidTemplateEscape() const { + return invalidTemplateEscapeType != InvalidEscapeType::None; + } + void clearInvalidTemplateEscape() { + invalidTemplateEscapeType = InvalidEscapeType::None; + } + + private: + // This is private because it should only be called by the tokenizer while + // tokenizing not by, for example, BytecodeEmitter. + bool strictMode() const { + return strictModeGetter_ && strictModeGetter_->strictMode(); + } + + void setInvalidTemplateEscape(uint32_t offset, InvalidEscapeType type) { + MOZ_ASSERT(type != InvalidEscapeType::None); + if (invalidTemplateEscapeType != InvalidEscapeType::None) { + return; + } + invalidTemplateEscapeOffset = offset; + invalidTemplateEscapeType = type; + } + + public: + // Call this immediately after parsing an OrExpression to allow scanning the + // next token with SlashIsRegExp without asserting (even though we just + // peeked at it in SlashIsDiv mode). + // + // It's OK to disable the assertion because the places where this is called + // have peeked at the next token in SlashIsDiv mode, and checked that it is + // *not* a Div token. + // + // To see why it is necessary to disable the assertion, consider these two + // programs: + // + // x = arg => q // per spec, this is all one statement, and the + // /a/g; // slashes are division operators + // + // x = arg => {} // per spec, ASI at the end of this line + // /a/g; // and that's a regexp literal + // + // The first program shows why orExpr() has use SlashIsDiv mode when peeking + // ahead for the next operator after parsing `q`. The second program shows + // why matchOrInsertSemicolon() must use SlashIsRegExp mode when scanning + // ahead for a semicolon. + void allowGettingNextTokenWithSlashIsRegExp() { +#ifdef DEBUG + // Check the precondition: Caller already peeked ahead at the next token, + // in SlashIsDiv mode, and it is *not* a Div token. + MOZ_ASSERT(hasLookahead()); + const Token& next = nextToken(); + MOZ_ASSERT(next.modifier == SlashIsDiv); + MOZ_ASSERT(next.type != TokenKind::Div); + tokens[nextCursor()].modifier = SlashIsRegExp; +#endif + } + +#ifdef DEBUG + inline bool debugHasNoLookahead() const { return lookahead == 0; } +#endif + + bool hasDisplayURL() const { return displayURL_ != nullptr; } + + char16_t* displayURL() { return displayURL_.get(); } + + bool hasSourceMapURL() const { return sourceMapURL_ != nullptr; } + + char16_t* sourceMapURL() { return sourceMapURL_.get(); } + + FrontendContext* context() const { return fc; } + + using LineToken = SourceCoords::LineToken; + + LineToken lineToken(uint32_t offset) const { + return srcCoords.lineToken(offset); + } + + uint32_t lineNumber(LineToken lineToken) const { + return srcCoords.lineNumber(lineToken); + } + + uint32_t lineStart(LineToken lineToken) const { + return srcCoords.lineStart(lineToken); + } + + /** + * Fill in |err|. + * + * If the token stream doesn't have location info for this error, use the + * caller's location (including line/column number) and return false. (No + * line of context is set.) + * + * Otherwise fill in everything in |err| except 1) line/column numbers and + * 2) line-of-context-related fields and return true. The caller *must* + * fill in the line/column number; filling the line of context is optional. + */ + bool fillExceptingContext(ErrorMetadata* err, uint32_t offset) const; + + MOZ_ALWAYS_INLINE void updateFlagsForEOL() { flags.isDirtyLine = false; } + + private: + /** + * Compute the "partial" column number in Unicode code points of the absolute + * |offset| within source text on the line of |lineToken| (which must have + * been computed from |offset|). + * + * A partial column number on a line that isn't the first line is just the + * actual column number. But a partial column number on the first line is the + * column number *ignoring the initial line/column of the script*. For + * example, consider this HTML with line/column number keys: + * + * 1 2 3 + * 0123456789012345678901234 567890 + * ------------------------------------ + * 1 | + * 2 | + * 3 | + * 5 | + * 6 | + * 7 | + * + * The script would be compiled specifying initial (line, column) of (3, 10) + * using |JS::ReadOnlyCompileOptions::{lineno,column}|. And the column + * reported by |computeColumn| for the "v" of |var| would be 10. But the + * partial column number of the "v" in |var|, that this function returns, + * would be 0. On the other hand, the column reported by |computeColumn| and + * the partial column number returned by this function for the "c" in |const| + * would both be 0, because it's not in the first line of source text. + * + * The partial column is with respect *only* to the JavaScript source text as + * SpiderMonkey sees it. In the example, the "<" is converted to "<" by + * the browser before SpiderMonkey would see it. So the partial column of the + * "4" in the inequality would be 16, not 19. + * + * Code points are not all equal length, so counting requires *some* kind of + * linear-time counting from the start of the line. This function attempts + * various tricks to reduce this cost. If these optimizations succeed, + * repeated calls to this function on a line will pay a one-time cost linear + * in the length of the line, then each call pays a separate constant-time + * cost. If the optimizations do not succeed, this function works in time + * linear in the length of the line. + * + * It's unusual for a function in *this* class to be |Unit|-templated, but + * while this operation manages |Unit|-agnostic fields in this class and in + * |srcCoords|, it must *perform* |Unit|-sensitive computations to fill them. + * And this is the best place to do that. + */ + template + uint32_t computePartialColumn(const LineToken lineToken, + const uint32_t offset, + const SourceUnits& sourceUnits) const; + + /** + * Update line/column information for the start of a new line at + * |lineStartOffset|. + */ + [[nodiscard]] MOZ_ALWAYS_INLINE bool internalUpdateLineInfoForEOL( + uint32_t lineStartOffset); + + public: + const Token& nextToken() const { + MOZ_ASSERT(hasLookahead()); + return tokens[nextCursor()]; + } + + bool hasLookahead() const { return lookahead > 0; } + + void advanceCursor() { cursor_ = (cursor_ + 1) & ntokensMask; } + + void retractCursor() { cursor_ = (cursor_ - 1) & ntokensMask; } + + Token* allocateToken() { + advanceCursor(); + + Token* tp = &tokens[cursor()]; + MOZ_MAKE_MEM_UNDEFINED(tp, sizeof(*tp)); + + return tp; + } + + // Push the last scanned token back into the stream. + void ungetToken() { + MOZ_ASSERT(lookahead < maxLookahead); + lookahead++; + retractCursor(); + } + + public: + void adoptState(TokenStreamAnyChars& other) { + // If |other| has fresh information from directives, overwrite any + // previously recorded directives. (There is no specification directing + // that last-in-source-order directive controls, sadly. We behave this way + // in the ordinary case, so we ought do so here too.) + if (auto& url = other.displayURL_) { + displayURL_ = std::move(url); + } + if (auto& url = other.sourceMapURL_) { + sourceMapURL_ = std::move(url); + } + } + + // Compute error metadata for an error at no offset. + void computeErrorMetadataNoOffset(ErrorMetadata* err) const; + + // ErrorReporter API Helpers + + // Provide minimal set of error reporting API given we cannot use + // ErrorReportMixin here. "report" prefix is added to avoid conflict with + // ErrorReportMixin methods in TokenStream class. + void reportErrorNoOffset(unsigned errorNumber, ...) const; + void reportErrorNoOffsetVA(unsigned errorNumber, va_list* args) const; + + const JS::ReadOnlyCompileOptions& options() const { return options_; } + + const char* getFilename() const { return filename_; } +}; + +constexpr char16_t CodeUnitValue(char16_t unit) { return unit; } + +constexpr uint8_t CodeUnitValue(mozilla::Utf8Unit unit) { + return unit.toUint8(); +} + +template +class TokenStreamCharsBase; + +template +inline bool IsLineTerminator(T) = delete; + +inline bool IsLineTerminator(char32_t codePoint) { + return codePoint == '\n' || codePoint == '\r' || + codePoint == unicode::LINE_SEPARATOR || + codePoint == unicode::PARA_SEPARATOR; +} + +inline bool IsLineTerminator(char16_t unit) { + // Every LineTerminator fits in char16_t, so this is exact. + return IsLineTerminator(static_cast(unit)); +} + +template +struct SourceUnitTraits; + +template <> +struct SourceUnitTraits { + public: + static constexpr uint8_t maxUnitsLength = 2; + + static constexpr size_t lengthInUnits(char32_t codePoint) { + return codePoint < unicode::NonBMPMin ? 1 : 2; + } +}; + +template <> +struct SourceUnitTraits { + public: + static constexpr uint8_t maxUnitsLength = 4; + + static constexpr size_t lengthInUnits(char32_t codePoint) { + return codePoint < 0x80 ? 1 + : codePoint < 0x800 ? 2 + : codePoint < 0x10000 ? 3 + : 4; + } +}; + +/** + * PeekedCodePoint represents the result of peeking ahead in some source text + * to determine the next validly-encoded code point. + * + * If there isn't a valid code point, then |isNone()|. + * + * But if there *is* a valid code point, then |!isNone()|, the code point has + * value |codePoint()| and its length in code units is |lengthInUnits()|. + * + * Conceptually, this class is |Maybe|. + */ +template +class PeekedCodePoint final { + char32_t codePoint_ = 0; + uint8_t lengthInUnits_ = 0; + + private: + using SourceUnitTraits = frontend::SourceUnitTraits; + + PeekedCodePoint() = default; + + public: + /** + * Create a peeked code point with the given value and length in code + * units. + * + * While the latter value is computable from the former for both UTF-8 and + * JS's version of UTF-16, the caller likely computed a length in units in + * the course of determining the peeked value. Passing both here avoids + * recomputation and lets us do a consistency-checking assertion. + */ + PeekedCodePoint(char32_t codePoint, uint8_t lengthInUnits) + : codePoint_(codePoint), lengthInUnits_(lengthInUnits) { + MOZ_ASSERT(codePoint <= unicode::NonBMPMax); + MOZ_ASSERT(lengthInUnits != 0, "bad code point length"); + MOZ_ASSERT(lengthInUnits == SourceUnitTraits::lengthInUnits(codePoint)); + } + + /** Create a PeekedCodeUnit that represents no valid code point. */ + static PeekedCodePoint none() { return PeekedCodePoint(); } + + /** True if no code point was found, false otherwise. */ + bool isNone() const { return lengthInUnits_ == 0; } + + /** If a code point was found, its value. */ + char32_t codePoint() const { + MOZ_ASSERT(!isNone()); + return codePoint_; + } + + /** If a code point was found, its length in code units. */ + uint8_t lengthInUnits() const { + MOZ_ASSERT(!isNone()); + return lengthInUnits_; + } +}; + +inline PeekedCodePoint PeekCodePoint(const char16_t* const ptr, + const char16_t* const end) { + if (MOZ_UNLIKELY(ptr >= end)) { + return PeekedCodePoint::none(); + } + + char16_t lead = ptr[0]; + + char32_t c; + uint8_t len; + if (MOZ_LIKELY(!unicode::IsLeadSurrogate(lead)) || + MOZ_UNLIKELY(ptr + 1 >= end || !unicode::IsTrailSurrogate(ptr[1]))) { + c = lead; + len = 1; + } else { + c = unicode::UTF16Decode(lead, ptr[1]); + len = 2; + } + + return PeekedCodePoint(c, len); +} + +inline PeekedCodePoint PeekCodePoint( + const mozilla::Utf8Unit* const ptr, const mozilla::Utf8Unit* const end) { + if (MOZ_UNLIKELY(ptr >= end)) { + return PeekedCodePoint::none(); + } + + const mozilla::Utf8Unit lead = ptr[0]; + if (mozilla::IsAscii(lead)) { + return PeekedCodePoint(lead.toUint8(), 1); + } + + const mozilla::Utf8Unit* afterLead = ptr + 1; + mozilla::Maybe codePoint = + mozilla::DecodeOneUtf8CodePoint(lead, &afterLead, end); + if (codePoint.isNothing()) { + return PeekedCodePoint::none(); + } + + auto len = + mozilla::AssertedCast(mozilla::PointerRangeSize(ptr, afterLead)); + MOZ_ASSERT(len <= 4); + + return PeekedCodePoint(codePoint.value(), len); +} + +inline bool IsSingleUnitLineTerminator(mozilla::Utf8Unit unit) { + // BEWARE: The Unicode line/paragraph separators don't fit in a single + // UTF-8 code unit, so this test is exact for Utf8Unit but inexact + // for UTF-8 as a whole. Users must handle |unit| as start of a + // Unicode LineTerminator themselves! + return unit == mozilla::Utf8Unit('\n') || unit == mozilla::Utf8Unit('\r'); +} + +// This is the low-level interface to the JS source code buffer. It just gets +// raw Unicode code units -- 16-bit char16_t units of source text that are not +// (always) full code points, and 8-bit units of UTF-8 source text soon. +// TokenStreams functions are layered on top and do some extra stuff like +// converting all EOL sequences to '\n', tracking the line number, and setting +// |flags.isEOF|. (The "raw" in "raw Unicode code units" refers to the lack of +// EOL sequence normalization.) +// +// buf[0..length-1] often represents a substring of some larger source, +// where we have only the substring in memory. The |startOffset| argument +// indicates the offset within this larger string at which our string +// begins, the offset of |buf[0]|. +template +class SourceUnits { + private: + /** Base of buffer. */ + const Unit* base_; + + /** Offset of base_[0]. */ + uint32_t startOffset_; + + /** Limit for quick bounds check. */ + const Unit* limit_; + + /** Next char to get. */ + const Unit* ptr; + + public: + SourceUnits(const Unit* units, size_t length, size_t startOffset) + : base_(units), + startOffset_(startOffset), + limit_(units + length), + ptr(units) {} + + bool atStart() const { + MOZ_ASSERT(!isPoisoned(), "shouldn't be using if poisoned"); + return ptr == base_; + } + + bool atEnd() const { + MOZ_ASSERT(!isPoisoned(), "shouldn't be using if poisoned"); + MOZ_ASSERT(ptr <= limit_, "shouldn't have overrun"); + return ptr >= limit_; + } + + size_t remaining() const { + MOZ_ASSERT(!isPoisoned(), + "can't get a count of remaining code units if poisoned"); + return mozilla::PointerRangeSize(ptr, limit_); + } + + size_t startOffset() const { return startOffset_; } + + size_t offset() const { + return startOffset_ + mozilla::PointerRangeSize(base_, ptr); + } + + const Unit* codeUnitPtrAt(size_t offset) const { + MOZ_ASSERT(!isPoisoned(), "shouldn't be using if poisoned"); + MOZ_ASSERT(startOffset_ <= offset); + MOZ_ASSERT(offset - startOffset_ <= + mozilla::PointerRangeSize(base_, limit_)); + return base_ + (offset - startOffset_); + } + + const Unit* current() const { return ptr; } + + const Unit* limit() const { return limit_; } + + Unit previousCodeUnit() { + MOZ_ASSERT(!isPoisoned(), "can't get previous code unit if poisoned"); + MOZ_ASSERT(!atStart(), "must have a previous code unit to get"); + return *(ptr - 1); + } + + MOZ_ALWAYS_INLINE Unit getCodeUnit() { + return *ptr++; // this will nullptr-crash if poisoned + } + + Unit peekCodeUnit() const { + return *ptr; // this will nullptr-crash if poisoned + } + + /** + * Determine the next code point in source text. The code point is not + * normalized: '\r', '\n', '\u2028', and '\u2029' are returned literally. + * If there is no next code point because |atEnd()|, or if an encoding + * error is encountered, return a |PeekedCodePoint| that |isNone()|. + * + * This function does not report errors: code that attempts to get the next + * code point must report any error. + * + * If a next code point is found, it may be consumed by passing it to + * |consumeKnownCodePoint|. + */ + PeekedCodePoint peekCodePoint() const { + return PeekCodePoint(ptr, limit_); + } + + private: +#ifdef DEBUG + void assertNextCodePoint(const PeekedCodePoint& peeked); +#endif + + public: + /** + * Consume a peeked code point that |!isNone()|. + * + * This call DOES NOT UPDATE LINE-STATUS. You may need to call + * |updateLineInfoForEOL()| and |updateFlagsForEOL()| if this consumes a + * LineTerminator. Note that if this consumes '\r', you also must consume + * an optional '\n' (i.e. a full LineTerminatorSequence) before doing so. + */ + void consumeKnownCodePoint(const PeekedCodePoint& peeked) { + MOZ_ASSERT(!peeked.isNone()); + MOZ_ASSERT(peeked.lengthInUnits() <= remaining()); + +#ifdef DEBUG + assertNextCodePoint(peeked); +#endif + + ptr += peeked.lengthInUnits(); + } + + /** Match |n| hexadecimal digits and store their value in |*out|. */ + bool matchHexDigits(uint8_t n, char16_t* out) { + MOZ_ASSERT(!isPoisoned(), "shouldn't peek into poisoned SourceUnits"); + MOZ_ASSERT(n <= 4, "hexdigit value can't overflow char16_t"); + if (n > remaining()) { + return false; + } + + char16_t v = 0; + for (uint8_t i = 0; i < n; i++) { + auto unit = CodeUnitValue(ptr[i]); + if (!mozilla::IsAsciiHexDigit(unit)) { + return false; + } + + v = (v << 4) | mozilla::AsciiAlphanumericToNumber(unit); + } + + *out = v; + ptr += n; + return true; + } + + bool matchCodeUnits(const char* chars, uint8_t length) { + MOZ_ASSERT(!isPoisoned(), "shouldn't match into poisoned SourceUnits"); + if (length > remaining()) { + return false; + } + + const Unit* start = ptr; + const Unit* end = ptr + length; + while (ptr < end) { + if (*ptr++ != Unit(*chars++)) { + ptr = start; + return false; + } + } + + return true; + } + + void skipCodeUnits(uint32_t n) { + MOZ_ASSERT(!isPoisoned(), "shouldn't use poisoned SourceUnits"); + MOZ_ASSERT(n <= remaining(), "shouldn't skip beyond end of SourceUnits"); + ptr += n; + } + + void unskipCodeUnits(uint32_t n) { + MOZ_ASSERT(!isPoisoned(), "shouldn't use poisoned SourceUnits"); + MOZ_ASSERT(n <= mozilla::PointerRangeSize(base_, ptr), + "shouldn't unskip beyond start of SourceUnits"); + ptr -= n; + } + + private: + friend class TokenStreamCharsBase; + + bool internalMatchCodeUnit(Unit c) { + MOZ_ASSERT(!isPoisoned(), "shouldn't use poisoned SourceUnits"); + if (MOZ_LIKELY(!atEnd()) && *ptr == c) { + ptr++; + return true; + } + return false; + } + + public: + void consumeKnownCodeUnit(Unit c) { + MOZ_ASSERT(!isPoisoned(), "shouldn't use poisoned SourceUnits"); + MOZ_ASSERT(*ptr == c, "consuming the wrong code unit"); + ptr++; + } + + /** Unget U+2028 LINE SEPARATOR or U+2029 PARAGRAPH SEPARATOR. */ + inline void ungetLineOrParagraphSeparator(); + + void ungetCodeUnit() { + MOZ_ASSERT(!isPoisoned(), "can't unget from poisoned units"); + MOZ_ASSERT(!atStart(), "can't unget if currently at start"); + ptr--; + } + + const Unit* addressOfNextCodeUnit(bool allowPoisoned = false) const { + MOZ_ASSERT_IF(!allowPoisoned, !isPoisoned()); + return ptr; + } + + // Use this with caution! + void setAddressOfNextCodeUnit(const Unit* a, bool allowPoisoned = false) { + MOZ_ASSERT_IF(!allowPoisoned, a); + ptr = a; + } + + // Poison the SourceUnits so they can't be accessed again. + void poisonInDebug() { +#ifdef DEBUG + ptr = nullptr; +#endif + } + + private: + bool isPoisoned() const { +#ifdef DEBUG + // |ptr| can be null for unpoisoned SourceUnits if this was initialized with + // |units == nullptr| and |length == 0|. In that case, for lack of any + // better options, consider this to not be poisoned. + return ptr == nullptr && ptr != limit_; +#else + return false; +#endif + } + + public: + /** + * Consume the rest of a single-line comment (but not the EOL/EOF that + * terminates it). + * + * If an encoding error is encountered -- possible only for UTF-8 because + * JavaScript's conception of UTF-16 encompasses any sequence of 16-bit + * code units -- valid code points prior to the encoding error are consumed + * and subsequent invalid code units are not consumed. For example, given + * these UTF-8 code units: + * + * 'B' 'A' 'D' ':' + * 0x42 0x41 0x44 0x3A 0xD0 0x00 ... + * + * the first four code units are consumed, but 0xD0 and 0x00 are not + * consumed because 0xD0 encodes a two-byte lead unit but 0x00 is not a + * valid trailing code unit. + * + * It is expected that the caller will report such an encoding error when + * it attempts to consume the next code point. + */ + void consumeRestOfSingleLineComment(); + + /** + * The maximum radius of code around the location of an error that should + * be included in a syntax error message -- this many code units to either + * side. The resulting window of data is then accordinngly trimmed so that + * the window contains only validly-encoded data. + * + * Because this number is the same for both UTF-8 and UTF-16, windows in + * UTF-8 may contain fewer code points than windows in UTF-16. As we only + * use this for error messages, we don't particularly care. + */ + static constexpr size_t WindowRadius = ErrorMetadata::lineOfContextRadius; + + /** + * From absolute offset |offset|, search backward to find an absolute + * offset within source text, no further than |WindowRadius| code units + * away from |offset|, such that all code points from that offset to + * |offset| are valid, non-LineTerminator code points. + */ + size_t findWindowStart(size_t offset) const; + + /** + * From absolute offset |offset|, find an absolute offset within source + * text, no further than |WindowRadius| code units away from |offset|, such + * that all code units from |offset| to that offset are valid, + * non-LineTerminator code points. + */ + size_t findWindowEnd(size_t offset) const; + + /** + * Given a |window| of |encodingSpecificWindowLength| units encoding valid + * Unicode text, with index |encodingSpecificTokenOffset| indicating a + * particular code point boundary in |window|, compute the corresponding + * token offset and length if |window| were encoded in UTF-16. For + * example: + * + * // U+03C0 GREEK SMALL LETTER PI is encoded as 0xCF 0x80. + * const Utf8Unit* encodedWindow = + * reinterpret_cast(u8"ππππ = @ FAIL"); + * size_t encodedTokenOffset = 11; // 2 * 4 + ' = '.length + * size_t encodedWindowLength = 17; // 2 * 4 + ' = @ FAIL'.length + * size_t utf16Offset, utf16Length; + * computeWindowOffsetAndLength(encodedWindow, + * encodedTokenOffset, &utf16Offset, + * encodedWindowLength, &utf16Length); + * MOZ_ASSERT(utf16Offset == 7); + * MOZ_ASSERT(utf16Length = 13); + * + * This function asserts if called for UTF-16: the sole caller can avoid + * computing UTF-16 offsets when they're definitely the same as the encoded + * offsets. + */ + inline void computeWindowOffsetAndLength(const Unit* encodeWindow, + size_t encodingSpecificTokenOffset, + size_t* utf16TokenOffset, + size_t encodingSpecificWindowLength, + size_t* utf16WindowLength) const; +}; + +template <> +inline void SourceUnits::ungetLineOrParagraphSeparator() { +#ifdef DEBUG + char16_t prev = previousCodeUnit(); +#endif + MOZ_ASSERT(prev == unicode::LINE_SEPARATOR || + prev == unicode::PARA_SEPARATOR); + + ungetCodeUnit(); +} + +template <> +inline void SourceUnits::ungetLineOrParagraphSeparator() { + unskipCodeUnits(3); + + MOZ_ASSERT(ptr[0].toUint8() == 0xE2); + MOZ_ASSERT(ptr[1].toUint8() == 0x80); + +#ifdef DEBUG + uint8_t last = ptr[2].toUint8(); +#endif + MOZ_ASSERT(last == 0xA8 || last == 0xA9); +} + +/** + * An all-purpose buffer type for accumulating text during tokenizing. + * + * In principle we could make this buffer contain |char16_t|, |Utf8Unit|, or + * |Unit|. We use |char16_t| because: + * + * * we don't have a UTF-8 regular expression parser, so in general regular + * expression text must be copied to a separate UTF-16 buffer to parse it, + * and + * * |TokenStreamCharsShared::copyCharBufferTo|, which copies a shared + * |CharBuffer| to a |char16_t*|, is simpler if it doesn't have to convert. + */ +using CharBuffer = Vector; + +/** + * Append the provided code point (in the range [U+0000, U+10FFFF], surrogate + * code points included) to the buffer. + */ +[[nodiscard]] extern bool AppendCodePointToCharBuffer(CharBuffer& charBuffer, + char32_t codePoint); + +/** + * Accumulate the range of UTF-16 text (lone surrogates permitted, because JS + * allows them in source text) into |charBuffer|. Normalize '\r', '\n', and + * "\r\n" into '\n'. + */ +[[nodiscard]] extern bool FillCharBufferFromSourceNormalizingAsciiLineBreaks( + CharBuffer& charBuffer, const char16_t* cur, const char16_t* end); + +/** + * Accumulate the range of previously-validated UTF-8 text into |charBuffer|. + * Normalize '\r', '\n', and "\r\n" into '\n'. + */ +[[nodiscard]] extern bool FillCharBufferFromSourceNormalizingAsciiLineBreaks( + CharBuffer& charBuffer, const mozilla::Utf8Unit* cur, + const mozilla::Utf8Unit* end); + +class TokenStreamCharsShared { + protected: + FrontendContext* fc; + + /** + * Buffer transiently used to store sequences of identifier or string code + * points when such can't be directly processed from the original source + * text (e.g. because it contains escapes). + */ + CharBuffer charBuffer; + + /** Information for parsing with a lifetime longer than the parser itself. */ + ParserAtomsTable* parserAtoms; + + protected: + explicit TokenStreamCharsShared(FrontendContext* fc, + ParserAtomsTable* parserAtoms) + : fc(fc), charBuffer(fc), parserAtoms(parserAtoms) {} + + [[nodiscard]] bool copyCharBufferTo( + UniquePtr* destination); + + /** + * Determine whether a code unit constitutes a complete ASCII code point. + * (The code point's exact value might not be used, however, if subsequent + * code observes that |unit| is part of a LineTerminatorSequence.) + */ + [[nodiscard]] static constexpr MOZ_ALWAYS_INLINE bool isAsciiCodePoint( + int32_t unit) { + return mozilla::IsAscii(static_cast(unit)); + } + + TaggedParserAtomIndex drainCharBufferIntoAtom() { + // Add to parser atoms table. + auto atom = this->parserAtoms->internChar16(fc, charBuffer.begin(), + charBuffer.length()); + charBuffer.clear(); + return atom; + } + + protected: + void adoptState(TokenStreamCharsShared& other) { + // The other stream's buffer may contain information for a + // gotten-then-ungotten token, that we must transfer into this stream so + // that token's final get behaves as desired. + charBuffer = std::move(other.charBuffer); + } + + public: + CharBuffer& getCharBuffer() { return charBuffer; } +}; + +template +class TokenStreamCharsBase : public TokenStreamCharsShared { + protected: + using SourceUnits = frontend::SourceUnits; + + /** Code units in the source code being tokenized. */ + SourceUnits sourceUnits; + + // End of fields. + + protected: + TokenStreamCharsBase(FrontendContext* fc, ParserAtomsTable* parserAtoms, + const Unit* units, size_t length, size_t startOffset); + + /** + * Convert a non-EOF code unit returned by |getCodeUnit()| or + * |peekCodeUnit()| to a Unit code unit. + */ + inline Unit toUnit(int32_t codeUnitValue); + + void ungetCodeUnit(int32_t c) { + if (c == EOF) { + MOZ_ASSERT(sourceUnits.atEnd()); + return; + } + + MOZ_ASSERT(sourceUnits.previousCodeUnit() == toUnit(c)); + sourceUnits.ungetCodeUnit(); + } + + MOZ_ALWAYS_INLINE TaggedParserAtomIndex + atomizeSourceChars(mozilla::Span units); + + /** + * Try to match a non-LineTerminator ASCII code point. Return true iff it + * was matched. + */ + bool matchCodeUnit(char expect) { + MOZ_ASSERT(mozilla::IsAscii(expect)); + MOZ_ASSERT(expect != '\r'); + MOZ_ASSERT(expect != '\n'); + return this->sourceUnits.internalMatchCodeUnit(Unit(expect)); + } + + /** + * Try to match an ASCII LineTerminator code point. Return true iff it was + * matched. + */ + MOZ_NEVER_INLINE bool matchLineTerminator(char expect) { + MOZ_ASSERT(expect == '\r' || expect == '\n'); + return this->sourceUnits.internalMatchCodeUnit(Unit(expect)); + } + + template + bool matchCodeUnit(T) = delete; + template + bool matchLineTerminator(T) = delete; + + int32_t peekCodeUnit() { + return MOZ_LIKELY(!sourceUnits.atEnd()) + ? CodeUnitValue(sourceUnits.peekCodeUnit()) + : EOF; + } + + /** Consume a known, non-EOF code unit. */ + inline void consumeKnownCodeUnit(int32_t unit); + + // Forbid accidental calls to consumeKnownCodeUnit *not* with the single + // unit-or-EOF type. Unit should use SourceUnits::consumeKnownCodeUnit; + // CodeUnitValue() results should go through toUnit(), or better yet just + // use the original Unit. + template + inline void consumeKnownCodeUnit(T) = delete; + + /** + * Add a null-terminated line of context to error information, for the line + * in |sourceUnits| that contains |offset|. Also record the window's + * length and the offset of the error in the window. (Don't bother adding + * a line of context if it would be empty.) + * + * The window will contain no LineTerminators of any kind, and it will not + * extend more than |SourceUnits::WindowRadius| to either side of |offset|, + * nor into the previous or next lines. + * + * This function is quite internal, and you probably should be calling one + * of its existing callers instead. + */ + [[nodiscard]] bool addLineOfContext(ErrorMetadata* err, + uint32_t offset) const; +}; + +template <> +inline char16_t TokenStreamCharsBase::toUnit(int32_t codeUnitValue) { + MOZ_ASSERT(codeUnitValue != EOF, "EOF is not a Unit"); + return mozilla::AssertedCast(codeUnitValue); +} + +template <> +inline mozilla::Utf8Unit TokenStreamCharsBase::toUnit( + int32_t value) { + MOZ_ASSERT(value != EOF, "EOF is not a Unit"); + return mozilla::Utf8Unit(mozilla::AssertedCast(value)); +} + +template +inline void TokenStreamCharsBase::consumeKnownCodeUnit(int32_t unit) { + sourceUnits.consumeKnownCodeUnit(toUnit(unit)); +} + +template <> +MOZ_ALWAYS_INLINE TaggedParserAtomIndex +TokenStreamCharsBase::atomizeSourceChars( + mozilla::Span units) { + return this->parserAtoms->internChar16(fc, units.data(), units.size()); +} + +template <> +/* static */ MOZ_ALWAYS_INLINE TaggedParserAtomIndex +TokenStreamCharsBase::atomizeSourceChars( + mozilla::Span units) { + return this->parserAtoms->internUtf8(fc, units.data(), units.size()); +} + +template +class SpecializedTokenStreamCharsBase; + +template <> +class SpecializedTokenStreamCharsBase + : public TokenStreamCharsBase { + using CharsBase = TokenStreamCharsBase; + + protected: + using TokenStreamCharsShared::isAsciiCodePoint; + // Deliberately don't |using| |sourceUnits| because of bug 1472569. :-( + + using typename CharsBase::SourceUnits; + + protected: + // These APIs are only usable by UTF-16-specific code. + + /** + * Given |lead| already consumed, consume and return the code point encoded + * starting from it. Infallible because lone surrogates in JS encode a + * "code point" of the same value. + */ + char32_t infallibleGetNonAsciiCodePointDontNormalize(char16_t lead) { + MOZ_ASSERT(!isAsciiCodePoint(lead)); + MOZ_ASSERT(this->sourceUnits.previousCodeUnit() == lead); + + // Handle single-unit code points and lone trailing surrogates. + if (MOZ_LIKELY(!unicode::IsLeadSurrogate(lead)) || + // Or handle lead surrogates not paired with trailing surrogates. + MOZ_UNLIKELY( + this->sourceUnits.atEnd() || + !unicode::IsTrailSurrogate(this->sourceUnits.peekCodeUnit()))) { + return lead; + } + + // Otherwise it's a multi-unit code point. + return unicode::UTF16Decode(lead, this->sourceUnits.getCodeUnit()); + } + + protected: + // These APIs are in both SpecializedTokenStreamCharsBase specializations + // and so are usable in subclasses no matter what Unit is. + + using CharsBase::CharsBase; +}; + +template <> +class SpecializedTokenStreamCharsBase + : public TokenStreamCharsBase { + using CharsBase = TokenStreamCharsBase; + + protected: + // Deliberately don't |using| |sourceUnits| because of bug 1472569. :-( + + protected: + // These APIs are only usable by UTF-8-specific code. + + using typename CharsBase::SourceUnits; + + /** + * A mutable iterator-wrapper around |SourceUnits| that translates + * operators to calls to |SourceUnits::getCodeUnit()| and similar. + * + * This class is expected to be used in concert with |SourceUnitsEnd|. + */ + class SourceUnitsIterator { + SourceUnits& sourceUnits_; +#ifdef DEBUG + // In iterator copies created by the post-increment operator, a pointer + // at the next source text code unit when the post-increment operator + // was called, cleared when the iterator is dereferenced. + mutable mozilla::Maybe + currentBeforePostIncrement_; +#endif + + public: + explicit SourceUnitsIterator(SourceUnits& sourceUnits) + : sourceUnits_(sourceUnits) {} + + mozilla::Utf8Unit operator*() const { + // operator* is expected to get the *next* value from an iterator + // not pointing at the end of the underlying range. However, the + // sole use of this is in the context of an expression of the form + // |*iter++|, that performed the |sourceUnits_.getCodeUnit()| in + // the |operator++(int)| below -- so dereferencing acts on a + // |sourceUnits_| already advanced. Therefore the correct unit to + // return is the previous one. + MOZ_ASSERT(currentBeforePostIncrement_.value() + 1 == + sourceUnits_.current()); +#ifdef DEBUG + currentBeforePostIncrement_.reset(); +#endif + return sourceUnits_.previousCodeUnit(); + } + + SourceUnitsIterator operator++(int) { + MOZ_ASSERT(currentBeforePostIncrement_.isNothing(), + "the only valid operation on a post-incremented " + "iterator is dereferencing a single time"); + + SourceUnitsIterator copy = *this; +#ifdef DEBUG + copy.currentBeforePostIncrement_.emplace(sourceUnits_.current()); +#endif + + sourceUnits_.getCodeUnit(); + return copy; + } + + void operator-=(size_t n) { + MOZ_ASSERT(currentBeforePostIncrement_.isNothing(), + "the only valid operation on a post-incremented " + "iterator is dereferencing a single time"); + sourceUnits_.unskipCodeUnits(n); + } + + mozilla::Utf8Unit operator[](ptrdiff_t index) { + MOZ_ASSERT(currentBeforePostIncrement_.isNothing(), + "the only valid operation on a post-incremented " + "iterator is dereferencing a single time"); + MOZ_ASSERT(index == -1, + "must only be called to verify the value of the " + "previous code unit"); + return sourceUnits_.previousCodeUnit(); + } + + size_t remaining() const { + MOZ_ASSERT(currentBeforePostIncrement_.isNothing(), + "the only valid operation on a post-incremented " + "iterator is dereferencing a single time"); + return sourceUnits_.remaining(); + } + }; + + /** A sentinel representing the end of |SourceUnits| data. */ + class SourceUnitsEnd {}; + + friend inline size_t operator-(const SourceUnitsEnd& aEnd, + const SourceUnitsIterator& aIter); + + protected: + // These APIs are in both SpecializedTokenStreamCharsBase specializations + // and so are usable in subclasses no matter what Unit is. + + using CharsBase::CharsBase; +}; + +inline size_t operator-(const SpecializedTokenStreamCharsBase< + mozilla::Utf8Unit>::SourceUnitsEnd& aEnd, + const SpecializedTokenStreamCharsBase< + mozilla::Utf8Unit>::SourceUnitsIterator& aIter) { + return aIter.remaining(); +} + +/** A small class encapsulating computation of the start-offset of a Token. */ +class TokenStart { + uint32_t startOffset_; + + public: + /** + * Compute a starting offset that is the current offset of |sourceUnits|, + * offset by |adjust|. (For example, |adjust| of -1 indicates the code + * unit one backwards from |sourceUnits|'s current offset.) + */ + template + TokenStart(const SourceUnits& sourceUnits, ptrdiff_t adjust) + : startOffset_(sourceUnits.offset() + adjust) {} + + TokenStart(const TokenStart&) = default; + + uint32_t offset() const { return startOffset_; } +}; + +template +class GeneralTokenStreamChars : public SpecializedTokenStreamCharsBase { + using CharsBase = TokenStreamCharsBase; + using SpecializedCharsBase = SpecializedTokenStreamCharsBase; + + using LineToken = TokenStreamAnyChars::LineToken; + + private: + Token* newTokenInternal(TokenKind kind, TokenStart start, TokenKind* out); + + /** + * Allocates a new Token from the given offset to the current offset, + * ascribes it the given kind, and sets |*out| to that kind. + */ + Token* newToken(TokenKind kind, TokenStart start, + TokenStreamShared::Modifier modifier, TokenKind* out) { + Token* token = newTokenInternal(kind, start, out); + +#ifdef DEBUG + // Save the modifier used to get this token, so that if an ungetToken() + // occurs and then the token is re-gotten (or peeked, etc.), we can + // assert both gets used compatible modifiers. + token->modifier = modifier; +#endif + + return token; + } + + uint32_t matchUnicodeEscape(char32_t* codePoint); + uint32_t matchExtendedUnicodeEscape(char32_t* codePoint); + + protected: + using CharsBase::addLineOfContext; + using CharsBase::matchCodeUnit; + using CharsBase::matchLineTerminator; + using TokenStreamCharsShared::drainCharBufferIntoAtom; + using TokenStreamCharsShared::isAsciiCodePoint; + // Deliberately don't |using CharsBase::sourceUnits| because of bug 1472569. + // :-( + using CharsBase::toUnit; + + using typename CharsBase::SourceUnits; + + protected: + using SpecializedCharsBase::SpecializedCharsBase; + + TokenStreamAnyChars& anyCharsAccess() { + return AnyCharsAccess::anyChars(this); + } + + const TokenStreamAnyChars& anyCharsAccess() const { + return AnyCharsAccess::anyChars(this); + } + + using TokenStreamSpecific = + frontend::TokenStreamSpecific; + + TokenStreamSpecific* asSpecific() { + static_assert( + std::is_base_of_v, + "static_cast below presumes an inheritance relationship"); + + return static_cast(this); + } + + protected: + /** + * Compute the column number in Unicode code points of the absolute |offset| + * within source text on the line corresponding to |lineToken|. + * + * |offset| must be a code point boundary, preceded only by validly-encoded + * source units. (It doesn't have to be *followed* by valid source units.) + */ + uint32_t computeColumn(LineToken lineToken, uint32_t offset) const; + void computeLineAndColumn(uint32_t offset, uint32_t* line, + uint32_t* column) const; + + /** + * Fill in |err| completely, except for line-of-context information. + * + * Return true if the caller can compute a line of context from the token + * stream. Otherwise return false. + */ + [[nodiscard]] bool fillExceptingContext(ErrorMetadata* err, + uint32_t offset) const { + if (anyCharsAccess().fillExceptingContext(err, offset)) { + computeLineAndColumn(offset, &err->lineNumber, &err->columnNumber); + return true; + } + return false; + } + + void newSimpleToken(TokenKind kind, TokenStart start, + TokenStreamShared::Modifier modifier, TokenKind* out) { + newToken(kind, start, modifier, out); + } + + void newNumberToken(double dval, DecimalPoint decimalPoint, TokenStart start, + TokenStreamShared::Modifier modifier, TokenKind* out) { + Token* token = newToken(TokenKind::Number, start, modifier, out); + token->setNumber(dval, decimalPoint); + } + + void newBigIntToken(TokenStart start, TokenStreamShared::Modifier modifier, + TokenKind* out) { + newToken(TokenKind::BigInt, start, modifier, out); + } + + void newAtomToken(TokenKind kind, TaggedParserAtomIndex atom, + TokenStart start, TokenStreamShared::Modifier modifier, + TokenKind* out) { + MOZ_ASSERT(kind == TokenKind::String || kind == TokenKind::TemplateHead || + kind == TokenKind::NoSubsTemplate); + + Token* token = newToken(kind, start, modifier, out); + token->setAtom(atom); + } + + void newNameToken(TaggedParserAtomIndex name, TokenStart start, + TokenStreamShared::Modifier modifier, TokenKind* out) { + Token* token = newToken(TokenKind::Name, start, modifier, out); + token->setName(name); + } + + void newPrivateNameToken(TaggedParserAtomIndex name, TokenStart start, + TokenStreamShared::Modifier modifier, + TokenKind* out) { + Token* token = newToken(TokenKind::PrivateName, start, modifier, out); + token->setName(name); + } + + void newRegExpToken(JS::RegExpFlags reflags, TokenStart start, + TokenKind* out) { + Token* token = newToken(TokenKind::RegExp, start, + TokenStreamShared::SlashIsRegExp, out); + token->setRegExpFlags(reflags); + } + + MOZ_COLD bool badToken(); + + /** + * Get the next code unit -- the next numeric sub-unit of source text, + * possibly smaller than a full code point -- without updating line/column + * counters or consuming LineTerminatorSequences. + * + * Because of these limitations, only use this if (a) the resulting code + * unit is guaranteed to be ungotten (by ungetCodeUnit()) if it's an EOL, + * and (b) the line-related state (lineno, linebase) is not used before + * it's ungotten. + */ + int32_t getCodeUnit() { + if (MOZ_LIKELY(!this->sourceUnits.atEnd())) { + return CodeUnitValue(this->sourceUnits.getCodeUnit()); + } + + anyCharsAccess().flags.isEOF = true; + return EOF; + } + + void ungetCodeUnit(int32_t c) { + MOZ_ASSERT_IF(c == EOF, anyCharsAccess().flags.isEOF); + + CharsBase::ungetCodeUnit(c); + } + + /** + * Given a just-consumed ASCII code unit/point |lead|, consume a full code + * point or LineTerminatorSequence (normalizing it to '\n'). Return true on + * success, otherwise return false. + * + * If a LineTerminatorSequence was consumed, also update line/column info. + * + * This may change the current |sourceUnits| offset. + */ + [[nodiscard]] MOZ_ALWAYS_INLINE bool getFullAsciiCodePoint(int32_t lead) { + MOZ_ASSERT(isAsciiCodePoint(lead), + "non-ASCII code units must be handled separately"); + MOZ_ASSERT(toUnit(lead) == this->sourceUnits.previousCodeUnit(), + "getFullAsciiCodePoint called incorrectly"); + + if (MOZ_UNLIKELY(lead == '\r')) { + matchLineTerminator('\n'); + } else if (MOZ_LIKELY(lead != '\n')) { + return true; + } + return updateLineInfoForEOL(); + } + + [[nodiscard]] MOZ_NEVER_INLINE bool updateLineInfoForEOL() { + return anyCharsAccess().internalUpdateLineInfoForEOL( + this->sourceUnits.offset()); + } + + uint32_t matchUnicodeEscapeIdStart(char32_t* codePoint); + bool matchUnicodeEscapeIdent(char32_t* codePoint); + bool matchIdentifierStart(); + + /** + * If possible, compute a line of context for an otherwise-filled-in |err| + * at the given offset in this token stream. + * + * This function is very-internal: almost certainly you should use one of + * its callers instead. It basically exists only to make those callers + * more readable. + */ + [[nodiscard]] bool internalComputeLineOfContext(ErrorMetadata* err, + uint32_t offset) const { + // We only have line-start information for the current line. If the error + // is on a different line, we can't easily provide context. (This means + // any error in a multi-line token, e.g. an unterminated multiline string + // literal, won't have context.) + if (err->lineNumber != anyCharsAccess().lineno) { + return true; + } + + return addLineOfContext(err, offset); + } + + public: + /** + * Consume any hashbang comment at the start of a Script or Module, if one is + * present. Stops consuming just before any terminating LineTerminator or + * before an encoding error is encountered. + */ + void consumeOptionalHashbangComment(); + + TaggedParserAtomIndex getRawTemplateStringAtom() { + TokenStreamAnyChars& anyChars = anyCharsAccess(); + + MOZ_ASSERT(anyChars.currentToken().type == TokenKind::TemplateHead || + anyChars.currentToken().type == TokenKind::NoSubsTemplate); + const Unit* cur = + this->sourceUnits.codeUnitPtrAt(anyChars.currentToken().pos.begin + 1); + const Unit* end; + if (anyChars.currentToken().type == TokenKind::TemplateHead) { + // Of the form |`...${| or |}...${| + end = + this->sourceUnits.codeUnitPtrAt(anyChars.currentToken().pos.end - 2); + } else { + // NO_SUBS_TEMPLATE is of the form |`...`| or |}...`| + end = + this->sourceUnits.codeUnitPtrAt(anyChars.currentToken().pos.end - 1); + } + + // |charBuffer| should be empty here, but we may as well code defensively. + MOZ_ASSERT(this->charBuffer.length() == 0); + this->charBuffer.clear(); + + // Template literals normalize only '\r' and "\r\n" to '\n'; Unicode + // separators don't need special handling. + // https://tc39.github.io/ecma262/#sec-static-semantics-tv-and-trv + if (!FillCharBufferFromSourceNormalizingAsciiLineBreaks(this->charBuffer, + cur, end)) { + return TaggedParserAtomIndex::null(); + } + + return drainCharBufferIntoAtom(); + } +}; + +template +class TokenStreamChars; + +template +class TokenStreamChars + : public GeneralTokenStreamChars { + using CharsBase = TokenStreamCharsBase; + using SpecializedCharsBase = SpecializedTokenStreamCharsBase; + using GeneralCharsBase = GeneralTokenStreamChars; + using Self = TokenStreamChars; + + using GeneralCharsBase::asSpecific; + + using typename GeneralCharsBase::TokenStreamSpecific; + + protected: + using CharsBase::matchLineTerminator; + using GeneralCharsBase::anyCharsAccess; + using GeneralCharsBase::getCodeUnit; + using SpecializedCharsBase::infallibleGetNonAsciiCodePointDontNormalize; + using TokenStreamCharsShared::isAsciiCodePoint; + // Deliberately don't |using| |sourceUnits| because of bug 1472569. :-( + using GeneralCharsBase::ungetCodeUnit; + using GeneralCharsBase::updateLineInfoForEOL; + + protected: + using GeneralCharsBase::GeneralCharsBase; + + /** + * Given the non-ASCII |lead| code unit just consumed, consume and return a + * complete non-ASCII code point. Line/column updates are not performed, + * and line breaks are returned as-is without normalization. + */ + [[nodiscard]] bool getNonAsciiCodePointDontNormalize(char16_t lead, + char32_t* codePoint) { + // There are no encoding errors in 16-bit JS, so implement this so that + // the compiler knows it, too. + *codePoint = infallibleGetNonAsciiCodePointDontNormalize(lead); + return true; + } + + /** + * Given a just-consumed non-ASCII code unit |lead| (which may also be a + * full code point, for UTF-16), consume a full code point or + * LineTerminatorSequence (normalizing it to '\n') and store it in + * |*codePoint|. Return true on success, otherwise return false and leave + * |*codePoint| undefined on failure. + * + * If a LineTerminatorSequence was consumed, also update line/column info. + * + * This may change the current |sourceUnits| offset. + */ + [[nodiscard]] bool getNonAsciiCodePoint(int32_t lead, char32_t* codePoint); +}; + +template +class TokenStreamChars + : public GeneralTokenStreamChars { + using CharsBase = TokenStreamCharsBase; + using SpecializedCharsBase = + SpecializedTokenStreamCharsBase; + using GeneralCharsBase = + GeneralTokenStreamChars; + using Self = TokenStreamChars; + + using typename SpecializedCharsBase::SourceUnitsEnd; + using typename SpecializedCharsBase::SourceUnitsIterator; + + protected: + using GeneralCharsBase::anyCharsAccess; + using GeneralCharsBase::computeLineAndColumn; + using GeneralCharsBase::fillExceptingContext; + using GeneralCharsBase::internalComputeLineOfContext; + using TokenStreamCharsShared::isAsciiCodePoint; + // Deliberately don't |using| |sourceUnits| because of bug 1472569. :-( + using GeneralCharsBase::updateLineInfoForEOL; + + private: + static char toHexChar(uint8_t nibble) { + MOZ_ASSERT(nibble < 16); + return "0123456789ABCDEF"[nibble]; + } + + static void byteToString(uint8_t n, char* str) { + str[0] = '0'; + str[1] = 'x'; + str[2] = toHexChar(n >> 4); + str[3] = toHexChar(n & 0xF); + } + + static void byteToTerminatedString(uint8_t n, char* str) { + byteToString(n, str); + str[4] = '\0'; + } + + /** + * Report a UTF-8 encoding-related error for a code point starting AT THE + * CURRENT OFFSET. + * + * |relevantUnits| indicates how many code units from the current offset + * are potentially relevant to the reported error, such that they may be + * included in the error message. For example, if at the current offset we + * have + * + * 0b1111'1111 ... + * + * a code unit never allowed in UTF-8, then |relevantUnits| might be 1 + * because only that unit is relevant. Or if we have + * + * 0b1111'0111 0b1011'0101 0b0000'0000 ... + * + * where the first two code units are a valid prefix to a four-unit code + * point but the third unit *isn't* a valid trailing code unit, then + * |relevantUnits| might be 3. + */ + MOZ_COLD void internalEncodingError(uint8_t relevantUnits, + unsigned errorNumber, ...); + + // Don't use |internalEncodingError|! Use one of the elaborated functions + // that calls it, below -- all of which should be used to indicate an error + // in a code point starting AT THE CURRENT OFFSET as with + // |internalEncodingError|. + + /** Report an error for an invalid lead code unit |lead|. */ + MOZ_COLD void badLeadUnit(mozilla::Utf8Unit lead); + + /** + * Report an error when there aren't enough code units remaining to + * constitute a full code point after |lead|: only |remaining| code units + * were available for a code point starting with |lead|, when at least + * |required| code units were required. + */ + MOZ_COLD void notEnoughUnits(mozilla::Utf8Unit lead, uint8_t remaining, + uint8_t required); + + /** + * Report an error for a bad trailing UTF-8 code unit, where the bad + * trailing unit was the last of |unitsObserved| units examined from the + * current offset. + */ + MOZ_COLD void badTrailingUnit(uint8_t unitsObserved); + + // Helper used for both |badCodePoint| and |notShortestForm| for code units + // that have all the requisite high bits set/unset in a manner that *could* + // encode a valid code point, but the remaining bits encoding its actual + // value do not define a permitted value. + MOZ_COLD void badStructurallyValidCodePoint(char32_t codePoint, + uint8_t codePointLength, + const char* reason); + + /** + * Report an error for UTF-8 that encodes a UTF-16 surrogate or a number + * outside the Unicode range. + */ + MOZ_COLD void badCodePoint(char32_t codePoint, uint8_t codePointLength) { + MOZ_ASSERT(unicode::IsSurrogate(codePoint) || + codePoint > unicode::NonBMPMax); + + badStructurallyValidCodePoint(codePoint, codePointLength, + unicode::IsSurrogate(codePoint) + ? "it's a UTF-16 surrogate" + : "the maximum code point is U+10FFFF"); + } + + /** + * Report an error for UTF-8 that encodes a code point not in its shortest + * form. + */ + MOZ_COLD void notShortestForm(char32_t codePoint, uint8_t codePointLength) { + MOZ_ASSERT(!unicode::IsSurrogate(codePoint)); + MOZ_ASSERT(codePoint <= unicode::NonBMPMax); + + badStructurallyValidCodePoint( + codePoint, codePointLength, + "it wasn't encoded in shortest possible form"); + } + + protected: + using GeneralCharsBase::GeneralCharsBase; + + /** + * Given the non-ASCII |lead| code unit just consumed, consume the rest of + * a non-ASCII code point. The code point is not normalized: on success + * |*codePoint| may be U+2028 LINE SEPARATOR or U+2029 PARAGRAPH SEPARATOR. + * + * Report an error if an invalid code point is encountered. + */ + [[nodiscard]] bool getNonAsciiCodePointDontNormalize(mozilla::Utf8Unit lead, + char32_t* codePoint); + + /** + * Given a just-consumed non-ASCII code unit |lead|, consume a full code + * point or LineTerminatorSequence (normalizing it to '\n') and store it in + * |*codePoint|. Return true on success, otherwise return false and leave + * |*codePoint| undefined on failure. + * + * If a LineTerminatorSequence was consumed, also update line/column info. + * + * This function will change the current |sourceUnits| offset. + */ + [[nodiscard]] bool getNonAsciiCodePoint(int32_t lead, char32_t* codePoint); +}; + +// TokenStream is the lexical scanner for JavaScript source text. +// +// It takes a buffer of Unit code units (currently only char16_t encoding +// UTF-16, but we're adding either UTF-8 or Latin-1 single-byte text soon) and +// linearly scans it into |Token|s. +// +// Internally the class uses a four element circular buffer |tokens| of +// |Token|s. As an index for |tokens|, the member |cursor_| points to the +// current token. Calls to getToken() increase |cursor_| by one and return the +// new current token. If a TokenStream was just created, the current token is +// uninitialized. It's therefore important that one of the first four member +// functions listed below is called first. The circular buffer lets us go back +// up to two tokens from the last scanned token. Internally, the relative +// number of backward steps that were taken (via ungetToken()) after the last +// token was scanned is stored in |lookahead|. +// +// The following table lists in which situations it is safe to call each listed +// function. No checks are made by the functions in non-debug builds. +// +// Function Name | Precondition; changes to |lookahead| +// ------------------+--------------------------------------------------------- +// getToken | none; if |lookahead > 0| then |lookahead--| +// peekToken | none; if |lookahead == 0| then |lookahead == 1| +// peekTokenSameLine | none; if |lookahead == 0| then |lookahead == 1| +// matchToken | none; if |lookahead > 0| and the match succeeds then +// | |lookahead--| +// consumeKnownToken | none; if |lookahead > 0| then |lookahead--| +// ungetToken | 0 <= |lookahead| <= |maxLookahead - 1|; |lookahead++| +// +// The behavior of the token scanning process (see getTokenInternal()) can be +// modified by calling one of the first four above listed member functions with +// an optional argument of type Modifier. However, the modifier will be +// ignored unless |lookahead == 0| holds. Due to constraints of the grammar, +// this turns out not to be a problem in practice. See the +// mozilla.dev.tech.js-engine.internals thread entitled 'Bug in the scanner?' +// for more details: +// https://groups.google.com/forum/?fromgroups=#!topic/mozilla.dev.tech.js-engine.internals/2JLH5jRcr7E). +// +// The method seek() allows rescanning from a previously visited location of +// the buffer, initially computed by constructing a Position local variable. +// +template +class MOZ_STACK_CLASS TokenStreamSpecific + : public TokenStreamChars, + public TokenStreamShared, + public ErrorReporter { + public: + using CharsBase = TokenStreamCharsBase; + using SpecializedCharsBase = SpecializedTokenStreamCharsBase; + using GeneralCharsBase = GeneralTokenStreamChars; + using SpecializedChars = TokenStreamChars; + + using Position = TokenStreamPosition; + + // Anything inherited through a base class whose type depends upon this + // class's template parameters can only be accessed through a dependent + // name: prefixed with |this|, by explicit qualification, and so on. (This + // is so that references to inherited fields are statically distinguishable + // from references to names outside of the class.) This is tedious and + // onerous. + // + // As an alternative, we directly add every one of these functions to this + // class, using explicit qualification to address the dependent-name + // problem. |this| or other qualification is no longer necessary -- at + // cost of this ever-changing laundry list of |using|s. So it goes. + public: + using GeneralCharsBase::anyCharsAccess; + using GeneralCharsBase::computeLineAndColumn; + using TokenStreamCharsShared::adoptState; + + private: + using typename CharsBase::SourceUnits; + + private: + using CharsBase::atomizeSourceChars; + using GeneralCharsBase::badToken; + // Deliberately don't |using| |charBuffer| because of bug 1472569. :-( + using CharsBase::consumeKnownCodeUnit; + using CharsBase::matchCodeUnit; + using CharsBase::matchLineTerminator; + using CharsBase::peekCodeUnit; + using GeneralCharsBase::computeColumn; + using GeneralCharsBase::fillExceptingContext; + using GeneralCharsBase::getCodeUnit; + using GeneralCharsBase::getFullAsciiCodePoint; + using GeneralCharsBase::internalComputeLineOfContext; + using GeneralCharsBase::matchUnicodeEscapeIdent; + using GeneralCharsBase::matchUnicodeEscapeIdStart; + using GeneralCharsBase::newAtomToken; + using GeneralCharsBase::newBigIntToken; + using GeneralCharsBase::newNameToken; + using GeneralCharsBase::newNumberToken; + using GeneralCharsBase::newPrivateNameToken; + using GeneralCharsBase::newRegExpToken; + using GeneralCharsBase::newSimpleToken; + using SpecializedChars::getNonAsciiCodePoint; + using SpecializedChars::getNonAsciiCodePointDontNormalize; + using TokenStreamCharsShared::copyCharBufferTo; + using TokenStreamCharsShared::drainCharBufferIntoAtom; + using TokenStreamCharsShared::isAsciiCodePoint; + // Deliberately don't |using| |sourceUnits| because of bug 1472569. :-( + using CharsBase::toUnit; + using GeneralCharsBase::ungetCodeUnit; + using GeneralCharsBase::updateLineInfoForEOL; + + template + friend class TokenStreamPosition; + + public: + TokenStreamSpecific(FrontendContext* fc, ParserAtomsTable* parserAtoms, + const JS::ReadOnlyCompileOptions& options, + const Unit* units, size_t length); + + /** + * Get the next code point, converting LineTerminatorSequences to '\n' and + * updating internal line-counter state if needed. Return true on success. + * Return false on failure. + */ + [[nodiscard]] MOZ_ALWAYS_INLINE bool getCodePoint() { + int32_t unit = getCodeUnit(); + if (MOZ_UNLIKELY(unit == EOF)) { + MOZ_ASSERT(anyCharsAccess().flags.isEOF, + "flags.isEOF should have been set by getCodeUnit()"); + return true; + } + + if (isAsciiCodePoint(unit)) { + return getFullAsciiCodePoint(unit); + } + + char32_t cp; + return getNonAsciiCodePoint(unit, &cp); + } + + // If there is an invalid escape in a template, report it and return false, + // otherwise return true. + bool checkForInvalidTemplateEscapeError() { + if (anyCharsAccess().invalidTemplateEscapeType == InvalidEscapeType::None) { + return true; + } + + reportInvalidEscapeError(anyCharsAccess().invalidTemplateEscapeOffset, + anyCharsAccess().invalidTemplateEscapeType); + return false; + } + + public: + // Implement ErrorReporter. + + bool isOnThisLine(size_t offset, uint32_t lineNum, + bool* onThisLine) const final { + return anyCharsAccess().srcCoords.isOnThisLine(offset, lineNum, onThisLine); + } + + uint32_t lineAt(size_t offset) const final { + const auto& anyChars = anyCharsAccess(); + auto lineToken = anyChars.lineToken(offset); + return anyChars.lineNumber(lineToken); + } + + uint32_t columnAt(size_t offset) const final { + return computeColumn(anyCharsAccess().lineToken(offset), offset); + } + + private: + // Implement ErrorReportMixin. + + FrontendContext* getContext() const override { + return anyCharsAccess().context(); + } + + [[nodiscard]] bool strictMode() const override { + return anyCharsAccess().strictMode(); + } + + public: + // Implement ErrorReportMixin. + + const JS::ReadOnlyCompileOptions& options() const final { + return anyCharsAccess().options(); + } + + [[nodiscard]] bool computeErrorMetadata( + ErrorMetadata* err, const ErrorOffset& errorOffset) const override; + + private: + void reportInvalidEscapeError(uint32_t offset, InvalidEscapeType type) { + switch (type) { + case InvalidEscapeType::None: + MOZ_ASSERT_UNREACHABLE("unexpected InvalidEscapeType"); + return; + case InvalidEscapeType::Hexadecimal: + errorAt(offset, JSMSG_MALFORMED_ESCAPE, "hexadecimal"); + return; + case InvalidEscapeType::Unicode: + errorAt(offset, JSMSG_MALFORMED_ESCAPE, "Unicode"); + return; + case InvalidEscapeType::UnicodeOverflow: + errorAt(offset, JSMSG_UNICODE_OVERFLOW, "escape sequence"); + return; + case InvalidEscapeType::Octal: + errorAt(offset, JSMSG_DEPRECATED_OCTAL_ESCAPE); + return; + case InvalidEscapeType::EightOrNine: + errorAt(offset, JSMSG_DEPRECATED_EIGHT_OR_NINE_ESCAPE); + return; + } + } + + void reportIllegalCharacter(int32_t cp); + + [[nodiscard]] bool putIdentInCharBuffer(const Unit* identStart); + + using IsIntegerUnit = bool (*)(int32_t); + [[nodiscard]] MOZ_ALWAYS_INLINE bool matchInteger(IsIntegerUnit isIntegerUnit, + int32_t* nextUnit); + [[nodiscard]] MOZ_ALWAYS_INLINE bool matchIntegerAfterFirstDigit( + IsIntegerUnit isIntegerUnit, int32_t* nextUnit); + + /** + * Tokenize a decimal number that begins at |numStart| into the provided + * token. + * + * |unit| must be one of these values: + * + * 1. The first decimal digit in the integral part of a decimal number + * not starting with '.', e.g. '1' for "17", '0' for "0.14", or + * '8' for "8.675309e6". + * + * In this case, the next |getCodeUnit()| must return the code unit after + * |unit| in the overall number. + * + * 2. The '.' in a "."-prefixed decimal number, e.g. ".17" or ".1e3". + * + * In this case, the next |getCodeUnit()| must return the code unit + * *after* the '.'. + * + * 3. (Non-strict mode code only) The first non-ASCII-digit unit for a + * "noctal" number that begins with a '0' but contains a non-octal digit + * in its integer part so is interpreted as decimal, e.g. '.' in "09.28" + * or EOF for "0386" or '+' in "09+7" (three separate tokens). + * + * In this case, the next |getCodeUnit()| returns the code unit after + * |unit|: '2', 'EOF', or '7' in the examples above. + * + * This interface is super-hairy and horribly stateful. Unfortunately, its + * hair merely reflects the intricacy of ECMAScript numeric literal syntax. + * And incredibly, it *improves* on the goto-based horror that predated it. + */ + [[nodiscard]] bool decimalNumber(int32_t unit, TokenStart start, + const Unit* numStart, Modifier modifier, + TokenKind* out); + + /** Tokenize a regular expression literal beginning at |start|. */ + [[nodiscard]] bool regexpLiteral(TokenStart start, TokenKind* out); + + /** + * Slurp characters between |start| and sourceUnits.current() into + * charBuffer, to later parse into a bigint. + */ + [[nodiscard]] bool bigIntLiteral(TokenStart start, Modifier modifier, + TokenKind* out); + + public: + // Advance to the next token. If the token stream encountered an error, + // return false. Otherwise return true and store the token kind in |*ttp|. + [[nodiscard]] bool getToken(TokenKind* ttp, Modifier modifier = SlashIsDiv) { + // Check for a pushed-back token resulting from mismatching lookahead. + TokenStreamAnyChars& anyChars = anyCharsAccess(); + if (anyChars.lookahead != 0) { + MOZ_ASSERT(!anyChars.flags.hadError); + anyChars.lookahead--; + anyChars.advanceCursor(); + TokenKind tt = anyChars.currentToken().type; + MOZ_ASSERT(tt != TokenKind::Eol); + verifyConsistentModifier(modifier, anyChars.currentToken()); + *ttp = tt; + return true; + } + + return getTokenInternal(ttp, modifier); + } + + [[nodiscard]] bool peekToken(TokenKind* ttp, Modifier modifier = SlashIsDiv) { + TokenStreamAnyChars& anyChars = anyCharsAccess(); + if (anyChars.lookahead > 0) { + MOZ_ASSERT(!anyChars.flags.hadError); + verifyConsistentModifier(modifier, anyChars.nextToken()); + *ttp = anyChars.nextToken().type; + return true; + } + if (!getTokenInternal(ttp, modifier)) { + return false; + } + anyChars.ungetToken(); + return true; + } + + [[nodiscard]] bool peekTokenPos(TokenPos* posp, + Modifier modifier = SlashIsDiv) { + TokenStreamAnyChars& anyChars = anyCharsAccess(); + if (anyChars.lookahead == 0) { + TokenKind tt; + if (!getTokenInternal(&tt, modifier)) { + return false; + } + anyChars.ungetToken(); + MOZ_ASSERT(anyChars.hasLookahead()); + } else { + MOZ_ASSERT(!anyChars.flags.hadError); + verifyConsistentModifier(modifier, anyChars.nextToken()); + } + *posp = anyChars.nextToken().pos; + return true; + } + + [[nodiscard]] bool peekOffset(uint32_t* offset, + Modifier modifier = SlashIsDiv) { + TokenPos pos; + if (!peekTokenPos(&pos, modifier)) { + return false; + } + *offset = pos.begin; + return true; + } + + // This is like peekToken(), with one exception: if there is an EOL + // between the end of the current token and the start of the next token, it + // return true and store Eol in |*ttp|. In that case, no token with + // Eol is actually created, just a Eol TokenKind is returned, and + // currentToken() shouldn't be consulted. (This is the only place Eol + // is produced.) + [[nodiscard]] MOZ_ALWAYS_INLINE bool peekTokenSameLine( + TokenKind* ttp, Modifier modifier = SlashIsDiv) { + TokenStreamAnyChars& anyChars = anyCharsAccess(); + const Token& curr = anyChars.currentToken(); + + // If lookahead != 0, we have scanned ahead at least one token, and + // |lineno| is the line that the furthest-scanned token ends on. If + // it's the same as the line that the current token ends on, that's a + // stronger condition than what we are looking for, and we don't need + // to return Eol. + if (anyChars.lookahead != 0) { + bool onThisLine; + if (!anyChars.srcCoords.isOnThisLine(curr.pos.end, anyChars.lineno, + &onThisLine)) { + error(JSMSG_OUT_OF_MEMORY); + return false; + } + + if (onThisLine) { + MOZ_ASSERT(!anyChars.flags.hadError); + verifyConsistentModifier(modifier, anyChars.nextToken()); + *ttp = anyChars.nextToken().type; + return true; + } + } + + // The above check misses two cases where we don't have to return + // Eol. + // - The next token starts on the same line, but is a multi-line token. + // - The next token starts on the same line, but lookahead==2 and there + // is a newline between the next token and the one after that. + // The following test is somewhat expensive but gets these cases (and + // all others) right. + TokenKind tmp; + if (!getToken(&tmp, modifier)) { + return false; + } + + const Token& next = anyChars.currentToken(); + anyChars.ungetToken(); + + // Careful, |next| points to an initialized-but-not-allocated Token! + // This is safe because we don't modify token data below. + + auto currentEndToken = anyChars.lineToken(curr.pos.end); + auto nextBeginToken = anyChars.lineToken(next.pos.begin); + + *ttp = + currentEndToken.isSameLine(nextBeginToken) ? next.type : TokenKind::Eol; + return true; + } + + // Get the next token from the stream if its kind is |tt|. + [[nodiscard]] bool matchToken(bool* matchedp, TokenKind tt, + Modifier modifier = SlashIsDiv) { + TokenKind token; + if (!getToken(&token, modifier)) { + return false; + } + if (token == tt) { + *matchedp = true; + } else { + anyCharsAccess().ungetToken(); + *matchedp = false; + } + return true; + } + + void consumeKnownToken(TokenKind tt, Modifier modifier = SlashIsDiv) { + bool matched; + MOZ_ASSERT(anyCharsAccess().hasLookahead()); + MOZ_ALWAYS_TRUE(matchToken(&matched, tt, modifier)); + MOZ_ALWAYS_TRUE(matched); + } + + [[nodiscard]] bool nextTokenEndsExpr(bool* endsExpr) { + TokenKind tt; + if (!peekToken(&tt)) { + return false; + } + + *endsExpr = anyCharsAccess().isExprEnding[size_t(tt)]; + if (*endsExpr) { + // If the next token ends an overall Expression, we'll parse this + // Expression without ever invoking Parser::orExpr(). But we need that + // function's DEBUG-only side effect of marking this token as safe to get + // with SlashIsRegExp, so we have to do it manually here. + anyCharsAccess().allowGettingNextTokenWithSlashIsRegExp(); + } + return true; + } + + [[nodiscard]] bool advance(size_t position); + + void seekTo(const Position& pos); + [[nodiscard]] bool seekTo(const Position& pos, + const TokenStreamAnyChars& other); + + void rewind(const Position& pos) { + MOZ_ASSERT(pos.buf <= this->sourceUnits.addressOfNextCodeUnit(), + "should be rewinding here"); + seekTo(pos); + } + + [[nodiscard]] bool rewind(const Position& pos, + const TokenStreamAnyChars& other) { + MOZ_ASSERT(pos.buf <= this->sourceUnits.addressOfNextCodeUnit(), + "should be rewinding here"); + return seekTo(pos, other); + } + + void fastForward(const Position& pos) { + MOZ_ASSERT(this->sourceUnits.addressOfNextCodeUnit() <= pos.buf, + "should be moving forward here"); + seekTo(pos); + } + + [[nodiscard]] bool fastForward(const Position& pos, + const TokenStreamAnyChars& other) { + MOZ_ASSERT(this->sourceUnits.addressOfNextCodeUnit() <= pos.buf, + "should be moving forward here"); + return seekTo(pos, other); + } + + const Unit* codeUnitPtrAt(size_t offset) const { + return this->sourceUnits.codeUnitPtrAt(offset); + } + + [[nodiscard]] bool identifierName(TokenStart start, const Unit* identStart, + IdentifierEscapes escaping, + Modifier modifier, + NameVisibility visibility, TokenKind* out); + + [[nodiscard]] bool matchIdentifierStart(IdentifierEscapes* sawEscape); + + [[nodiscard]] bool getTokenInternal(TokenKind* const ttp, + const Modifier modifier); + + [[nodiscard]] bool getStringOrTemplateToken(char untilChar, Modifier modifier, + TokenKind* out); + + // Parse a TemplateMiddle or TemplateTail token (one of the string-like parts + // of a template string) after already consuming the leading `RightCurly`. + // (The spec says the `}` is the first character of the TemplateMiddle/ + // TemplateTail, but we treat it as a separate token because that's much + // easier to implement in both TokenStream and the parser.) + // + // This consumes a token and sets the current token, like `getToken()`. It + // doesn't take a Modifier because there's no risk of encountering a division + // operator or RegExp literal. + // + // On success, `*ttp` is either `TokenKind::TemplateHead` (if we got a + // TemplateMiddle token) or `TokenKind::NoSubsTemplate` (if we got a + // TemplateTail). That may seem strange; there are four different template + // token types in the spec, but we only use two. We use `TemplateHead` for + // TemplateMiddle because both end with `...${`, and `NoSubsTemplate` for + // TemplateTail because both contain the end of the template, including the + // closing quote mark. They're not treated differently, either in the parser + // or in the tokenizer. + [[nodiscard]] bool getTemplateToken(TokenKind* ttp) { + MOZ_ASSERT(anyCharsAccess().currentToken().type == TokenKind::RightCurly); + return getStringOrTemplateToken('`', SlashIsInvalid, ttp); + } + + [[nodiscard]] bool getDirectives(bool isMultiline, bool shouldWarnDeprecated); + [[nodiscard]] bool getDirective( + bool isMultiline, bool shouldWarnDeprecated, const char* directive, + uint8_t directiveLength, const char* errorMsgPragma, + UniquePtr* destination); + [[nodiscard]] bool getDisplayURL(bool isMultiline, bool shouldWarnDeprecated); + [[nodiscard]] bool getSourceMappingURL(bool isMultiline, + bool shouldWarnDeprecated); +}; + +// It's preferable to define this in TokenStream.cpp, but its template-ness +// means we'd then have to *instantiate* this constructor for all possible +// (Unit, AnyCharsAccess) pairs -- and that gets super-messy as AnyCharsAccess +// *itself* is templated. This symbol really isn't that huge compared to some +// defined inline in TokenStreamSpecific, so just rely on the linker commoning +// stuff up. +template +template +inline TokenStreamPosition::TokenStreamPosition( + TokenStreamSpecific& tokenStream) + : currentToken(tokenStream.anyCharsAccess().currentToken()) { + TokenStreamAnyChars& anyChars = tokenStream.anyCharsAccess(); + + buf = + tokenStream.sourceUnits.addressOfNextCodeUnit(/* allowPoisoned = */ true); + flags = anyChars.flags; + lineno = anyChars.lineno; + linebase = anyChars.linebase; + prevLinebase = anyChars.prevLinebase; + lookahead = anyChars.lookahead; + currentToken = anyChars.currentToken(); + for (unsigned i = 0; i < anyChars.lookahead; i++) { + lookaheadTokens[i] = anyChars.tokens[anyChars.aheadCursor(1 + i)]; + } +} + +class TokenStreamAnyCharsAccess { + public: + template + static inline TokenStreamAnyChars& anyChars(TokenStreamSpecific* tss); + + template + static inline const TokenStreamAnyChars& anyChars( + const TokenStreamSpecific* tss); +}; + +class MOZ_STACK_CLASS TokenStream + : public TokenStreamAnyChars, + public TokenStreamSpecific { + using Unit = char16_t; + + public: + TokenStream(FrontendContext* fc, ParserAtomsTable* parserAtoms, + const JS::ReadOnlyCompileOptions& options, const Unit* units, + size_t length, StrictModeGetter* smg) + : TokenStreamAnyChars(fc, options, smg), + TokenStreamSpecific( + fc, parserAtoms, options, units, length) {} +}; + +class MOZ_STACK_CLASS DummyTokenStream final : public TokenStream { + public: + DummyTokenStream(FrontendContext* fc, + const JS::ReadOnlyCompileOptions& options) + : TokenStream(fc, nullptr, options, nullptr, 0, nullptr) {} +}; + +template +/* static */ inline TokenStreamAnyChars& TokenStreamAnyCharsAccess::anyChars( + TokenStreamSpecific* tss) { + auto* ts = static_cast(tss); + return *static_cast(ts); +} + +template +/* static */ inline const TokenStreamAnyChars& +TokenStreamAnyCharsAccess::anyChars(const TokenStreamSpecific* tss) { + const auto* ts = static_cast(tss); + return *static_cast(ts); +} + +extern const char* TokenKindToDesc(TokenKind tt); + +} // namespace frontend +} // namespace js + +#ifdef DEBUG +extern const char* TokenKindToString(js::frontend::TokenKind tt); +#endif + +#endif /* frontend_TokenStream_h */ diff --git a/js/src/frontend/TryEmitter.cpp b/js/src/frontend/TryEmitter.cpp new file mode 100644 index 0000000000..63c8a6723e --- /dev/null +++ b/js/src/frontend/TryEmitter.cpp @@ -0,0 +1,317 @@ +/* -*- 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/TryEmitter.h" + +#include "mozilla/Assertions.h" // MOZ_ASSERT + +#include "frontend/BytecodeEmitter.h" // BytecodeEmitter +#include "frontend/IfEmitter.h" // BytecodeEmitter +#include "frontend/SharedContext.h" // StatementKind +#include "vm/Opcodes.h" // JSOp + +using namespace js; +using namespace js::frontend; + +using mozilla::Maybe; + +TryEmitter::TryEmitter(BytecodeEmitter* bce, Kind kind, ControlKind controlKind) + : bce_(bce), + kind_(kind), + controlKind_(controlKind), + depth_(0), + tryOpOffset_(0) +#ifdef DEBUG + , + state_(State::Start) +#endif +{ + if (controlKind_ == ControlKind::Syntactic) { + controlInfo_.emplace( + bce_, hasFinally() ? StatementKind::Finally : StatementKind::Try); + } +} + +bool TryEmitter::emitTry() { + MOZ_ASSERT(state_ == State::Start); + + // Since an exception can be thrown at any place inside the try block, + // we need to restore the stack and the scope chain before we transfer + // the control to the exception handler. + // + // For that we store in a try note associated with the catch or + // finally block the stack depth upon the try entry. The interpreter + // uses this depth to properly unwind the stack and the scope chain. + depth_ = bce_->bytecodeSection().stackDepth(); + + tryOpOffset_ = bce_->bytecodeSection().offset(); + if (!bce_->emit1(JSOp::Try)) { + return false; + } + +#ifdef DEBUG + state_ = State::Try; +#endif + return true; +} + +bool TryEmitter::emitJumpToFinallyWithFallthrough() { + uint32_t stackDepthForNextBlock = bce_->bytecodeSection().stackDepth(); + + // The fallthrough continuation is special-cased with index 0. + uint32_t idx = TryFinallyControl::SpecialContinuations::Fallthrough; + if (!bce_->emitJumpToFinally(&controlInfo_->finallyJumps_, idx)) { + return false; + } + + // Reset the stack depth for the following catch or finally block. + bce_->bytecodeSection().setStackDepth(stackDepthForNextBlock); + return true; +} + +bool TryEmitter::emitTryEnd() { + MOZ_ASSERT(state_ == State::Try); + MOZ_ASSERT(depth_ == bce_->bytecodeSection().stackDepth()); + + if (hasFinally() && controlInfo_) { + if (!emitJumpToFinallyWithFallthrough()) { + return false; + } + } else { + // Emit jump over catch + if (!bce_->emitJump(JSOp::Goto, &catchAndFinallyJump_)) { + return false; + } + } + + if (!bce_->emitJumpTarget(&tryEnd_)) { + return false; + } + + return true; +} + +bool TryEmitter::emitCatch() { + MOZ_ASSERT(state_ == State::Try); + if (!emitTryEnd()) { + return false; + } + + MOZ_ASSERT(bce_->bytecodeSection().stackDepth() == depth_); + + if (shouldUpdateRval()) { + // Clear the frame's return value that might have been set by the + // try block: + // + // eval("try { 1; throw 2 } catch(e) {}"); // undefined, not 1 + if (!bce_->emit1(JSOp::Undefined)) { + return false; + } + if (!bce_->emit1(JSOp::SetRval)) { + return false; + } + } + + if (!bce_->emit1(JSOp::Exception)) { + return false; + } + +#ifdef DEBUG + state_ = State::Catch; +#endif + return true; +} + +bool TryEmitter::emitCatchEnd() { + MOZ_ASSERT(state_ == State::Catch); + + if (!controlInfo_) { + return true; + } + + // Jump to , if required. + if (hasFinally()) { + if (!emitJumpToFinallyWithFallthrough()) { + return false; + } + } + + return true; +} + +bool TryEmitter::emitFinally( + const Maybe& finallyPos /* = Nothing() */) { + // If we are using controlInfo_ (i.e., emitting a syntactic try + // blocks), we must have specified up front if there will be a finally + // close. For internal non-syntactic try blocks, like those emitted for + // yield* and IteratorClose inside for-of loops, we can emitFinally even + // without specifying up front, since the internal non-syntactic try + // blocks emit no GOSUBs. + if (!controlInfo_) { + if (kind_ == Kind::TryCatch) { + kind_ = Kind::TryCatchFinally; + } + } else { + MOZ_ASSERT(hasFinally()); + } + + if (!hasCatch()) { + MOZ_ASSERT(state_ == State::Try); + if (!emitTryEnd()) { + return false; + } + } else { + MOZ_ASSERT(state_ == State::Catch); + if (!emitCatchEnd()) { + return false; + } + } + + MOZ_ASSERT(bce_->bytecodeSection().stackDepth() == depth_); + + // Upon entry to the finally, there are two additional values on the stack: + // a boolean value to indicate whether we're throwing an exception, and + // either that exception (if we're throwing) or a resume index to which we + // will return (if we're not throwing). + bce_->bytecodeSection().setStackDepth(depth_ + 2); + + if (!bce_->emitJumpTarget(&finallyStart_)) { + return false; + } + + if (controlInfo_) { + // Fix up the jumps to the finally code. + bce_->patchJumpsToTarget(controlInfo_->finallyJumps_, finallyStart_); + + // Indicate that we're emitting a subroutine body. + controlInfo_->setEmittingSubroutine(); + } + if (finallyPos) { + if (!bce_->updateSourceCoordNotes(finallyPos.value())) { + return false; + } + } + if (!bce_->emit1(JSOp::Finally)) { + return false; + } + + if (shouldUpdateRval()) { + if (!bce_->emit1(JSOp::GetRval)) { + return false; + } + + // Clear the frame's return value to make break/continue return + // correct value even if there's no other statement before them: + // + // eval("x: try { 1 } finally { break x; }"); // undefined, not 1 + if (!bce_->emit1(JSOp::Undefined)) { + return false; + } + if (!bce_->emit1(JSOp::SetRval)) { + return false; + } + } + +#ifdef DEBUG + state_ = State::Finally; +#endif + return true; +} + +bool TryEmitter::emitFinallyEnd() { + MOZ_ASSERT(state_ == State::Finally); + + if (shouldUpdateRval()) { + if (!bce_->emit1(JSOp::SetRval)) { + return false; + } + } + + InternalIfEmitter ifThrowing(bce_); + if (!ifThrowing.emitThenElse()) { + return false; + } + + if (!bce_->emit1(JSOp::Throw)) { + return false; + } + + if (!ifThrowing.emitElse()) { + return false; + } + + if (controlInfo_ && !controlInfo_->continuations_.empty()) { + if (!controlInfo_->emitContinuations(bce_)) { + return false; + } + } else { + // If there are no non-local jumps, then the only possible jump target + // is the code immediately following this finally block. Instead of + // emitting a tableswitch, we can simply pop the continuation index + // and fall through. + if (!bce_->emit1(JSOp::Pop)) { + return false; + } + } + + if (!ifThrowing.emitEnd()) { + return false; + } + + bce_->hasTryFinally = true; + return true; +} + +bool TryEmitter::emitEnd() { + if (!hasFinally()) { + MOZ_ASSERT(state_ == State::Catch); + if (!emitCatchEnd()) { + return false; + } + } else { + MOZ_ASSERT(state_ == State::Finally); + if (!emitFinallyEnd()) { + return false; + } + } + + MOZ_ASSERT(bce_->bytecodeSection().stackDepth() == depth_); + + if (catchAndFinallyJump_.offset.valid()) { + // Fix up the end-of-try/catch jumps to come here. + if (!bce_->emitJumpTargetAndPatch(catchAndFinallyJump_)) { + return false; + } + } + + // Add the try note last, to let post-order give us the right ordering + // (first to last for a given nesting level, inner to outer by level). + if (hasCatch()) { + if (!bce_->addTryNote(TryNoteKind::Catch, depth_, offsetAfterTryOp(), + tryEnd_.offset)) { + return false; + } + } + + // If we've got a finally, mark try+catch region with additional + // trynote to catch exceptions (re)thrown from a catch block or + // for the try{}finally{} case. + if (hasFinally()) { + if (!bce_->addTryNote(TryNoteKind::Finally, depth_, offsetAfterTryOp(), + finallyStart_.offset)) { + return false; + } + } + +#ifdef DEBUG + state_ = State::End; +#endif + return true; +} + +bool TryEmitter::shouldUpdateRval() const { + return controlKind_ == ControlKind::Syntactic && !bce_->sc->noScriptRval(); +} diff --git a/js/src/frontend/TryEmitter.h b/js/src/frontend/TryEmitter.h new file mode 100644 index 0000000000..f32d477889 --- /dev/null +++ b/js/src/frontend/TryEmitter.h @@ -0,0 +1,226 @@ +/* -*- 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/. */ + +#ifndef frontend_TryEmitter_h +#define frontend_TryEmitter_h + +#include "mozilla/Attributes.h" // MOZ_STACK_CLASS +#include "mozilla/Maybe.h" // mozilla::Maybe, mozilla::Nothing + +#include // uint32_t + +#include "frontend/BytecodeControlStructures.h" // TryFinallyControl +#include "frontend/BytecodeOffset.h" // BytecodeOffset +#include "frontend/JumpList.h" // JumpList, JumpTarget + +namespace js { +namespace frontend { + +struct BytecodeEmitter; + +// Class for emitting bytecode for blocks like try-catch-finally. +// +// Usage: (check for the return value is omitted for simplicity) +// +// `try { try_block } catch (ex) { catch_block }` +// TryEmitter tryCatch(this, TryEmitter::Kind::TryCatch, +// TryEmitter::ControlKind::Syntactic); +// tryCatch.emitTry(); +// emit(try_block); +// tryCatch.emitCatch(); +// emit(catch_block); // Pending exception is on stack +// tryCatch.emitEnd(); +// +// `try { try_block } finally { finally_block }` +// TryEmitter tryCatch(this, TryEmitter::Kind::TryFinally, +// TryEmitter::ControlKind::Syntactic); +// tryCatch.emitTry(); +// emit(try_block); +// // finally_pos: The "{" character's position in the source code text. +// tryCatch.emitFinally(Some(finally_pos)); +// emit(finally_block); +// tryCatch.emitEnd(); +// +// `try { try_block } catch (ex) {catch_block} finally { finally_block }` +// TryEmitter tryCatch(this, TryEmitter::Kind::TryCatchFinally, +// TryEmitter::ControlKind::Syntactic); +// tryCatch.emitTry(); +// emit(try_block); +// tryCatch.emitCatch(); +// emit(catch_block); +// tryCatch.emitFinally(Some(finally_pos)); +// emit(finally_block); +// tryCatch.emitEnd(); +// +class MOZ_STACK_CLASS TryEmitter { + public: + enum class Kind { TryCatch, TryCatchFinally, TryFinally }; + + // Syntactic try-catch-finally and internally used non-syntactic + // try-catch-finally behave differently for 2 points. + // + // The first one is whether TryFinallyControl is used or not. + // See the comment for `controlInfo_`. + // + // The second one is whether the catch and finally blocks handle the frame's + // return value. For syntactic try-catch-finally, the bytecode marked with + // "*" are emitted to clear return value with `undefined` before the catch + // block and the finally block, and also to save/restore the return value + // before/after the finally block. Note that these instructions are not + // emitted for noScriptRval scripts that don't track the return value. + // + // JSOp::Try offsetOf(jumpToEnd) + // + // try_body... + // + // JSOp::Goto finally + // JSOp::JumpTarget + // jumpToEnd: + // JSOp::Goto end: + // + // catch: + // JSOp::JumpTarget + // * JSOp::Undefined + // * JSOp::SetRval + // + // catch_body... + // + // JSOp::Goto finally + // JSOp::JumpTarget + // JSOp::Goto end + // + // finally: + // JSOp::JumpTarget + // * JSOp::GetRval + // * JSOp::Undefined + // * JSOp::SetRval + // + // finally_body... + // + // * JSOp::SetRval + // JSOp::Nop + // + // end: + // JSOp::JumpTarget + // + // For syntactic try-catch-finally, Syntactic should be used. + // For non-syntactic try-catch-finally, NonSyntactic should be used. + enum class ControlKind { Syntactic, NonSyntactic }; + + private: + BytecodeEmitter* bce_; + Kind kind_; + ControlKind controlKind_; + + // Tracks jumps to the finally block for later fixup. + // + // When a finally block is active, non-local jumps (including + // jumps-over-catches) result in a goto being written into the bytecode + // stream and fixed-up later. + // + // For non-syntactic try-catch-finally, all that handling is skipped. + // The non-syntactic try-catch-finally must: + // * have only one catch block + // * have JSOp::Goto at the end of catch-block + // * have no non-local-jump + // * don't use finally block for normal completion of try-block and + // catch-block + // + // Additionally, a finally block may be emitted for non-syntactic + // try-catch-finally, even if the kind is TryCatch, because GOSUBs are not + // emitted. + mozilla::Maybe controlInfo_; + + // The stack depth before emitting JSOp::Try. + int depth_; + + // The offset of the JSOp::Try op. + BytecodeOffset tryOpOffset_; + + // JSOp::JumpTarget after the entire try-catch-finally block. + JumpList catchAndFinallyJump_; + + // The offset of JSOp::Goto at the end of the try block. + JumpTarget tryEnd_; + + // The offset of JSOp::JumpTarget at the beginning of the finally block. + JumpTarget finallyStart_; + +#ifdef DEBUG + // The state of this emitter. + // + // +-------+ emitTry +-----+ emitCatch +-------+ emitEnd +-----+ + // | Start |-------->| Try |-+---------->| Catch |-+->+--------->| End | + // +-------+ +-----+ | +-------+ | ^ +-----+ + // | | | + // | +------------------+ +----+ + // | | | + // | v emitFinally +---------+ | + // +->+------------>| Finally |--+ + // +---------+ + enum class State { + // The initial state. + Start, + + // After calling emitTry. + Try, + + // After calling emitCatch. + Catch, + + // After calling emitFinally. + Finally, + + // After calling emitEnd. + End + }; + State state_; +#endif + + bool hasCatch() const { + return kind_ == Kind::TryCatch || kind_ == Kind::TryCatchFinally; + } + bool hasFinally() const { + return kind_ == Kind::TryCatchFinally || kind_ == Kind::TryFinally; + } + + BytecodeOffset offsetAfterTryOp() const { + return tryOpOffset_ + BytecodeOffsetDiff(JSOpLength_Try); + } + + // Returns true if catch and finally blocks should handle the frame's + // return value. + bool shouldUpdateRval() const; + + // Jump to the finally block. After the finally block executes, + // fall through to the code following the finally block. + [[nodiscard]] bool emitJumpToFinallyWithFallthrough(); + + public: + TryEmitter(BytecodeEmitter* bce, Kind kind, ControlKind controlKind); + + [[nodiscard]] bool emitTry(); + [[nodiscard]] bool emitCatch(); + + // If `finallyPos` is specified, it's an offset of the finally block's + // "{" character in the source code text, to improve line:column number in + // the error reporting. + // For non-syntactic try-catch-finally, `finallyPos` can be omitted. + [[nodiscard]] bool emitFinally( + const mozilla::Maybe& finallyPos = mozilla::Nothing()); + + [[nodiscard]] bool emitEnd(); + + private: + [[nodiscard]] bool emitTryEnd(); + [[nodiscard]] bool emitCatchEnd(); + [[nodiscard]] bool emitFinallyEnd(); +}; + +} /* namespace frontend */ +} /* namespace js */ + +#endif /* frontend_TryEmitter_h */ diff --git a/js/src/frontend/TypedIndex.h b/js/src/frontend/TypedIndex.h new file mode 100644 index 0000000000..c75d59ae3d --- /dev/null +++ b/js/src/frontend/TypedIndex.h @@ -0,0 +1,42 @@ +/* -*- 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/. */ + +#ifndef frontend_TypedIndex_h +#define frontend_TypedIndex_h + +#include +#include + +namespace js { +namespace frontend { + +// TypedIndex allows discrimination in variants between different +// index types. Used as a typesafe index for various stencil arrays. +template +struct TypedIndex { + TypedIndex() = default; + constexpr explicit TypedIndex(uint32_t index) : index(index){}; + + uint32_t index = 0; + + // For Vector::operator[] + operator size_t() const { return index; } + + TypedIndex& operator=(size_t idx) { + index = idx; + return *this; + } + + bool operator<(TypedIndex other) const { return index < other.index; } + bool operator<=(TypedIndex other) const { return index <= other.index; } + bool operator>(TypedIndex other) const { return index > other.index; } + bool operator>=(TypedIndex other) const { return index >= other.index; } +}; + +} // namespace frontend +} // namespace js + +#endif diff --git a/js/src/frontend/UsedNameTracker.h b/js/src/frontend/UsedNameTracker.h new file mode 100644 index 0000000000..50245f0ce2 --- /dev/null +++ b/js/src/frontend/UsedNameTracker.h @@ -0,0 +1,259 @@ +/* -*- 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/. */ + +#ifndef frontend_UsedNameTracker_h +#define frontend_UsedNameTracker_h + +#include "mozilla/Assertions.h" +#include "mozilla/Maybe.h" + +#include + +#include "frontend/ParserAtom.h" // TaggedParserAtomIndex +#include "frontend/TaggedParserAtomIndexHasher.h" // TaggedParserAtomIndexHasher +#include "frontend/Token.h" +#include "js/AllocPolicy.h" +#include "js/HashTable.h" +#include "js/Vector.h" + +namespace js { +namespace frontend { + +// A data structure for tracking used names per parsing session in order to +// compute which bindings are closed over. Scripts and scopes are numbered +// monotonically in textual order and unresolved uses of a name are tracked by +// lists of identifier uses, which are a pair of (ScriptId,ScopeId). +// +// For an identifier `i` with a use (ScriptId,ScopeId) in the Used list, +// ScriptId tracks the most nested script that has a use of u, and ScopeId +// tracks the most nested scope that is still being parsed (as the lists will be +// filtered as we finish processing a particular scope). +// +// ScriptId is used to answer the question "is `i` used by a nested function?" +// ScopeId is used to answer the question "is `i` used in any scopes currently +// being parsed?" +// +// The algorithm: +// +// Let Used be a map of names to lists. +// Let Declared(ScopeId) be a list of declarations for a scope numbered with +// ScopeId +// +// 1. Number all scopes in monotonic increasing order in textual order. +// 2. Number all scripts in monotonic increasing order in textual order. +// 3. When an identifier `i` is used in (ScriptId,ScopeId), append that use to +// the list Used[i] (creating the list and table entry if necessary). +// 4. When an identifier `i` is declared in a scope numbered ScopeId, append `i` +// to Declared(ScopeId). +// 5. When we finish parsing a scope numbered with ScopeId, in script numbered +// ScriptId, for each declared name d in Declared(ScopeId): +// a. If d is found in Used, mark d as closed over if there is a value +// (UsedScriptId, UsedScopeId) in Used[d] such that UsedScriptId > ScriptId +// and UsedScopeId > ScopeId. +// b. Remove all values uses in Used[d] where UsedScopeId > ScopeId. +// +// Steps 1 and 2 are implemented by UsedNameTracker::next{Script,Scope}Id. +// Step 3 is implemented by UsedNameTracker::noteUsedInScope. +// Step 4 is implemented by ParseContext::Scope::addDeclaredName. +// Step 5 is implemented by UsedNameTracker::noteBoundInScope and +// Parser::propagateFreeNamesAndMarkClosedOverBindings +// +// The following is a worked example to show how the algorithm works on a +// relatively simple piece of code. (clang-format is disabled due to the width +// of the example). + +// clang-format off +// +// // Script 1, Scope 1 +// var x = 1; // Declared(1) = [x]; +// function f() {// Script 2, Scope 2 +// if (x > 10) { //Scope 3 // Used[x] = [(2,2)]; +// var x = 12; // Declared(3) = [x]; +// function g() // Script 3 +// { // Scope 4 +// return x; // Used[x] = [(2,2), (3,4)] +// } // Leaving Script 3, Scope 4: No declared variables. +// } // Leaving Script 2, Scope 3: Declared(3) = [x]; +// // - Used[x][1] = (2,2) is not > (2,3) +// // - Used[x][2] = (3,4) is > (2,3), so mark x as closed over. +// // - Update Used[x]: [] -- Makes sense, as at this point we have +// // bound all the unbound x to a particlar +// // declaration.. +// +// else { // Scope 5 +// var x = 14; // Declared(5) = [x] +// function g() // Script 4 +// { // Scope 6 +// return y; // Used[y] = [(4,6)] +// } // Leaving Script 4, Scope 6: No declarations. +// } // Leaving Script 2, Scope 5: Declared(5) = [x] +// // - Used[x] = [], so don't mark x as closed over. +// var y = 12; // Declared(2) = [y] +// } // Leaving Script 2, Scope 2: Declared(2) = [y] +// // - Used[y][1] = (4,6) is > (2,2), so mark y as closed over. +// // - Update Used[y]: []. + +// clang-format on + +struct UnboundPrivateName { + TaggedParserAtomIndex atom; + TokenPos position; + + UnboundPrivateName(TaggedParserAtomIndex atom, TokenPos position) + : atom(atom), position(position) {} +}; + +class UsedNameTracker { + public: + struct Use { + uint32_t scriptId; + uint32_t scopeId; + }; + + class UsedNameInfo { + friend class UsedNameTracker; + + Vector uses_; + + void resetToScope(uint32_t scriptId, uint32_t scopeId); + + NameVisibility visibility_ = NameVisibility::Public; + + // The first place this name was used. This is important to track + // for private names, as we will use this location to issue + // diagnostics for using a name that's not defined lexically. + mozilla::Maybe firstUsePos_; + + public: + explicit UsedNameInfo(FrontendContext* fc, NameVisibility visibility, + mozilla::Maybe position) + : uses_(fc), visibility_(visibility), firstUsePos_(position) {} + + UsedNameInfo(UsedNameInfo&& other) = default; + + bool noteUsedInScope(uint32_t scriptId, uint32_t scopeId) { + if (uses_.empty() || uses_.back().scopeId < scopeId) { + return uses_.append(Use{scriptId, scopeId}); + } + return true; + } + + void noteBoundInScope(uint32_t scriptId, uint32_t scopeId, + bool* closedOver) { + *closedOver = false; + while (!uses_.empty()) { + Use& innermost = uses_.back(); + if (innermost.scopeId < scopeId) { + break; + } + if (innermost.scriptId > scriptId) { + *closedOver = true; + } + uses_.popBack(); + } + } + + bool isUsedInScript(uint32_t scriptId) const { + return !uses_.empty() && uses_.back().scriptId >= scriptId; + } + + // To allow disambiguating public and private symbols + bool isPublic() { return visibility_ == NameVisibility::Public; } + + bool empty() { return uses_.empty(); } + + mozilla::Maybe pos() { return firstUsePos_; } + + // When we leave a scope, and subsequently find a new private name + // reference, we don't want our error messages to be attributed to an old + // scope, so we update the position in that scenario. + void maybeUpdatePos(mozilla::Maybe p) { + MOZ_ASSERT_IF(!isPublic(), p.isSome()); + + if (empty() && !isPublic()) { + firstUsePos_ = p; + } + } + }; + + using UsedNameMap = + HashMap; + + private: + // The map of names to chains of uses. + UsedNameMap map_; + + // Monotonically increasing id for all nested scripts. + uint32_t scriptCounter_; + + // Monotonically increasing id for all nested scopes. + uint32_t scopeCounter_; + + // Set if a private name was encountered. + // Used to short circuit some private field early error checks + bool hasPrivateNames_; + + public: + explicit UsedNameTracker(FrontendContext* fc) + : map_(fc), + scriptCounter_(0), + scopeCounter_(0), + hasPrivateNames_(false) {} + + uint32_t nextScriptId() { + MOZ_ASSERT(scriptCounter_ != UINT32_MAX, + "ParseContext::Scope::init should have prevented wraparound"); + return scriptCounter_++; + } + + uint32_t nextScopeId() { + MOZ_ASSERT(scopeCounter_ != UINT32_MAX); + return scopeCounter_++; + } + + UsedNameMap::Ptr lookup(TaggedParserAtomIndex name) const { + return map_.lookup(name); + } + + [[nodiscard]] bool noteUse( + FrontendContext* fc, TaggedParserAtomIndex name, + NameVisibility visibility, uint32_t scriptId, uint32_t scopeId, + mozilla::Maybe tokenPosition = mozilla::Nothing()); + + // Fill maybeUnboundName with the first (source order) unbound name, or + // Nothing() if there are no unbound names. + [[nodiscard]] bool hasUnboundPrivateNames( + FrontendContext* fc, + mozilla::Maybe& maybeUnboundName); + + // Return a list of unbound private names, sorted by increasing location in + // the source. + [[nodiscard]] bool getUnboundPrivateNames( + Vector& unboundPrivateNames); + + struct RewindToken { + private: + friend class UsedNameTracker; + uint32_t scriptId; + uint32_t scopeId; + }; + + RewindToken getRewindToken() const { + RewindToken token; + token.scriptId = scriptCounter_; + token.scopeId = scopeCounter_; + return token; + } + + // Resets state so that scriptId and scopeId are the innermost script and + // scope, respectively. Used for rewinding state on syntax parse failure. + void rewind(RewindToken token); +}; + +} // namespace frontend +} // namespace js + +#endif diff --git a/js/src/frontend/ValueUsage.h b/js/src/frontend/ValueUsage.h new file mode 100644 index 0000000000..562272c5f8 --- /dev/null +++ b/js/src/frontend/ValueUsage.h @@ -0,0 +1,30 @@ +/* -*- 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/. */ + +#ifndef frontend_ValueUsage_h +#define frontend_ValueUsage_h + +namespace js { +namespace frontend { + +// Used to control whether the expression result is used. This enables various +// optimizations, for example it allows to emit JSOp::CallIgnoresRv for function +// calls. +enum class ValueUsage { + // Assume the value of the current expression may be used. This is always + // correct but prohibits optimizations like JSOp::CallIgnoresRv. + WantValue, + + // Pass this when emitting an expression if the expression's value is + // definitely unused by later instructions. You must make sure the next + // instruction is JSOp::Pop, a jump to a JSOp::Pop, or something similar. + IgnoreValue +}; + +} /* namespace frontend */ +} /* namespace js */ + +#endif /* frontend_ValueUsage_h */ diff --git a/js/src/frontend/WhileEmitter.cpp b/js/src/frontend/WhileEmitter.cpp new file mode 100644 index 0000000000..bc002b2ea6 --- /dev/null +++ b/js/src/frontend/WhileEmitter.cpp @@ -0,0 +1,85 @@ +/* -*- 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/WhileEmitter.h" + +#include "frontend/BytecodeEmitter.h" +#include "vm/Opcodes.h" +#include "vm/StencilEnums.h" // TryNoteKind + +using namespace js; +using namespace js::frontend; + +WhileEmitter::WhileEmitter(BytecodeEmitter* bce) : bce_(bce) {} + +bool WhileEmitter::emitCond(uint32_t whilePos, uint32_t condPos, + uint32_t endPos) { + MOZ_ASSERT(state_ == State::Start); + + // If we have a single-line while, like "while (x) ;", we want to emit the + // line note before the loop, so that the debugger sees a single entry point. + // This way, if there is a breakpoint on the line, it will only fire once; and + // "next"ing will skip the whole loop. However, for the multi-line case we + // want to emit the line note for the JSOp::LoopHead, so that "cont" stops on + // each iteration -- but without a stop before the first iteration. + if (bce_->errorReporter().lineAt(whilePos) == + bce_->errorReporter().lineAt(endPos)) { + if (!bce_->updateSourceCoordNotes(whilePos)) { + return false; + } + // Emit a Nop to ensure the source position is not part of the loop. + if (!bce_->emit1(JSOp::Nop)) { + return false; + } + } + + loopInfo_.emplace(bce_, StatementKind::WhileLoop); + + if (!loopInfo_->emitLoopHead(bce_, mozilla::Some(condPos))) { + return false; + } + +#ifdef DEBUG + state_ = State::Cond; +#endif + return true; +} + +bool WhileEmitter::emitBody() { + MOZ_ASSERT(state_ == State::Cond); + + if (!bce_->emitJump(JSOp::JumpIfFalse, &loopInfo_->breaks)) { + return false; + } + + tdzCacheForBody_.emplace(bce_); + +#ifdef DEBUG + state_ = State::Body; +#endif + return true; +} + +bool WhileEmitter::emitEnd() { + MOZ_ASSERT(state_ == State::Body); + + tdzCacheForBody_.reset(); + + if (!loopInfo_->emitContinueTarget(bce_)) { + return false; + } + + if (!loopInfo_->emitLoopEnd(bce_, JSOp::Goto, TryNoteKind::Loop)) { + return false; + } + + loopInfo_.reset(); + +#ifdef DEBUG + state_ = State::End; +#endif + return true; +} diff --git a/js/src/frontend/WhileEmitter.h b/js/src/frontend/WhileEmitter.h new file mode 100644 index 0000000000..5bc7fecccb --- /dev/null +++ b/js/src/frontend/WhileEmitter.h @@ -0,0 +1,90 @@ +/* -*- 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/. */ + +#ifndef frontend_WhileEmitter_h +#define frontend_WhileEmitter_h + +#include "mozilla/Attributes.h" +#include "mozilla/Maybe.h" + +#include + +#include "frontend/BytecodeControlStructures.h" +#include "frontend/TDZCheckCache.h" + +namespace js { +namespace frontend { + +struct BytecodeEmitter; + +// Class for emitting bytecode for while loop. +// +// Usage: (check for the return value is omitted for simplicity) +// +// `while (cond) body` +// WhileEmitter wh(this); +// wh.emitCond(offset_of_while, +// offset_of_body, +// offset_of_end); +// emit(cond); +// wh.emitBody(); +// emit(body); +// wh.emitEnd(); +// +class MOZ_STACK_CLASS WhileEmitter { + BytecodeEmitter* bce_; + + mozilla::Maybe loopInfo_; + + // Cache for the loop body, which is enclosed by the cache in `loopInfo_`, + // which is effectively for the loop condition. + mozilla::Maybe tdzCacheForBody_; + +#ifdef DEBUG + // The state of this emitter. + // + // +-------+ emitCond +------+ emitBody +------+ emitEnd +-----+ + // | Start |--------->| Cond |--------->| Body |--------->| End | + // +-------+ +------+ +------+ +-----+ + enum class State { + // The initial state. + Start, + + // After calling emitCond. + Cond, + + // After calling emitBody. + Body, + + // After calling emitEnd. + End + }; + State state_ = State::Start; +#endif + + public: + explicit WhileEmitter(BytecodeEmitter* bce); + + // Parameters are the offset in the source code for each character below: + // + // while ( x < 20 ) { ... } + // ^ ^ ^ + // | | | + // | | endPos_ + // | | + // | condPos_ + // | + // whilePos_ + [[nodiscard]] bool emitCond(uint32_t whilePos, uint32_t condPos, + uint32_t endPos); + [[nodiscard]] bool emitBody(); + [[nodiscard]] bool emitEnd(); +}; + +} /* namespace frontend */ +} /* namespace js */ + +#endif /* frontend_WhileEmitter_h */ diff --git a/js/src/frontend/align_stack_comment.py b/js/src/frontend/align_stack_comment.py new file mode 100755 index 0000000000..28d5d8cf7f --- /dev/null +++ b/js/src/frontend/align_stack_comment.py @@ -0,0 +1,108 @@ +#!/usr/bin/python -B +# 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/. + + +""" Usage: align_stack_comment.py FILE + + This script aligns the stack transition comment in BytecodeEmitter and + its helper classes. + + The stack transition comment looks like the following: + // [stack] VAL1 VAL2 VAL3 +""" + +import re +import sys + +# The column index of '[' of '[stack]' +ALIGNMENT_COLUMN = 20 + +# The maximum column for comment +MAX_CHARS_PER_LINE = 80 + +stack_comment_pat = re.compile("^( *//) *(\[stack\].*)$") + + +def align_stack_comment(path): + lines = [] + changed = False + + with open(path) as f: + max_head_len = 0 + max_comment_len = 0 + + line_num = 0 + + for line in f: + line_num += 1 + # Python includes \n in lines. + line = line.rstrip("\n") + + m = stack_comment_pat.search(line) + if m: + head = m.group(1) + " " + head_len = len(head) + comment = m.group(2) + comment_len = len(comment) + + if head_len > ALIGNMENT_COLUMN: + print( + "Warning: line {} overflows from alignment column {}: {}".format( + line_num, ALIGNMENT_COLUMN, head_len + ), + file=sys.stderr, + ) + + line_len = max(head_len, ALIGNMENT_COLUMN) + comment_len + if line_len > MAX_CHARS_PER_LINE: + print( + "Warning: line {} overflows from {} chars: {}".format( + line_num, MAX_CHARS_PER_LINE, line_len + ), + file=sys.stderr, + ) + + max_head_len = max(max_head_len, head_len) + max_comment_len = max(max_comment_len, comment_len) + + spaces = max(ALIGNMENT_COLUMN - head_len, 0) + formatted = head + " " * spaces + comment + + if formatted != line: + changed = True + + lines.append(formatted) + else: + lines.append(line) + + print( + "Info: Minimum column number for [stack]: {}".format(max_head_len), + file=sys.stderr, + ) + print( + "Info: Alignment column number for [stack]: {}".format(ALIGNMENT_COLUMN), + file=sys.stderr, + ) + print( + "Info: Max length of stack transition comments: {}".format(max_comment_len), + file=sys.stderr, + ) + + if changed: + with open(path, "w") as f: + for line in lines: + print(line, file=f) + else: + print("No change.") + + +if __name__ == "__main__": + if len(sys.argv) < 2: + print("Usage: align_stack_comment.py FILE", file=sys.stderr) + sys.exit(1) + + for path in sys.argv[1:]: + print(path) + align_stack_comment(path) diff --git a/js/src/frontend/moz.build b/js/src/frontend/moz.build new file mode 100644 index 0000000000..0a820086a5 --- /dev/null +++ b/js/src/frontend/moz.build @@ -0,0 +1,98 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +FINAL_LIBRARY = "js" + +# Includes should be relative to parent path +LOCAL_INCLUDES += ["!..", ".."] + +include("../js-config.mozbuild") +include("../js-cxxflags.mozbuild") + + +# Generate frontend/ReservedWordsGenerated.h from frontend/ReservedWords.h +if CONFIG["ENABLE_DECORATORS"]: + GeneratedFile( + "ReservedWordsGenerated.h", + script="GenerateReservedWords.py", + inputs=["ReservedWords.h"], + flags=["--enable-decorators"], + ) +else: + GeneratedFile( + "ReservedWordsGenerated.h", + script="GenerateReservedWords.py", + inputs=["ReservedWords.h"], + ) + +if CONFIG["JS_ENABLE_SMOOSH"]: + CbindgenHeader("smoosh_generated.h", inputs=["/js/src/frontend/smoosh"]) + +UNIFIED_SOURCES += [ + "AbstractScopePtr.cpp", + "AsyncEmitter.cpp", + "BytecodeCompiler.cpp", + "BytecodeControlStructures.cpp", + "BytecodeEmitter.cpp", + "BytecodeSection.cpp", + "CallOrNewEmitter.cpp", + "CForEmitter.cpp", + "CompileScript.cpp", + "DefaultEmitter.cpp", + "DoWhileEmitter.cpp", + "ElemOpEmitter.cpp", + "EmitterScope.cpp", + "ExpressionStatementEmitter.cpp", + "FoldConstants.cpp", + "ForInEmitter.cpp", + "ForOfEmitter.cpp", + "ForOfLoopControl.cpp", + "FrontendContext.cpp", + "FunctionEmitter.cpp", + "IfEmitter.cpp", + "JumpList.cpp", + "LabelEmitter.cpp", + "LexicalScopeEmitter.cpp", + "NameFunctions.cpp", + "NameOpEmitter.cpp", + "ObjectEmitter.cpp", + "ObjLiteral.cpp", + "OptionalEmitter.cpp", + "ParseContext.cpp", + "ParseNode.cpp", + "ParseNodeVerify.cpp", + "ParserAtom.cpp", + "PrivateOpEmitter.cpp", + "PropOpEmitter.cpp", + "SharedContext.cpp", + "SourceNotes.cpp", + "Stencil.cpp", + "StencilXdr.cpp", + "SwitchEmitter.cpp", + "TDZCheckCache.cpp", + "TokenStream.cpp", + "TryEmitter.cpp", + "WhileEmitter.cpp", +] + +if CONFIG["JS_ENABLE_SMOOSH"]: + UNIFIED_SOURCES += [ + "Frontend2.cpp", + ] + +if CONFIG["ENABLE_DECORATORS"]: + UNIFIED_SOURCES += [ + "DecoratorEmitter.cpp", + ] + +# Parser.cpp cannot be built in unified mode because of explicit +# template instantiations. +SOURCES += [ + "Parser.cpp", +] + +if CONFIG["FUZZING_INTERFACES"] and CONFIG["LIBFUZZER"]: + include("/tools/fuzzing/libfuzzer-config.mozbuild") diff --git a/js/src/frontend/smoosh/Cargo.toml b/js/src/frontend/smoosh/Cargo.toml new file mode 100644 index 0000000000..260d62bcc9 --- /dev/null +++ b/js/src/frontend/smoosh/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "smoosh" +version = "0.1.0" +authors = ["The jsparagus Project Developers"] +edition = "2018" +license = "MIT/Apache-2.0" + +[dependencies] +bumpalo = "3.4.0" +log = "0.4" +# Setup RUST_LOG logging. +# Disable regex feature for code size. +env_logger = {version = "0.10", default-features = false} +# For non-jsparagus developers. +jsparagus = { git = "https://github.com/mozilla-spidermonkey/jsparagus", rev = "64ba08e24749616de2344112f226d1ef4ba893ae" } +# For local development, replace above with +# jsparagus = { path = "{path to jsparagus}" } + +[build-dependencies] +# For non-jsparagus developers. +jsparagus = { git = "https://github.com/mozilla-spidermonkey/jsparagus", rev = "64ba08e24749616de2344112f226d1ef4ba893ae" } +# For local development, replace above with +# jsparagus = { path = "{path to jsparagus}" } diff --git a/js/src/frontend/smoosh/build.rs b/js/src/frontend/smoosh/build.rs new file mode 100644 index 0000000000..367c682f86 --- /dev/null +++ b/js/src/frontend/smoosh/build.rs @@ -0,0 +1,27 @@ +use jsparagus::stencil::opcode_info; + +fn compare(name: &str, orig: &str, copied: &str) { + if copied != orig { + panic!( + "{} is out of sync. \ + It's possible that the bytecode generated by jsparagus is \ + based on older opcodes. Please run \ + update_stencil.py in jsparagus. \ + You can disable this check by setting \ + JS_SMOOSH_DISABLE_OPCODE_CHECK environment variable.", + name + ); + } +} + +fn main() { + if std::env::var("JS_SMOOSH_DISABLE_OPCODE_CHECK").is_ok() { + return; + } + + compare( + "Opcodes.h", + include_str!("../../vm/Opcodes.h"), + opcode_info::get_opcodes_source(), + ); +} diff --git a/js/src/frontend/smoosh/cbindgen.toml b/js/src/frontend/smoosh/cbindgen.toml new file mode 100644 index 0000000000..71a9568b35 --- /dev/null +++ b/js/src/frontend/smoosh/cbindgen.toml @@ -0,0 +1,19 @@ +header = """/* 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/. */""" +autogen_warning = """/* DO NOT MODIFY THIS MANUALLY! This file was generated using cbindgen. See RunCbindgen.py */ + +#include "mozilla/Assertions.h" // MOZ_ASSERT +""" +include_version = true +braces = "SameLine" +line_length = 100 +tab_width = 2 +language = "C++" + +[enum] +derive_helper_methods = true +derive_const_casts = true +derive_mut_casts = true + +cast_assert_name = "MOZ_ASSERT" diff --git a/js/src/frontend/smoosh/moz.build b/js/src/frontend/smoosh/moz.build new file mode 100644 index 0000000000..d75c4c18ba --- /dev/null +++ b/js/src/frontend/smoosh/moz.build @@ -0,0 +1,15 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +FINAL_LIBRARY = "js" + +# Includes should be relative to parent path +LOCAL_INCLUDES += ["!../..", "../.."] + +include("../../js-config.mozbuild") +include("../../js-cxxflags.mozbuild") + +DIRS += ["../../rust"] diff --git a/js/src/frontend/smoosh/src/lib.rs b/js/src/frontend/smoosh/src/lib.rs new file mode 100644 index 0000000000..f663706533 --- /dev/null +++ b/js/src/frontend/smoosh/src/lib.rs @@ -0,0 +1,769 @@ +/* Copyright Mozilla Foundation + * + * Licensed under the Apache License, Version 2.0, or the MIT license, + * (the "Licenses") at your option. You may not use this file except in + * compliance with one of the Licenses. You may obtain copies of the + * Licenses at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * http://opensource.org/licenses/MIT + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the Licenses is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the Licenses for the specific language governing permissions and + * limitations under the Licenses. + */ + +use bumpalo; +use env_logger; +use jsparagus::ast::source_atom_set::SourceAtomSet; +use jsparagus::ast::source_slice_list::SourceSliceList; +use jsparagus::ast::types::Program; +use jsparagus::emitter::{emit, EmitError, EmitOptions}; +use jsparagus::parser::{parse_module, parse_script, ParseError, ParseOptions}; +use jsparagus::stencil::gcthings::GCThing; +use jsparagus::stencil::regexp::RegExpItem; +use jsparagus::stencil::result::EmitResult; +use jsparagus::stencil::scope::{BindingName, ScopeData}; +use jsparagus::stencil::scope_notes::ScopeNote; +use jsparagus::stencil::script::{ImmutableScriptData, ScriptStencil, SourceExtent}; +use std::boxed::Box; +use std::cell::RefCell; +use std::collections::HashMap; +use std::convert::TryInto; +use std::os::raw::{c_char, c_void}; +use std::rc::Rc; +use std::{mem, slice, str}; + +#[repr(C)] +pub struct CVec { + pub data: *mut T, + pub len: usize, + pub capacity: usize, +} + +impl CVec { + fn empty() -> CVec { + Self { + data: std::ptr::null_mut(), + len: 0, + capacity: 0, + } + } + + fn from(mut v: Vec) -> CVec { + let result = Self { + data: v.as_mut_ptr(), + len: v.len(), + capacity: v.capacity(), + }; + mem::forget(v); + result + } + + unsafe fn into(self) -> Vec { + Vec::from_raw_parts(self.data, self.len, self.capacity) + } +} + +#[repr(C)] +pub struct SmooshCompileOptions { + no_script_rval: bool, +} + +#[repr(C, u8)] +pub enum SmooshGCThing { + Null, + Atom(usize), + Function(usize), + Scope(usize), + RegExp(usize), +} + +fn convert_gcthing(item: GCThing, scope_index_map: &HashMap) -> SmooshGCThing { + match item { + GCThing::Null => SmooshGCThing::Null, + GCThing::Atom(index) => SmooshGCThing::Atom(index.into()), + GCThing::Function(index) => SmooshGCThing::Function(index.into()), + GCThing::RegExp(index) => SmooshGCThing::RegExp(index.into()), + GCThing::Scope(index) => { + let mapped_index = *scope_index_map + .get(&index.into()) + .expect("Scope map should be populated"); + SmooshGCThing::Scope(mapped_index) + } + } +} + +#[repr(C)] +pub struct SmooshBindingName { + name: usize, + is_closed_over: bool, + is_top_level_function: bool, +} + +impl From for SmooshBindingName { + fn from(info: BindingName) -> Self { + Self { + name: info.name.into(), + is_closed_over: info.is_closed_over, + is_top_level_function: info.is_top_level_function, + } + } +} + +#[repr(C)] +pub struct SmooshGlobalScopeData { + bindings: CVec, + let_start: usize, + const_start: usize, +} + +#[repr(C)] +pub struct SmooshVarScopeData { + bindings: CVec, + enclosing: usize, + function_has_extensible_scope: bool, + first_frame_slot: u32, +} + +#[repr(C)] +pub struct SmooshLexicalScopeData { + bindings: CVec, + const_start: usize, + enclosing: usize, + first_frame_slot: u32, +} + +#[repr(C)] +pub struct SmooshFunctionScopeData { + bindings: CVec>, + has_parameter_exprs: bool, + non_positional_formal_start: usize, + var_start: usize, + enclosing: usize, + first_frame_slot: u32, + function_index: usize, + is_arrow: bool, +} + +#[repr(C, u8)] +pub enum SmooshScopeData { + Global(SmooshGlobalScopeData), + Var(SmooshVarScopeData), + Lexical(SmooshLexicalScopeData), + Function(SmooshFunctionScopeData), +} + +/// Convert single Scope data, resolving enclosing index with scope_index_map. +fn convert_scope(scope: ScopeData, scope_index_map: &mut HashMap) -> SmooshScopeData { + match scope { + ScopeData::Alias(_) => panic!("alias should be handled in convert_scopes"), + ScopeData::Global(data) => SmooshScopeData::Global(SmooshGlobalScopeData { + bindings: CVec::from(data.base.bindings.into_iter().map(|x| x.into()).collect()), + let_start: data.let_start, + const_start: data.const_start, + }), + ScopeData::Var(data) => { + let enclosing: usize = data.enclosing.into(); + SmooshScopeData::Var(SmooshVarScopeData { + bindings: CVec::from(data.base.bindings.into_iter().map(|x| x.into()).collect()), + enclosing: *scope_index_map + .get(&enclosing) + .expect("Alias target should be earlier index"), + function_has_extensible_scope: data.function_has_extensible_scope, + first_frame_slot: data.first_frame_slot.into(), + }) + } + ScopeData::Lexical(data) => { + let enclosing: usize = data.enclosing.into(); + SmooshScopeData::Lexical(SmooshLexicalScopeData { + bindings: CVec::from(data.base.bindings.into_iter().map(|x| x.into()).collect()), + const_start: data.const_start, + enclosing: *scope_index_map + .get(&enclosing) + .expect("Alias target should be earlier index"), + first_frame_slot: data.first_frame_slot.into(), + }) + } + ScopeData::Function(data) => { + let enclosing: usize = data.enclosing.into(); + SmooshScopeData::Function(SmooshFunctionScopeData { + bindings: CVec::from( + data.base + .bindings + .into_iter() + .map(|x| COption::from(x.map(|x| x.into()))) + .collect(), + ), + has_parameter_exprs: data.has_parameter_exprs, + non_positional_formal_start: data.non_positional_formal_start, + var_start: data.var_start, + enclosing: *scope_index_map + .get(&enclosing) + .expect("Alias target should be earlier index"), + first_frame_slot: data.first_frame_slot.into(), + function_index: data.function_index.into(), + is_arrow: data.is_arrow, + }) + } + } +} + +/// Convert list of Scope data, removing aliases. +/// Also create a map between original index into index into result vector +/// without aliases. +fn convert_scopes( + scopes: Vec, + scope_index_map: &mut HashMap, +) -> CVec { + let mut result = Vec::with_capacity(scopes.len()); + for (i, scope) in scopes.into_iter().enumerate() { + if let ScopeData::Alias(index) = scope { + let mapped_index = *scope_index_map + .get(&index.into()) + .expect("Alias target should be earlier index"); + scope_index_map.insert(i, mapped_index); + + continue; + } + + scope_index_map.insert(i, result.len()); + result.push(convert_scope(scope, scope_index_map)) + } + + CVec::from(result) +} + +#[repr(C)] +pub struct SmooshScopeNote { + index: u32, + start: u32, + length: u32, + parent: u32, +} + +impl From for SmooshScopeNote { + fn from(note: ScopeNote) -> Self { + let start = usize::from(note.start) as u32; + let end = usize::from(note.end) as u32; + let parent = match note.parent { + Some(index) => usize::from(index) as u32, + None => std::u32::MAX, + }; + Self { + index: usize::from(note.index) as u32, + start, + length: end - start, + parent, + } + } +} + +#[repr(C)] +pub struct SmooshRegExpItem { + pattern: usize, + global: bool, + ignore_case: bool, + multi_line: bool, + dot_all: bool, + sticky: bool, + unicode: bool, +} + +impl From for SmooshRegExpItem { + fn from(data: RegExpItem) -> Self { + Self { + pattern: data.pattern.into(), + global: data.global, + ignore_case: data.ignore_case, + multi_line: data.multi_line, + dot_all: data.dot_all, + sticky: data.sticky, + unicode: data.unicode, + } + } +} + +#[repr(C)] +pub struct SmooshImmutableScriptData { + pub main_offset: u32, + pub nfixed: u32, + pub nslots: u32, + pub body_scope_index: u32, + pub num_ic_entries: u32, + pub fun_length: u16, + pub bytecode: CVec, + pub scope_notes: CVec, +} + +#[repr(C)] +pub struct SmooshSourceExtent { + pub source_start: u32, + pub source_end: u32, + pub to_string_start: u32, + pub to_string_end: u32, + pub lineno: u32, + pub column: u32, +} + +#[repr(C, u8)] +pub enum COption { + Some(T), + None, +} + +impl COption { + fn from(v: Option) -> Self { + match v { + Option::Some(v) => COption::Some(v), + Option::None => COption::None, + } + } +} + +#[repr(C)] +pub struct SmooshScriptStencil { + pub immutable_flags: u32, + pub gcthings: CVec, + pub immutable_script_data: COption, + pub extent: SmooshSourceExtent, + pub fun_name: COption, + pub fun_nargs: u16, + pub fun_flags: u16, + pub lazy_function_enclosing_scope_index: COption, + pub is_standalone_function: bool, + pub was_function_emitted: bool, + pub is_singleton_function: bool, +} + +#[repr(C)] +pub struct SmooshResult { + unimplemented: bool, + error: CVec, + + scopes: CVec, + regexps: CVec, + + scripts: CVec, + script_data_list: CVec, + + all_atoms: *mut c_void, + all_atoms_len: usize, + slices: *mut c_void, + slices_len: usize, + allocator: *mut c_void, +} + +impl SmooshResult { + fn unimplemented() -> Self { + Self::unimplemented_or_error(true, CVec::empty()) + } + + fn error(message: String) -> Self { + let error = CVec::from(format!("{}\0", message).into_bytes()); + Self::unimplemented_or_error(false, error) + } + + fn unimplemented_or_error(unimplemented: bool, error: CVec) -> Self { + Self { + unimplemented, + error, + + scopes: CVec::empty(), + regexps: CVec::empty(), + + scripts: CVec::empty(), + script_data_list: CVec::empty(), + + all_atoms: std::ptr::null_mut(), + all_atoms_len: 0, + slices: std::ptr::null_mut(), + slices_len: 0, + allocator: std::ptr::null_mut(), + } + } +} + +enum SmooshError { + GenericError(String), + NotImplemented, +} + +#[no_mangle] +pub unsafe extern "C" fn smoosh_init() { + // Gecko might set a logger before we do, which is all fine; try to + // initialize ours, and reset the FilterLevel env_logger::try_init might + // have set to what it was in case of initialization failure + let filter = log::max_level(); + match env_logger::try_init() { + Ok(_) => {} + Err(_) => { + log::set_max_level(filter); + } + } +} + +fn convert_extent(extent: SourceExtent) -> SmooshSourceExtent { + SmooshSourceExtent { + source_start: extent.source_start, + source_end: extent.source_end, + to_string_start: extent.to_string_start, + to_string_end: extent.to_string_end, + lineno: extent.lineno, + column: extent.column, + } +} + +fn convert_script( + script: ScriptStencil, + scope_index_map: &HashMap, +) -> SmooshScriptStencil { + let immutable_flags = script.immutable_flags.into(); + + let gcthings = CVec::from( + script + .gcthings + .into_iter() + .map(|x| convert_gcthing(x, scope_index_map)) + .collect(), + ); + + let immutable_script_data = COption::from(script.immutable_script_data.map(|n| n.into())); + + let extent = convert_extent(script.extent); + + let fun_name = COption::from(script.fun_name.map(|n| n.into())); + let fun_nargs = script.fun_nargs; + let fun_flags = script.fun_flags.into(); + + let lazy_function_enclosing_scope_index = + COption::from(script.lazy_function_enclosing_scope_index.map(|index| { + *scope_index_map + .get(&index.into()) + .expect("Alias target should be earlier index") + })); + + let is_standalone_function = script.is_standalone_function; + let was_function_emitted = script.was_function_emitted; + let is_singleton_function = script.is_singleton_function; + + SmooshScriptStencil { + immutable_flags, + gcthings, + immutable_script_data, + extent, + fun_name, + fun_nargs, + fun_flags, + lazy_function_enclosing_scope_index, + is_standalone_function, + was_function_emitted, + is_singleton_function, + } +} + +fn convert_script_data(script_data: ImmutableScriptData) -> SmooshImmutableScriptData { + let main_offset = script_data.main_offset; + let nfixed = script_data.nfixed.into(); + let nslots = script_data.nslots; + let body_scope_index = script_data.body_scope_index; + let num_ic_entries = script_data.num_ic_entries; + let fun_length = script_data.fun_length; + + let bytecode = CVec::from(script_data.bytecode); + + let scope_notes = CVec::from( + script_data + .scope_notes + .into_iter() + .map(|x| x.into()) + .collect(), + ); + + SmooshImmutableScriptData { + main_offset, + nfixed, + nslots, + body_scope_index, + num_ic_entries, + fun_length, + bytecode, + scope_notes, + } +} + +#[no_mangle] +pub unsafe extern "C" fn smoosh_run( + text: *const u8, + text_len: usize, + options: &SmooshCompileOptions, +) -> SmooshResult { + let text = str::from_utf8(slice::from_raw_parts(text, text_len)).expect("Invalid UTF8"); + let allocator = Box::new(bumpalo::Bump::new()); + match smoosh(&allocator, text, options) { + Ok(result) => { + let mut scope_index_map = HashMap::new(); + + let scopes = convert_scopes(result.scopes, &mut scope_index_map); + let regexps = CVec::from(result.regexps.into_iter().map(|x| x.into()).collect()); + + let scripts = CVec::from( + result + .scripts + .into_iter() + .map(|x| convert_script(x, &scope_index_map)) + .collect(), + ); + + let script_data_list = CVec::from( + result + .script_data_list + .into_iter() + .map(convert_script_data) + .collect(), + ); + + let all_atoms_len = result.atoms.len(); + let all_atoms = Box::new(result.atoms); + let raw_all_atoms = Box::into_raw(all_atoms); + let opaque_all_atoms = raw_all_atoms as *mut c_void; + + let slices_len = result.slices.len(); + let slices = Box::new(result.slices); + let raw_slices = Box::into_raw(slices); + let opaque_slices = raw_slices as *mut c_void; + + let raw_allocator = Box::into_raw(allocator); + let opaque_allocator = raw_allocator as *mut c_void; + + SmooshResult { + unimplemented: false, + error: CVec::empty(), + + scopes, + regexps, + + scripts, + script_data_list, + + all_atoms: opaque_all_atoms, + all_atoms_len, + slices: opaque_slices, + slices_len, + allocator: opaque_allocator, + } + } + Err(SmooshError::GenericError(message)) => SmooshResult::error(message), + Err(SmooshError::NotImplemented) => SmooshResult::unimplemented(), + } +} + +#[repr(C)] +pub struct SmooshParseResult { + unimplemented: bool, + error: CVec, +} + +fn convert_parse_result<'alloc, T>(r: jsparagus::parser::Result) -> SmooshParseResult { + match r { + Ok(_) => SmooshParseResult { + unimplemented: false, + error: CVec::empty(), + }, + Err(err) => match *err { + ParseError::NotImplemented(_) => { + let message = err.message(); + SmooshParseResult { + unimplemented: true, + error: CVec::from(format!("{}\0", message).into_bytes()), + } + } + _ => { + let message = err.message(); + SmooshParseResult { + unimplemented: false, + error: CVec::from(format!("{}\0", message).into_bytes()), + } + } + }, + } +} + +#[no_mangle] +pub unsafe extern "C" fn smoosh_test_parse_script( + text: *const u8, + text_len: usize, +) -> SmooshParseResult { + let text = match str::from_utf8(slice::from_raw_parts(text, text_len)) { + Ok(text) => text, + Err(_) => { + return SmooshParseResult { + unimplemented: false, + error: CVec::from("Invalid UTF-8\0".to_string().into_bytes()), + }; + } + }; + let allocator = bumpalo::Bump::new(); + let parse_options = ParseOptions::new(); + let atoms = Rc::new(RefCell::new(SourceAtomSet::new())); + let slices = Rc::new(RefCell::new(SourceSliceList::new())); + convert_parse_result(parse_script( + &allocator, + text, + &parse_options, + atoms, + slices, + )) +} + +#[no_mangle] +pub unsafe extern "C" fn smoosh_test_parse_module( + text: *const u8, + text_len: usize, +) -> SmooshParseResult { + let text = match str::from_utf8(slice::from_raw_parts(text, text_len)) { + Ok(text) => text, + Err(_) => { + return SmooshParseResult { + unimplemented: false, + error: CVec::from("Invalid UTF-8\0".to_string().into_bytes()), + }; + } + }; + let allocator = bumpalo::Bump::new(); + let parse_options = ParseOptions::new(); + let atoms = Rc::new(RefCell::new(SourceAtomSet::new())); + let slices = Rc::new(RefCell::new(SourceSliceList::new())); + convert_parse_result(parse_module( + &allocator, + text, + &parse_options, + atoms, + slices, + )) +} + +#[no_mangle] +pub unsafe extern "C" fn smoosh_free_parse_result(result: SmooshParseResult) { + let _ = result.error.into(); +} + +#[no_mangle] +pub unsafe extern "C" fn smoosh_get_atom_at(result: SmooshResult, index: usize) -> *const c_char { + let all_atoms = result.all_atoms as *const Vec<&str>; + let atom = (*all_atoms)[index]; + atom.as_ptr() as *const c_char +} + +#[no_mangle] +pub unsafe extern "C" fn smoosh_get_atom_len_at(result: SmooshResult, index: usize) -> usize { + let all_atoms = result.all_atoms as *const Vec<&str>; + let atom = (*all_atoms)[index]; + atom.len() +} + +#[no_mangle] +pub unsafe extern "C" fn smoosh_get_slice_at(result: SmooshResult, index: usize) -> *const c_char { + let slices = result.slices as *const Vec<&str>; + let slice = (*slices)[index]; + slice.as_ptr() as *const c_char +} + +#[no_mangle] +pub unsafe extern "C" fn smoosh_get_slice_len_at(result: SmooshResult, index: usize) -> usize { + let slices = result.slices as *const Vec<&str>; + let slice = (*slices)[index]; + slice.len() +} + +unsafe fn free_script(script: SmooshScriptStencil) { + let _ = script.gcthings.into(); +} + +unsafe fn free_script_data(script_data: SmooshImmutableScriptData) { + let _ = script_data.bytecode.into(); + let _ = script_data.scope_notes.into(); +} + +#[no_mangle] +pub unsafe extern "C" fn smoosh_free(result: SmooshResult) { + let _ = result.error.into(); + + let _ = result.scopes.into(); + let _ = result.regexps.into(); + + for fun in result.scripts.into() { + free_script(fun); + } + + for script_data in result.script_data_list.into() { + free_script_data(script_data); + } + + if !result.all_atoms.is_null() { + let _ = Box::from_raw(result.all_atoms as *mut Vec<&str>); + } + if !result.slices.is_null() { + let _ = Box::from_raw(result.slices as *mut Vec<&str>); + } + if !result.allocator.is_null() { + let _ = Box::from_raw(result.allocator as *mut bumpalo::Bump); + } +} + +fn smoosh<'alloc>( + allocator: &'alloc bumpalo::Bump, + text: &'alloc str, + options: &SmooshCompileOptions, +) -> Result, SmooshError> { + let parse_options = ParseOptions::new(); + let atoms = Rc::new(RefCell::new(SourceAtomSet::new())); + let slices = Rc::new(RefCell::new(SourceSliceList::new())); + let text_length = text.len(); + + let parse_result = match parse_script( + &allocator, + text, + &parse_options, + atoms.clone(), + slices.clone(), + ) { + Ok(result) => result, + Err(err) => match *err { + ParseError::NotImplemented(_) => { + println!("Unimplemented: {}", err.message()); + return Err(SmooshError::NotImplemented); + } + _ => { + return Err(SmooshError::GenericError(err.message())); + } + }, + }; + + let extent = SourceExtent::top_level_script(text_length.try_into().unwrap(), 1, 0); + let mut emit_options = EmitOptions::new(extent); + emit_options.no_script_rval = options.no_script_rval; + let script = parse_result.unbox(); + match emit( + allocator.alloc(Program::Script(script)), + &emit_options, + atoms.replace(SourceAtomSet::new_uninitialized()), + slices.replace(SourceSliceList::new()), + ) { + Ok(result) => Ok(result), + Err(EmitError::NotImplemented(message)) => { + println!("Unimplemented: {}", message); + return Err(SmooshError::NotImplemented); + } + } +} + +#[cfg(test)] +mod tests { + #[test] + fn it_works() { + assert_eq!(2 + 2, 4); + } +} -- cgit v1.2.3