diff options
Diffstat (limited to '')
118 files changed, 68987 insertions, 0 deletions
diff --git a/js/src/frontend/AbstractScopePtr.cpp b/js/src/frontend/AbstractScopePtr.cpp new file mode 100644 index 0000000000..5d147bfb75 --- /dev/null +++ b/js/src/frontend/AbstractScopePtr.cpp @@ -0,0 +1,66 @@ +/* -*- 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/Maybe.h" + +#include "frontend/CompilationInfo.h" // CompilationState +#include "frontend/Stencil.h" +#include "js/GCPolicyAPI.h" +#include "js/GCVariant.h" + +using namespace js; +using namespace js::frontend; + +ScopeStencil& AbstractScopePtr::scopeData() const { + const Deferred& data = scope_.as<Deferred>(); + return data.compilationState.scopeData[data.index]; +} + +CompilationState& AbstractScopePtr::compilationState() const { + const Deferred& data = scope_.as<Deferred>(); + return data.compilationState; +} + +ScopeKind AbstractScopePtr::kind() const { + MOZ_ASSERT(!isNullptr()); + if (isScopeStencil()) { + return scopeData().kind(); + } + return scope()->kind(); +} + +AbstractScopePtr AbstractScopePtr::enclosing() const { + MOZ_ASSERT(!isNullptr()); + if (isScopeStencil()) { + return scopeData().enclosing(compilationState()); + } + return AbstractScopePtr(scope()->enclosing()); +} + +bool AbstractScopePtr::hasEnvironment() const { + MOZ_ASSERT(!isNullptr()); + if (isScopeStencil()) { + return scopeData().hasEnvironment(); + } + return scope()->hasEnvironment(); +} + +bool AbstractScopePtr::isArrow() const { + // nullptr will also fail the below assert, so effectively also checking + // !isNullptr() + MOZ_ASSERT(is<FunctionScope>()); + if (isScopeStencil()) { + return scopeData().isArrow(); + } + MOZ_ASSERT(scope()->as<FunctionScope>().canonicalFunction()); + return scope()->as<FunctionScope>().canonicalFunction()->isArrow(); +} + +void AbstractScopePtr::trace(JSTracer* trc) { + JS::GCPolicy<ScopeType>::trace(trc, &scope_, "AbstractScopePtr"); +} diff --git a/js/src/frontend/AbstractScopePtr.h b/js/src/frontend/AbstractScopePtr.h new file mode 100644 index 0000000000..276470d5f3 --- /dev/null +++ b/js/src/frontend/AbstractScopePtr.h @@ -0,0 +1,143 @@ +/* -*- 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 "mozilla/Maybe.h" +#include "mozilla/Variant.h" + +#include "frontend/TypedIndex.h" +#include "gc/Barrier.h" +#include "gc/Rooting.h" +#include "gc/Tracer.h" +#include "vm/Scope.h" +#include "vm/ScopeKind.h" // For ScopeKind + +namespace js { +class Scope; +class GlobalScope; +class EvalScope; +struct MemberInitializers; +class GCMarker; + +namespace frontend { +struct CompilationState; +struct CompilationGCOutput; +class ScopeStencil; +} // namespace frontend + +using ScopeIndex = frontend::TypedIndex<Scope>; +using HeapPtrScope = HeapPtr<Scope*>; + +// 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. +// +// Because a AbstractScopePtr may hold onto a Scope, it must be rooted if a GC +// may occur to ensure that the scope is traced. +class AbstractScopePtr { + public: + // Used to hold index and the compilationState together to avoid having a + // potentially nullable compilationState. + struct Deferred { + ScopeIndex index; + frontend::CompilationState& compilationState; + }; + + // To make writing code and managing invariants easier, we require that + // any nullptr scopes be stored on the HeapPtrScope arm of the variant. + using ScopeType = mozilla::Variant<HeapPtrScope, Deferred>; + + private: + ScopeType scope_ = ScopeType(HeapPtrScope()); + + Scope* scope() const { return scope_.as<HeapPtrScope>(); } + + public: + friend class js::Scope; + + AbstractScopePtr() = default; + + explicit AbstractScopePtr(Scope* scope) : scope_(HeapPtrScope(scope)) {} + + AbstractScopePtr(frontend::CompilationState& compilationState, + ScopeIndex scope) + : scope_(Deferred{scope, compilationState}) {} + + bool isNullptr() const { + if (isScopeStencil()) { + return false; + } + return scope_.as<HeapPtrScope>() == nullptr; + } + + // Return true if this AbstractScopePtr represents a Scope, either existant + // or to be reified. This indicates that queries can be executed on this + // scope data. Returning false is the equivalent to a nullptr, and usually + // indicates the end of the scope chain. + explicit operator bool() const { return !isNullptr(); } + + bool isScopeStencil() const { return scope_.is<Deferred>(); } + + // Note: this handle is rooted in the CompilationState. + frontend::ScopeStencil& scopeData() const; + frontend::CompilationState& compilationState() const; + + // This allows us to check whether or not this provider wraps + // or otherwise would reify to a particular scope type. + template <typename T> + bool is() const { + static_assert(std::is_base_of_v<Scope, T>, + "Trying to ask about non-Scope type"); + if (isNullptr()) { + return false; + } + return kind() == T::classScopeKind_; + } + + ScopeKind kind() const; + AbstractScopePtr enclosing() const; + bool hasEnvironment() const; + // Valid iff is<FunctionScope> + bool isArrow() const; + + bool hasOnChain(ScopeKind kind) const { + for (AbstractScopePtr it = *this; it; it = it.enclosing()) { + if (it.kind() == kind) { + return true; + } + } + return false; + } + + void trace(JSTracer* trc); +}; + +// Specializations of AbstractScopePtr::is +template <> +inline bool AbstractScopePtr::is<GlobalScope>() const { + return !isNullptr() && + (kind() == ScopeKind::Global || kind() == ScopeKind::NonSyntactic); +} + +template <> +inline bool AbstractScopePtr::is<EvalScope>() const { + return !isNullptr() && + (kind() == ScopeKind::Eval || kind() == ScopeKind::StrictEval); +} + +} // namespace js + +namespace JS { +template <> +struct GCPolicy<js::AbstractScopePtr::Deferred> + : JS::IgnoreGCPolicy<js::AbstractScopePtr::Deferred> {}; +} // 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..baa9d87f20 --- /dev/null +++ b/js/src/frontend/AsyncEmitter.cpp @@ -0,0 +1,198 @@ +/* -*- 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 "vm/AsyncFunctionResolveKind.h" // AsyncFunctionResolveKind +#include "vm/Opcodes.h" // JSOp + +using namespace js; +using namespace js::frontend; + +bool AsyncEmitter::prepareForParamsWithExpression() { + 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::prepareForParamsWithoutExpression() { + 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(bce_->cx->parserNames().dotGenerator).hasKnownSlot()); + + NameOpEmitter noe(bce_, bce_->cx->parserNames().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::emitEnd() { +#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..7ed945351c --- /dev/null +++ b/js/src/frontend/AsyncEmitter.h @@ -0,0 +1,163 @@ +/* -*- 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, MOZ_MUST_USE + +#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 parameters: +// `async function f(<params>) {<body>}`, +// AsyncEmitter ae(this); +// +// ae.prepareForParamsWithoutExpression(); +// // 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.emitEnd(); +// +// Complex case - For a function with expression parameters: +// `async function f(<expression>) {<body>}`, +// AsyncEmitter ae(this); +// +// ae.prepareForParamsWithExpression(); +// +// // 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.emitEnd(); +// +// +// 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.emitEnd(); +// + +class MOZ_STACK_CLASS AsyncEmitter { + private: + BytecodeEmitter* bce_; + + // try-catch block for async function parameter and body. + mozilla::Maybe<TryEmitter> rejectTryCatch_; + +#ifdef DEBUG + // The state of this emitter. + // + // +-------+ + // | Start |-+ + // +-------+ | + // | + // +----------+ + // | + // | [Parameters with Expression] + // | prepareForParamsWithExpression +------------+ + // +-------------------------------------| Parameters |-->+ + // | +------------+ | + // | | + // | [Parameters Without Expression] | + // | prepareForParamsWithoutExpression +------------+ | + // +-------------------------------------| Parameters |-->+ + // | +------------+ | + // | [Modules] | + // | prepareForModule +----------------+ | + // +-------------------->| ModulePrologue |--+ | + // +----------------+ | | + // | | + // | | + // +-----------------------------------------+ | + // | | + // | | + // V +------------+ paramsEpilogue | + // +<--------------------| PostParams |<------------------+ + // | +------------+ + // | + // | [Script body] + // | prepareForBody +---------+ + // +-------------------->| Body |--------+ + // +---------+ | <emit script body> + // +----------------------------------------+ + // | + // | emitEnd +-----+ + // +--------------------->| End | + // +-----+ + + enum class State { + // The initial state. + Start, + + Parameters, + + ModulePrologue, + + PostParams, + + Body, + + End, + }; + + State state_ = State::Start; +#endif + + MOZ_MUST_USE bool emitRejectCatch(); + MOZ_MUST_USE bool emitFinalYield(); + + public: + explicit AsyncEmitter(BytecodeEmitter* bce) : bce_(bce){}; + + MOZ_MUST_USE bool prepareForParamsWithoutExpression(); + MOZ_MUST_USE bool prepareForParamsWithExpression(); + MOZ_MUST_USE bool prepareForModule(); + MOZ_MUST_USE bool emitParamsEpilogue(); + MOZ_MUST_USE bool prepareForBody(); + MOZ_MUST_USE bool emitEnd(); +}; + +} /* namespace frontend */ +} /* namespace js */ + +#endif /* frontend_AsyncEmitter_h */ diff --git a/js/src/frontend/BCEParserHandle.h b/js/src/frontend/BCEParserHandle.h new file mode 100644 index 0000000000..4abb5a6f66 --- /dev/null +++ b/js/src/frontend/BCEParserHandle.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_BCEParserHandle_h +#define frontend_BCEParserHandle_h + +#include "frontend/ErrorReporter.h" +#include "frontend/FullParseHandler.h" +#include "frontend/Parser.h" +#include "frontend/ParserAtom.h" + +namespace js { +namespace frontend { + +struct BCEParserHandle { + virtual ErrorReporter& errorReporter() = 0; + virtual const ErrorReporter& errorReporter() const = 0; + + virtual const JS::ReadOnlyCompileOptions& options() const = 0; + + virtual FullParseHandler& astGenerator() = 0; +}; + +} // namespace frontend +} // namespace js + +#endif // frontend_BCEParserHandle_h diff --git a/js/src/frontend/BytecodeCompilation.h b/js/src/frontend/BytecodeCompilation.h new file mode 100644 index 0000000000..25737a559f --- /dev/null +++ b/js/src/frontend/BytecodeCompilation.h @@ -0,0 +1,130 @@ +/* -*- 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/Assertions.h" // MOZ_ASSERT +#include "mozilla/Attributes.h" // MOZ_MUST_USE, MOZ_STACK_CLASS +#include "mozilla/Maybe.h" // mozilla::Maybe, mozilla::Nothing +#include "mozilla/Utf8.h" // mozilla::Utf8Unit + +#include <stddef.h> // size_t +#include <stdint.h> // uint32_t + +#include "jstypes.h" // JS_PUBLIC_API + +#include "frontend/CompilationInfo.h" // CompilationStencil, CompilationStencilSet, CompilationGCOutput +#include "frontend/ParseContext.h" // js::frontend::UsedNameTracker +#include "frontend/SharedContext.h" // js::frontend::Directives, js::frontend::{,Eval,Global}SharedContext +#include "js/CompileOptions.h" // JS::ReadOnlyCompileOptions +#include "js/RootingAPI.h" // JS::{,Mutable}Handle, JS::Rooted +#include "js/SourceText.h" // JS::SourceText +#include "js/UniquePtr.h" // js::UniquePtr +#include "vm/JSScript.h" // js::{FunctionAsync,Generator}Kind, js::BaseScript, JSScript, js::ScriptSource, js::ScriptSourceObject +#include "vm/Scope.h" // js::ScopeKind + +class JS_PUBLIC_API JSFunction; +class JS_PUBLIC_API JSObject; + +class JSObject; + +namespace js { + +class Scope; + +namespace frontend { + +struct BytecodeEmitter; +class EitherParser; + +template <typename Unit> +class SourceAwareCompiler; +template <typename Unit> +class ScriptCompiler; +template <typename Unit> +class ModuleCompiler; +template <typename Unit> +class StandaloneFunctionCompiler; + +extern bool CompileGlobalScriptToStencil(JSContext* cx, + CompilationStencil& stencil, + JS::SourceText<char16_t>& srcBuf, + ScopeKind scopeKind); + +extern bool CompileGlobalScriptToStencil( + JSContext* cx, CompilationStencil& stencil, + JS::SourceText<mozilla::Utf8Unit>& srcBuf, ScopeKind scopeKind); + +extern UniquePtr<CompilationStencil> CompileGlobalScriptToStencil( + JSContext* cx, const JS::ReadOnlyCompileOptions& options, + JS::SourceText<char16_t>& srcBuf, ScopeKind scopeKind); + +extern UniquePtr<CompilationStencil> CompileGlobalScriptToStencil( + JSContext* cx, const JS::ReadOnlyCompileOptions& options, + JS::SourceText<mozilla::Utf8Unit>& 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. +extern bool PrepareForInstantiate(JSContext* cx, CompilationStencil& stencil, + CompilationGCOutput& gcOutput); +extern bool PrepareForInstantiate( + JSContext* cx, CompilationStencilSet& stencilSet, + CompilationGCOutput& gcOutput, + CompilationGCOutput& gcOutputForDelazification); + +extern bool InstantiateStencils(JSContext* cx, CompilationStencil& stencil, + CompilationGCOutput& gcOutput); + +extern bool InstantiateStencils(JSContext* cx, + CompilationStencilSet& stencilSet, + CompilationGCOutput& gcOutput, + CompilationGCOutput& gcOutputForDelazification); + +extern JSScript* CompileGlobalScript(JSContext* cx, + const JS::ReadOnlyCompileOptions& options, + JS::SourceText<char16_t>& srcBuf, + ScopeKind scopeKind); + +extern JSScript* CompileGlobalScript(JSContext* cx, + const JS::ReadOnlyCompileOptions& options, + JS::SourceText<mozilla::Utf8Unit>& srcBuf, + ScopeKind scopeKind); + +extern JSScript* CompileEvalScript(JSContext* cx, + const JS::ReadOnlyCompileOptions& options, + JS::SourceText<char16_t>& srcBuf, + JS::Handle<js::Scope*> enclosingScope, + JS::Handle<JSObject*> enclosingEnv); + +extern void FillCompileOptionsForLazyFunction(JS::CompileOptions& options, + Handle<BaseScript*> lazy); + +extern MOZ_MUST_USE bool CompileLazyFunctionToStencil( + JSContext* cx, CompilationStencil& stencil, JS::Handle<BaseScript*> lazy, + const char16_t* units, size_t length); + +extern MOZ_MUST_USE bool CompileLazyFunctionToStencil( + JSContext* cx, CompilationStencil& stencil, JS::Handle<BaseScript*> lazy, + const mozilla::Utf8Unit* units, size_t length); + +extern bool InstantiateStencilsForDelazify(JSContext* cx, + CompilationStencil& stencil); + +// Certain compile options will disable the syntax parser entirely. +inline bool CanLazilyParse(const JS::ReadOnlyCompileOptions& options) { + return !options.discardSource && !options.sourceIsLazy && + !options.forceFullParse(); +} + +} // 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..a5ac534445 --- /dev/null +++ b/js/src/frontend/BytecodeCompiler.cpp @@ -0,0 +1,1231 @@ +/* -*- 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/IntegerPrintfMacros.h" +#include "mozilla/Maybe.h" +#include "mozilla/Utf8.h" // mozilla::Utf8Unit + +#include "builtin/ModuleObject.h" +#include "frontend/BytecodeCompilation.h" +#include "frontend/BytecodeEmitter.h" +#include "frontend/EitherParser.h" +#include "frontend/ErrorReporter.h" +#include "frontend/FoldConstants.h" +#ifdef JS_ENABLE_SMOOSH +# include "frontend/Frontend2.h" // Smoosh +#endif +#include "frontend/ModuleSharedContext.h" +#include "frontend/Parser.h" +#include "js/SourceText.h" +#include "vm/FunctionFlags.h" // FunctionFlags +#include "vm/GeneratorAndAsyncKind.h" // js::GeneratorKind, js::FunctionAsyncKind +#include "vm/GlobalObject.h" +#include "vm/HelperThreadState.h" // ParseTask +#include "vm/JSContext.h" +#include "vm/JSScript.h" +#include "vm/ModuleBuilder.h" // js::ModuleBuilder +#include "vm/TraceLogging.h" +#include "wasm/AsmJS.h" + +#include "debugger/DebugAPI-inl.h" // DebugAPI +#include "vm/EnvironmentObject-inl.h" +#include "vm/GeckoProfiler-inl.h" +#include "vm/JSContext-inl.h" + +using namespace js; +using namespace js::frontend; + +using mozilla::Maybe; +using mozilla::Nothing; +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* cx_; + bool check_; + + public: + explicit AutoAssertReportedException(JSContext* cx) : cx_(cx), check_(true) {} + void reset() { check_ = false; } + ~AutoAssertReportedException() { + if (!check_) { + return; + } + + if (!cx_->isHelperThreadContext()) { + MOZ_ASSERT(cx_->isExceptionPending()); + return; + } + + ParseTask* task = cx_->parseTask(); + MOZ_ASSERT(task->outOfMemory || task->overRecursed || + !task->errors.empty()); + } +#else + public: + explicit AutoAssertReportedException(JSContext*) {} + void reset() {} +#endif +}; + +static bool EmplaceEmitter(CompilationStencil& stencil, + CompilationState& compilationState, + Maybe<BytecodeEmitter>& emitter, + const EitherParser& parser, SharedContext* sc); + +template <typename Unit> +class MOZ_STACK_CLASS frontend::SourceAwareCompiler { + protected: + SourceText<Unit>& sourceBuffer_; + + frontend::CompilationState compilationState_; + + Maybe<Parser<SyntaxParseHandler, Unit>> syntaxParser; + Maybe<Parser<FullParseHandler, Unit>> parser; + + using TokenStreamPosition = frontend::TokenStreamPosition<Unit>; + + protected: + explicit SourceAwareCompiler(JSContext* cx, LifoAllocScope& allocScope, + const JS::ReadOnlyCompileOptions& options, + CompilationStencil& stencil, + SourceText<Unit>& sourceBuffer, + InheritThis inheritThis = InheritThis::No, + js::Scope* enclosingScope = nullptr, + JSObject* enclosingEnv = nullptr) + : sourceBuffer_(sourceBuffer), + compilationState_(cx, allocScope, options, stencil, inheritThis, + enclosingScope, enclosingEnv) { + MOZ_ASSERT(sourceBuffer_.get() != nullptr); + } + + // Call this before calling compile{Global,Eval}Script. + MOZ_MUST_USE bool createSourceAndParser(JSContext* cx, + CompilationStencil& stencil); + + void assertSourceAndParserCreated(CompilationInput& compilationInput) const { + MOZ_ASSERT(compilationInput.source() != nullptr); + MOZ_ASSERT(parser.isSome()); + } + + void assertSourceParserAndScriptCreated(CompilationInput& compilationInput) { + assertSourceAndParserCreated(compilationInput); + } + + MOZ_MUST_USE bool emplaceEmitter(CompilationStencil& stencil, + Maybe<BytecodeEmitter>& emitter, + SharedContext* sharedContext) { + return EmplaceEmitter(stencil, compilationState_, emitter, + EitherParser(parser.ptr()), sharedContext); + } + + bool canHandleParseFailure(const Directives& newDirectives); + + void handleParseFailure(CompilationStencil& stencil, + const Directives& newDirectives, + TokenStreamPosition& startPosition, + CompilationStencil::RewindToken& startObj); + + public: + frontend::CompilationState& compilationState() { return compilationState_; }; +}; + +template <typename Unit> +class MOZ_STACK_CLASS frontend::ScriptCompiler + : public SourceAwareCompiler<Unit> { + using Base = SourceAwareCompiler<Unit>; + + 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(JSContext* cx, LifoAllocScope& allocScope, + const JS::ReadOnlyCompileOptions& options, + CompilationStencil& stencil, + SourceText<Unit>& sourceBuffer, + InheritThis inheritThis = InheritThis::No, + js::Scope* enclosingScope = nullptr, + JSObject* enclosingEnv = nullptr) + : Base(cx, allocScope, options, stencil, sourceBuffer, inheritThis, + enclosingScope, enclosingEnv) {} + + using Base::createSourceAndParser; + + bool compileScriptToStencil(JSContext* cx, CompilationStencil& stencil, + SharedContext* sc); +}; + +#ifdef JS_ENABLE_SMOOSH +bool TrySmoosh(JSContext* cx, CompilationStencil& stencil, + JS::SourceText<Utf8Unit>& srcBuf, bool* fallback) { + if (!cx->options().trySmoosh()) { + *fallback = true; + return true; + } + + bool unimplemented = false; + JSRuntime* rt = cx->runtime(); + bool result = + Smoosh::compileGlobalScriptToStencil(cx, stencil, srcBuf, &unimplemented); + if (!unimplemented) { + *fallback = false; + + if (!stencil.input.assignSource(cx, srcBuf)) { + return false; + } + + if (cx->options().trackNotImplemented()) { + rt->parserWatcherFile.put("1"); + } + return result; + } + *fallback = true; + + if (cx->options().trackNotImplemented()) { + rt->parserWatcherFile.put("0"); + } + fprintf(stderr, "Falling back!\n"); + + return true; +} + +bool TrySmoosh(JSContext* cx, CompilationStencil& stencil, + JS::SourceText<char16_t>& srcBuf, bool* fallback) { + *fallback = true; + return true; +} +#endif // JS_ENABLE_SMOOSH + +template <typename Unit> +static bool CompileGlobalScriptToStencilImpl(JSContext* cx, + CompilationStencil& stencil, + JS::SourceText<Unit>& srcBuf, + ScopeKind scopeKind) { +#ifdef JS_ENABLE_SMOOSH + bool fallback = false; + if (!TrySmoosh(cx, stencil, srcBuf, &fallback)) { + return false; + } + if (!fallback) { + return true; + } +#endif // JS_ENABLE_SMOOSH + + AutoAssertReportedException assertException(cx); + + LifoAllocScope allocScope(&cx->tempLifoAlloc()); + frontend::ScriptCompiler<Unit> compiler(cx, allocScope, stencil.input.options, + stencil, srcBuf); + + if (!compiler.createSourceAndParser(cx, stencil)) { + return false; + } + + SourceExtent extent = SourceExtent::makeGlobalExtent( + srcBuf.length(), stencil.input.options.lineno, + stencil.input.options.column); + frontend::GlobalSharedContext globalsc( + cx, scopeKind, stencil, compiler.compilationState().directives, extent); + + if (!compiler.compileScriptToStencil(cx, stencil, &globalsc)) { + return false; + } + + assertException.reset(); + return true; +} + +bool frontend::CompileGlobalScriptToStencil(JSContext* cx, + CompilationStencil& stencil, + JS::SourceText<char16_t>& srcBuf, + ScopeKind scopeKind) { + return CompileGlobalScriptToStencilImpl(cx, stencil, srcBuf, scopeKind); +} + +bool frontend::CompileGlobalScriptToStencil(JSContext* cx, + CompilationStencil& stencil, + JS::SourceText<Utf8Unit>& srcBuf, + ScopeKind scopeKind) { + return CompileGlobalScriptToStencilImpl(cx, stencil, srcBuf, scopeKind); +} + +template <typename Unit> +static UniquePtr<CompilationStencil> CompileGlobalScriptToStencilImpl( + JSContext* cx, const ReadOnlyCompileOptions& options, + JS::SourceText<Unit>& srcBuf, ScopeKind scopeKind) { + Rooted<UniquePtr<frontend::CompilationStencil>> stencil( + cx, js_new<frontend::CompilationStencil>(cx, options)); + if (!stencil) { + ReportOutOfMemory(cx); + return nullptr; + } + + if (!stencil.get()->input.initForGlobal(cx)) { + return nullptr; + } + + if (!CompileGlobalScriptToStencil(cx, *stencil, srcBuf, scopeKind)) { + return nullptr; + } + + return std::move(stencil.get()); +} + +UniquePtr<CompilationStencil> frontend::CompileGlobalScriptToStencil( + JSContext* cx, const ReadOnlyCompileOptions& options, + JS::SourceText<char16_t>& srcBuf, ScopeKind scopeKind) { + return CompileGlobalScriptToStencilImpl(cx, options, srcBuf, scopeKind); +} + +UniquePtr<CompilationStencil> frontend::CompileGlobalScriptToStencil( + JSContext* cx, const ReadOnlyCompileOptions& options, + JS::SourceText<Utf8Unit>& srcBuf, ScopeKind scopeKind) { + return CompileGlobalScriptToStencilImpl(cx, options, srcBuf, scopeKind); +} + +bool frontend::InstantiateStencils(JSContext* cx, CompilationStencil& stencil, + CompilationGCOutput& gcOutput) { + { + AutoGeckoProfilerEntry pseudoFrame(cx, "stencil instantiate", + JS::ProfilingCategoryPair::JS_Parsing); + + if (!CompilationStencil::instantiateStencils(cx, stencil, gcOutput)) { + return false; + } + } + + // Enqueue an off-thread source compression task after finishing parsing. + if (!cx->isHelperThreadContext()) { + if (!stencil.input.source()->tryCompressOffThread(cx)) { + return false; + } + + Rooted<JSScript*> script(cx, gcOutput.script); + if (!stencil.input.options.hideScriptFromDebugger) { + DebugAPI::onNewScript(cx, script); + } + } + + return true; +} + +bool frontend::InstantiateStencils( + JSContext* cx, CompilationStencilSet& stencilSet, + CompilationGCOutput& gcOutput, + CompilationGCOutput& gcOutputForDelazification_) { + { + AutoGeckoProfilerEntry pseudoFrame(cx, "stencil instantiate", + JS::ProfilingCategoryPair::JS_Parsing); + + if (!stencilSet.instantiateStencils(cx, gcOutput, + gcOutputForDelazification_)) { + return false; + } + } + + // Enqueue an off-thread source compression task after finishing parsing. + if (!cx->isHelperThreadContext()) { + if (!stencilSet.input.source()->tryCompressOffThread(cx)) { + return false; + } + + Rooted<JSScript*> script(cx, gcOutput.script); + if (!stencilSet.input.options.hideScriptFromDebugger) { + DebugAPI::onNewScript(cx, script); + } + } + + return true; +} + +bool frontend::PrepareForInstantiate(JSContext* cx, CompilationStencil& stencil, + CompilationGCOutput& gcOutput) { + AutoGeckoProfilerEntry pseudoFrame(cx, "stencil instantiate", + JS::ProfilingCategoryPair::JS_Parsing); + + return CompilationStencil::prepareForInstantiate(cx, stencil, gcOutput); +} + +bool frontend::PrepareForInstantiate( + JSContext* cx, CompilationStencilSet& stencilSet, + CompilationGCOutput& gcOutput, + CompilationGCOutput& gcOutputForDelazification_) { + AutoGeckoProfilerEntry pseudoFrame(cx, "stencil instantiate", + JS::ProfilingCategoryPair::JS_Parsing); + + return stencilSet.prepareForInstantiate(cx, gcOutput, + gcOutputForDelazification_); +} + +template <typename Unit> +static JSScript* CompileGlobalScriptImpl( + JSContext* cx, const JS::ReadOnlyCompileOptions& options, + JS::SourceText<Unit>& srcBuf, ScopeKind scopeKind) { + Rooted<CompilationStencil> stencil(cx, CompilationStencil(cx, options)); + if (options.selfHostingMode) { + if (!stencil.get().input.initForSelfHostingGlobal(cx)) { + return nullptr; + } + } else { + if (!stencil.get().input.initForGlobal(cx)) { + return nullptr; + } + } + + if (!CompileGlobalScriptToStencil(cx, stencil.get(), srcBuf, scopeKind)) { + return nullptr; + } + + Rooted<frontend::CompilationGCOutput> gcOutput(cx); + if (!InstantiateStencils(cx, stencil.get(), gcOutput.get())) { + return nullptr; + } + + return gcOutput.get().script; +} + +JSScript* frontend::CompileGlobalScript( + JSContext* cx, const JS::ReadOnlyCompileOptions& options, + JS::SourceText<char16_t>& srcBuf, ScopeKind scopeKind) { + return CompileGlobalScriptImpl(cx, options, srcBuf, scopeKind); +} + +JSScript* frontend::CompileGlobalScript( + JSContext* cx, const JS::ReadOnlyCompileOptions& options, + JS::SourceText<Utf8Unit>& srcBuf, ScopeKind scopeKind) { + return CompileGlobalScriptImpl(cx, options, srcBuf, scopeKind); +} + +template <typename Unit> +static JSScript* CompileEvalScriptImpl( + JSContext* cx, const JS::ReadOnlyCompileOptions& options, + SourceText<Unit>& srcBuf, JS::Handle<js::Scope*> enclosingScope, + JS::Handle<JSObject*> enclosingEnv) { + AutoAssertReportedException assertException(cx); + + Rooted<CompilationStencil> stencil(cx, CompilationStencil(cx, options)); + if (!stencil.get().input.initForEval(cx, enclosingScope)) { + return nullptr; + } + + LifoAllocScope allocScope(&cx->tempLifoAlloc()); + + frontend::ScriptCompiler<Unit> compiler( + cx, allocScope, stencil.get().input.options, stencil.get(), srcBuf, + InheritThis::Yes, enclosingScope, enclosingEnv); + if (!compiler.createSourceAndParser(cx, stencil.get())) { + return nullptr; + } + + uint32_t len = srcBuf.length(); + SourceExtent extent = + SourceExtent::makeGlobalExtent(len, stencil.get().input.options.lineno, + stencil.get().input.options.column); + frontend::EvalSharedContext evalsc(cx, stencil.get(), + compiler.compilationState(), extent); + if (!compiler.compileScriptToStencil(cx, stencil.get(), &evalsc)) { + return nullptr; + } + + Rooted<frontend::CompilationGCOutput> gcOutput(cx); + if (!InstantiateStencils(cx, stencil.get(), gcOutput.get())) { + return nullptr; + } + + assertException.reset(); + return gcOutput.get().script; +} + +JSScript* frontend::CompileEvalScript(JSContext* cx, + const JS::ReadOnlyCompileOptions& options, + JS::SourceText<char16_t>& srcBuf, + JS::Handle<js::Scope*> enclosingScope, + JS::Handle<JSObject*> enclosingEnv) { + return CompileEvalScriptImpl(cx, options, srcBuf, enclosingScope, + enclosingEnv); +} + +template <typename Unit> +class MOZ_STACK_CLASS frontend::ModuleCompiler final + : public SourceAwareCompiler<Unit> { + using Base = SourceAwareCompiler<Unit>; + + using Base::assertSourceParserAndScriptCreated; + using Base::compilationState_; + using Base::createSourceAndParser; + using Base::emplaceEmitter; + using Base::parser; + + public: + explicit ModuleCompiler(JSContext* cx, LifoAllocScope& allocScope, + const JS::ReadOnlyCompileOptions& options, + CompilationStencil& stencil, + SourceText<Unit>& sourceBuffer, + js::Scope* enclosingScope = nullptr, + JSObject* enclosingEnv = nullptr) + : Base(cx, allocScope, options, stencil, sourceBuffer, InheritThis::No, + enclosingScope, enclosingEnv) {} + + bool compile(JSContext* cx, CompilationStencil& stencil); +}; + +template <typename Unit> +class MOZ_STACK_CLASS frontend::StandaloneFunctionCompiler final + : public SourceAwareCompiler<Unit> { + using Base = SourceAwareCompiler<Unit>; + + 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(JSContext* cx, LifoAllocScope& allocScope, + const JS::ReadOnlyCompileOptions& options, + CompilationStencil& stencil, + SourceText<Unit>& sourceBuffer, + InheritThis inheritThis = InheritThis::No, + js::Scope* enclosingScope = nullptr, + JSObject* enclosingEnv = nullptr) + : Base(cx, allocScope, options, stencil, sourceBuffer, inheritThis, + enclosingScope, enclosingEnv) {} + + using Base::createSourceAndParser; + + FunctionNode* parse(JSContext* cx, CompilationStencil& stencil, + FunctionSyntaxKind syntaxKind, + GeneratorKind generatorKind, FunctionAsyncKind asyncKind, + const Maybe<uint32_t>& parameterListEnd); + + bool compile(JSContext* cx, CompilationStencil& stencil, + FunctionNode* parsedFunction, CompilationGCOutput& gcOutput); +}; + +AutoFrontendTraceLog::AutoFrontendTraceLog(JSContext* cx, + const TraceLoggerTextId id, + const ErrorReporter& errorReporter) +#ifdef JS_TRACE_LOGGING + : logger_(TraceLoggerForCurrentThread(cx)) { + if (!logger_) { + return; + } + + // If the tokenizer hasn't yet gotten any tokens, use the line and column + // numbers from CompileOptions. + uint32_t line, column; + if (errorReporter.hasTokenizationStarted()) { + line = errorReporter.options().lineno; + column = errorReporter.options().column; + } else { + errorReporter.currentLineAndColumn(&line, &column); + } + frontendEvent_.emplace(TraceLogger_Frontend, errorReporter.getFilename(), + line, column); + frontendLog_.emplace(logger_, *frontendEvent_); + typeLog_.emplace(logger_, id); +} +#else +{ +} +#endif + +AutoFrontendTraceLog::AutoFrontendTraceLog(JSContext* cx, + const TraceLoggerTextId id, + const ErrorReporter& errorReporter, + FunctionBox* funbox) +#ifdef JS_TRACE_LOGGING + : logger_(TraceLoggerForCurrentThread(cx)) { + if (!logger_) { + return; + } + + frontendEvent_.emplace(TraceLogger_Frontend, errorReporter.getFilename(), + funbox->extent().lineno, funbox->extent().column); + frontendLog_.emplace(logger_, *frontendEvent_); + typeLog_.emplace(logger_, id); +} +#else +{ +} +#endif + +AutoFrontendTraceLog::AutoFrontendTraceLog(JSContext* cx, + const TraceLoggerTextId id, + const ErrorReporter& errorReporter, + ParseNode* pn) +#ifdef JS_TRACE_LOGGING + : logger_(TraceLoggerForCurrentThread(cx)) { + if (!logger_) { + return; + } + + uint32_t line, column; + errorReporter.lineAndColumnAt(pn->pn_pos.begin, &line, &column); + frontendEvent_.emplace(TraceLogger_Frontend, errorReporter.getFilename(), + line, column); + frontendLog_.emplace(logger_, *frontendEvent_); + typeLog_.emplace(logger_, id); +} +#else +{ +} +#endif + +template <typename Unit> +bool frontend::SourceAwareCompiler<Unit>::createSourceAndParser( + JSContext* cx, CompilationStencil& stencil) { + if (!stencil.input.assignSource(cx, sourceBuffer_)) { + return false; + } + + if (CanLazilyParse(stencil.input.options)) { + syntaxParser.emplace(cx, stencil.input.options, sourceBuffer_.units(), + sourceBuffer_.length(), + /* foldConstants = */ false, stencil, + compilationState_, nullptr, nullptr); + if (!syntaxParser->checkOptions()) { + return false; + } + } + + parser.emplace(cx, stencil.input.options, sourceBuffer_.units(), + sourceBuffer_.length(), + /* foldConstants = */ true, stencil, compilationState_, + syntaxParser.ptrOr(nullptr), nullptr); + parser->ss = stencil.input.source(); + return parser->checkOptions(); +} + +static bool EmplaceEmitter(CompilationStencil& stencil, + CompilationState& compilationState, + Maybe<BytecodeEmitter>& emitter, + const EitherParser& parser, SharedContext* sc) { + BytecodeEmitter::EmitterMode emitterMode = + sc->selfHosted() ? BytecodeEmitter::SelfHosting : BytecodeEmitter::Normal; + emitter.emplace(/* parent = */ nullptr, parser, sc, stencil, compilationState, + emitterMode); + return emitter->init(); +} + +template <typename Unit> +bool frontend::SourceAwareCompiler<Unit>::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 <typename Unit> +void frontend::SourceAwareCompiler<Unit>::handleParseFailure( + CompilationStencil& stencil, const Directives& newDirectives, + TokenStreamPosition& startPosition, + CompilationStencil::RewindToken& startObj) { + MOZ_ASSERT(canHandleParseFailure(newDirectives)); + + // Rewind to starting position to retry. + parser->tokenStream.rewind(startPosition); + stencil.rewind(compilationState_, startObj); + + // 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 <typename Unit> +bool frontend::ScriptCompiler<Unit>::compileScriptToStencil( + JSContext* cx, CompilationStencil& stencil, SharedContext* sc) { + assertSourceParserAndScriptCreated(stencil.input); + + TokenStreamPosition startPosition(parser->tokenStream); + + // Emplace the topLevel stencil + MOZ_ASSERT(compilationState_.scriptData.length() == + CompilationStencil::TopLevelIndex); + if (!compilationState_.scriptData.emplaceBack()) { + ReportOutOfMemory(cx); + return false; + } + if (!compilationState_.scriptExtra.emplaceBack()) { + ReportOutOfMemory(cx); + return false; + } + + ParseNode* pn; + { + AutoGeckoProfilerEntry pseudoFrame(cx, "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. + AutoGeckoProfilerEntry pseudoFrame(cx, "script emit", + JS::ProfilingCategoryPair::JS_Parsing); + + Maybe<BytecodeEmitter> emitter; + if (!emplaceEmitter(stencil, emitter, sc)) { + return false; + } + + if (!emitter->emitScript(pn)) { + return false; + } + + if (!compilationState_.finish(cx, stencil)) { + return false; + } + } + + MOZ_ASSERT_IF(!cx->isHelperThreadContext(), !cx->isExceptionPending()); + + return true; +} + +template <typename Unit> +bool frontend::ModuleCompiler<Unit>::compile(JSContext* cx, + CompilationStencil& stencil) { + if (!createSourceAndParser(cx, stencil)) { + return false; + } + + // Emplace the topLevel stencil + MOZ_ASSERT(compilationState_.scriptData.length() == + CompilationStencil::TopLevelIndex); + if (!compilationState_.scriptData.emplaceBack()) { + ReportOutOfMemory(cx); + return false; + } + if (!compilationState_.scriptExtra.emplaceBack()) { + ReportOutOfMemory(cx); + return false; + } + + ModuleBuilder builder(cx, parser.ptr()); + + uint32_t len = this->sourceBuffer_.length(); + SourceExtent extent = SourceExtent::makeGlobalExtent( + len, stencil.input.options.lineno, stencil.input.options.column); + ModuleSharedContext modulesc(cx, stencil, builder, extent); + + ParseNode* pn = parser->moduleBody(&modulesc); + if (!pn) { + return false; + } + + Maybe<BytecodeEmitter> emitter; + if (!emplaceEmitter(stencil, emitter, &modulesc)) { + return false; + } + + if (!emitter->emitScript(pn->as<ModuleNode>().body())) { + return false; + } + + if (!compilationState_.finish(cx, stencil)) { + return false; + } + + StencilModuleMetadata& moduleMetadata = *stencil.moduleMetadata; + + builder.finishFunctionDecls(moduleMetadata); + + MOZ_ASSERT_IF(!cx->isHelperThreadContext(), !cx->isExceptionPending()); + return true; +} + +// Parse a standalone JS function, which might appear as the value of an +// event handler attribute in an HTML <INPUT> tag, or in a Function() +// constructor. +template <typename Unit> +FunctionNode* frontend::StandaloneFunctionCompiler<Unit>::parse( + JSContext* cx, CompilationStencil& stencil, FunctionSyntaxKind syntaxKind, + GeneratorKind generatorKind, FunctionAsyncKind asyncKind, + const Maybe<uint32_t>& parameterListEnd) { + assertSourceAndParserCreated(stencil.input); + + TokenStreamPosition startPosition(parser->tokenStream); + CompilationStencil::RewindToken startObj = + stencil.getRewindToken(compilationState_); + + // 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(stencil, newDirectives, startPosition, startObj); + } + + return fn; +} + +// Compile a standalone JS function. +template <typename Unit> +bool frontend::StandaloneFunctionCompiler<Unit>::compile( + JSContext* cx, CompilationStencil& stencil, FunctionNode* parsedFunction, + CompilationGCOutput& gcOutput) { + FunctionBox* funbox = parsedFunction->funbox(); + + if (funbox->isInterpreted()) { + Maybe<BytecodeEmitter> emitter; + if (!emplaceEmitter(stencil, 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. + compilationState_.scriptExtra[CompilationStencil::TopLevelIndex].extent = + SourceExtent{/* sourceStart = */ 0, + sourceBuffer_.length(), + funbox->extent().toStringStart, + funbox->extent().toStringEnd, + stencil.input.options.lineno, + stencil.input.options.column}; + + if (!compilationState_.finish(cx, stencil)) { + return false; + } + } else { + if (!compilationState_.finish(cx, stencil)) { + return false; + } + + // The asm.js module was created by parser. Instantiation below will + // allocate the JSFunction that wraps it. + MOZ_ASSERT(funbox->isAsmJSModule()); + MOZ_ASSERT(stencil.asmJS.has(funbox->index())); + MOZ_ASSERT(compilationState_.scriptData[CompilationStencil::TopLevelIndex] + .functionFlags.isAsmJSNative()); + } + + if (!CompilationStencil::instantiateStencils(cx, stencil, gcOutput)) { + return false; + } + +#ifdef DEBUG + JSFunction* fun = gcOutput.functions[CompilationStencil::TopLevelIndex]; + MOZ_ASSERT(fun->hasBytecode() || IsAsmJSModule(fun)); +#endif + + // Enqueue an off-thread source compression task after finishing parsing. + if (!cx->isHelperThreadContext()) { + if (!stencil.input.source()->tryCompressOffThread(cx)) { + return false; + } + } + + return true; +} + +template <typename Unit> +static bool ParseModuleToStencilImpl(JSContext* cx, CompilationStencil& stencil, + SourceText<Unit>& srcBuf) { + MOZ_ASSERT(srcBuf.get()); + + AutoAssertReportedException assertException(cx); + + LifoAllocScope allocScope(&cx->tempLifoAlloc()); + ModuleCompiler<Unit> compiler(cx, allocScope, stencil.input.options, stencil, + srcBuf); + if (!compiler.compile(cx, stencil)) { + return false; + } + + assertException.reset(); + return true; +} + +bool frontend::ParseModuleToStencil(JSContext* cx, CompilationStencil& stencil, + SourceText<char16_t>& srcBuf) { + return ParseModuleToStencilImpl(cx, stencil, srcBuf); +} + +bool frontend::ParseModuleToStencil(JSContext* cx, CompilationStencil& stencil, + SourceText<Utf8Unit>& srcBuf) { + return ParseModuleToStencilImpl(cx, stencil, srcBuf); +} + +template <typename Unit> +static UniquePtr<CompilationStencil> ParseModuleToStencilImpl( + JSContext* cx, const ReadOnlyCompileOptions& options, + SourceText<Unit>& srcBuf) { + Rooted<UniquePtr<frontend::CompilationStencil>> stencil( + cx, js_new<frontend::CompilationStencil>(cx, options)); + if (!stencil) { + ReportOutOfMemory(cx); + return nullptr; + } + + if (!stencil.get()->input.initForModule(cx)) { + return nullptr; + } + + if (!ParseModuleToStencilImpl(cx, *stencil, srcBuf)) { + return nullptr; + } + + return std::move(stencil.get()); +} + +UniquePtr<CompilationStencil> frontend::ParseModuleToStencil( + JSContext* cx, const ReadOnlyCompileOptions& options, + SourceText<char16_t>& srcBuf) { + return ParseModuleToStencilImpl(cx, options, srcBuf); +} + +UniquePtr<CompilationStencil> frontend::ParseModuleToStencil( + JSContext* cx, const ReadOnlyCompileOptions& options, + SourceText<Utf8Unit>& srcBuf) { + return ParseModuleToStencilImpl(cx, options, srcBuf); +} + +template <typename Unit> +static ModuleObject* CompileModuleImpl( + JSContext* cx, const JS::ReadOnlyCompileOptions& optionsInput, + SourceText<Unit>& srcBuf) { + AutoAssertReportedException assertException(cx); + + if (!GlobalObject::ensureModulePrototypesCreated(cx, cx->global())) { + return nullptr; + } + + CompileOptions options(cx, optionsInput); + options.setModule(); + + Rooted<CompilationStencil> stencil(cx, CompilationStencil(cx, options)); + if (!stencil.get().input.initForModule(cx)) { + return nullptr; + } + + if (!ParseModuleToStencil(cx, stencil.get(), srcBuf)) { + return nullptr; + } + + Rooted<CompilationGCOutput> gcOutput(cx); + if (!InstantiateStencils(cx, stencil.get(), gcOutput.get())) { + return nullptr; + } + + assertException.reset(); + return gcOutput.get().module; +} + +ModuleObject* frontend::CompileModule(JSContext* cx, + const JS::ReadOnlyCompileOptions& options, + SourceText<char16_t>& srcBuf) { + return CompileModuleImpl(cx, options, srcBuf); +} + +ModuleObject* frontend::CompileModule(JSContext* cx, + const JS::ReadOnlyCompileOptions& options, + SourceText<Utf8Unit>& srcBuf) { + return CompileModuleImpl(cx, options, srcBuf); +} + +void frontend::FillCompileOptionsForLazyFunction(JS::CompileOptions& options, + JS::Handle<BaseScript*> lazy) { + options.setMutedErrors(lazy->mutedErrors()) + .setFileAndLine(lazy->filename(), lazy->lineno()) + .setColumn(lazy->column()) + .setScriptSourceOffset(lazy->sourceStart()) + .setNoScriptRval(false) + .setSelfHostingMode(false); +} + +template <typename Unit> +static bool CompileLazyFunctionToStencilImpl(JSContext* cx, + CompilationStencil& stencil, + Handle<BaseScript*> lazy, + const Unit* units, size_t length) { + MOZ_ASSERT(cx->compartment() == lazy->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(lazy->isReadyForDelazification()); + + AutoAssertReportedException assertException(cx); + + Rooted<JSFunction*> fun(cx, lazy->function()); + + InheritThis inheritThis = fun->isArrow() ? InheritThis::Yes : InheritThis::No; + + LifoAllocScope allocScope(&cx->tempLifoAlloc()); + frontend::CompilationState compilationState( + cx, allocScope, stencil.input.options, stencil, inheritThis, + fun->enclosingScope()); + + Parser<FullParseHandler, Unit> parser( + cx, stencil.input.options, units, length, + /* foldConstants = */ true, stencil, compilationState, nullptr, lazy); + if (!parser.checkOptions()) { + return false; + } + + AutoGeckoProfilerEntry pseudoFrame(cx, "script delazify", + JS::ProfilingCategoryPair::JS_Parsing); + + FunctionNode* pn = + parser.standaloneLazyFunction(fun, lazy->toStringStart(), lazy->strict(), + lazy->generatorKind(), lazy->asyncKind()); + if (!pn) { + return false; + } + + BytecodeEmitter bce(/* parent = */ nullptr, &parser, pn->funbox(), stencil, + 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 = lazy->hasPrivateScriptData(); + bool isRelazifiableAfterDelazify = lazy->isRelazifiableAfterDelazify(); + if (isRelazifiableAfterDelazify && !hadLazyScriptData) { + compilationState.scriptData[CompilationStencil::TopLevelIndex] + .setAllowRelazify(); + } + + if (!compilationState.finish(cx, stencil)) { + return false; + } + + // Record the FunctionKey in the BaseCompilationStencil since it does not + // contain any of the SourceExtents itself. + stencil.functionKey = BaseCompilationStencil::toFunctionKey(lazy->extent()); + + assertException.reset(); + return true; +} + +MOZ_MUST_USE bool frontend::CompileLazyFunctionToStencil( + JSContext* cx, CompilationStencil& stencil, JS::Handle<BaseScript*> lazy, + const char16_t* units, size_t length) { + return CompileLazyFunctionToStencilImpl(cx, stencil, lazy, units, length); +} + +MOZ_MUST_USE bool frontend::CompileLazyFunctionToStencil( + JSContext* cx, CompilationStencil& stencil, JS::Handle<BaseScript*> lazy, + const mozilla::Utf8Unit* units, size_t length) { + return CompileLazyFunctionToStencilImpl(cx, stencil, lazy, units, length); +} + +bool frontend::InstantiateStencilsForDelazify(JSContext* cx, + CompilationStencil& stencil) { + AutoAssertReportedException assertException(cx); + + mozilla::DebugOnly<uint32_t> lazyFlags = + static_cast<uint32_t>(stencil.input.lazy->immutableFlags()); + + Rooted<CompilationGCOutput> gcOutput(cx); + if (!CompilationStencil::instantiateStencils(cx, stencil, gcOutput.get())) { + return false; + } + + MOZ_ASSERT(lazyFlags == gcOutput.get().script->immutableFlags()); + MOZ_ASSERT(gcOutput.get().script->outermostScope()->hasOnChain( + ScopeKind::NonSyntactic) == + gcOutput.get().script->immutableFlags().hasFlag( + JSScript::ImmutableFlags::HasNonSyntacticScope)); + + assertException.reset(); + return true; +} + +static JSFunction* CompileStandaloneFunction( + JSContext* cx, const JS::ReadOnlyCompileOptions& options, + JS::SourceText<char16_t>& srcBuf, const Maybe<uint32_t>& parameterListEnd, + FunctionSyntaxKind syntaxKind, GeneratorKind generatorKind, + FunctionAsyncKind asyncKind, HandleScope enclosingScope = nullptr) { + AutoAssertReportedException assertException(cx); + + RootedScope scope(cx, enclosingScope); + if (!scope) { + scope = &cx->global()->emptyGlobalScope(); + } + + Rooted<CompilationStencil> stencil(cx, CompilationStencil(cx, options)); + if (!stencil.get().input.initForStandaloneFunction(cx, scope)) { + return nullptr; + } + + LifoAllocScope allocScope(&cx->tempLifoAlloc()); + InheritThis inheritThis = (syntaxKind == FunctionSyntaxKind::Arrow) + ? InheritThis::Yes + : InheritThis::No; + StandaloneFunctionCompiler<char16_t> compiler( + cx, allocScope, stencil.get().input.options, stencil.get(), srcBuf, + inheritThis, enclosingScope); + if (!compiler.createSourceAndParser(cx, stencil.get())) { + return nullptr; + } + + FunctionNode* parsedFunction = + compiler.parse(cx, stencil.get(), syntaxKind, generatorKind, asyncKind, + parameterListEnd); + if (!parsedFunction) { + return nullptr; + } + + Rooted<CompilationGCOutput> gcOutput(cx); + if (!compiler.compile(cx, stencil.get(), parsedFunction, gcOutput.get())) { + 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) { + stencil.get().input.source()->setParameterListEnd(*parameterListEnd); + } + + MOZ_ASSERT(!cx->isHelperThreadContext()); + + Rooted<JSScript*> script(cx, gcOutput.get().script); + if (!options.hideScriptFromDebugger) { + DebugAPI::onNewScript(cx, script); + } + } + + assertException.reset(); + return gcOutput.get().functions[CompilationStencil::TopLevelIndex]; +} + +JSFunction* frontend::CompileStandaloneFunction( + JSContext* cx, const JS::ReadOnlyCompileOptions& options, + JS::SourceText<char16_t>& srcBuf, const Maybe<uint32_t>& parameterListEnd, + FunctionSyntaxKind syntaxKind, HandleScope enclosingScope /* = nullptr */) { + return CompileStandaloneFunction(cx, options, srcBuf, parameterListEnd, + syntaxKind, GeneratorKind::NotGenerator, + FunctionAsyncKind::SyncFunction, + enclosingScope); +} + +JSFunction* frontend::CompileStandaloneGenerator( + JSContext* cx, const JS::ReadOnlyCompileOptions& options, + JS::SourceText<char16_t>& srcBuf, const Maybe<uint32_t>& parameterListEnd, + FunctionSyntaxKind syntaxKind) { + return CompileStandaloneFunction(cx, options, srcBuf, parameterListEnd, + syntaxKind, GeneratorKind::Generator, + FunctionAsyncKind::SyncFunction); +} + +JSFunction* frontend::CompileStandaloneAsyncFunction( + JSContext* cx, const ReadOnlyCompileOptions& options, + JS::SourceText<char16_t>& srcBuf, const Maybe<uint32_t>& parameterListEnd, + FunctionSyntaxKind syntaxKind) { + return CompileStandaloneFunction(cx, options, srcBuf, parameterListEnd, + syntaxKind, GeneratorKind::NotGenerator, + FunctionAsyncKind::AsyncFunction); +} + +JSFunction* frontend::CompileStandaloneAsyncGenerator( + JSContext* cx, const ReadOnlyCompileOptions& options, + JS::SourceText<char16_t>& srcBuf, const Maybe<uint32_t>& parameterListEnd, + FunctionSyntaxKind syntaxKind) { + return CompileStandaloneFunction(cx, options, srcBuf, parameterListEnd, + syntaxKind, GeneratorKind::Generator, + FunctionAsyncKind::AsyncFunction); +} + +bool frontend::CompilationInput::initScriptSource(JSContext* cx) { + ScriptSource* ss = cx->new_<ScriptSource>(); + if (!ss) { + return false; + } + setSource(ss); + + return ss->initFromOptions(cx, options); +} + +void CompilationInput::trace(JSTracer* trc) { + atomCache.trace(trc); + TraceNullableRoot(trc, &lazy, "compilation-input-lazy"); + source_.trace(trc); + TraceNullableRoot(trc, &enclosingScope, "compilation-input-enclosing-scope"); +} + +void CompilationAtomCache::trace(JSTracer* trc) { atoms_.trace(trc); } + +void CompilationStencil::trace(JSTracer* trc) { input.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); +} diff --git a/js/src/frontend/BytecodeCompiler.h b/js/src/frontend/BytecodeCompiler.h new file mode 100644 index 0000000000..52019b212c --- /dev/null +++ b/js/src/frontend/BytecodeCompiler.h @@ -0,0 +1,224 @@ +/* -*- 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 "frontend/FunctionSyntaxKind.h" +#include "js/CompileOptions.h" // JS::ReadOnlyCompileOptions +#include "js/SourceText.h" +#include "js/UniquePtr.h" // js::UniquePtr +#include "vm/Scope.h" +#include "vm/TraceLogging.h" + +/* + * 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 ModuleObject; +class ScriptSourceObject; + +namespace frontend { + +struct CompilationStencil; +struct CompilationGCOutput; +class ErrorReporter; +class FunctionBox; +class ParseNode; +class ParserAtom; + +// Compile a module of the given source using the given options. +ModuleObject* CompileModule(JSContext* cx, + const JS::ReadOnlyCompileOptions& options, + JS::SourceText<char16_t>& srcBuf); +ModuleObject* CompileModule(JSContext* cx, + const JS::ReadOnlyCompileOptions& options, + JS::SourceText<mozilla::Utf8Unit>& 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. +bool ParseModuleToStencil(JSContext* cx, CompilationStencil& stencil, + JS::SourceText<char16_t>& srcBuf); +bool ParseModuleToStencil(JSContext* cx, CompilationStencil& stencil, + JS::SourceText<mozilla::Utf8Unit>& srcBuf); + +UniquePtr<CompilationStencil> ParseModuleToStencil( + JSContext* cx, const JS::ReadOnlyCompileOptions& options, + JS::SourceText<char16_t>& srcBuf); +UniquePtr<CompilationStencil> ParseModuleToStencil( + JSContext* cx, const JS::ReadOnlyCompileOptions& options, + JS::SourceText<mozilla::Utf8Unit>& 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;}") +// +MOZ_MUST_USE JSFunction* CompileStandaloneFunction( + JSContext* cx, const JS::ReadOnlyCompileOptions& options, + JS::SourceText<char16_t>& srcBuf, + const mozilla::Maybe<uint32_t>& parameterListEnd, + frontend::FunctionSyntaxKind syntaxKind, + HandleScope enclosingScope = nullptr); + +MOZ_MUST_USE JSFunction* CompileStandaloneGenerator( + JSContext* cx, const JS::ReadOnlyCompileOptions& options, + JS::SourceText<char16_t>& srcBuf, + const mozilla::Maybe<uint32_t>& parameterListEnd, + frontend::FunctionSyntaxKind syntaxKind); + +MOZ_MUST_USE JSFunction* CompileStandaloneAsyncFunction( + JSContext* cx, const JS::ReadOnlyCompileOptions& options, + JS::SourceText<char16_t>& srcBuf, + const mozilla::Maybe<uint32_t>& parameterListEnd, + frontend::FunctionSyntaxKind syntaxKind); + +MOZ_MUST_USE JSFunction* CompileStandaloneAsyncGenerator( + JSContext* cx, const JS::ReadOnlyCompileOptions& options, + JS::SourceText<char16_t>& srcBuf, + const mozilla::Maybe<uint32_t>& parameterListEnd, + frontend::FunctionSyntaxKind syntaxKind); + +/* + * 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 IsIdentifier(const ParserAtom* atom); + +bool IsIdentifierNameOrPrivateName(JSLinearString* str); +bool IsIdentifierNameOrPrivateName(const ParserAtom* atom); + +/* + * As above, but taking chars + length. + */ +bool IsIdentifier(const Latin1Char* chars, size_t length); +bool IsIdentifier(const char16_t* chars, size_t length); + +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(const ParserAtom* atom); +bool IsKeyword(JSLinearString* str); + +class MOZ_STACK_CLASS AutoFrontendTraceLog { +#ifdef JS_TRACE_LOGGING + TraceLoggerThread* logger_; + mozilla::Maybe<TraceLoggerEvent> frontendEvent_; + mozilla::Maybe<AutoTraceLog> frontendLog_; + mozilla::Maybe<AutoTraceLog> typeLog_; +#endif + + public: + AutoFrontendTraceLog(JSContext* cx, const TraceLoggerTextId id, + const ErrorReporter& reporter); + + AutoFrontendTraceLog(JSContext* cx, const TraceLoggerTextId id, + const ErrorReporter& reporter, FunctionBox* funbox); + + AutoFrontendTraceLog(JSContext* cx, const TraceLoggerTextId id, + const ErrorReporter& reporter, ParseNode* pn); +}; + +} /* 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..401e27d8b2 --- /dev/null +++ b/js/src/frontend/BytecodeControlStructures.cpp @@ -0,0 +1,111 @@ +/* -*- 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 "vm/Opcodes.h" // JSOp + +using namespace js; +using namespace js::frontend; + +using mozilla::Maybe; + +NestableControl::NestableControl(BytecodeEmitter* bce, StatementKind kind) + : Nestable<NestableControl>(&bce->innermostNestableControl), + kind_(kind), + emitterScope_(bce->innermostEmitterScopeNoCheck()) {} + +BreakableControl::BreakableControl(BytecodeEmitter* bce, StatementKind kind) + : NestableControl(bce, kind) { + MOZ_ASSERT(is<BreakableControl>()); +} + +bool BreakableControl::patchBreaks(BytecodeEmitter* bce) { + return bce->emitJumpTargetAndPatch(breaks); +} + +LabelControl::LabelControl(BytecodeEmitter* bce, const ParserAtom* 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>()); + + LoopControl* enclosingLoop = findNearest<LoopControl>(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<uint32_t>& 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), emittingSubroutine_(false) { + MOZ_ASSERT(is<TryFinallyControl>()); +} diff --git a/js/src/frontend/BytecodeControlStructures.h b/js/src/frontend/BytecodeControlStructures.h new file mode 100644 index 0000000000..a86163ac55 --- /dev/null +++ b/js/src/frontend/BytecodeControlStructures.h @@ -0,0 +1,164 @@ +/* -*- 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/Attributes.h" // MOZ_MUST_USE +#include "mozilla/Maybe.h" // mozilla::Maybe + +#include <stdint.h> // int32_t, uint32_t + +#include "ds/Nestable.h" // Nestable +#include "frontend/BytecodeSection.h" // BytecodeOffset +#include "frontend/JumpList.h" // JumpList, JumpTarget +#include "frontend/ParserAtom.h" // ParserAtom +#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<NestableControl> { + StatementKind kind_; + + // The innermost scope when this was pushed. + EmitterScope* emitterScope_; + + protected: + NestableControl(BytecodeEmitter* bce, StatementKind kind); + + public: + using Nestable<NestableControl>::enclosing; + using Nestable<NestableControl>::findNearest; + + StatementKind kind() const { return kind_; } + + EmitterScope* emitterScope() const { return emitterScope_; } + + template <typename T> + bool is() const; + + template <typename T> + T& as() { + MOZ_ASSERT(this->is<T>()); + return static_cast<T&>(*this); + } +}; + +class BreakableControl : public NestableControl { + public: + // Offset of the last break. + JumpList breaks; + + BreakableControl(BytecodeEmitter* bce, StatementKind kind); + + MOZ_MUST_USE bool patchBreaks(BytecodeEmitter* bce); +}; +template <> +inline bool NestableControl::is<BreakableControl>() const { + return StatementKindIsUnlabeledBreakTarget(kind_) || + kind_ == StatementKind::Label; +} + +class LabelControl : public BreakableControl { + const ParserAtom* label_; + + // The code offset when this was pushed. Used for effectfulness checking. + BytecodeOffset startOffset_; + + public: + LabelControl(BytecodeEmitter* bce, const ParserAtom* label, + BytecodeOffset startOffset); + + const ParserAtom* label() const { return label_; } + + BytecodeOffset startOffset() const { return startOffset_; } +}; +template <> +inline bool NestableControl::is<LabelControl>() 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::IfNe 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; } + + MOZ_MUST_USE 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. + MOZ_MUST_USE bool emitLoopHead(BytecodeEmitter* bce, + const mozilla::Maybe<uint32_t>& nextPos); + + MOZ_MUST_USE bool emitLoopEnd(BytecodeEmitter* bce, JSOp op, + TryNoteKind tryNoteKind); +}; +template <> +inline bool NestableControl::is<LoopControl>() const { + return StatementKindIsLoop(kind_); +} + +class TryFinallyControl : public NestableControl { + bool emittingSubroutine_; + + public: + // The subroutine when emitting a finally block. + JumpList gosubs; + + TryFinallyControl(BytecodeEmitter* bce, StatementKind kind); + + void setEmittingSubroutine() { emittingSubroutine_ = true; } + + bool emittingSubroutine() const { return emittingSubroutine_; } +}; +template <> +inline bool NestableControl::is<TryFinallyControl>() const { + return kind_ == StatementKind::Try || kind_ == StatementKind::Finally; +} + +} /* 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..cc9d109407 --- /dev/null +++ b/js/src/frontend/BytecodeEmitter.cpp @@ -0,0 +1,11322 @@ +/* -*- 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/Maybe.h" // mozilla::{Maybe,Nothing,Some} +#include "mozilla/PodOperations.h" // mozilla::PodCopy +#include "mozilla/Sprintf.h" // SprintfLiteral +#include "mozilla/Unused.h" // mozilla::Unused +#include "mozilla/Variant.h" // mozilla::AsVariant + +#include <algorithm> +#include <iterator> +#include <string.h> + +#include "jstypes.h" // JS_BIT + +#include "ds/Nestable.h" // Nestable +#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/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/ForOfLoopControl.h" // ForOfLoopControl +#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 +#include "frontend/PropOpEmitter.h" // PropOpEmitter +#include "frontend/SourceNotes.h" // SrcNote, SrcNoteType, SrcNoteWriter +#include "frontend/SwitchEmitter.h" // SwitchEmitter +#include "frontend/TDZCheckCache.h" // TDZCheckCache +#include "frontend/TryEmitter.h" // TryEmitter +#include "frontend/WhileEmitter.h" // WhileEmitter +#include "js/CompileOptions.h" // TransitiveCompileOptions, CompileOptions +#include "js/friend/ErrorMessages.h" // JSMSG_* +#include "js/friend/StackLimits.h" // CheckRecursionLimit +#include "util/StringBuffer.h" // StringBuffer +#include "vm/AsyncFunctionResolveKind.h" // AsyncFunctionResolveKind +#include "vm/BytecodeUtil.h" // JOF_*, IsArgOp, IsLocalOp, SET_UINT24, SET_ICINDEX, BytecodeFallsThrough, BytecodeIsJumpTarget +#include "vm/FunctionPrefixKind.h" // FunctionPrefixKind +#include "vm/GeneratorObject.h" // AbstractGeneratorObject +#include "vm/JSAtom.h" // JSAtom, js_*_str +#include "vm/JSContext.h" // JSContext +#include "vm/JSFunction.h" // JSFunction, +#include "vm/JSScript.h" // JSScript, ScriptSourceObject, MemberInitializers, BaseScript +#include "vm/Opcodes.h" // JSOp, JSOpLength_* +#include "vm/SharedStencil.h" // ScopeNote +#include "vm/ThrowMsgKind.h" // ThrowMsgKind +#include "wasm/AsmJS.h" // IsAsmJSModule + +#include "vm/JSObject-inl.h" // JSObject + +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; +using mozilla::Unused; + +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 isStatic) { + return member->is<ClassField>() && + member->as<ClassField>().isStatic() == isStatic; +} + +static bool NeedsMethodInitializer(ParseNode* member, bool isStatic) { + if (isStatic) { + return false; + } + return member->is<ClassMethod>() && + member->as<ClassMethod>().name().isKind(ParseNodeKind::PrivateName) && + !member->as<ClassMethod>().isStatic(); +} + +BytecodeEmitter::BytecodeEmitter(BytecodeEmitter* parent, SharedContext* sc, + CompilationStencil& stencil, + CompilationState& compilationState, + EmitterMode emitterMode) + : sc(sc), + cx(sc->cx_), + parent(parent), + bytecodeSection_(cx, sc->extent().lineno, sc->extent().column), + perScriptData_(cx, stencil, compilationState), + stencil(stencil), + compilationState(compilationState), + emitterMode(emitterMode) {} + +BytecodeEmitter::BytecodeEmitter(BytecodeEmitter* parent, + BCEParserHandle* handle, SharedContext* sc, + CompilationStencil& stencil, + CompilationState& compilationState, + EmitterMode emitterMode) + : BytecodeEmitter(parent, sc, stencil, compilationState, emitterMode) { + parser = handle; + instrumentationKinds = parser->options().instrumentationKinds; +} + +BytecodeEmitter::BytecodeEmitter(BytecodeEmitter* parent, + const EitherParser& parser, SharedContext* sc, + CompilationStencil& stencil, + CompilationState& compilationState, + EmitterMode emitterMode) + : BytecodeEmitter(parent, sc, stencil, compilationState, emitterMode) { + ep_.emplace(parser); + this->parser = ep_.ptr(); + instrumentationKinds = this->parser->options().instrumentationKinds; +} + +void BytecodeEmitter::initFromBodyPosition(TokenPos bodyPosition) { + setScriptStartOffsetIfUnset(bodyPosition.begin); + setFunctionBodyEndPos(bodyPosition.end); +} + +bool BytecodeEmitter::init() { + if (!parent) { + if (!stencil.prepareStorageFor(cx, compilationState)) { + return false; + } + } + return perScriptData_.init(cx); +} + +bool BytecodeEmitter::init(TokenPos bodyPosition) { + initFromBodyPosition(bodyPosition); + return init(); +} + +template <typename T> +T* BytecodeEmitter::findInnermostNestableControl() const { + return NestableControl::findNearest<T>(innermostNestableControl); +} + +template <typename T, typename Predicate /* (T*) -> bool */> +T* BytecodeEmitter::findInnermostNestableControl(Predicate predicate) const { + return NestableControl::findNearest<T>(innermostNestableControl, predicate); +} + +NameLocation BytecodeEmitter::lookupName(const ParserAtom* name) { + return innermostEmitterScope()->lookup(this, name); +} + +Maybe<NameLocation> BytecodeEmitter::locationOfNameBoundInScope( + const ParserAtom* name, EmitterScope* target) { + return innermostEmitterScope()->locationBoundInScope(name, target); +} + +template <typename T> +Maybe<NameLocation> BytecodeEmitter::locationOfNameBoundInScopeType( + const ParserAtom* name, EmitterScope* source) { + EmitterScope* aScope = source; + while (!aScope->scope(this).is<T>()) { + aScope = aScope->enclosingInFrame(); + } + return source->locationBoundInScope(name, aScope); +} + +bool BytecodeEmitter::markStepBreakpoint() { + if (skipBreakpointSrcNotes()) { + return true; + } + + if (!emitInstrumentation(InstrumentationKind::Breakpoint)) { + return false; + } + + 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 (!emitInstrumentation(InstrumentationKind::Breakpoint)) { + return false; + } + + 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(cx); + 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(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(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(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(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(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<uint32_t>& 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; + } + + ErrorReporter* er = &parser->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 = parser->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; +} + +Maybe<uint32_t> BytecodeEmitter::getOffsetForLoop(ParseNode* nextpn) { + if (!nextpn) { + return Nothing(); + } + + // 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<LexicalScopeNode>()) { + nextpn = nextpn->as<LexicalScopeNode>().scopeBody(); + } + if (nextpn->isKind(ParseNodeKind::StatementList)) { + if (ParseNode* firstStatement = nextpn->as<ListNode>().head()) { + nextpn = firstStatement; + } + } + + return Some(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; +} + +namespace { + +class NonLocalExitControl { + public: + enum Kind { + // IteratorClose is handled especially inside the exception unwinder. + Throw, + + // A 'continue' statement does not call IteratorClose for the loop it + // is continuing, i.e. excluding the target loop. + Continue, + + // A 'break' or 'return' statement does call IteratorClose for the + // loop it is breaking out of or returning from, i.e. including the + // target loop. + Break, + Return + }; + + private: + BytecodeEmitter* bce_; + const uint32_t savedScopeNoteIndex_; + const int savedDepth_; + uint32_t openScopeNoteIndex_; + Kind kind_; + + NonLocalExitControl(const NonLocalExitControl&) = delete; + + MOZ_MUST_USE bool leaveScope(EmitterScope* scope); + + public: + NonLocalExitControl(BytecodeEmitter* bce, Kind kind) + : bce_(bce), + savedScopeNoteIndex_(bce->bytecodeSection().scopeNoteList().length()), + savedDepth_(bce->bytecodeSection().stackDepth()), + openScopeNoteIndex_(bce->innermostEmitterScope()->noteIndex()), + kind_(kind) {} + + ~NonLocalExitControl() { + for (uint32_t n = savedScopeNoteIndex_; + n < bce_->bytecodeSection().scopeNoteList().length(); n++) { + bce_->bytecodeSection().scopeNoteList().recordEnd( + n, bce_->bytecodeSection().offset()); + } + bce_->bytecodeSection().setStackDepth(savedDepth_); + } + + MOZ_MUST_USE bool prepareForNonLocalJump(NestableControl* target); + + MOZ_MUST_USE bool prepareForNonLocalJumpToOutermost() { + return prepareForNonLocalJump(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::prepareForNonLocalJump(NestableControl* target) { + EmitterScope* es = bce_->innermostEmitterScope(); + int npops = 0; + + AutoCheckUnstableEmitterScope cues(bce_); + + // For 'continue', 'break', and 'return' statements, emit IteratorClose + // bytecode inline. 'continue' statements do not call IteratorClose for + // the loop they are continuing. + bool emitIteratorClose = + kind_ == Continue || kind_ == Break || kind_ == Return; + bool emitIteratorCloseAtTarget = emitIteratorClose && kind_ != 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<BytecodeOffset, 4> forOfIterCloseScopeStarts(bce_->cx); + + // Walk the nestable control stack and patch jumps. + for (NestableControl* control = bce_->innermostNestableControl; + control != target; 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<TryFinallyControl>(); + if (finallyControl.emittingSubroutine()) { + /* + * There's a [exception or hole, retsub pc-index] pair and the + * possible return value on the stack that we need to pop. + */ + npops += 3; + } else { + if (!flushPops(bce_)) { + return false; + } + if (!bce_->emitGoSub(&finallyControl.gosubs)) { + // [stack] ... + return false; + } + } + break; + } + + case StatementKind::ForOfLoop: + if (emitIteratorClose) { + if (!flushPops(bce_)) { + return false; + } + BytecodeOffset tryNoteStart; + ForOfLoopControl& loopinfo = control->as<ForOfLoopControl>(); + if (!loopinfo.emitPrepareForNonLocalJumpFromScope( + bce_, *es, + /* isTarget = */ false, &tryNoteStart)) { + // [stack] ... + return false; + } + if (!forOfIterCloseScopeStarts.append(tryNoteStart)) { + return false; + } + } else { + // The iterator next method, the iterator, and the current + // value are on the stack. + npops += 3; + } + 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 (target && emitIteratorCloseAtTarget && target->is<ForOfLoopControl>()) { + BytecodeOffset tryNoteStart; + ForOfLoopControl& loopinfo = target->as<ForOfLoopControl>(); + 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; + } + } + + // 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; +} + +} // anonymous namespace + +bool BytecodeEmitter::emitGoto(NestableControl* target, JumpList* jumplist, + GotoKind kind) { + NonLocalExitControl nle(this, kind == GotoKind::Continue + ? NonLocalExitControl::Continue + : NonLocalExitControl::Break); + + if (!nle.prepareForNonLocalJump(target)) { + return false; + } + + return emitJump(JSOp::Goto, jumplist); +} + +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(offset); + return true; +} + +bool BytecodeEmitter::emitAtomOp(JSOp op, const ParserAtom* atom, + ShouldInstrument shouldInstrument) { + 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 != cx->parserNames().dotGenerator); + + GCThingIndex index; + if (!makeAtomIndex(atom, &index)) { + return false; + } + + return emitAtomOp(op, index, shouldInstrument); +} + +bool BytecodeEmitter::emitAtomOp(JSOp op, GCThingIndex atomIndex, + ShouldInstrument shouldInstrument) { + MOZ_ASSERT(JOF_OPTYPE(op) == JOF_ATOM); + + if (shouldInstrument != ShouldInstrument::No && + !emitInstrumentationForOpcode(op, atomIndex)) { + return false; + } + + 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::emitObjectPairOp(GCThingIndex index1, GCThingIndex index2, + JSOp op) { + MOZ_ASSERT(index1 + 1 == index2, "object pair indices must be adjacent"); + return emitInternedObjectOp(index1, op); +} + +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); + + 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) { + if (!CheckRecursionLimit(cx)) { + 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<NullaryNode>()); + *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<NameNode>()); + *answer = false; + return true; + + case ParseNodeKind::RegExpExpr: + MOZ_ASSERT(pn->is<RegExpLiteral>()); + *answer = false; + return true; + + case ParseNodeKind::NumberExpr: + MOZ_ASSERT(pn->is<NumericLiteral>()); + *answer = false; + return true; + + case ParseNodeKind::BigIntExpr: + MOZ_ASSERT(pn->is<BigIntLiteral>()); + *answer = false; + return true; + + // |this| can throw in derived class constructors, including nested arrow + // functions or eval. + case ParseNodeKind::ThisExpr: + MOZ_ASSERT(pn->is<UnaryNode>()); + *answer = sc->needsThisTDZChecks(); + return true; + + // Trivial binary nodes with more token pos holders. + case ParseNodeKind::NewTargetExpr: + case ParseNodeKind::ImportMetaExpr: { + MOZ_ASSERT(pn->as<BinaryNode>().left()->isKind(ParseNodeKind::PosHolder)); + MOZ_ASSERT( + pn->as<BinaryNode>().right()->isKind(ParseNodeKind::PosHolder)); + *answer = false; + return true; + } + + case ParseNodeKind::BreakStmt: + MOZ_ASSERT(pn->is<BreakStatement>()); + *answer = true; + return true; + + case ParseNodeKind::ContinueStmt: + MOZ_ASSERT(pn->is<ContinueStatement>()); + *answer = true; + return true; + + case ParseNodeKind::DebuggerStmt: + MOZ_ASSERT(pn->is<DebuggerStatement>()); + *answer = true; + return true; + + // Watch out for getters! + case ParseNodeKind::OptionalDotExpr: + case ParseNodeKind::DotExpr: + MOZ_ASSERT(pn->is<BinaryNode>()); + *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<UnaryNode>().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<UnaryNode>()); + *answer = true; + return true; + + // Looking up or evaluating the associated name could throw. + case ParseNodeKind::TypeOfNameExpr: + MOZ_ASSERT(pn->is<UnaryNode>()); + *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<UnaryNode>().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<UnaryNode>()); + *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<UnaryNode>()); + *answer = true; + return true; + + // This invokes the (user-controllable) iterator protocol. + case ParseNodeKind::Spread: + MOZ_ASSERT(pn->is<UnaryNode>()); + *answer = true; + return true; + + case ParseNodeKind::InitialYield: + case ParseNodeKind::YieldStarExpr: + case ParseNodeKind::YieldExpr: + case ParseNodeKind::AwaitExpr: + MOZ_ASSERT(pn->is<UnaryNode>()); + *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<UnaryNode>()); + *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<UnaryNode>().kid(); + return checkSideEffects(expr, answer); + } + + case ParseNodeKind::ExpressionStmt: + return checkSideEffects(pn->as<UnaryNode>().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<AssignmentNode>()); + *answer = true; + return true; + + case ParseNodeKind::SetThis: + MOZ_ASSERT(pn->is<BinaryNode>()); + *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<ListNode>().empty()); + [[fallthrough]]; + // Subcomponents of a literal may be effectful. + case ParseNodeKind::ArrayExpr: + case ParseNodeKind::ObjectExpr: + for (ParseNode* item : pn->as<ListNode>().contents()) { + if (!checkSideEffects(item, answer)) { + return false; + } + if (*answer) { + return true; + } + } + return true; + + // 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::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<ListNode>().count() >= 2); + *answer = true; + return true; + + case ParseNodeKind::PropertyDefinition: + case ParseNodeKind::Case: { + BinaryNode* node = &pn->as<BinaryNode>(); + if (!checkSideEffects(node->left(), answer)) { + return false; + } + if (*answer) { + return true; + } + return checkSideEffects(node->right(), answer); + } + + // More getters. + case ParseNodeKind::OptionalElemExpr: + case ParseNodeKind::ElemExpr: + MOZ_ASSERT(pn->is<BinaryNode>()); + *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<BinaryNode>()); + *answer = true; + return true; + + // Likewise. + case ParseNodeKind::ExportStmt: + MOZ_ASSERT(pn->is<UnaryNode>()); + *answer = true; + return true; + + case ParseNodeKind::CallImportExpr: + MOZ_ASSERT(pn->is<BinaryNode>()); + *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<BinaryNode>()); + *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<ListNode>()); + *answer = true; + return true; + + case ParseNodeKind::IfStmt: + case ParseNodeKind::ConditionalExpr: { + TernaryNode* node = &pn->as<TernaryNode>(); + 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<BinaryNode>()); + *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<ListNode>()); + *answer = true; + return true; + + case ParseNodeKind::OptionalChain: + MOZ_ASSERT(pn->is<UnaryNode>()); + *answer = true; + return true; + + case ParseNodeKind::PipelineExpr: + MOZ_ASSERT(pn->as<ListNode>().count() >= 2); + *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<ClassNode>()); + *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<BinaryNode>()); + *answer = true; + return true; + + case ParseNodeKind::ReturnStmt: + MOZ_ASSERT(pn->is<BinaryNode>()); + *answer = true; + return true; + + case ParseNodeKind::Name: + MOZ_ASSERT(pn->is<NameNode>()); + *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<BinaryNode>()); + *answer = true; + return true; + + case ParseNodeKind::Function: + MOZ_ASSERT(pn->is<FunctionNode>()); + /* + * 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<TryNode>(); + 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<BinaryNode>(); + 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<SwitchStatement>(); + if (!checkSideEffects(&switchStmt->discriminant(), answer)) { + return false; + } + return *answer || + checkSideEffects(&switchStmt->lexicalForCaseList(), answer); + } + + case ParseNodeKind::LabelStmt: + return checkSideEffects(pn->as<LabeledStatement>().statement(), answer); + + case ParseNodeKind::LexicalScope: + return checkSideEffects(pn->as<LexicalScopeNode>().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<ListNode>(); + 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::ClassMethod: // by ParseNodeKind::ClassDecl + case ParseNodeKind::ClassField: // by ParseNodeKind::ClassDecl + case ParseNodeKind::ClassNames: // by ParseNodeKind::ClassDecl + case ParseNodeKind::ClassMemberList: // by ParseNodeKind::ClassDecl + case ParseNodeKind::ImportSpecList: // by ParseNodeKind::Import + case ParseNodeKind::ImportSpec: // by ParseNodeKind::Import + case ParseNodeKind::ExportBatchSpecStmt: // by ParseNodeKind::Export + case ParseNodeKind::ExportSpecList: // by ParseNodeKind::Export + case ParseNodeKind::ExportSpec: // 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<LoopControl>(); +} + +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<FunctionScope>()) { + 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"); + + // Note: we need to check numHops here because we don't call + // checkEnvironmentChainLength in all cases (like 'eval'). + if (numHops >= ENVCOORD_HOPS_LIMIT - 1) { + reportError(nullptr, JSMSG_TOO_DEEP, js_function_str); + return false; + } + + return emit2(JSOp::EnvCallee, numHops); +} + +bool BytecodeEmitter::emitSuperBase() { + if (!emitThisEnvironmentCallee()) { + return false; + } + + return emit1(JSOp::SuperBase); +} + +void BytecodeEmitter::reportNeedMoreArgsError(ParseNode* pn, + const char* errorName, + const char* requiredArgs, + const char* pluralizer, + const ListNode* argsList) { + char actualArgsStr[40]; + SprintfLiteral(actualArgsStr, "%u", argsList->count()); + reportError(pn, JSMSG_MORE_ARGS_NEEDED, errorName, requiredArgs, pluralizer, + actualArgsStr); +} + +void BytecodeEmitter::reportError(ParseNode* pn, unsigned errorNumber, ...) { + uint32_t offset = pn ? pn->pn_pos.begin : *scriptStartOffset; + + va_list args; + va_start(args, errorNumber); + + parser->errorReporter().errorWithNotesAtVA(nullptr, AsVariant(offset), + errorNumber, &args); + + va_end(args); +} + +void BytecodeEmitter::reportError(const Maybe<uint32_t>& maybeOffset, + unsigned errorNumber, ...) { + uint32_t offset = maybeOffset ? *maybeOffset : *scriptStartOffset; + + va_list args; + va_start(args, errorNumber); + + parser->errorReporter().errorWithNotesAtVA(nullptr, AsVariant(offset), + errorNumber, &args); + + va_end(args); +} + +bool BytecodeEmitter::addObjLiteralData(ObjLiteralWriter& writer, + GCThingIndex* outIndex) { + size_t len = writer.getCode().size(); + auto* code = stencil.alloc.newArrayUninitialized<uint8_t>(len); + if (!code) { + js::ReportOutOfMemory(cx); + return false; + } + memcpy(code, writer.getCode().data(), len); + + ObjLiteralIndex objIndex(stencil.objLiteralData.length()); + if (uint32_t(objIndex) >= TaggedScriptThingIndex::IndexLimit) { + ReportAllocationOverflow(cx); + return false; + } + if (!stencil.objLiteralData.emplaceBack(code, len, writer.getFlags())) { + js::ReportOutOfMemory(cx); + return false; + } + + return perScriptData().gcThingList().append(objIndex, outIndex); +} + +bool BytecodeEmitter::iteratorResultShape(GCThingIndex* outShape) { + ObjLiteralFlags flags; + + ObjLiteralWriter writer; + writer.beginObject(flags); + + using WellKnownName = js::frontend::WellKnownParserAtoms; + for (auto name : {&WellKnownName::value, &WellKnownName::done}) { + const ParserAtom* propName = cx->parserNames().*name; + writer.setPropName(propName); + + if (!writer.propWithUndefinedValue(cx)) { + return false; + } + } + + return addObjLiteralData(writer, outShape); +} + +bool BytecodeEmitter::emitPrepareIteratorResult() { + GCThingIndex shape; + if (!iteratorResultShape(&shape)) { + return false; + } + return emitGCIndexOp(JSOp::NewObject, shape); +} + +bool BytecodeEmitter::emitFinishIteratorResult(bool done) { + if (!emitAtomOp(JSOp::InitProp, cx->parserNames().value)) { + return false; + } + if (!emit1(done ? JSOp::True : JSOp::False)) { + return false; + } + if (!emitAtomOp(JSOp::InitProp, cx->parserNames().done)) { + return false; + } + return true; +} + +bool BytecodeEmitter::emitGetNameAtLocation(const ParserAtom* 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)); + + const ParserAtom* nameAtom = name->name(); + return emitGetName(nameAtom); +} + +bool BytecodeEmitter::emitGetPrivateName(NameNode* name) { + MOZ_ASSERT(name->isKind(ParseNodeKind::PrivateName)); + return emitGetPrivateName(name->name()); +} + +bool BytecodeEmitter::emitGetPrivateName(const ParserAtom* nameAtom) { + // The parser ensures the private name is present on the environment chain. + NameLocation location = lookupName(nameAtom); + MOZ_ASSERT(location.kind() == NameLocation::Kind::FrameSlot || + location.kind() == NameLocation::Kind::EnvironmentCoordinate || + location.kind() == NameLocation::Kind::Dynamic); + + return emitGetNameAtLocation(nameAtom, location); +} + +bool BytecodeEmitter::emitTDZCheckIfNeeded(const ParserAtom* 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()); + + Maybe<MaybeCheckTDZ> 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<PropertyAccess>() || expr->as<PropertyAccess>().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<PropertyAccess>(); + ParseNode* pnup = nullptr; + ParseNode* pndown; + for (;;) { + // Reverse pndot->expression() to point up, not down. + pndown = &pndot->expression(); + pndot->setExpression(pnup); + if (!pndown->is<PropertyAccess>() || + pndown->as<PropertyAccess>().isSuper()) { + break; + } + pnup = pndot; + pndot = &pndown->as<PropertyAccess>(); + } + + // 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(), + ShouldInstrument::Yes)) { + return false; + } + + // Reverse the pndot->expression() link again. + pnup = pndot->maybeExpression(); + pndot->setExpression(pndown); + pndown = pndot; + if (!pnup) { + break; + } + pndot = &pnup->as<PropertyAccess>(); + } + return true; +} + +bool BytecodeEmitter::emitPropIncDec(UnaryNode* incDec) { + PropertyAccess* prop = &incDec->kid()->as<PropertyAccess>(); + 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<UnaryNode>(); + if (!emitGetThisForSuperBase(base)) { + // [stack] THIS + return false; + } + } else { + if (!emitPropLHS(prop)) { + // [stack] OBJ + return false; + } + } + if (!poe.emitIncDec(prop->key().atom())) { + // [stack] RESULT + return false; + } + + return true; +} + +bool BytecodeEmitter::emitNameIncDec(UnaryNode* incDec) { + MOZ_ASSERT(incDec->kid()->isKind(ParseNodeKind::Name)); + + ParseNodeKind kind = incDec->getKind(); + NameNode* name = &incDec->kid()->as<NameNode>(); + const ParserAtom* nameAtom = name->atom(); + NameOpEmitter noe(this, nameAtom, + kind == ParseNodeKind::PostIncrementExpr + ? NameOpEmitter::Kind::PostIncrement + : kind == ParseNodeKind::PreIncrementExpr + ? NameOpEmitter::Kind::PreIncrement + : kind == ParseNodeKind::PostDecrementExpr + ? NameOpEmitter::Kind::PostDecrement + : NameOpEmitter::Kind::PreDecrement); + if (!noe.emitIncDec()) { + return false; + } + + return true; +} + +bool BytecodeEmitter::emitElemOpBase(JSOp op, + ShouldInstrument shouldInstrument) { + GCThingIndex unused; + if (shouldInstrument != ShouldInstrument::No && + !emitInstrumentationForOpcode(op, unused)) { + return false; + } + + if (!emit1(op)) { + return false; + } + + return true; +} + +bool BytecodeEmitter::emitElemObjAndKey(PropertyByValue* elem, bool isSuper, + ElemOpEmitter& eoe) { + if (isSuper) { + if (!eoe.prepareForObj()) { + // [stack] + return false; + } + UnaryNode* base = &elem->expression().as<UnaryNode>(); + if (!emitGetThisForSuperBase(base)) { + // [stack] THIS + return false; + } + if (!eoe.prepareForKey()) { + // [stack] THIS + return false; + } + if (!emitTree(&elem->key())) { + // [stack] THIS KEY + return false; + } + + return true; + } + + if (!eoe.prepareForObj()) { + // [stack] + return false; + } + if (!emitTree(&elem->expression())) { + // [stack] OBJ + return false; + } + if (!eoe.prepareForKey()) { + // [stack] OBJ? OBJ + return false; + } + if (!emitTree(&elem->key())) { + // [stack] OBJ? OBJ KEY + return false; + } + + return true; +} + +bool BytecodeEmitter::emitElemIncDec(UnaryNode* incDec) { + PropertyByValue* elemExpr = &incDec->kid()->as<PropertyByValue>(); + bool isSuper = elemExpr->isSuper(); + bool isPrivate = elemExpr->key().isKind(ParseNodeKind::PrivateName); + ParseNodeKind kind = incDec->getKind(); + ElemOpEmitter eoe( + this, + kind == ParseNodeKind::PostIncrementExpr + ? ElemOpEmitter::Kind::PostIncrement + : kind == ParseNodeKind::PreIncrementExpr + ? ElemOpEmitter::Kind::PreIncrement + : kind == ParseNodeKind::PostDecrementExpr + ? ElemOpEmitter::Kind::PostDecrement + : ElemOpEmitter::Kind::PreDecrement, + isSuper ? ElemOpEmitter::ObjKind::Super : ElemOpEmitter::ObjKind::Other, + isPrivate ? NameVisibility::Private : NameVisibility::Public); + if (!emitElemObjAndKey(elemExpr, isSuper, eoe)) { + // [stack] # if Super + // [stack] THIS KEY + // [stack] # otherwise + // [stack] OBJ KEY + return false; + } + if (!eoe.emitIncDec()) { + // [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::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(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<ListNode>(); + MOZ_ASSERT(cases->isKind(ParseNodeKind::StatementList)); + + SwitchEmitter se(this); + if (!se.emitDiscriminant(Some(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<CaseClause>(); + 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<CaseClause>(); + if (caseClause->isDefault()) { + continue; + } + + ParseNode* caseValue = caseClause->caseExpression(); + + if (caseValue->getKind() != ParseNodeKind::NumberExpr) { + tableGen.setInvalid(); + break; + } + + int32_t i; + if (!NumberEqualsInt32(caseValue->as<NumericLiteral>().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<CaseClause>(); + 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<CaseClause>(); + 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<NumericLiteral>(); +#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<BytecodeOffset> 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) { + // All yield operations pop or suspend the current frame. + if (!emitInstrumentation(InstrumentationKind::Exit)) { + return false; + } + + 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); + + if (!emitInstrumentation(InstrumentationKind::Entry)) { + return false; + } + + 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)); + + const ParserAtom* name = setThisNode->left()->as<NameNode>().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<uint8_t>(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()) { + return false; + } + + return true; +} + +bool BytecodeEmitter::defineHoistedTopLevelFunctions(ParseNode* body) { + MOZ_ASSERT(inPrologue()); + MOZ_ASSERT(sc->isGlobalContext() || (sc->isEvalContext() && !sc->strict())); + MOZ_ASSERT(body->is<LexicalScopeNode>() || body->is<ListNode>()); + + if (body->is<LexicalScopeNode>()) { + body = body->as<LexicalScopeNode>().scopeBody(); + MOZ_ASSERT(body->is<ListNode>()); + } + + if (!body->as<ListNode>().hasTopLevelFunctionDeclarations()) { + return true; + } + + return emitHoistedFunctionsInList(&body->as<ListNode>()); +} + +// 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. + if (!emitGCIndexOp(JSOp::GlobalOrEvalDeclInstantiation, lastFun)) { + return false; + } + + return true; +} + +bool BytecodeEmitter::emitScript(ParseNode* body) { + AutoFrontendTraceLog traceLog(cx, TraceLogger_BytecodeEmission, + parser->errorReporter(), body); + + setScriptStartOffsetIfUnset(body->pn_pos.begin); + + MOZ_ASSERT(inPrologue()); + + TDZCheckCache tdzCache(this); + EmitterScope emitterScope(this); + Maybe<AsyncEmitter> 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<LexicalScopeNode>() && + !body->as<LexicalScopeNode>().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<LexicalScopeNode>(); + + if (!lexicalEmitterScope.enterLexical(this, ScopeKind::Lexical, + scope->scopeBindings())) { + return false; + } + + if (!emitDeclarationInstantiation(scope->scopeBody())) { + return false; + } + + if (!switchToMain()) { + return false; + } + + ParseNode* scopeBody = scope->scopeBody(); + if (!emitLexicalScopeBody(scopeBody, EMIT_LINENOTE)) { + 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; + } + } + + if (!switchToMain()) { + return false; + } + + 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->emitEnd()) { + return false; + } + } + + if (!markSimpleBreakpoint()) { + return false; + } + + if (!emitReturnRval()) { + return false; + } + + if (!emitterScope.leave(this)) { + return false; + } + + if (!NameFunctions(cx, compilationState.parserAtoms, body)) { + return false; + } + + // Create a Stencil and convert it into a JSScript. + return intoScriptStencil(CompilationStencil::TopLevelIndex); +} + +js::UniquePtr<ImmutableScriptData> BytecodeEmitter::createImmutableScriptData( + JSContext* cx) { + uint32_t nslots; + if (!getNslots(&nslots)) { + return nullptr; + } + + bool isFunction = sc->isFunctionBox(); + uint16_t funLength = isFunction ? sc->asFunctionBox()->length() : 0; + + return ImmutableScriptData::new_( + cx, mainOffset(), maxFixedSlots, nslots, bodyScopeIndex, + bytecodeSection().numICEntries(), isFunction, funLength, + 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<uint64_t>(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()); + ListNode* paramsBody = &funNode->body()->as<ListNode>(); + MOZ_ASSERT(paramsBody->isKind(ParseNodeKind::ParamsBody)); + FunctionBox* funbox = sc->asFunctionBox(); + AutoFrontendTraceLog traceLog(cx, TraceLogger_BytecodeEmission, + parser->errorReporter(), funbox); + + 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->last())) { + // [stack] + return false; + } + + if (!fse.emitEndBody()) { + // [stack] + return false; + } + + if (funbox->index() == CompilationStencil::TopLevelIndex) { + if (!NameFunctions(cx, compilationState.parserAtoms, funNode)) { + return false; + } + } + + return fse.intoStencil(); +} + +bool BytecodeEmitter::emitDestructuringLHSRef(ParseNode* target, + size_t* emitted) { + *emitted = 0; + + if (target->isKind(ParseNodeKind::Spread)) { + target = target->as<UnaryNode>().kid(); + } else if (target->isKind(ParseNodeKind::AssignExpr)) { + target = target->as<AssignmentNode>().left(); + } + + // No need to recur into ParseNodeKind::Array and + // ParseNodeKind::Object subpatterns here, since + // emitSetOrInitializeDestructuring does the recursion when + // setting or initializing value. Getting reference doesn't recur. + if (target->isKind(ParseNodeKind::Name) || + target->isKind(ParseNodeKind::ArrayExpr) || + target->isKind(ParseNodeKind::ObjectExpr)) { + return true; + } + +#ifdef DEBUG + int depth = bytecodeSection().stackDepth(); +#endif + + switch (target->getKind()) { + case ParseNodeKind::DotExpr: { + PropertyAccess* prop = &target->as<PropertyAccess>(); + 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<UnaryNode>(); + if (!emitGetThisForSuperBase(base)) { + // [stack] THIS SUPERBASE + return false; + } + // SUPERBASE is pushed onto THIS in poe.prepareForRhs below. + *emitted = 2; + } else { + if (!emitTree(&prop->expression())) { + // [stack] OBJ + return false; + } + *emitted = 1; + } + if (!poe.prepareForRhs()) { + // [stack] # if Super + // [stack] THIS SUPERBASE + // [stack] # otherwise + // [stack] OBJ + return false; + } + break; + } + + case ParseNodeKind::ElemExpr: { + PropertyByValue* elem = &target->as<PropertyByValue>(); + bool isSuper = elem->isSuper(); + bool isPrivate = elem->key().isKind(ParseNodeKind::PrivateName); + ElemOpEmitter eoe( + this, ElemOpEmitter::Kind::SimpleAssignment, + isSuper ? ElemOpEmitter::ObjKind::Super + : ElemOpEmitter::ObjKind::Other, + isPrivate ? NameVisibility::Private : NameVisibility::Public); + 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.prepareForRhs below. + *emitted = 3; + } else { + *emitted = 2; + } + if (!eoe.prepareForRhs()) { + // [stack] # if Super + // [stack] THIS KEY SUPERBASE + // [stack] # otherwise + // [stack] OBJ KEY + return false; + } + 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. + if (target->isKind(ParseNodeKind::Spread)) { + target = target->as<UnaryNode>().kid(); + } else if (target->isKind(ParseNodeKind::AssignExpr)) { + target = target->as<AssignmentNode>().left(); + } + if (target->isKind(ParseNodeKind::ArrayExpr) || + target->isKind(ParseNodeKind::ObjectExpr)) { + if (!emitDestructuringOps(&target->as<ListNode>(), flav)) { + return false; + } + // Per its post-condition, emitDestructuringOps has left the + // to-be-destructured value on top of the stack. + if (!emit1(JSOp::Pop)) { + return false; + } + } else { + switch (target->getKind()) { + case ParseNodeKind::Name: { + const ParserAtom* name = target->as<NameNode>().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<PropertyAccess>(); + 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<PropertyByValue>(); + bool isSuper = elem->isSuper(); + bool isPrivate = elem->key().isKind(ParseNodeKind::PrivateName); + ElemOpEmitter eoe( + this, ElemOpEmitter::Kind::SimpleAssignment, + isSuper ? ElemOpEmitter::ObjKind::Super + : ElemOpEmitter::ObjKind::Other, + isPrivate ? NameVisibility::Private : NameVisibility::Public); + if (!eoe.skipObjAndKeyAndRhs()) { + return false; + } + if (!eoe.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; +} + +bool BytecodeEmitter::emitIteratorNext( + const Maybe<uint32_t>& callSourceCoordOffset, + IteratorKind iterKind /* = IteratorKind::Sync */, + bool allowSelfHosted /* = false */) { + // TODO: migrate Module code to cpp, to avoid having the extra check here. + MOZ_ASSERT(allowSelfHosted || emitterMode != BytecodeEmitter::SelfHosting || + (sc->isModuleContext() && sc->asModuleContext()->isAsync()), + ".next() iteration is prohibited in non-module self-hosted code " + "because it" + "can run user-modifiable iteration code"); + + // [stack] ... NEXT ITER + MOZ_ASSERT(bytecodeSection().stackDepth() >= 2); + + if (!emitCall(JSOp::Call, 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::emitPushNotUndefinedOrNull() { + // [stack] V + MOZ_ASSERT(bytecodeSection().stackDepth() > 0); + + if (!emit1(JSOp::Dup)) { + // [stack] V V + return false; + } + if (!emit1(JSOp::Undefined)) { + // [stack] V V UNDEFINED + return false; + } + if (!emit1(JSOp::StrictNe)) { + // [stack] V NEQ + return false; + } + + JumpList undefinedOrNullJump; + if (!emitJump(JSOp::And, &undefinedOrNullJump)) { + // [stack] V NEQ + return false; + } + + if (!emit1(JSOp::Pop)) { + // [stack] V + return false; + } + if (!emit1(JSOp::Dup)) { + // [stack] V V + return false; + } + if (!emit1(JSOp::Null)) { + // [stack] V V NULL + return false; + } + if (!emit1(JSOp::StrictNe)) { + // [stack] V NEQ + return false; + } + + if (!emitJumpTargetAndPatch(undefinedOrNullJump)) { + // [stack] V NOT-UNDEF-OR-NULL + return false; + } + + return true; +} + +bool BytecodeEmitter::emitIteratorCloseInScope( + EmitterScope& currentScope, + IteratorKind iterKind /* = IteratorKind::Sync */, + CompletionKind completionKind /* = CompletionKind::Normal */, + bool allowSelfHosted /* = false */) { + MOZ_ASSERT( + allowSelfHosted || emitterMode != BytecodeEmitter::SelfHosting, + ".close() on iterators is prohibited in self-hosted code because it " + "can run user-modifiable iteration code"); + + // 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<TryEmitter> 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, cx->parserNames().return_)) { + // [stack] ... ITER RET + return false; + } + + // Step 5. + // + // Do nothing if "return" is undefined or null. + InternalIfEmitter ifReturnMethodIsDefined(this); + if (!emitPushNotUndefinedOrNull()) { + // [stack] ... ITER RET NOT-UNDEF-OR-NULL + return false; + } + + if (!ifReturnMethodIsDefined.emitThenElse()) { + // [stack] ... ITER RET + return false; + } + + // Steps 5.c, 7. + // + // Call the "return" method. + if (!emit1(JSOp::Swap)) { + // [stack] ... RET ITER + return false; + } + + if (!emitCall(JSOp::Call, 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 <typename InnerEmitter> +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, + const ParserAtom* name) { + MOZ_ASSERT(node->isDirectRHSAnonFunction()); + + if (node->is<FunctionNode>()) { + // Function doesn't have 'name' property at this point. + // Set function's name at compile time. + if (!setFunName(node->as<FunctionNode>().funbox(), name)) { + return false; + } + + return emitTree(node); + } + + MOZ_ASSERT(node->is<ClassNode>()); + + return emitClass(&node->as<ClassNode>(), ClassNameKind::InferredName, name); +} + +bool BytecodeEmitter::emitAnonymousFunctionWithComputedName( + ParseNode* node, FunctionPrefixKind prefixKind) { + MOZ_ASSERT(node->isDirectRHSAnonFunction()); + + if (node->is<FunctionNode>()) { + 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<ClassNode>()); + MOZ_ASSERT(prefixKind == FunctionPrefixKind::None); + + return emitClass(&node->as<ClassNode>(), ClassNameKind::ComputedName); +} + +bool BytecodeEmitter::setFunName(FunctionBox* funbox, const ParserAtom* 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()); + const ParserAtom* name = pattern->as<NameNode>().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<bool> hasNext = !!member->pn_next; + + size_t emitted = 0; + + // Spec requires LHS reference to be evaluated first. + ParseNode* lhsPattern = member; + if (lhsPattern->isKind(ParseNodeKind::AssignExpr)) { + lhsPattern = lhsPattern->as<AssignmentNode>().left(); + } + + 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 + 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 (!emit2(JSOp::Unpick, emitted + 1)) { + // [stack] ... OBJ NEXT ITER TRUE LREF* ARRAY + return false; + } + + auto emitAssignment = [member, flav](BytecodeEmitter* bce) { + return bce->emitSetOrInitializeDestructuring(member, flav); + // [stack] ... OBJ NEXT ITER TRUE + }; + if (!wrapWithDestructuringTryNote(tryNoteDepth, emitAssignment)) { + return false; + } + + MOZ_ASSERT(!hasNext); + break; + } + + ParseNode* pndefault = nullptr; + if (member->isKind(ParseNodeKind::AssignExpr)) { + pndefault = member->as<AssignmentNode>().right(); + } + + MOZ_ASSERT(!member->isKind(ParseNodeKind::Spread)); + + 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 (!emit2(JSOp::Unpick, 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, cx->parserNames().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 (!emit2(JSOp::Unpick, 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, cx->parserNames().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<UnaryNode>().kid(); + } else { + MOZ_ASSERT(member->isKind(ParseNodeKind::PropertyDefinition) || + member->isKind(ParseNodeKind::Shorthand)); + subpattern = member->as<BinaryNode>().right(); + } + + ParseNode* lhs = subpattern; + MOZ_ASSERT_IF(member->isKind(ParseNodeKind::Spread), + !lhs->isKind(ParseNodeKind::AssignExpr)); + if (lhs->isKind(ParseNodeKind::AssignExpr)) { + lhs = lhs->as<AssignmentNode>().left(); + } + + size_t emitted; + 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 name currently being matched, which is the + // current property name "label" on the left of a colon in the object + // initialiser. + bool needsGetElem = true; + + if (member->isKind(ParseNodeKind::MutateProto)) { + if (!emitAtomOp(JSOp::GetProp, cx->parserNames().proto)) { + // [stack] ... SET? RHS LREF* PROP + return false; + } + needsGetElem = false; + } else { + MOZ_ASSERT(member->isKind(ParseNodeKind::PropertyDefinition) || + member->isKind(ParseNodeKind::Shorthand)); + + ParseNode* key = member->as<BinaryNode>().left(); + if (key->isKind(ParseNodeKind::NumberExpr)) { + if (!emitNumberOp(key->as<NumericLiteral>().value())) { + // [stack]... SET? RHS LREF* RHS KEY + return false; + } + } else if (key->isKind(ParseNodeKind::BigIntExpr)) { + if (!emitBigIntOp(&key->as<BigIntLiteral>())) { + // [stack]... SET? RHS LREF* RHS KEY + return false; + } + } else if (key->isKind(ParseNodeKind::ObjectPropertyName) || + key->isKind(ParseNodeKind::StringExpr)) { + if (!emitAtomOp(JSOp::GetProp, key->as<NameNode>().atom(), + ShouldInstrument::Yes)) { + // [stack] ... SET? RHS LREF* PROP + return false; + } + needsGetElem = false; + } else { + if (!emitComputedPropertyName(&key->as<UnaryNode>())) { + // [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 not done already. + if (needsGetElem && !emitElemOpBase(JSOp::GetElem)) { + // [stack] ... SET? RHS LREF* PROP + return false; + } + + if (subpattern->isKind(ParseNodeKind::AssignExpr)) { + if (!emitDefault(subpattern->as<AssignmentNode>().right(), lhs)) { + // [stack] ... SET? RHS LREF* VALUE + return false; + } + } + + // Destructure PROP per this member's lhs. + if (!emitSetOrInitializeDestructuring(subpattern, flav)) { + // [stack] ... SET? RHS + return false; + } + } + + return true; +} + +static bool IsDestructuringRestExclusionSetObjLiteralCompatible( + ListNode* pattern) { + int32_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<BinaryNode>().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 >= PropertyTree::MAX_HEIGHT) { + // 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; + } + } + + const ParserAtom* pnatom = nullptr; + for (ParseNode* member : pattern->contents()) { + if (member->isKind(ParseNodeKind::Spread)) { + MOZ_ASSERT(!member->pn_next, "unexpected trailing element after spread"); + break; + } + + bool isIndex = false; + if (member->isKind(ParseNodeKind::MutateProto)) { + pnatom = cx->parserNames().proto; + } else { + ParseNode* key = member->as<BinaryNode>().left(); + if (key->isKind(ParseNodeKind::NumberExpr)) { + if (!emitNumberOp(key->as<NumericLiteral>().value())) { + return false; + } + isIndex = true; + } else if (key->isKind(ParseNodeKind::BigIntExpr)) { + if (!emitBigIntOp(&key->as<BigIntLiteral>())) { + return false; + } + isIndex = true; + } else if (key->isKind(ParseNodeKind::ObjectPropertyName) || + key->isKind(ParseNodeKind::StringExpr)) { + pnatom = key->as<NameNode>().atom(); + } else { + // Otherwise this is a computed property name which needs to + // be added dynamically. + MOZ_ASSERT(key->isKind(ParseNodeKind::ComputedName)); + continue; + } + } + + // Initialize elements with |undefined|. + if (!emit1(JSOp::Undefined)) { + return false; + } + + if (isIndex) { + 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<NameNode>().atom()->length() == 0) { + 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 (!emitAtomOp(JSOp::String, cx->parserNames().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<AssignmentNode>(); + pattern = assignNode->left(); + initializer = assignNode->right(); + } + + if (pattern->isKind(ParseNodeKind::Name)) { + // initializer can be null here. + if (!emitSingleDeclaration(declList, &pattern->as<NameNode>(), + 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<ListNode>(), + 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; + } + + const ParserAtom* 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, + const ParserAtom* 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; + + MOZ_ASSERT_IF(isInit, lhs->isKind(ParseNodeKind::DotExpr) || + lhs->isKind(ParseNodeKind::ElemExpr)); + + // |name| is used within NameOpEmitter, so its lifetime must surpass |noe|. + const ParserAtom* name = nullptr; + + Maybe<NameOpEmitter> noe; + Maybe<PropOpEmitter> poe; + Maybe<ElemOpEmitter> eoe; + + // 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. + const ParserAtom* anonFunctionName = nullptr; + + switch (lhs->getKind()) { + case ParseNodeKind::Name: { + name = lhs->as<NameNode>().name(); + anonFunctionName = name; + noe.emplace(this, name, + isCompound ? NameOpEmitter::Kind::CompoundAssignment + : NameOpEmitter::Kind::SimpleAssignment); + break; + } + case ParseNodeKind::DotExpr: { + PropertyAccess* prop = &lhs->as<PropertyAccess>(); + 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<UnaryNode>(); + 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<PropertyByValue>(); + bool isSuper = elem->isSuper(); + bool isPrivate = 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, + isPrivate ? NameVisibility::Private : NameVisibility::Public); + 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::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<PropertyAccess>(); + 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::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; + 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<PropertyAccess>(); + 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::ArrayExpr: + case ParseNodeKind::ObjectExpr: + if (!emitDestructuringOps(&lhs->as<ListNode>(), + 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|. + const ParserAtom* name = nullptr; + + // Select the appropriate emitter based on the left-hand side. + Maybe<NameOpEmitter> noe; + Maybe<PropOpEmitter> poe; + Maybe<ElemOpEmitter> eoe; + + 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<NameNode>().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<PropertyAccess>(); + 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<UnaryNode>(); + 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<PropertyByValue>(); + bool isSuper = elem->isSuper(); + bool isPrivate = elem->key().isKind(ParseNodeKind::PrivateName); + eoe.emplace(this, ElemOpEmitter::Kind::CompoundAssignment, + isSuper ? ElemOpEmitter::ObjKind::Super + : ElemOpEmitter::ObjKind::Other, + isPrivate ? NameVisibility::Private : NameVisibility::Public); + + 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; + } + + 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<PropertyAccess>(); + + if (!poe->emitAssignment(prop->key().atom())) { + // [stack] RHS + return false; + } + break; + } + + case ParseNodeKind::ElemExpr: { + if (!eoe->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(ListNode* cookedOrRaw, + GCThingIndex* outArrayIndex) { + uint32_t count = cookedOrRaw->count(); + ParseNode* pn = cookedOrRaw->head(); + + // The first element of a call-site node is the raw-values list. Skip over it. + if (cookedOrRaw->isKind(ParseNodeKind::CallSiteObj)) { + MOZ_ASSERT(pn->isKind(ParseNodeKind::ArrayExpr)); + pn = pn->pn_next; + count--; + } else { + MOZ_ASSERT(cookedOrRaw->isKind(ParseNodeKind::ArrayExpr)); + } + + ObjLiteralWriter writer; + + ObjLiteralFlags flags(ObjLiteralFlag::Array); + writer.beginObject(flags); + writer.beginDenseArrayElements(); + + size_t idx; + for (idx = 0; pn; idx++, pn = pn->pn_next) { + MOZ_ASSERT(pn->isKind(ParseNodeKind::TemplateStringExpr) || + pn->isKind(ParseNodeKind::RawUndefinedExpr)); + + if (!emitObjLiteralValue(writer, pn)) { + return false; + } + } + MOZ_ASSERT(idx == count); + + return addObjLiteralData(writer, outArrayIndex); +} + +bool BytecodeEmitter::emitCallSiteObject(CallSiteNode* callSiteObj) { + GCThingIndex cookedIndex; + if (!emitCallSiteObjectArray(callSiteObj, &cookedIndex)) { + return false; + } + + GCThingIndex rawIndex; + if (!emitCallSiteObjectArray(callSiteObj->rawNodes(), &rawIndex)) { + return false; + } + + MOZ_ASSERT(sc->hasCallSiteObj()); + + return emitObjectPairOp(cookedIndex, rawIndex, JSOp::CallSiteObj); +} + +bool BytecodeEmitter::emitCatch(BinaryNode* catchClause) { + // We must be nested under a try-finally statement. + MOZ_ASSERT(innermostNestableControl->is<TryFinallyControl>()); + + 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<ListNode>(), + DestructuringFlavor::Declaration)) { + return false; + } + if (!emit1(JSOp::Pop)) { + return false; + } + break; + + case ParseNodeKind::Name: + if (!emitLexicalInitialization(¶m->as<NameNode>())) { + 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: + // gosub <finally> + // goto <after finally> + 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; +} + +MOZ_MUST_USE bool BytecodeEmitter::emitGoSub(JumpList* jump) { + // Emit the following: + // + // False + // ResumeIndex <resumeIndex> + // Gosub <target> + // resumeOffset: + // JumpTarget + // + // The order is important: the Baseline Interpreter relies on JSOp::JumpTarget + // setting the frame's ICEntry when resuming at resumeOffset. + + if (!emit1(JSOp::False)) { + return false; + } + + BytecodeOffset off; + if (!emitN(JSOp::ResumeIndex, 3, &off)) { + return false; + } + + if (!emitJumpNoFallthrough(JSOp::Gosub, jump)) { + return false; + } + + uint32_t resumeIndex; + if (!allocateResumeIndex(bytecodeSection().offset(), &resumeIndex)) { + return false; + } + + SET_RESUMEINDEX(bytecodeSection().code(off), resumeIndex); + + JumpTarget target; + return emitJumpTarget(&target); +} + +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<UnaryNode>().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<TernaryNode>(); + + 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<LabeledStatement>().statement(); + } + } + + if (maybeFun->is<FunctionNode>() && + maybeFun->as<FunctionNode>().functionIsHoisted()) { + if (!emitTree(maybeFun)) { + return false; + } + } + } + + return true; +} + +bool BytecodeEmitter::emitLexicalScopeBody( + ParseNode* body, EmitLineNumberNote emitLineNote /* = EMIT_LINENOTE */) { + if (body->isKind(ParseNodeKind::StatementList) && + body->as<ListNode>().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<ListNode>())) { + 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<BinaryNode>(); + 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<ForNode>(), &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<int32_t> depth = bytecodeSection().stackDepth(); + + uint32_t argc; + if (option == CopyOption::Filtered) { + MOZ_ASSERT(depth > 2); + // [stack] TARGET SOURCE SET + argc = 3; + + if (!emitAtomOp(JSOp::GetIntrinsic, cx->parserNames().CopyDataProperties)) { + // [stack] TARGET SOURCE SET COPYDATAPROPERTIES + return false; + } + } else { + MOZ_ASSERT(depth > 1); + // [stack] TARGET SOURCE + argc = 2; + + if (!emitAtomOp(JSOp::GetIntrinsic, + cx->parserNames().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; + } + } + 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() { + // 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(JSOp::CallIter, 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, cx->parserNames().next)) { + // [stack] ITER NEXT + return false; + } + if (!emit1(JSOp::Swap)) { + // [stack] NEXT ITER + return false; + } + return true; +} + +bool BytecodeEmitter::emitAsyncIterator() { + // 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 ITERFN + return false; + } + + InternalIfEmitter ifAsyncIterIsUndefined(this); + if (!emitPushNotUndefinedOrNull()) { + // [stack] OBJ ITERFN !UNDEF-OR-NULL + return false; + } + if (!ifAsyncIterIsUndefined.emitThenElse( + IfEmitter::ConditionKind::Negative)) { + // [stack] OBJ ITERFN + return false; + } + + if (!emit1(JSOp::Pop)) { + // [stack] OBJ + return false; + } + 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(JSOp::CallIter, 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, cx->parserNames().next)) { + // [stack] ITER SYNCNEXT + return false; + } + + if (!emit1(JSOp::ToAsyncIter)) { + // [stack] ITER + return false; + } + + if (!ifAsyncIterIsUndefined.emitElse()) { + // [stack] OBJ ITERFN + return false; + } + + if (!emit1(JSOp::Swap)) { + // [stack] ITERFN OBJ + return false; + } + if (!emitCall(JSOp::CallIter, 0)) { + // [stack] ITER + return false; + } + if (!emitCheckIsObj(CheckIsObjectKind::GetIterator)) { + // [stack] ITER + return false; + } + + if (!ifAsyncIterIsUndefined.emitEnd()) { + // [stack] ITER + return false; + } + + if (!emit1(JSOp::Dup)) { + // [stack] ITER ITER + return false; + } + if (!emitAtomOp(JSOp::GetProp, cx->parserNames().next)) { + // [stack] ITER NEXT + return false; + } + if (!emit1(JSOp::Swap)) { + // [stack] NEXT ITER + return false; + } + + return true; +} + +bool BytecodeEmitter::emitSpread(bool allowSelfHosted) { + LoopControl loopInfo(this, StatementKind::Spread); + + if (!loopInfo.emitLoopHead(this, Nothing())) { + // [stack] NEXT ITER ARR I + 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(3, 2)) { + // [stack] NEXT ITER ARR I NEXT + return false; + } + if (!emitIteratorNext(Nothing(), IteratorKind::Sync, allowSelfHosted)) { + // [stack] NEXT ITER ARR I RESULT + return false; + } + if (!emit1(JSOp::Dup)) { + // [stack] NEXT ITER ARR I RESULT RESULT + return false; + } + if (!emitAtomOp(JSOp::GetProp, cx->parserNames().done)) { + // [stack] NEXT ITER ARR I RESULT DONE + return false; + } + if (!emitJump(JSOp::IfNe, &loopInfo.breaks)) { + // [stack] NEXT ITER ARR I RESULT + return false; + } + + // Emit code to assign result.value to the iteration variable. + if (!emitAtomOp(JSOp::GetProp, cx->parserNames().value)) { + // [stack] NEXT ITER ARR I VALUE + return false; + } + if (!emit1(JSOp::InitElemInc)) { + // [stack] NEXT ITER ARR (I+1) + return false; + } + + if (!loopInfo.emitLoopEnd(this, JSOp::Goto, TryNoteKind::ForOf)) { + // [stack] NEXT ITER ARR (I+1) + 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, 4)) { + // [stack] ITER ARR FINAL_INDEX RESULT NEXT + return false; + } + if (!emit2(JSOp::Pick, 4)) { + // [stack] ARR FINAL_INDEX RESULT NEXT ITER + return false; + } + + return emitPopN(3); + // [stack] ARR FINAL_INDEX +} + +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 (!parser->astGenerator().isDeclarationList(target)) { + 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. + + if (!updateSourceCoordNotes(target->pn_pos.begin)) { + return false; + } + + MOZ_ASSERT(target->isForLoopDeclaration()); + target = parser->astGenerator().singleBindingFromDeclaration( + &target->as<ListNode>()); + + NameNode* nameNode = nullptr; + if (target->isKind(ParseNodeKind::Name)) { + nameNode = &target->as<NameNode>(); + } else if (target->isKind(ParseNodeKind::AssignExpr) || + target->isKind(ParseNodeKind::InitExpr)) { + BinaryNode* assignNode = &target->as<BinaryNode>(); + if (assignNode->left()->is<NameNode>()) { + nameNode = &assignNode->left()->as<NameNode>(); + } + } + + if (nameNode) { + const ParserAtom* 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) && + !target->isKind(ParseNodeKind::InitExpr), + "for-in/of loop destructuring declarations can't have initializers"); + + MOZ_ASSERT(target->isKind(ParseNodeKind::ArrayExpr) || + target->isKind(ParseNodeKind::ObjectExpr)); + return emitDestructuringOps(&target->as<ListNode>(), + 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, + allowSelfHostedIter(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<ParseNode*> forOfTarget = forOfHead->kid1(); + MOZ_ASSERT(forOfTarget->isKind(ParseNodeKind::LetDecl) || + forOfTarget->isKind(ParseNodeKind::ConstDecl)); + } + + if (!forOf.emitInitialize(Some(forOfHead->pn_pos.begin))) { + // [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(Some(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 (parser->astGenerator().isDeclarationList(forInTarget)) { + ParseNode* decl = parser->astGenerator().singleBindingFromDeclaration( + &forInTarget->as<ListNode>()); + if (decl->isKind(ParseNodeKind::AssignExpr) || + decl->isKind(ParseNodeKind::InitExpr)) { + BinaryNode* assignNode = &decl->as<BinaryNode>(); + if (assignNode->left()->is<NameNode>()) { + NameNode* nameNode = &assignNode->left()->as<NameNode>(); + 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; + } + + const ParserAtom* 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(Some(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->isForLoopDeclaration()) { + 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(Some(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 */, + ListNode* classContentsIfConstructor /* = nullptr */) { + FunctionBox* funbox = funNode->funbox(); + + MOZ_ASSERT((classContentsIfConstructor != nullptr) == + funbox->isClassConstructor()); + + // [stack] + + FunctionEmitter fe(this, funbox, funNode->syntaxKind(), + funNode->functionIsHoisted() + ? FunctionEmitter::IsHoisted::Yes + : FunctionEmitter::IsHoisted::No); + + // Set the |wasEmitted| flag in the funbox 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->wasEmitted()) { + if (!fe.emitAgain()) { + // [stack] + return false; + } + MOZ_ASSERT(funNode->functionIsHoisted()); + return true; + } + + if (funbox->isInterpreted()) { + // Compute the field initializers data and update the funbox. + // + // NOTE: For a lazy function, this will be applied to any existing function + // in UpdateEmittedInnerFunctions(). + if (classContentsIfConstructor) { + mozilla::Maybe<MemberInitializers> memberInitializers = + setupMemberInitializers(classContentsIfConstructor, + FieldPlacement::Instance); + if (!memberInitializers) { + ReportAllocationOverflow(cx); + return false; + } + funbox->setMemberInitializers(*memberInitializers); + } + + if (!funbox->emitBytecode) { + return fe.emitLazy(); + // [stack] FUN? + } + + if (!fe.prepareForNonLazy()) { + // [stack] + return false; + } + + BytecodeEmitter bce2(this, parser, funbox, stencil, compilationState, + emitterMode); + 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; + } + + return true; + } + + if (!fe.emitAsmJSModule()) { + // [stack] + return false; + } + + return true; +} + +bool BytecodeEmitter::emitDo(BinaryNode* doNode) { + ParseNode* bodyNode = doNode->left(); + + DoWhileEmitter doWhile(this); + if (!doWhile.emitBody(Some(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(Some(whileNode->pn_pos.begin), getOffsetForLoop(condNode), + Some(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(const ParserName* 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<LabelControl>(hasSameLabel); + } else { + auto isNotLabel = [](BreakableControl* control) { + return !control->is<LabelControl>(); + }; + target = findInnermostNestableControl<BreakableControl>(isNotLabel); + } + + return emitGoto(target, &target->breaks, GotoKind::Break); +} + +bool BytecodeEmitter::emitContinue(const ParserName* label) { + LoopControl* target = nullptr; + if (label) { + // Find the loop statement enclosed by the matching label. + NestableControl* control = innermostNestableControl; + while (!control->is<LabelControl>() || + control->as<LabelControl>().label() != label) { + if (control->is<LoopControl>()) { + target = &control->as<LoopControl>(); + } + control = control->enclosing(); + } + } else { + target = findInnermostNestableControl<LoopControl>(); + } + return emitGoto(target, &target->continues, GotoKind::Continue); +} + +bool BytecodeEmitter::emitGetFunctionThis(NameNode* thisName) { + MOZ_ASSERT(sc->hasFunctionThisBinding()); + MOZ_ASSERT(thisName->isName(cx->parserNames().dotThis)); + + return emitGetFunctionThis(Some(thisName->pn_pos.begin)); +} + +bool BytecodeEmitter::emitGetFunctionThis( + const mozilla::Maybe<uint32_t>& offset) { + if (offset) { + if (!updateLineNumberNotes(*offset)) { + return false; + } + } + + if (!emitGetName(cx->parserNames().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<NameNode>(); + return emitGetFunctionThis(nameNode); + // [stack] THIS +} + +bool BytecodeEmitter::emitThisLiteral(ThisLiteral* pn) { + if (ParseNode* kid = pn->kid()) { + NameNode* thisName = &kid->as<NameNode>(); + return emitGetFunctionThis(thisName); + // [stack] THIS + } + + if (sc->thisBinding() == ThisBinding::Module) { + return emit1(JSOp::Undefined); + // [stack] UNDEF + } + + MOZ_ASSERT(sc->thisBinding() == ThisBinding::Global); + return emit1(JSOp::GlobalThis); + // [stack] THIS +} + +bool BytecodeEmitter::emitCheckDerivedClassConstructorReturn() { + MOZ_ASSERT(lookupName(cx->parserNames().dotThis).hasKnownSlot()); + if (!emitGetName(cx->parserNames().dotThis)) { + return false; + } + if (!emit1(JSOp::CheckReturn)) { + return false; + } + return true; +} + +bool BytecodeEmitter::emitReturn(UnaryNode* returnNode) { + if (!updateSourceCoordNotes(returnNode->pn_pos.begin)) { + return false; + } + + bool needsIteratorResult = + sc->isFunctionBox() && sc->asFunctionBox()->needsIteratorResult(); + if (needsIteratorResult) { + if (!emitPrepareIteratorResult()) { + return false; + } + } + + 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; + } + } + + if (needsIteratorResult) { + if (!emitFinishIteratorResult(true)) { + 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; + } + + /* + * EmitNonLocalJumpFixup may add fixup bytecode to close open try + * blocks having finally clauses and to exit intermingled let blocks. + * We can't simply transfer control flow to our caller in that case, + * because we must gosub to those finally clauses from inner to outer, + * with the correct stack pointer (i.e., after popping any with, + * for/in, etc., slots nested inside the finally's try). + * + * In this case we mutate JSOp::Return into JSOp::SetRval and add an + * extra JSOp::RetRval after the fixups. + */ + BytecodeOffset top = bytecodeSection().offset(); + + bool needsFinalYield = + sc->isFunctionBox() && sc->asFunctionBox()->needsFinalYield(); + bool isDerivedClassConstructor = + sc->isFunctionBox() && sc->asFunctionBox()->isDerivedClassConstructor(); + + if (!emit1((needsFinalYield || isDerivedClassConstructor) ? JSOp::SetRval + : JSOp::Return)) { + return false; + } + + // Make sure that we emit this before popping the blocks in + // prepareForNonLocalJump, to ensure that the error is thrown while the + // scope-chain is still intact. + if (isDerivedClassConstructor) { + if (!emitCheckDerivedClassConstructorReturn()) { + return false; + } + } + + NonLocalExitControl nle(this, NonLocalExitControl::Return); + + if (!nle.prepareForNonLocalJumpToOutermost()) { + return false; + } + + if (needsFinalYield) { + // We know that .generator is on the function scope, as we just exited + // all nested scopes. + NameLocation loc = *locationOfNameBoundInScopeType<FunctionScope>( + cx->parserNames().dotGenerator, varEmitterScope); + + // Resolve the return value before emitting the final yield. + if (sc->asFunctionBox()->needsPromiseResult()) { + if (!emit1(JSOp::GetRval)) { + // [stack] RVAL + return false; + } + if (!emitGetNameAtLocation(cx->parserNames().dotGenerator, loc)) { + // [stack] RVAL GEN + return false; + } + if (!emit2(JSOp::AsyncResolve, + uint8_t(AsyncFunctionResolveKind::Fulfill))) { + // [stack] PROMISE + return false; + } + if (!emit1(JSOp::SetRval)) { + // [stack] + return false; + } + } + + if (!emitGetNameAtLocation(cx->parserNames().dotGenerator, loc)) { + return false; + } + if (!emitYieldOp(JSOp::FinalYieldRval)) { + return false; + } + } else if (isDerivedClassConstructor) { + MOZ_ASSERT(JSOp(bytecodeSection().code()[top.value()]) == JSOp::SetRval); + if (!emitReturnRval()) { + return false; + } + } else if (top + BytecodeOffsetDiff(JSOpLength_Return) != + bytecodeSection().offset() || + // If we are instrumenting, make sure we use RetRval and add any + // instrumentation for the frame exit. + instrumentationKinds) { + bytecodeSection().code()[top.value()] = jsbytecode(JSOp::SetRval); + if (!emitReturnRval()) { + return false; + } + } + + return true; +} + +bool BytecodeEmitter::emitGetDotGeneratorInScope(EmitterScope& currentScope) { + if (!sc->isFunction() && sc->isModuleContext() && + sc->asModuleContext()->isAsync()) { + NameLocation loc = *locationOfNameBoundInScopeType<ModuleScope>( + cx->parserNames().dotGenerator, ¤tScope); + return emitGetNameAtLocation(cx->parserNames().dotGenerator, loc); + } + NameLocation loc = *locationOfNameBoundInScopeType<FunctionScope>( + cx->parserNames().dotGenerator, ¤tScope); + return emitGetNameAtLocation(cx->parserNames().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; + } + } + + // 25.5.3.7 AsyncGeneratorYield step 5. + 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(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, cx->parserNames().throw_)) { + // [stack] NEXT ITER RECEIVED ITER THROW + return false; + } + + // Step 7.b.ii. + InternalIfEmitter ifThrowMethodIsNotDefined(this); + if (!emitPushNotUndefinedOrNull()) { + // [stack] NEXT ITER RECEIVED ITER THROW + // [stack] NOT-UNDEF-OR_NULL + return false; + } + + if (!ifThrowMethodIsNotDefined.emitThenElse()) { + // [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, + allowSelfHostedIter(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, cx->parserNames().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 (!emitPushNotUndefinedOrNull()) { + // [stack] NEXT ITER RECEIVED ITER RET NOT-UNDEF-OR_NULL + return false; + } + + // Step 7.c.iv. + // + // Call "return" with the argument passed to Generator.prototype.return. + if (!ifReturnMethodIsDefined.emitThenElse()) { + // [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, cx->parserNames().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, cx->parserNames().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, cx->parserNames().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, cx->parserNames().done)) { + // [stack] NEXT ITER RESULT DONE + return false; + } + if (!emitJump(JSOp::IfNe, &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, cx->parserNames().value)) { + // [stack] NEXT ITER RESULT + return false; + } + if (!emitAwaitInInnermostScope()) { + // [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, cx->parserNames().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<LabelControl>() && + innermostNestableControl->as<LabelControl>().startOffset() >= + bytecodeSection().offset()) { + useful = true; + } + } + + if (useful) { + ValueUsage valueUsage = + wantval ? ValueUsage::WantValue : ValueUsage::IgnoreValue; + ExpressionStatementEmitter ese(this, valueUsage); + if (!ese.prepareForExpr(Some(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<NameNode>(); + 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<PropertyAccess>(); + PropOpEmitter poe(this, PropOpEmitter::Kind::Delete, + propExpr->as<PropertyAccess>().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<UnaryNode>(); + 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<PropertyByValue>(); + bool isSuper = elemExpr->isSuper(); + DebugOnly<bool> isPrivate = + elemExpr->key().isKind(ParseNodeKind::PrivateName); + MOZ_ASSERT(!isPrivate); + ElemOpEmitter eoe( + this, ElemOpEmitter::Kind::Delete, + isSuper ? ElemOpEmitter::ObjKind::Super : ElemOpEmitter::ObjKind::Other, + NameVisibility::Public); // Can't delete a private name. + 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<UnaryNode>(); + 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 <expr>| to + // effectively |<expr>, 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<PropertyByValueBase>(); + 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<PropertyAccessBase>(); + 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<PropertyAccess>(), + !propExpr->as<PropertyAccess>().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<PropertyByValue>(), + !elemExpr->as<PropertyByValue>().isSuper()); + ElemOpEmitter eoe(this, ElemOpEmitter::Kind::Delete, + ElemOpEmitter::ObjKind::Other, NameVisibility::Public); + + 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; +} + +static const char* SelfHostedCallFunctionName(const ParserAtom* name, + JSContext* cx) { + if (name == cx->parserNames().callFunction) { + return "callFunction"; + } + if (name == cx->parserNames().callContentFunction) { + return "callContentFunction"; + } + if (name == cx->parserNames().constructContentFunction) { + return "constructContentFunction"; + } + + MOZ_CRASH("Unknown self-hosted call function name"); +} + +bool BytecodeEmitter::emitSelfHostedCallFunction(CallNode* callNode) { + // 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<NameNode>(); + ListNode* argsList = &callNode->right()->as<ListNode>(); + + const char* errorName = SelfHostedCallFunctionName(calleeNode->name(), cx); + + if (argsList->count() < 2) { + reportNeedMoreArgsError(calleeNode, errorName, "2", "s", argsList); + return false; + } + + JSOp callOp = callNode->callOp(); + if (callOp != JSOp::Call) { + reportError(callNode, JSMSG_NOT_CONSTRUCTOR, errorName); + return false; + } + + bool constructing = + calleeNode->name() == cx->parserNames().constructContentFunction; + ParseNode* funNode = argsList->head(); + if (constructing) { + callOp = JSOp::New; + } else if (funNode->isName(cx->parserNames().std_Function_apply)) { + callOp = JSOp::FunApply; + } + + if (!emitTree(funNode)) { + return false; + } + +#ifdef DEBUG + if (emitterMode == BytecodeEmitter::SelfHosting && + calleeNode->name() == cx->parserNames().callFunction) { + if (!emit1(JSOp::DebugCheckSelfHosted)) { + 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)) { + return false; + } + } else { + // It's |this|, emit it. + if (!emitTree(thisOrNewTarget)) { + return false; + } + } + + for (ParseNode* argpn = thisOrNewTarget->pn_next; argpn; + argpn = argpn->pn_next) { + if (!emitTree(argpn)) { + return false; + } + } + + if (constructing) { + if (!emitTree(thisOrNewTarget)) { + return false; + } + } + + uint32_t argc = argsList->count() - 2; + if (!emitCall(callOp, argc)) { + return false; + } + + return true; +} + +bool BytecodeEmitter::emitSelfHostedResumeGenerator(BinaryNode* callNode) { + ListNode* argsList = &callNode->right()->as<ListNode>(); + + // Syntax: resumeGenerator(gen, value, 'next'|'throw'|'return') + if (argsList->count() != 3) { + reportNeedMoreArgsError(callNode, "resumeGenerator", "3", "s", argsList); + return false; + } + + 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(cx, kindNode->as<NameNode>().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(BinaryNode* callNode) { + ListNode* argsList = &callNode->right()->as<ListNode>(); + + if (argsList->count() != 1) { + reportNeedMoreArgsError(callNode, "allowContentIter", "1", "", argsList); + return false; + } + + // We're just here as a sentinel. Pass the value through directly. + return emitTree(argsList->head()); +} + +bool BytecodeEmitter::emitSelfHostedDefineDataProperty(BinaryNode* callNode) { + ListNode* argsList = &callNode->right()->as<ListNode>(); + + // 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(BinaryNode* callNode) { + ListNode* argsList = &callNode->right()->as<ListNode>(); + + if (argsList->count() != 2) { + reportNeedMoreArgsError(callNode, "hasOwn", "2", "s", argsList); + return false; + } + + 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(BinaryNode* callNode) { + ListNode* argsList = &callNode->right()->as<ListNode>(); + + if (argsList->count() != 3) { + reportNeedMoreArgsError(callNode, "getPropertySuper", "3", "s", argsList); + return false; + } + + 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(BinaryNode* callNode) { + ListNode* argsList = &callNode->right()->as<ListNode>(); + + if (argsList->count() != 1) { + reportNeedMoreArgsError(callNode, "ToNumeric", "1", "", argsList); + return false; + } + + ParseNode* argNode = argsList->head(); + + if (!emitTree(argNode)) { + return false; + } + + return emit1(JSOp::ToNumeric); +} + +bool BytecodeEmitter::emitSelfHostedToString(BinaryNode* callNode) { + ListNode* argsList = &callNode->right()->as<ListNode>(); + + if (argsList->count() != 1) { + reportNeedMoreArgsError(callNode, "ToString", "1", "", argsList); + return false; + } + + ParseNode* argNode = argsList->head(); + + if (!emitTree(argNode)) { + return false; + } + + return emit1(JSOp::ToString); +} + +bool BytecodeEmitter::emitSelfHostedGetBuiltinConstructorOrPrototype( + BinaryNode* callNode, bool isConstructor) { + ListNode* argsList = &callNode->right()->as<ListNode>(); + + if (argsList->count() != 1) { + const char* name = + isConstructor ? "GetBuiltinConstructor" : "GetBuiltinPrototype"; + reportNeedMoreArgsError(callNode, name, "1", "", argsList); + return false; + } + + ParseNode* argNode = argsList->head(); + + if (!argNode->isKind(ParseNodeKind::StringExpr)) { + reportError(callNode, JSMSG_UNEXPECTED_TYPE, "built-in name", + "not a string constant"); + return false; + } + + const ParserAtom* name = argNode->as<NameNode>().atom(); + + BuiltinObjectKind kind; + if (isConstructor) { + kind = BuiltinConstructorForName(cx, name); + } else { + kind = BuiltinPrototypeForName(cx, 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( + BinaryNode* callNode) { + return emitSelfHostedGetBuiltinConstructorOrPrototype( + callNode, /* isConstructor = */ true); +} + +bool BytecodeEmitter::emitSelfHostedGetBuiltinPrototype(BinaryNode* callNode) { + return emitSelfHostedGetBuiltinConstructorOrPrototype( + callNode, /* isConstructor = */ false); +} + +#ifdef DEBUG +bool BytecodeEmitter::checkSelfHostedUnsafeGetReservedSlot( + BinaryNode* callNode) { + ListNode* argsList = &callNode->right()->as<ListNode>(); + + if (argsList->count() != 2) { + reportNeedMoreArgsError(callNode, "UnsafeGetReservedSlot", "2", "", + argsList); + return false; + } + + ParseNode* objNode = argsList->head(); + ParseNode* slotNode = objNode->pn_next; + + // Ensure that the slot argument is fixed, this is required by the JITs. + if (!slotNode->isKind(ParseNodeKind::NumberExpr)) { + reportError(callNode, JSMSG_UNEXPECTED_TYPE, "slot argument", + "not a constant"); + return false; + } + + return true; +} + +bool BytecodeEmitter::checkSelfHostedUnsafeSetReservedSlot( + BinaryNode* callNode) { + ListNode* argsList = &callNode->right()->as<ListNode>(); + + if (argsList->count() != 3) { + reportNeedMoreArgsError(callNode, "UnsafeSetReservedSlot", "3", "", + argsList); + return false; + } + + ParseNode* objNode = argsList->head(); + ParseNode* slotNode = objNode->pn_next; + + // Ensure that the slot argument is fixed, this is required by the JITs. + if (!slotNode->isKind(ParseNodeKind::NumberExpr)) { + reportError(callNode, JSMSG_UNEXPECTED_TYPE, "slot argument", + "not a constant"); + return false; + } + + return true; +} +#endif + +bool BytecodeEmitter::isRestParameter(ParseNode* expr) { + if (!sc->isFunctionBox()) { + return false; + } + + FunctionBox* funbox = sc->asFunctionBox(); + if (!funbox->hasRest()) { + return false; + } + + if (!expr->isKind(ParseNodeKind::Name)) { + return allowSelfHostedIter(expr) && + isRestParameter( + expr->as<BinaryNode>().right()->as<ListNode>().head()); + } + + const ParserAtom* name = expr->as<NameNode>().name(); + Maybe<NameLocation> paramLoc = locationOfNameBoundInFunctionScope(name); + if (paramLoc && lookupName(name) == *paramLoc) { + FunctionScope::ParserData* bindings = funbox->functionScopeBindings(); + if (bindings->slotInfo.nonPositionalFormalStart > 0) { + auto index = + bindings + ->trailingNames[bindings->slotInfo.nonPositionalFormalStart - 1] + .name(); + if (index.isNull()) { + // Rest parameter name can be null when the rest destructuring syntax is + // used: `function f(...[]) {}`. + return false; + } + const ParserAtom* paramName = compilationState.getParserAtomAt(cx, index); + return name == paramName; + } + } + + return false; +} + +/* A version of emitCalleeAndThis for the optional cases: + * * a?.() + * * a?.b() + * * a?.["b"]() + * * (a?.b)() + * + * See emitCallOrNew and emitOptionalCall for more context. + */ +bool BytecodeEmitter::emitOptionalCalleeAndThis(ParseNode* callee, + CallNode* call, + CallOrNewEmitter& cone, + OptionalEmitter& oe) { + if (!CheckRecursionLimit(cx)) { + return false; + } + + switch (ParseNodeKind kind = callee->getKind()) { + case ParseNodeKind::Name: { + const ParserAtom* nameAtom = callee->as<NameNode>().name(); + if (!cone.emitNameCallee(nameAtom)) { + // [stack] CALLEE THIS + return false; + } + break; + } + + case ParseNodeKind::OptionalDotExpr: { + MOZ_ASSERT(emitterMode != BytecodeEmitter::SelfHosting); + OptionalPropertyAccess* prop = &callee->as<OptionalPropertyAccess>(); + 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<PropertyAccess>(); + 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<OptionalPropertyByValue>(); + bool isSuper = false; + bool isPrivate = elem->key().isKind(ParseNodeKind::PrivateName); + ElemOpEmitter& eoe = cone.prepareForElemCallee(isSuper, isPrivate); + if (!emitOptionalElemExpression(elem, eoe, isSuper, oe)) { + // [stack] CALLEE THIS + return false; + } + break; + } + case ParseNodeKind::ElemExpr: { + PropertyByValue* elem = &callee->as<PropertyByValue>(); + bool isSuper = elem->isSuper(); + bool isPrivate = elem->key().isKind(ParseNodeKind::PrivateName); + ElemOpEmitter& eoe = cone.prepareForElemCallee(isSuper, isPrivate); + if (!emitOptionalElemExpression(elem, eoe, isSuper, 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<UnaryNode>(), 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: { + const ParserAtom* nameAtom = callee->as<NameNode>().name(); + if (!cone.emitNameCallee(nameAtom)) { + // [stack] CALLEE THIS + return false; + } + break; + } + case ParseNodeKind::DotExpr: { + MOZ_ASSERT(emitterMode != BytecodeEmitter::SelfHosting); + PropertyAccess* prop = &callee->as<PropertyAccess>(); + bool isSuper = prop->isSuper(); + + PropOpEmitter& poe = cone.prepareForPropCallee(isSuper); + if (!poe.prepareForObj()) { + return false; + } + if (isSuper) { + UnaryNode* base = &prop->expression().as<UnaryNode>(); + 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<PropertyByValue>(); + bool isSuper = elem->isSuper(); + bool isPrivate = elem->key().isKind(ParseNodeKind::PrivateName); + ElemOpEmitter& eoe = cone.prepareForElemCallee(isSuper, isPrivate); + 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::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(parser->astGenerator().isSuperBase(callee)); + if (!cone.emitSuperCallee()) { + // [stack] CALLEE THIS + return false; + } + break; + case ParseNodeKind::OptionalChain: { + return emitCalleeAndThisForOptionalChain(&callee->as<UnaryNode>(), + &call->as<CallNode>(), cone); + } + default: + if (!cone.prepareForOtherCallee()) { + return false; + } + if (!emitTree(callee)) { + return false; + } + break; + } + + if (!cone.emitThis()) { + // [stack] CALLEE THIS + return false; + } + + return true; +} + +bool BytecodeEmitter::emitPipeline(ListNode* node) { + MOZ_ASSERT(node->count() >= 2); + + if (!emitTree(node->head())) { + // [stack] ARG + return false; + } + + ParseNode* callee = node->head()->pn_next; + CallOrNewEmitter cone(this, JSOp::Call, + CallOrNewEmitter::ArgumentsKind::Other, + ValueUsage::WantValue); + do { + if (!emitCalleeAndThis(callee, node, cone)) { + // [stack] ARG CALLEE THIS + return false; + } + if (!emit2(JSOp::Pick, 2)) { + // [stack] CALLEE THIS ARG + return false; + } + if (!cone.emitEnd(1, Some(node->pn_pos.begin))) { + // [stack] RVAL + return false; + } + + cone.reset(); + } while ((callee = callee->pn_next)); + + return true; +} + +ParseNode* BytecodeEmitter::getCoordNode(ParseNode* callNode, + ParseNode* calleeNode, JSOp op, + ListNode* argsList) { + ParseNode* coordNode = callNode; + if (op == JSOp::Call || op == JSOp::SpreadCall || op == JSOp::FunCall || + op == JSOp::FunApply) { + // 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<PropertyAccess>().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()) { + UnaryNode* spreadNode = &argsList->head()->as<UnaryNode>(); + if (!emitTree(spreadNode->kid())) { + // [stack] CALLEE THIS ARG0 + return false; + } + } + if (!cone.emitSpreadArgumentsTest()) { + // [stack] CALLEE THIS + return false; + } + if (!emitArray(argsList->head(), argc)) { + // [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<ListNode>(); + bool isSpread = JOF_OPTYPE(callNode->callOp()) == JOF_BYTE; + JSOp op = callNode->callOp(); + uint32_t argc = argsList->count(); + + CallOrNewEmitter cone( + this, op, + isSpread && (argc == 1) && + isRestParameter(argsList->head()->as<UnaryNode>().kid()) + ? CallOrNewEmitter::ArgumentsKind::SingleSpreadRest + : 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, Some(coordNode->pn_pos.begin))) { + // [stack] RVAL + return false; + } + + return true; +} + +bool BytecodeEmitter::emitCallOrNew( + CallNode* callNode, ValueUsage valueUsage /* = ValueUsage::WantValue */) { + /* + * 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<ListNode>(); + bool isSpread = JOF_OPTYPE(callNode->callOp()) == JOF_BYTE; + + if (calleeNode->isKind(ParseNodeKind::Name) && + emitterMode == BytecodeEmitter::SelfHosting && !isSpread) { + // Calls to "forceInterpreter", "callFunction", + // "callContentFunction", or "resumeGenerator" in self-hosted + // code generate inline bytecode. + const ParserName* calleeName = calleeNode->as<NameNode>().name(); + if (calleeName == cx->parserNames().callFunction || + calleeName == cx->parserNames().callContentFunction || + calleeName == cx->parserNames().constructContentFunction) { + return emitSelfHostedCallFunction(callNode); + } + if (calleeName == cx->parserNames().resumeGenerator) { + return emitSelfHostedResumeGenerator(callNode); + } + if (calleeName == cx->parserNames().forceInterpreter) { + return emitSelfHostedForceInterpreter(); + } + if (calleeName == cx->parserNames().allowContentIter) { + return emitSelfHostedAllowContentIter(callNode); + } + if (calleeName == cx->parserNames().defineDataPropertyIntrinsic && + argsList->count() == 3) { + return emitSelfHostedDefineDataProperty(callNode); + } + if (calleeName == cx->parserNames().hasOwn) { + return emitSelfHostedHasOwn(callNode); + } + if (calleeName == cx->parserNames().getPropertySuper) { + return emitSelfHostedGetPropertySuper(callNode); + } + if (calleeName == cx->parserNames().ToNumeric) { + return emitSelfHostedToNumeric(callNode); + } + if (calleeName == cx->parserNames().ToString) { + return emitSelfHostedToString(callNode); + } + if (calleeName == cx->parserNames().GetBuiltinConstructor) { + return emitSelfHostedGetBuiltinConstructor(callNode); + } + if (calleeName == cx->parserNames().GetBuiltinPrototype) { + return emitSelfHostedGetBuiltinPrototype(callNode); + } +#ifdef DEBUG + if (calleeName == cx->parserNames().UnsafeGetReservedSlot || + calleeName == cx->parserNames().UnsafeGetObjectFromReservedSlot || + calleeName == cx->parserNames().UnsafeGetInt32FromReservedSlot || + calleeName == cx->parserNames().UnsafeGetStringFromReservedSlot || + calleeName == cx->parserNames().UnsafeGetBooleanFromReservedSlot) { + // Make sure that this call is correct, but don't emit any special code. + if (!checkSelfHostedUnsafeGetReservedSlot(callNode)) { + return false; + } + } + if (calleeName == cx->parserNames().UnsafeSetReservedSlot) { + // Make sure that this call is correct, but don't emit any special code. + if (!checkSelfHostedUnsafeSetReservedSlot(callNode)) { + return false; + } + } +#endif + // Fall through + } + + JSOp op = callNode->callOp(); + uint32_t argc = argsList->count(); + CallOrNewEmitter cone( + this, op, + isSpread && (argc == 1) && + isRestParameter(argsList->head()->as<UnaryNode>().kid()) + ? CallOrNewEmitter::ArgumentsKind::SingleSpreadRest + : 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; + } + + ParseNode* coordNode = getCoordNode(callNode, calleeNode, op, argsList); + + if (!cone.emitEnd(argc, Some(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[] = { + // JSOp::Nop is for pipeline operator which does not emit its own JSOp + // but has highest precedence in binary operators + JSOp::Nop, 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::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); +#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; +} + +/* + * 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 */) { + if (!CheckRecursionLimit(cx)) { + return false; + } + ParseNodeKind kind = pn->getKind(); + switch (kind) { + case ParseNodeKind::OptionalDotExpr: { + OptionalPropertyAccess* prop = &pn->as<OptionalPropertyAccess>(); + 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<PropertyAccess>(); + 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<OptionalPropertyByValue>(); + bool isSuper = false; + bool isPrivate = elem->key().isKind(ParseNodeKind::PrivateName); + ElemOpEmitter eoe( + this, ElemOpEmitter::Kind::Get, ElemOpEmitter::ObjKind::Other, + isPrivate ? NameVisibility::Private : NameVisibility::Public); + + if (!emitOptionalElemExpression(elem, eoe, isSuper, oe)) { + return false; + } + break; + } + case ParseNodeKind::ElemExpr: { + PropertyByValue* elem = &pn->as<PropertyByValue>(); + bool isSuper = elem->isSuper(); + bool isPrivate = elem->key().isKind(ParseNodeKind::PrivateName); + ElemOpEmitter eoe( + this, ElemOpEmitter::Kind::Get, + isSuper ? ElemOpEmitter::ObjKind::Super + : ElemOpEmitter::ObjKind::Other, + isPrivate ? NameVisibility::Private : NameVisibility::Public); + + if (!emitOptionalElemExpression(elem, eoe, isSuper, oe)) { + return false; + } + break; + } + case ParseNodeKind::CallExpr: + case ParseNodeKind::OptionalCallExpr: + if (!emitOptionalCall(&pn->as<CallNode>(), 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<UnaryNode>(); + 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<UnaryNode>(); + 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::emitShortCircuit(ListNode* node) { + 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); + + /* Left-associative operator chain: avoid too much recursion. */ + ParseNode* expr = node->head(); + + if (!emitTree(expr)) { + return false; + } + + 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; + if (!emitJump(op, &jump)) { + return false; + } + if (!emit1(JSOp::Pop)) { + return false; + } + + /* Emit nodes between the head and the tail. */ + while ((expr = expr->pn_next)->pn_next) { + if (!emitTree(expr)) { + return false; + } + if (!emitJump(op, &jump)) { + return false; + } + if (!emit1(JSOp::Pop)) { + return false; + } + } + if (!emitTree(expr)) { + return false; + } + + if (!emitJumpTargetAndPatch(jump)) { + return false; + } + return true; +} + +bool BytecodeEmitter::emitSequenceExpr( + ListNode* node, ValueUsage valueUsage /* = ValueUsage::WantValue */) { + for (ParseNode* child = node->head();; child = child->pn_next) { + if (!updateSourceCoordNotes(child->pn_pos.begin)) { + return false; + } + if (!emitTree(child, + child->pn_next ? ValueUsage::IgnoreValue : valueUsage)) { + return false; + } + if (!child->pn_next) { + break; + } + if (!emit1(JSOp::Pop)) { + 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) { + switch (incDec->kid()->getKind()) { + case ParseNodeKind::DotExpr: + return emitPropIncDec(incDec); + case ParseNodeKind::ElemExpr: + return emitElemIncDec(incDec); + case ParseNodeKind::CallExpr: + return emitCallIncDec(incDec); + default: + return emitNameIncDec(incDec); + } +} + +// 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) { + const ParserAtom* 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 /* = ValueUsage::WantValue */) { + 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<UnaryNode>().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; + int propCount = 0; + + for (ParseNode* propdef : obj->contents()) { + if (!propdef->is<BinaryNode>()) { + keysOK = false; + break; + } + propCount++; + + BinaryNode* prop = &propdef->as<BinaryNode>(); + 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<NumericLiteral>().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<PropertyDefinition>() + ? prop->as<PropertyDefinition>().accessorType() + : AccessorType::None; + if (accessorType != AccessorType::None) { + keysOK = false; + break; + } + + if (!isRHSObjLiteralCompatible(value)) { + valuesOK = false; + } + } + + if (propCount >= PropertyTree::MAX_HEIGHT) { + // JSOp::NewObject cannot accept dictionary-mode objects. + keysOK = false; + } + + *withValues = keysOK && valuesOK; + *withoutValues = keysOK; +} + +bool BytecodeEmitter::isArrayObjLiteralCompatible(ParseNode* arrayHead) { + for (ParseNode* elem = arrayHead; elem; elem = elem->pn_next) { + 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<ClassField>()) { + MOZ_ASSERT(type == ClassBody); + // Only handle computing field keys here: the .initializers lambda array + // is created elsewhere. + ClassField* field = &propdef->as<ClassField>(); + if (field->name().getKind() == ParseNodeKind::ComputedName) { + const ParserName* fieldKeys = field->isStatic() + ? cx->parserNames().dotStaticFieldKeys + : cx->parserNames().dotFieldKeys; + if (!emitGetName(fieldKeys)) { + // [stack] CTOR? OBJ ARRAY + return false; + } + + ParseNode* nameExpr = field->name().as<UnaryNode>().kid(); + + if (!emitTree(nameExpr, ValueUsage::WantValue, EMIT_LINENOTE)) { + // [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->is<LexicalScopeNode>()) { + // Constructors are sometimes wrapped in LexicalScopeNodes. As we + // already handled emitting the constructor, skip it. + MOZ_ASSERT(propdef->as<LexicalScopeNode>().scopeBody()->isKind( + ParseNodeKind::ClassMethod)); + 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(Some(propdef->pn_pos.begin))) { + // [stack] OBJ + return false; + } + if (!emitTree(propdef->as<UnaryNode>().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(Some(propdef->pn_pos.begin))) { + // [stack] OBJ OBJ + return false; + } + if (!emitTree(propdef->as<UnaryNode>().kid())) { + // [stack] OBJ OBJ VAL + return false; + } + if (!pe.emitSpread()) { + // [stack] OBJ + return false; + } + continue; + } + + BinaryNode* prop = &propdef->as<BinaryNode>(); + + ParseNode* key = prop->left(); + ParseNode* propVal = prop->right(); + AccessorType accessorType; + if (prop->is<ClassMethod>()) { + if (!prop->as<ClassMethod>().isStatic() && + key->isKind(ParseNodeKind::PrivateName)) { + // Private instance methods are separately added to the .initializers + // array. + continue; + } + + accessorType = prop->as<ClassMethod>().accessorType(); + } else if (prop->is<PropertyDefinition>()) { + accessorType = prop->as<PropertyDefinition>().accessorType(); + } else { + accessorType = AccessorType::None; + } + + auto emitValue = [this, &key, &propVal, accessorType, &pe]() { + // [stack] CTOR? OBJ CTOR? KEY? + + if (propVal->isDirectRHSAnonFunction()) { + if (key->isKind(ParseNodeKind::NumberExpr)) { + MOZ_ASSERT(accessorType == AccessorType::None); + + const ParserAtom* keyAtom = key->as<NumericLiteral>().toAtom( + cx, compilationState.parserAtoms); + if (!keyAtom) { + return false; + } + if (!emitAnonymousFunctionWithName(propVal, keyAtom)) { + // [stack] CTOR? OBJ CTOR? KEY VAL + return false; + } + } else if (key->isKind(ParseNodeKind::ObjectPropertyName) || + key->isKind(ParseNodeKind::StringExpr)) { + MOZ_ASSERT(accessorType == AccessorType::None); + + const ParserAtom* keyAtom = key->as<NameNode>().atom(); + if (!emitAnonymousFunctionWithName(propVal, keyAtom)) { + // [stack] CTOR? OBJ CTOR? VAL + return false; + } + } else { + MOZ_ASSERT(key->isKind(ParseNodeKind::ComputedName) || + key->isKind(ParseNodeKind::BigIntExpr)); + + // If a function name is a BigInt, then treat it as a computed name + // equivalent to `[ToString(B)]` for some big-int value `B`. + if (key->isKind(ParseNodeKind::BigIntExpr)) { + MOZ_ASSERT(accessorType == AccessorType::None); + if (!emit1(JSOp::ToString)) { + return false; + } + } + + 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<FunctionNode>() && + propVal->as<FunctionNode>().funbox()->needsHomeObject()) { + if (!pe.emitInitHomeObject()) { + // [stack] CTOR? OBJ CTOR? KEY? FUN + return false; + } + } + return true; + }; + + PropertyEmitter::Kind kind = + (type == ClassBody && propdef->as<ClassMethod>().isStatic()) + ? PropertyEmitter::Kind::Static + : PropertyEmitter::Kind::Prototype; + if (key->isKind(ParseNodeKind::NumberExpr) || + key->isKind(ParseNodeKind::BigIntExpr)) { + // [stack] CTOR? OBJ + if (!pe.prepareForIndexPropKey(Some(propdef->pn_pos.begin), kind)) { + // [stack] CTOR? OBJ CTOR? + return false; + } + if (key->isKind(ParseNodeKind::NumberExpr)) { + if (!emitNumberOp(key->as<NumericLiteral>().value())) { + // [stack] CTOR? OBJ CTOR? KEY + return false; + } + } else { + if (!emitBigIntOp(&key->as<BigIntLiteral>())) { + // [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)) { + return false; + } + + continue; + } + + if (key->isKind(ParseNodeKind::ObjectPropertyName) || + key->isKind(ParseNodeKind::StringExpr)) { + // [stack] CTOR? OBJ + + // emitClass took care of constructor already. + if (type == ClassBody && + key->as<NameNode>().atom() == cx->parserNames().constructor && + !propdef->as<ClassMethod>().isStatic()) { + continue; + } + + if (!pe.prepareForPropValue(Some(propdef->pn_pos.begin), kind)) { + // [stack] CTOR? OBJ CTOR? + return false; + } + + if (!emitValue()) { + // [stack] CTOR? OBJ CTOR? VAL + return false; + } + + const ParserAtom* keyAtom = key->as<NameNode>().atom(); + + if (!pe.emitInit(accessorType, keyAtom)) { + return false; + } + + continue; + } + + MOZ_ASSERT(key->isKind(ParseNodeKind::ComputedName) || + key->isKind(ParseNodeKind::PrivateName)); + + // [stack] CTOR? OBJ + + if (!pe.prepareForComputedPropKey(Some(propdef->pn_pos.begin), kind)) { + // [stack] CTOR? OBJ CTOR? + return false; + } + if (key->is<NameNode>()) { + if (!emitGetPrivateName(&key->as<NameNode>())) { + // [stack] CTOR? OBJ CTOR? KEY + return false; + } + } else { + if (!emitTree(key->as<UnaryNode>().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)) { + return false; + } + + if (key->isKind(ParseNodeKind::PrivateName) && + key->as<NameNode>().privateNameKind() == PrivateNameKind::Setter) { + if (!emitGetPrivateName(&key->as<NameNode>())) { + // [stack] THIS NAME + return false; + } + if (!emitAtomOp(JSOp::GetIntrinsic, cx->parserNames().NoPrivateGetter)) { + // [stack] THIS NAME FUN + return false; + } + if (!emit1(JSOp::InitHiddenElemGetter)) { + // [stack] THIS + return false; + } + } + } + + return true; +} + +bool BytecodeEmitter::emitPropertyListObjLiteral(ListNode* obj, + ObjLiteralFlags flags, + bool useObjLiteralValues) { + ObjLiteralWriter writer; + + writer.beginObject(flags); + bool singleton = flags.contains(ObjLiteralFlag::Singleton); + + for (ParseNode* propdef : obj->contents()) { + BinaryNode* prop = &propdef->as<BinaryNode>(); + ParseNode* key = prop->left(); + + if (key->is<NameNode>()) { + writer.setPropName(key->as<NameNode>().atom()); + } else { + double numValue = key->as<NumericLiteral>().value(); + int32_t i = 0; + DebugOnly<bool> numIsInt = + NumberIsInt32(numValue, &i); // checked previously. + MOZ_ASSERT(numIsInt); + MOZ_ASSERT( + ObjLiteralWriter::arrayIndexInRange(i)); // checked previously. + writer.setPropIndex(i); + } + + if (useObjLiteralValues) { + MOZ_ASSERT(singleton); + ParseNode* value = prop->right(); + if (!emitObjLiteralValue(writer, value)) { + return false; + } + } else { + if (!writer.propWithUndefinedValue(cx)) { + 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(singleton, stencil.input.options.isRunOnce); + + JSOp op = singleton ? JSOp::Object : JSOp::NewObject; + if (!emitGCIndexOp(op, index)) { + // [stack] OBJ + return false; + } + + return true; +} + +bool BytecodeEmitter::emitDestructuringRestExclusionSetObjLiteral( + ListNode* pattern) { + ObjLiteralFlags flags; + + ObjLiteralWriter writer; + writer.beginObject(flags); + + for (ParseNode* member : pattern->contents()) { + if (member->isKind(ParseNodeKind::Spread)) { + MOZ_ASSERT(!member->pn_next, "unexpected trailing element after spread"); + break; + } + + const ParserAtom* atom = nullptr; + if (member->isKind(ParseNodeKind::MutateProto)) { + atom = cx->parserNames().proto; + } else { + ParseNode* key = member->as<BinaryNode>().left(); + atom = key->as<NameNode>().atom(); + } + + writer.setPropName(atom); + + if (!writer.propWithUndefinedValue(cx)) { + return false; + } + } + + GCThingIndex index; + if (!addObjLiteralData(writer, &index)) { + return false; + } + + // 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. But first we + // need to make sure this won't interfere with XDR, cf. the + // `RealmBehaviors::singletonsAsTemplates_` flag. + if (!emitGCIndexOp(JSOp::NewObject, index)) { + // [stack] OBJ + return false; + } + + return true; +} + +bool BytecodeEmitter::emitObjLiteralArray(ParseNode* arrayHead) { + MOZ_ASSERT(checkSingletonContext()); + + ObjLiteralWriter writer; + + ObjLiteralFlags flags(ObjLiteralFlag::Array, ObjLiteralFlag::Singleton); + + writer.beginObject(flags); + + writer.beginDenseArrayElements(); + for (ParseNode* elem = arrayHead; elem; elem = elem->pn_next) { + if (!emitObjLiteralValue(writer, elem)) { + return false; + } + } + + GCThingIndex index; + if (!addObjLiteralData(writer, &index)) { + return false; + } + + if (!emitGCIndexOp(JSOp::Object, 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<NumericLiteral>().value(); + int32_t i = 0; + js::Value v; + if (NumberIsInt32(numValue, &i)) { + v.setInt32(i); + } else { + v.setDouble(numValue); + } + if (!writer.propWithConstNumericValue(cx, v)) { + return false; + } + } else if (value->isKind(ParseNodeKind::TrueExpr)) { + if (!writer.propWithTrueValue(cx)) { + return false; + } + } else if (value->isKind(ParseNodeKind::FalseExpr)) { + if (!writer.propWithFalseValue(cx)) { + return false; + } + } else if (value->isKind(ParseNodeKind::NullExpr)) { + if (!writer.propWithNullValue(cx)) { + return false; + } + } else if (value->isKind(ParseNodeKind::RawUndefinedExpr)) { + if (!writer.propWithUndefinedValue(cx)) { + return false; + } + } else if (value->isKind(ParseNodeKind::StringExpr) || + value->isKind(ParseNodeKind::TemplateStringExpr)) { + if (!writer.propWithAtomValue(cx, value->as<NameNode>().atom())) { + return false; + } + } else { + MOZ_CRASH("Unexpected parse node"); + } + return true; +} + +mozilla::Maybe<MemberInitializers> BytecodeEmitter::setupMemberInitializers( + ListNode* classMembers, FieldPlacement placement) { + bool isStatic = placement == FieldPlacement::Static; + + size_t numFields = std::count_if( + classMembers->contents().begin(), classMembers->contents().end(), + [&isStatic](ParseNode* member) { + return NeedsFieldInitializer(member, isStatic); + }); + size_t numPrivateMethods = std::count_if( + classMembers->contents().begin(), classMembers->contents().end(), + [&isStatic](ParseNode* member) { + return NeedsMethodInitializer(member, isStatic); + }); + + // If there are more initializers than can be represented, return invalid. + if (numFields + numPrivateMethods > MemberInitializers::MaxInitializers) { + return Nothing(); + } + return Some(MemberInitializers(numFields + numPrivateMethods)); +} + +// 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<ClassField>() && + propdef->as<ClassField>().isStatic() == isStatic && + propdef->as<ClassField>().name().getKind() == + ParseNodeKind::ComputedName; + }; + + size_t numFieldKeys = std::count_if( + obj->contents().begin(), obj->contents().end(), isFieldWithComputedName); + if (numFieldKeys == 0) { + return true; + } + + const ParserName* fieldKeys = isStatic ? cx->parserNames().dotStaticFieldKeys + : cx->parserNames().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; +} + +bool BytecodeEmitter::emitCreateMemberInitializers(ClassEmitter& ce, + ListNode* obj, + FieldPlacement placement) { + // FieldPlacement::Instance + // [stack] HOMEOBJ HERITAGE? + // + // FieldPlacement::Static + // [stack] CTOR HOMEOBJ + mozilla::Maybe<MemberInitializers> memberInitializers = + setupMemberInitializers(obj, placement); + if (!memberInitializers) { + ReportAllocationOverflow(cx); + 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 methods could be used in the field initializers, + // so we stamp them onto the instance first. + // Static private methods aren't implemented, so skip this step + // if emitting static initializers. + if (!isStatic && !emitPrivateMethodInitializers(ce, obj)) { + return false; + } + + for (ParseNode* propdef : obj->contents()) { + if (!propdef->is<ClassField>()) { + continue; + } + if (propdef->as<ClassField>().isStatic() != isStatic) { + continue; + } + + FunctionNode* initializer = propdef->as<ClassField>().initializer(); + + 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; + } + } + + if (!ce.emitMemberInitializersEnd()) { + // [stack] HOMEOBJ HERITAGE? + // or: + // [stack] CTOR HOMEOBJ + return false; + } + + return true; +} + +bool BytecodeEmitter::emitPrivateMethodInitializers(ClassEmitter& ce, + ListNode* obj) { + for (ParseNode* propdef : obj->contents()) { + if (!propdef->is<ClassMethod>() || propdef->as<ClassMethod>().isStatic()) { + continue; + } + ParseNode* propName = &propdef->as<ClassMethod>().name(); + if (!propName->isKind(ParseNodeKind::PrivateName)) { + 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. + StringBuffer storedMethodName(cx); + if (!storedMethodName.append(propName->as<NameNode>().atom())) { + return false; + } + AccessorType accessorType = propdef->as<ClassMethod>().accessorType(); + switch (accessorType) { + case AccessorType::None: + if (!storedMethodName.append(".method")) { + return false; + } + break; + case AccessorType::Getter: + if (!storedMethodName.append(".getter")) { + return false; + } + break; + case AccessorType::Setter: + if (!storedMethodName.append(".setter")) { + return false; + } + break; + default: + MOZ_CRASH("Invalid private method accessor type"); + } + const ParserAtom* storedMethodAtom = + storedMethodName.finishParserAtom(compilationState.parserAtoms); + + // Emit the private method body and store it as a lexical var. + if (!emitFunction(&propdef->as<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 (!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(ce, propdef, propName, storedMethodAtom, + accessorType)) { + // [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( + ClassEmitter& ce, ParseNode* prop, ParseNode* propName, + const ParserAtom* storedMethodAtom, AccessorType accessorType) { + // Emit the synthesized initializer function. + FunctionNode* funNode = prop->as<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, parser, funbox, stencil, compilationState, + emitterMode); + if (!bce2.init(funNode->pn_pos)) { + return false; + } + ListNode* paramsBody = &funNode->body()->as<ListNode>(); + 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(&propName->as<NameNode>())) { + // [stack] THIS NAME + return false; + } + if (!bce2.emitGetName(storedMethodAtom)) { + // [stack] THIS NAME METHOD + return false; + } + + PrivateNameKind kind = propName->as<NameNode>().privateNameKind(); + switch (kind) { + case PrivateNameKind::Method: + if (!bce2.emit1(JSOp::InitLockedElem)) { + // [stack] THIS + return false; + } + break; + case PrivateNameKind::Setter: + if (!bce2.emit1(JSOp::InitHiddenElemSetter)) { + // [stack] THIS + return false; + } + if (!bce2.emitGetPrivateName(&propName->as<NameNode>())) { + // [stack] THIS NAME + return false; + } + if (!bce2.emitAtomOp(JSOp::GetIntrinsic, + cx->parserNames().NoPrivateGetter)) { + // [stack] THIS NAME FUN + return false; + } + if (!bce2.emit1(JSOp::InitHiddenElemGetter)) { + // [stack] THIS + return false; + } + break; + case PrivateNameKind::Getter: + case PrivateNameKind::GetterSetter: + if (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()); + + MOZ_ASSERT(funbox->memberInitializers().valid); + return funbox->memberInitializers(); + } + } + + MOZ_RELEASE_ASSERT(compilationState.scopeContext.memberInitializers); + return *compilationState.scopeContext.memberInitializers; +} + +bool BytecodeEmitter::emitInitializeInstanceMembers() { + const MemberInitializers& memberInitializers = + findMemberInitializersForCall(); + size_t numInitializers = memberInitializers.numMemberInitializers; + + if (numInitializers == 0) { + return true; + } + + if (!emitGetName(cx->parserNames().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(cx->parserNames().dotThis)) { + // [stack] ARRAY? FUNC THIS + return false; + } + + if (!emitCall(JSOp::CallIgnoresRv, 0)) { + // [stack] ARRAY? RVAL + return false; + } + + if (!emit1(JSOp::Pop)) { + // [stack] ARRAY? + return false; + } + } + + return true; +} + +bool BytecodeEmitter::emitInitializeStaticFields(ListNode* classMembers) { + auto isStaticField = [](ParseNode* propdef) { + return propdef->is<ClassField>() && propdef->as<ClassField>().isStatic(); + }; + size_t numFields = + std::count_if(classMembers->contents().begin(), + classMembers->contents().end(), isStaticField); + + if (numFields == 0) { + return true; + } + + if (!emitGetName(cx->parserNames().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; + } + + 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 = [&](const ParserName* 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(cx->parserNames().dotStaticInitializers)) { + return false; + } + + auto isStaticFieldWithComputedName = [](ParseNode* propdef) { + return propdef->is<ClassField>() && propdef->as<ClassField>().isStatic() && + propdef->as<ClassField>().name().getKind() == + ParseNodeKind::ComputedName; + }; + + if (std::any_of(classMembers->contents().begin(), + classMembers->contents().end(), + isStaticFieldWithComputedName)) { + if (!clearStaticFieldSlot(cx->parserNames().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 + // 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 this template object to speed the creation + // of the object each time it executes (stealing its shape, etc.). 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(); + + ObjLiteralFlags flags; + if (singleton) { + // Case 1 or 2. + flags += ObjLiteralFlag::Singleton; + } else { + // Case 3. + useObjLiteralValues = false; + } + + // 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 (case 1 + // or 2) or template-without-values (case 3) object can be allocated and + // the bytecode can be patched to refer to it. + if (!emitPropertyListObjLiteral(objNode, flags, 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->head() && isArrayObjLiteralCompatible(array->head())) { + return emitObjLiteralArray(array->head()); + } + + return emitArray(array->head(), array->count()); +} + +bool BytecodeEmitter::emitArray(ParseNode* arrayHead, uint32_t count) { + /* + * 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 = arrayHead; elem; elem = elem->pn_next) { + 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"); + 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; + } + + ParseNode* elem = arrayHead; + uint32_t index; + bool afterSpread = false; + for (index = 0; elem; index++, elem = elem->pn_next) { + if (!afterSpread && elem->isKind(ParseNodeKind::Spread)) { + afterSpread = true; + if (!emitNumberOp(index)) { + // [stack] ARRAY INDEX + return false; + } + } + if (!updateSourceCoordNotes(elem->pn_pos.begin)) { + return false; + } + + bool allowSelfHostedIterFlag = false; + if (elem->isKind(ParseNodeKind::Elision)) { + if (!emit1(JSOp::Hole)) { + return false; + } + } else { + ParseNode* expr; + if (elem->isKind(ParseNodeKind::Spread)) { + expr = elem->as<UnaryNode>().kid(); + + allowSelfHostedIterFlag = allowSelfHostedIter(expr); + } else { + expr = elem; + } + if (!emitTree(expr, ValueUsage::WantValue, EMIT_LINENOTE)) { + // [stack] ARRAY INDEX? VALUE + return false; + } + } + if (elem->isKind(ParseNodeKind::Spread)) { + if (!emitIterator()) { + // [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(allowSelfHostedIterFlag)) { + // [stack] ARRAY INDEX + return false; + } + } else if (afterSpread) { + if (!emit1(JSOp::InitElemInc)) { + return false; + } + } else { + if (!emitUint32Operand(JSOp::InitElemArray, index)) { + return false; + } + } + } + MOZ_ASSERT(index == count); + if (afterSpread) { + if (!emit1(JSOp::Pop)) { + // [stack] ARRAY + return false; + } + } + return true; +} + +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; + } + if (!emitTree(unaryNode->kid())) { + return false; + } + return emit1(UnaryOpParseNodeKindToJSOp(unaryNode->getKind())); +} + +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(ListNode* paramsBody) { + ParseNode* funBody = paramsBody->last(); + FunctionBox* funbox = sc->asFunctionBox(); + + bool hasRest = funbox->hasRest(); + + FunctionParamsEmitter fpe(this, funbox); + for (ParseNode* arg = paramsBody->head(); arg != funBody; + arg = arg->pn_next) { + ParseNode* bindingElement = arg; + ParseNode* initializer = nullptr; + if (arg->isKind(ParseNodeKind::AssignExpr) || + arg->isKind(ParseNodeKind::InitExpr)) { + bindingElement = arg->as<BinaryNode>().left(); + initializer = arg->as<BinaryNode>().right(); + } + bool hasInitializer = !!initializer; + bool isRest = hasRest && arg->pn_next == funBody; + 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<ListNode>(), + 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 { + const ParserName* paramName = bindingElement->as<NameNode>().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; + } + const ParserAtom* paramName = bindingElement->as<NameNode>().name(); + if (!fpe.emitDefaultEnd(paramName)) { + // [stack] + return false; + } + + continue; + } + + const ParserAtom* paramName = bindingElement->as<NameNode>().name(); + if (!fpe.emitSimple(paramName)) { + // [stack] + return false; + } + } + + return true; +} + +bool BytecodeEmitter::emitInitializeFunctionSpecialNames() { + FunctionBox* funbox = sc->asFunctionBox(); + + // [stack] + + auto emitInitializeFunctionSpecialName = [](BytecodeEmitter* bce, + const ParserName* 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 + return false; + } + if (!noe.emitAssignment()) { + // [stack] THIS/ARGUMENTS + 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->argumentsHasVarBinding()) { + if (!emitInitializeFunctionSpecialName(this, cx->parserNames().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, cx->parserNames().dotThis, + JSOp::FunctionThis)) { + return false; + } + } + + // Do nothing if the function doesn't implicitly return a promise result. + if (funbox->needsPromiseResult()) { + if (!emitInitializeFunctionSpecialName(this, cx->parserNames().dotGenerator, + JSOp::Generator)) { + // [stack] + return false; + } + } + return true; +} + +bool BytecodeEmitter::emitLexicalInitialization(NameNode* name) { + return emitLexicalInitialization(name->name()); +} + +bool BytecodeEmitter::emitLexicalInitialization(const ParserAtom* 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 + // name is lexical and no BIND[G]NAME ops were emitted. + MOZ_ASSERT(noe.loc().isLexical()); + MOZ_ASSERT(!noe.emittedBindOp()); + + if (!noe.emitAssignment()) { + return false; + } + + return true; +} + +static MOZ_ALWAYS_INLINE ParseNode* FindConstructor(JSContext* cx, + ListNode* classMethods) { + for (ParseNode* classElement : classMethods->contents()) { + ParseNode* unwrappedElement = classElement; + if (unwrappedElement->is<LexicalScopeNode>()) { + unwrappedElement = unwrappedElement->as<LexicalScopeNode>().scopeBody(); + } + if (unwrappedElement->is<ClassMethod>()) { + ClassMethod& method = unwrappedElement->as<ClassMethod>(); + ParseNode& methodName = method.name(); + if (!method.isStatic() && + (methodName.isKind(ParseNodeKind::ObjectPropertyName) || + methodName.isKind(ParseNodeKind::StringExpr)) && + methodName.as<NameNode>().atom() == cx->parserNames().constructor) { + return classElement; + } + } + } + return nullptr; +} + +template <class ClassMemberType> +bool BytecodeEmitter::emitNewPrivateNames(ListNode* classMembers) { + for (ParseNode* classElement : classMembers->contents()) { + if (!classElement->is<ClassMemberType>()) { + continue; + } + + ParseNode* elementName = &classElement->as<ClassMemberType>().name(); + if (!elementName->isKind(ParseNodeKind::PrivateName)) { + continue; + } + + const ParserAtom* privateName = elementName->as<NameNode>().name(); + + // TODO: Add a new bytecode to create private names. + if (!emitAtomOp(JSOp::GetIntrinsic, cx->parserNames().NewPrivateName)) { + // [stack] HERITAGE NEWPRIVATENAME + return false; + } + + // Push `undefined` as `this` parameter for call. + if (!emit1(JSOp::Undefined)) { + // [stack] HERITAGE NEWPRIVATENAME UNDEFINED + return false; + } + + if (!emitAtomOp(JSOp::String, privateName)) { + // [stack] HERITAGE NEWPRIVATENAME UNDEFINED NAME + return false; + } + + int argc = 1; + if (!emitCall(JSOp::Call, argc)) { + // [stack] HERITAGE PRIVATENAME + return false; + } + + // Add a binding for #name => privatename + if (!emitLexicalInitialization(privateName)) { + // [stack] HERITAGE PRIVATENAME + return false; + } + + // Pop Private name off the stack. + if (!emit1(JSOp::Pop)) { + // [stack] HERITAGE + 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 */, + const ParserAtom* nameForAnonymousClass /* = nullptr */) { + MOZ_ASSERT((nameKind == ClassNameKind::InferredName) == + bool(nameForAnonymousClass)); + + ParseNode* heritageExpression = classNode->heritage(); + ListNode* classMembers = classNode->memberList(); + ParseNode* constructor = FindConstructor(cx, classMembers); + + // If |nameKind != ClassNameKind::ComputedName| + // [stack] + // Else + // [stack] NAME + + ClassEmitter ce(this); + const ParserAtom* innerName = nullptr; + 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 (LexicalScopeNode* bodyScopeBindings = classNode->bodyScopeBindings()) { + if (!ce.emitBodyScope(bodyScopeBindings->scopeBindings())) { + // [stack] HERITAGE + return false; + } + + if (!emitNewPrivateNames<ClassField>(classMembers)) { + return false; + } + if (!emitNewPrivateNames<ClassMethod>(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. + if (constructor) { + // See |Parser::classMember(...)| for the reason why |.initializers| is + // created within its own scope. + Maybe<LexicalScopeEmitter> lse; + FunctionNode* ctor; + if (constructor->is<LexicalScopeNode>()) { + LexicalScopeNode* constructorScope = &constructor->as<LexicalScopeNode>(); + + // The constructor scope should only contain the |.initializers| binding. + MOZ_ASSERT(!constructorScope->isEmptyScope()); + MOZ_ASSERT(constructorScope->scopeBindings()->slotInfo.length == 1); + MOZ_ASSERT(constructorScope->scopeBindings()->trailingNames[0].name() == + cx->parserNames().dotInitializers->toIndex()); + + auto needsInitializer = [](ParseNode* propdef) { + return NeedsFieldInitializer(propdef, false) || + NeedsMethodInitializer(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<ClassMethod>().method(); + } else { + // The |.initializers| binding is never emitted when in self-hosting mode. + MOZ_ASSERT(emitterMode == BytecodeEmitter::SelfHosting); + ctor = &constructor->as<ClassMethod>().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, classMembers)) { + // [stack] HOMEOBJ CTOR + return false; + } + if (lse.isSome()) { + if (!lse->emitEnd()) { + return false; + } + lse.reset(); + } + if (!ce.emitInitConstructor(needsHomeObject)) { + // [stack] CTOR HOMEOBJ + return false; + } + } else { + if (!ce.emitInitDefaultConstructor(classNode->pn_pos.begin, + classNode->pn_pos.end)) { + // [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, cx->parserNames().default_)) { + return false; + } + } else { + if (!emitTree(valueNode)) { + return false; + } + } + + if (ParseNode* binding = exportNode->right()) { + if (!emitLexicalInitialization(&binding->as<NameNode>())) { + return false; + } + + if (!emit1(JSOp::Pop)) { + return false; + } + } + + return true; +} + +MOZ_NEVER_INLINE bool BytecodeEmitter::emitInstrumentationSlow( + InstrumentationKind kind, + const std::function<bool(uint32_t)>& pushOperandsCallback) { + MOZ_ASSERT(instrumentationKinds); + + if (!(instrumentationKinds & (uint32_t)kind)) { + return true; + } + + // Instrumentation is emitted in the form of a call to the realm's + // instrumentation callback, guarded by a test of whether instrumentation is + // currently active in the realm. The callback is invoked with the kind of + // operation which is executing, the current script's instrumentation ID, + // and the offset of the bytecode location after the instrumentation. Some + // operation kinds have more arguments, which will be pushed by + // pushOperandsCallback. + + unsigned initialDepth = bytecodeSection().stackDepth(); + InternalIfEmitter ifEmitter(this); + + if (!emit1(JSOp::InstrumentationActive)) { + return false; + } + // [stack] ACTIVE + + if (!ifEmitter.emitThen()) { + return false; + } + // [stack] + + // Push the instrumentation callback for the current realm as the callee. + if (!emit1(JSOp::InstrumentationCallback)) { + return false; + } + // [stack] CALLBACK + + // Push undefined for the call's |this| value. + if (!emit1(JSOp::Undefined)) { + return false; + } + // [stack] CALLBACK UNDEFINED + + const ParserAtom* atom = RealmInstrumentation::getInstrumentationKindName( + cx, compilationState.parserAtoms, kind); + if (!atom) { + return false; + } + + if (!emitAtomOp(JSOp::String, atom)) { + return false; + } + // [stack] CALLBACK UNDEFINED KIND + + if (!emit1(JSOp::InstrumentationScriptId)) { + return false; + } + // [stack] CALLBACK UNDEFINED KIND SCRIPT + + // Push the offset of the bytecode location following the instrumentation. + BytecodeOffset updateOffset; + if (!emitN(JSOp::Int32, 4, &updateOffset)) { + return false; + } + // [stack] CALLBACK UNDEFINED KIND SCRIPT OFFSET + + unsigned numPushed = bytecodeSection().stackDepth() - initialDepth; + + if (pushOperandsCallback && !pushOperandsCallback(numPushed)) { + return false; + } + // [stack] CALLBACK UNDEFINED KIND SCRIPT OFFSET ...EXTRA_ARGS + + unsigned argc = bytecodeSection().stackDepth() - initialDepth - 2; + if (!emitCall(JSOp::CallIgnoresRv, argc)) { + return false; + } + // [stack] RV + + if (!emit1(JSOp::Pop)) { + return false; + } + // [stack] + + if (!ifEmitter.emitEnd()) { + return false; + } + + SET_INT32(bytecodeSection().code(updateOffset), + bytecodeSection().code().length()); + + return true; +} + +MOZ_NEVER_INLINE bool BytecodeEmitter::emitInstrumentationForOpcodeSlow( + JSOp op, GCThingIndex atomIndex) { + MOZ_ASSERT(instrumentationKinds); + + switch (op) { + case JSOp::GetProp: + return emitInstrumentationSlow( + InstrumentationKind::GetProperty, [=](uint32_t pushed) { + return emitDupAt(pushed) && emitAtomOp(JSOp::String, atomIndex); + }); + case JSOp::SetProp: + case JSOp::StrictSetProp: + return emitInstrumentationSlow( + InstrumentationKind::SetProperty, [=](uint32_t pushed) { + return emitDupAt(pushed + 1) && + emitAtomOp(JSOp::String, atomIndex) && emitDupAt(pushed + 2); + }); + case JSOp::GetElem: + return emitInstrumentationSlow( + InstrumentationKind::GetElement, + [=](uint32_t pushed) { return emitDupAt(pushed + 1, 2); }); + case JSOp::SetElem: + case JSOp::StrictSetElem: + return emitInstrumentationSlow( + InstrumentationKind::SetElement, + [=](uint32_t pushed) { return emitDupAt(pushed + 2, 3); }); + default: + return true; + } +} + +bool BytecodeEmitter::emitTree( + ParseNode* pn, ValueUsage valueUsage /* = ValueUsage::WantValue */, + EmitLineNumberNote emitLineNote /* = EMIT_LINENOTE */) { + if (!CheckRecursionLimit(cx)) { + 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<FunctionNode>())) { + return false; + } + break; + + case ParseNodeKind::ParamsBody: + MOZ_ASSERT_UNREACHABLE( + "ParamsBody should be handled in emitFunctionScript."); + break; + + case ParseNodeKind::IfStmt: + if (!emitIf(&pn->as<TernaryNode>())) { + return false; + } + break; + + case ParseNodeKind::SwitchStmt: + if (!emitSwitch(&pn->as<SwitchStatement>())) { + return false; + } + break; + + case ParseNodeKind::WhileStmt: + if (!emitWhile(&pn->as<BinaryNode>())) { + return false; + } + break; + + case ParseNodeKind::DoWhileStmt: + if (!emitDo(&pn->as<BinaryNode>())) { + return false; + } + break; + + case ParseNodeKind::ForStmt: + if (!emitFor(&pn->as<ForNode>())) { + 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<BreakStatement>().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<ContinueStatement>().label())) { + return false; + } + break; + + case ParseNodeKind::WithStmt: + if (!emitWith(&pn->as<BinaryNode>())) { + return false; + } + break; + + case ParseNodeKind::TryStmt: + if (!emitTry(&pn->as<TryNode>())) { + return false; + } + break; + + case ParseNodeKind::Catch: + if (!emitCatch(&pn->as<BinaryNode>())) { + return false; + } + break; + + case ParseNodeKind::VarStmt: + if (!emitDeclarationList(&pn->as<ListNode>())) { + return false; + } + break; + + case ParseNodeKind::ReturnStmt: + if (!emitReturn(&pn->as<UnaryNode>())) { + return false; + } + break; + + case ParseNodeKind::YieldStarExpr: + if (!emitYieldStar(pn->as<UnaryNode>().kid())) { + return false; + } + break; + + case ParseNodeKind::Generator: + if (!emit1(JSOp::Generator)) { + return false; + } + break; + + case ParseNodeKind::InitialYield: + if (!emitInitialYield(&pn->as<UnaryNode>())) { + return false; + } + break; + + case ParseNodeKind::YieldExpr: + if (!emitYield(&pn->as<UnaryNode>())) { + return false; + } + break; + + case ParseNodeKind::AwaitExpr: + if (!emitAwaitInInnermostScope(&pn->as<UnaryNode>())) { + return false; + } + break; + + case ParseNodeKind::StatementList: + if (!emitStatementList(&pn->as<ListNode>())) { + return false; + } + break; + + case ParseNodeKind::EmptyStmt: + break; + + case ParseNodeKind::ExpressionStmt: + if (!emitExpressionStatement(&pn->as<UnaryNode>())) { + return false; + } + break; + + case ParseNodeKind::LabelStmt: + if (!emitLabeledStatement(&pn->as<LabeledStatement>())) { + return false; + } + break; + + case ParseNodeKind::CommaExpr: + if (!emitSequenceExpr(&pn->as<ListNode>(), 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<BinaryNode>(); + if (!emitAssignmentOrInit(assignNode->getKind(), assignNode->left(), + assignNode->right())) { + return false; + } + break; + } + + case ParseNodeKind::CoalesceAssignExpr: + case ParseNodeKind::OrAssignExpr: + case ParseNodeKind::AndAssignExpr: + if (!emitShortCircuitAssignment(&pn->as<AssignmentNode>())) { + return false; + } + break; + + case ParseNodeKind::ConditionalExpr: + if (!emitConditionalExpression(pn->as<ConditionalExpression>(), + valueUsage)) { + return false; + } + break; + + case ParseNodeKind::OrExpr: + case ParseNodeKind::CoalesceExpr: + case ParseNodeKind::AndExpr: + if (!emitShortCircuit(&pn->as<ListNode>())) { + 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<ListNode>())) { + return false; + } + break; + + case ParseNodeKind::PowExpr: + if (!emitRightAssociative(&pn->as<ListNode>())) { + return false; + } + break; + + case ParseNodeKind::PipelineExpr: + if (!emitPipeline(&pn->as<ListNode>())) { + return false; + } + break; + + case ParseNodeKind::TypeOfNameExpr: + if (!emitTypeof(&pn->as<UnaryNode>(), JSOp::Typeof)) { + return false; + } + break; + + case ParseNodeKind::TypeOfExpr: + if (!emitTypeof(&pn->as<UnaryNode>(), 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<UnaryNode>())) { + return false; + } + break; + + case ParseNodeKind::PreIncrementExpr: + case ParseNodeKind::PreDecrementExpr: + case ParseNodeKind::PostIncrementExpr: + case ParseNodeKind::PostDecrementExpr: + if (!emitIncOrDec(&pn->as<UnaryNode>())) { + return false; + } + break; + + case ParseNodeKind::DeleteNameExpr: + if (!emitDeleteName(&pn->as<UnaryNode>())) { + return false; + } + break; + + case ParseNodeKind::DeletePropExpr: + if (!emitDeleteProperty(&pn->as<UnaryNode>())) { + return false; + } + break; + + case ParseNodeKind::DeleteElemExpr: + if (!emitDeleteElement(&pn->as<UnaryNode>())) { + return false; + } + break; + + case ParseNodeKind::DeleteExpr: + if (!emitDeleteExpression(&pn->as<UnaryNode>())) { + return false; + } + break; + + case ParseNodeKind::DeleteOptionalChainExpr: + if (!emitDeleteOptionalChain(&pn->as<UnaryNode>())) { + return false; + } + break; + + case ParseNodeKind::OptionalChain: + if (!emitOptionalChain(&pn->as<UnaryNode>(), valueUsage)) { + return false; + } + break; + + case ParseNodeKind::DotExpr: { + PropertyAccess* prop = &pn->as<PropertyAccess>(); + 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<UnaryNode>(); + 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<PropertyByValue>(); + bool isSuper = elem->isSuper(); + bool isPrivate = elem->key().isKind(ParseNodeKind::PrivateName); + ElemOpEmitter eoe( + this, ElemOpEmitter::Kind::Get, + isSuper ? ElemOpEmitter::ObjKind::Super + : ElemOpEmitter::ObjKind::Other, + isPrivate ? NameVisibility::Private : NameVisibility::Public); + 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::NewExpr: + case ParseNodeKind::TaggedTemplateExpr: + case ParseNodeKind::CallExpr: + case ParseNodeKind::SuperCallExpr: + if (!emitCallOrNew(&pn->as<CallNode>(), valueUsage)) { + return false; + } + break; + + case ParseNodeKind::LexicalScope: + if (!emitLexicalScope(&pn->as<LexicalScopeNode>())) { + return false; + } + break; + + case ParseNodeKind::ConstDecl: + case ParseNodeKind::LetDecl: + if (!emitDeclarationList(&pn->as<ListNode>())) { + return false; + } + break; + + case ParseNodeKind::ImportDecl: + MOZ_ASSERT(sc->isModuleContext()); + break; + + case ParseNodeKind::ExportStmt: { + MOZ_ASSERT(sc->isModuleContext()); + UnaryNode* node = &pn->as<UnaryNode>(); + 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<BinaryNode>())) { + return false; + } + break; + + case ParseNodeKind::ExportFromStmt: + MOZ_ASSERT(sc->isModuleContext()); + break; + + case ParseNodeKind::CallSiteObj: + if (!emitCallSiteObject(&pn->as<CallSiteNode>())) { + return false; + } + break; + + case ParseNodeKind::ArrayExpr: + if (!emitArrayLiteral(&pn->as<ListNode>())) { + return false; + } + break; + + case ParseNodeKind::ObjectExpr: + if (!emitObject(&pn->as<ListNode>())) { + return false; + } + break; + + case ParseNodeKind::Name: + if (!emitGetName(&pn->as<NameNode>())) { + return false; + } + break; + + case ParseNodeKind::PrivateName: + if (!emitGetPrivateName(&pn->as<NameNode>())) { + return false; + } + break; + + case ParseNodeKind::TemplateStringListExpr: + if (!emitTemplateString(&pn->as<ListNode>())) { + return false; + } + break; + + case ParseNodeKind::TemplateStringExpr: + case ParseNodeKind::StringExpr: + if (!emitAtomOp(JSOp::String, pn->as<NameNode>().atom())) { + return false; + } + break; + + case ParseNodeKind::NumberExpr: + if (!emitNumberOp(pn->as<NumericLiteral>().value())) { + return false; + } + break; + + case ParseNodeKind::BigIntExpr: + if (!emitBigIntOp(&pn->as<BigIntLiteral>())) { + return false; + } + break; + + case ParseNodeKind::RegExpExpr: { + GCThingIndex index; + if (!perScriptData().gcThingList().append(&pn->as<RegExpLiteral>(), + &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<ThisLiteral>())) { + 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<ClassNode>())) { + return false; + } + break; + + case ParseNodeKind::NewTargetExpr: + if (!emit1(JSOp::NewTarget)) { + return false; + } + break; + + case ParseNodeKind::ImportMetaExpr: + if (!emit1(JSOp::ImportMeta)) { + return false; + } + break; + + case ParseNodeKind::CallImportExpr: + if (!emitTree(pn->as<BinaryNode>().right()) || + !emit1(JSOp::DynamicImport)) { + return false; + } + break; + + case ParseNodeKind::SetThis: + if (!emitSetThis(&pn->as<BinaryNode>())) { + return false; + } + break; + + 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(JSContext* cx, SrcNotesVector& notes, unsigned size, + unsigned* index) { + size_t oldLength = notes.length(); + + if (MOZ_UNLIKELY(oldLength + size > MaxSrcNotesLength)) { + ReportAllocationOverflow(cx); + 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(cx, 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(cx, notes, size, &index)) { + return nullptr; + } + return ¬es[index]; + }; + + return SrcNoteWriter::writeOperand(operand, allocator); +} + +bool BytecodeEmitter::intoScriptStencil(ScriptIndex scriptIndex) { + js::UniquePtr<ImmutableScriptData> immutableScriptData = + createImmutableScriptData(cx); + if (!immutableScriptData) { + return false; + } + + MOZ_ASSERT(outermostScope().hasOnChain(ScopeKind::NonSyntactic) == + sc->hasNonSyntacticScope()); + + auto& things = perScriptData().gcThingList().objects(); + if (!compilationState.appendGCThings(cx, scriptIndex, things)) { + return false; + } + + // Hand over the ImmutableScriptData instance generated by BCE. + auto* sharedData = + SharedImmutableScriptData::createWith(cx, std::move(immutableScriptData)); + if (!sharedData) { + return false; + } + + // De-duplicate the bytecode within the runtime. + if (!stencil.sharedData.addAndShare(cx, 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->copyScriptFields(script); + sc->copyScriptExtraFields(scriptExtra); + } + + return true; +} + +bool BytecodeEmitter::allowSelfHostedIter(ParseNode* parseNode) { + return emitterMode == BytecodeEmitter::SelfHosting && + parseNode->isKind(ParseNodeKind::CallExpr) && + parseNode->as<BinaryNode>().left()->isName( + cx->parserNames().allowContentIter); +} diff --git a/js/src/frontend/BytecodeEmitter.h b/js/src/frontend/BytecodeEmitter.h new file mode 100644 index 0000000000..d8576f59fe --- /dev/null +++ b/js/src/frontend/BytecodeEmitter.h @@ -0,0 +1,920 @@ +/* -*- 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_MUST_USE, MOZ_ALWAYS_INLINE, MOZ_NEVER_INLINE, MOZ_RAII +#include "mozilla/Maybe.h" // mozilla::Maybe, mozilla::Some +#include "mozilla/Span.h" // mozilla::Span +#include "mozilla/Vector.h" // mozilla::Vector + +#include <functional> // std::function +#include <stddef.h> // ptrdiff_t +#include <stdint.h> // uint16_t, uint32_t + +#include "jsapi.h" // CompletionKind + +#include "frontend/AbstractScopePtr.h" // ScopeIndex +#include "frontend/BCEParserHandle.h" // BCEParserHandle +#include "frontend/BytecodeControlStructures.h" // NestableControl +#include "frontend/BytecodeOffset.h" // BytecodeOffset +#include "frontend/BytecodeSection.h" // BytecodeSection, PerScriptData, CGScopeList +#include "frontend/DestructuringFlavor.h" // DestructuringFlavor +#include "frontend/EitherParser.h" // EitherParser +#include "frontend/ErrorReporter.h" // ErrorReporter +#include "frontend/FullParseHandler.h" // FullParseHandler +#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/ScriptIndex.h" // ScriptIndex +#include "frontend/SharedContext.h" // SharedContext, TopLevelFunction +#include "frontend/SourceNotes.h" // SrcNoteType +#include "frontend/TokenStream.h" // TokenPos +#include "frontend/ValueUsage.h" // ValueUsage +#include "js/RootingAPI.h" // JS::Rooted, JS::Handle +#include "js/TypeDecls.h" // jsbytecode +#include "vm/BuiltinObjectKind.h" // BuiltinObjectKind +#include "vm/BytecodeUtil.h" // JSOp +#include "vm/CheckIsObjectKind.h" // CheckIsObjectKind +#include "vm/FunctionPrefixKind.h" // FunctionPrefixKind +#include "vm/GeneratorResumeKind.h" // GeneratorResumeKind +#include "vm/Instrumentation.h" // InstrumentationKind +#include "vm/JSFunction.h" // JSFunction +#include "vm/JSScript.h" // JSScript, BaseScript, MemberInitializers +#include "vm/Runtime.h" // ReportOutOfMemory +#include "vm/SharedStencil.h" // GCThingIndex +#include "vm/StencilEnums.h" // TryNoteKind +#include "vm/StringType.h" // JSAtom +#include "vm/ThrowMsgKind.h" // ThrowMsgKind, ThrowCondition + +namespace js { +namespace frontend { + +class CallOrNewEmitter; +class ClassEmitter; +class ElemOpEmitter; +class EmitterScope; +class NestableControl; +class PropertyEmitter; +class PropOpEmitter; +class OptionalEmitter; +class TDZCheckCache; +class TryEmitter; +class ScriptStencil; + +enum class ValueIsOnStack { Yes, No }; + +struct MOZ_STACK_CLASS BytecodeEmitter { + // Context shared between parsing and bytecode generation. + SharedContext* const sc = nullptr; + + JSContext* const cx = 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<uint32_t> mainOffset_ = {}; + + public: + // Private storage for parser wrapper. DO NOT REFERENCE INTERNALLY. May not be + // initialized. Use |parser| instead. + mozilla::Maybe<EitherParser> ep_ = {}; + BCEParserHandle* parser = nullptr; + + CompilationStencil& stencil; + 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; + +#ifdef DEBUG + bool unstableEmitterScope = false; + + friend class AutoCheckUnstableEmitterScope; +#endif + + EmitterScope* innermostEmitterScope() const { + MOZ_ASSERT(!unstableEmitterScope); + return innermostEmitterScopeNoCheck(); + } + EmitterScope* innermostEmitterScopeNoCheck() const { + return innermostEmitterScope_; + } + + // 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<uint32_t> scriptStartOffset = {}; + + // The end location of a function body that is being emitted. + mozilla::Maybe<uint32_t> functionBodyEndPos = {}; + + // Mask of operation kinds which need instrumentation. This is obtained from + // the compile options and copied here for efficiency. + uint32_t instrumentationKinds = 0; + + /* + * 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, SharedContext* sc, + CompilationStencil& stencil, + CompilationState& compilationState, EmitterMode emitterMode); + + void initFromBodyPosition(TokenPos bodyPosition); + + /* + * Helper for reporting that we have insufficient args. pluralizer + * should be "s" if requiredArgs is anything other than "1" and "" + * if requiredArgs is "1". + */ + void reportNeedMoreArgsError(ParseNode* pn, const char* errorName, + const char* requiredArgs, const char* pluralizer, + const ListNode* argsList); + + public: + BytecodeEmitter(BytecodeEmitter* parent, BCEParserHandle* handle, + SharedContext* sc, CompilationStencil& stencil, + CompilationState& compilationState, + EmitterMode emitterMode = Normal); + + BytecodeEmitter(BytecodeEmitter* parent, const EitherParser& parser, + SharedContext* sc, CompilationStencil& stencil, + CompilationState& compilationState, + EmitterMode emitterMode = Normal); + + template <typename Unit> + BytecodeEmitter(BytecodeEmitter* parent, + Parser<FullParseHandler, Unit>* parser, SharedContext* sc, + CompilationStencil& stencil, + CompilationState& compilationState, + EmitterMode emitterMode = Normal) + : BytecodeEmitter(parent, EitherParser(parser), sc, stencil, + compilationState, emitterMode) {} + + MOZ_MUST_USE bool init(); + MOZ_MUST_USE bool init(TokenPos bodyPosition); + + template <typename T> + T* findInnermostNestableControl() const; + + template <typename T, typename Predicate /* (T*) -> bool */> + T* findInnermostNestableControl(Predicate predicate) const; + + NameLocation lookupName(const ParserAtom* name); + + // 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<NameLocation> locationOfNameBoundInScope( + const ParserAtom* name, EmitterScope* target); + + // Get the location of a name known to be bound in a given scope, + // starting at the source scope. + template <typename T> + mozilla::Maybe<NameLocation> locationOfNameBoundInScopeType( + const ParserAtom* name, EmitterScope* source); + + // Get the location of a name known to be bound in the function scope, + // starting at the source scope. + mozilla::Maybe<NameLocation> locationOfNameBoundInFunctionScope( + const ParserAtom* name) { + return locationOfNameBoundInScopeType<FunctionScope>( + 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; + + MOZ_ALWAYS_INLINE MOZ_MUST_USE bool makeAtomIndex(const ParserAtom* atom, + GCThingIndex* indexp) { + MOZ_ASSERT(perScriptData().atomIndices()); + AtomIndexMap::AddPtr p = perScriptData().atomIndices()->lookupForAdd(atom); + if (p) { + *indexp = GCThingIndex(p->value()); + return true; + } + + GCThingIndex index; + if (!perScriptData().gcThingList().append(atom, &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(cx); + return false; + } + + *indexp = index; + return true; + } + + bool isInLoop(); + MOZ_MUST_USE bool checkSingletonContext(); + + bool needsImplicitThis(); + + size_t countThisEnvironmentHops(); + MOZ_MUST_USE bool emitThisEnvironmentCallee(); + MOZ_MUST_USE bool emitSuperBase(); + + uint32_t mainOffset() const { return *mainOffset_; } + + bool inPrologue() const { return mainOffset_.isNothing(); } + + MOZ_MUST_USE bool switchToMain() { + MOZ_ASSERT(inPrologue()); + mainOffset_.emplace(bytecodeSection().code().length()); + + return emitInstrumentation(InstrumentationKind::Main); + } + + 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(const mozilla::Maybe<uint32_t>& maybeOffset, + 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. + MOZ_MUST_USE bool checkSideEffects(ParseNode* pn, bool* answer); + +#ifdef DEBUG + MOZ_MUST_USE bool checkStrictOrSloppy(JSOp op); +#endif + + // Add TryNote to the tryNoteList array. The start and end offset are + // relative to current section. + MOZ_MUST_USE 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() || (emitterMode == EmitterMode::SelfHosting); + } + bool skipBreakpointSrcNotes() const { + return inPrologue() || (emitterMode == EmitterMode::SelfHosting); + } + + // 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. + MOZ_MUST_USE bool newSrcNote(SrcNoteType type, unsigned* indexp = nullptr); + MOZ_MUST_USE bool newSrcNote2(SrcNoteType type, ptrdiff_t operand, + unsigned* indexp = nullptr); + + MOZ_MUST_USE 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. + MOZ_MUST_USE bool emitTree(ParseNode* pn, + ValueUsage valueUsage = ValueUsage::WantValue, + EmitLineNumberNote emitLineNote = EMIT_LINENOTE); + + MOZ_MUST_USE bool emitOptionalTree( + ParseNode* pn, OptionalEmitter& oe, + ValueUsage valueUsage = ValueUsage::WantValue); + + MOZ_MUST_USE bool emitDeclarationInstantiation(ParseNode* body); + + // Emit global, eval, or module code for tree rooted at body. Always + // encompasses the entire source. + MOZ_MUST_USE bool emitScript(ParseNode* body); + + // Calculate the `nslots` value for BCEScriptStencil constructor parameter. + // Fails if it overflows. + MOZ_MUST_USE bool getNslots(uint32_t* nslots); + + // Emit function code for the tree rooted at body. + MOZ_MUST_USE bool emitFunctionScript(FunctionNode* funNode); + + MOZ_MUST_USE bool markStepBreakpoint(); + MOZ_MUST_USE bool markSimpleBreakpoint(); + MOZ_MUST_USE bool updateLineNumberNotes(uint32_t offset); + MOZ_MUST_USE bool updateSourceCoordNotes(uint32_t offset); + + JSOp strictifySetNameOp(JSOp op); + + MOZ_MUST_USE bool emitCheck(JSOp op, ptrdiff_t delta, BytecodeOffset* offset); + + // Emit one bytecode. + MOZ_MUST_USE bool emit1(JSOp op); + + // Emit two bytecodes, an opcode (op) with a byte of immediate operand + // (op1). + MOZ_MUST_USE bool emit2(JSOp op, uint8_t op1); + + // Emit three bytecodes, an opcode with two bytes of immediate operands. + MOZ_MUST_USE 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. + MOZ_MUST_USE bool emitDupAt(unsigned slotFromTop, unsigned count = 1); + + // Helper to emit JSOp::Pop or JSOp::PopN. + MOZ_MUST_USE bool emitPopN(unsigned n); + + // Helper to emit JSOp::Swap or JSOp::Pick. + MOZ_MUST_USE bool emitPickN(uint8_t n); + + // Helper to emit JSOp::Swap or JSOp::Unpick. + MOZ_MUST_USE bool emitUnpickN(uint8_t n); + + // Helper to emit JSOp::CheckIsObj. + MOZ_MUST_USE bool emitCheckIsObj(CheckIsObjectKind kind); + + // Helper to emit JSOp::BuiltinObject. + MOZ_MUST_USE bool emitBuiltinObject(BuiltinObjectKind kind); + + // Push whether the value atop of the stack is non-undefined and non-null. + MOZ_MUST_USE bool emitPushNotUndefinedOrNull(); + + // Emit a bytecode followed by an uint16 immediate operand stored in + // big-endian order. + MOZ_MUST_USE bool emitUint16Operand(JSOp op, uint32_t operand); + + // Emit a bytecode followed by an uint32 immediate operand. + MOZ_MUST_USE bool emitUint32Operand(JSOp op, uint32_t operand); + + // Emit (1 + extra) bytecodes, for N bytes of op and its immediate operand. + MOZ_MUST_USE bool emitN(JSOp op, size_t extra, + BytecodeOffset* offset = nullptr); + + MOZ_MUST_USE bool emitDouble(double dval); + MOZ_MUST_USE bool emitNumberOp(double dval); + + MOZ_MUST_USE bool emitBigIntOp(BigIntLiteral* bigint); + + MOZ_MUST_USE bool emitThisLiteral(ThisLiteral* pn); + MOZ_MUST_USE bool emitGetFunctionThis(NameNode* thisName); + MOZ_MUST_USE bool emitGetFunctionThis(const mozilla::Maybe<uint32_t>& offset); + MOZ_MUST_USE bool emitGetThisForSuperBase(UnaryNode* superBase); + MOZ_MUST_USE bool emitSetThis(BinaryNode* setThisNode); + MOZ_MUST_USE bool emitCheckDerivedClassConstructorReturn(); + + // Handle jump opcodes and jump targets. + MOZ_MUST_USE bool emitJumpTargetOp(JSOp op, BytecodeOffset* off); + MOZ_MUST_USE bool emitJumpTarget(JumpTarget* target); + MOZ_MUST_USE bool emitJumpNoFallthrough(JSOp op, JumpList* jump); + MOZ_MUST_USE bool emitJump(JSOp op, JumpList* jump); + void patchJumpsToTarget(JumpList jump, JumpTarget target); + MOZ_MUST_USE bool emitJumpTargetAndPatch(JumpList jump); + + MOZ_MUST_USE bool emitCall(JSOp op, uint16_t argc, + const mozilla::Maybe<uint32_t>& sourceCoordOffset); + MOZ_MUST_USE bool emitCall(JSOp op, uint16_t argc, ParseNode* pn = nullptr); + MOZ_MUST_USE bool emitCallIncDec(UnaryNode* incDec); + + mozilla::Maybe<uint32_t> getOffsetForLoop(ParseNode* nextpn); + + enum class GotoKind { Break, Continue }; + MOZ_MUST_USE bool emitGoto(NestableControl* target, JumpList* jumplist, + GotoKind kind); + + MOZ_MUST_USE bool emitGCIndexOp(JSOp op, GCThingIndex index); + + MOZ_MUST_USE bool emitAtomOp( + JSOp op, const ParserAtom* atom, + ShouldInstrument shouldInstrument = ShouldInstrument::No); + MOZ_MUST_USE bool emitAtomOp( + JSOp op, GCThingIndex atomIndex, + ShouldInstrument shouldInstrument = ShouldInstrument::No); + + MOZ_MUST_USE bool emitArrayLiteral(ListNode* array); + MOZ_MUST_USE bool emitArray(ParseNode* arrayHead, uint32_t count); + + MOZ_MUST_USE bool emitInternedScopeOp(GCThingIndex index, JSOp op); + MOZ_MUST_USE bool emitInternedObjectOp(GCThingIndex index, JSOp op); + MOZ_MUST_USE bool emitObjectPairOp(GCThingIndex index1, GCThingIndex index2, + JSOp op); + MOZ_MUST_USE bool emitRegExp(GCThingIndex index); + + MOZ_NEVER_INLINE MOZ_MUST_USE bool emitFunction( + FunctionNode* funNode, bool needsProto = false, + ListNode* classContentsIfConstructor = nullptr); + MOZ_NEVER_INLINE MOZ_MUST_USE bool emitObject(ListNode* objNode); + + MOZ_MUST_USE 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(ParseNode* arrayHead); + + MOZ_MUST_USE bool emitPropertyList(ListNode* obj, PropertyEmitter& pe, + PropListType type); + + MOZ_MUST_USE bool emitPropertyListObjLiteral(ListNode* obj, + ObjLiteralFlags flags, + bool useObjLiteralValues); + + MOZ_MUST_USE bool emitDestructuringRestExclusionSetObjLiteral( + ListNode* pattern); + + MOZ_MUST_USE bool emitObjLiteralArray(ParseNode* arrayHead); + + // Is a field value OBJLITERAL-compatible? + MOZ_MUST_USE bool isRHSObjLiteralCompatible(ParseNode* value); + + MOZ_MUST_USE bool emitObjLiteralValue(ObjLiteralWriter& writer, + ParseNode* value); + + enum class FieldPlacement { Instance, Static }; + mozilla::Maybe<MemberInitializers> setupMemberInitializers( + ListNode* classMembers, FieldPlacement placement); + MOZ_MUST_USE bool emitCreateFieldKeys(ListNode* obj, + FieldPlacement placement); + MOZ_MUST_USE bool emitCreateMemberInitializers(ClassEmitter& ce, + ListNode* obj, + FieldPlacement placement); + const MemberInitializers& findMemberInitializersForCall(); + MOZ_MUST_USE bool emitInitializeInstanceMembers(); + MOZ_MUST_USE bool emitInitializeStaticFields(ListNode* classMembers); + + MOZ_MUST_USE bool emitPrivateMethodInitializers(ClassEmitter& ce, + ListNode* obj); + MOZ_MUST_USE bool emitPrivateMethodInitializer( + ClassEmitter& ce, ParseNode* prop, ParseNode* propName, + const ParserAtom* storedMethodAtom, AccessorType accessorType); + + // 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. + MOZ_MUST_USE bool emitLocalOp(JSOp op, uint32_t slot); + + MOZ_MUST_USE bool emitArgOp(JSOp op, uint16_t slot); + MOZ_MUST_USE bool emitEnvCoordOp(JSOp op, EnvironmentCoordinate ec); + + MOZ_MUST_USE bool emitGetNameAtLocation(const ParserAtom* name, + const NameLocation& loc); + MOZ_MUST_USE bool emitGetName(const ParserAtom* name) { + return emitGetNameAtLocation(name, lookupName(name)); + } + MOZ_MUST_USE bool emitGetName(NameNode* name); + MOZ_MUST_USE bool emitGetPrivateName(NameNode* name); + MOZ_MUST_USE bool emitGetPrivateName(const ParserAtom* name); + + MOZ_MUST_USE bool emitTDZCheckIfNeeded(const ParserAtom* name, + const NameLocation& loc, + ValueIsOnStack isOnStack); + + MOZ_MUST_USE bool emitNameIncDec(UnaryNode* incDec); + + MOZ_MUST_USE bool emitDeclarationList(ListNode* declList); + MOZ_MUST_USE bool emitSingleDeclaration(ListNode* declList, NameNode* decl, + ParseNode* initializer); + MOZ_MUST_USE bool emitAssignmentRhs(ParseNode* rhs, + const ParserAtom* anonFunctionName); + MOZ_MUST_USE bool emitAssignmentRhs(uint8_t offset); + + MOZ_MUST_USE bool emitPrepareIteratorResult(); + MOZ_MUST_USE bool emitFinishIteratorResult(bool done); + MOZ_MUST_USE bool iteratorResultShape(GCThingIndex* outShape); + + // 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. + MOZ_MUST_USE bool addObjLiteralData(ObjLiteralWriter& writer, + GCThingIndex* outIndex); + + MOZ_MUST_USE bool emitGetDotGeneratorInInnermostScope() { + return emitGetDotGeneratorInScope(*innermostEmitterScope()); + } + MOZ_MUST_USE bool emitGetDotGeneratorInScope(EmitterScope& currentScope); + + MOZ_MUST_USE bool allocateResumeIndex(BytecodeOffset offset, + uint32_t* resumeIndex); + MOZ_MUST_USE bool allocateResumeIndexRange( + mozilla::Span<BytecodeOffset> offsets, uint32_t* firstResumeIndex); + + MOZ_MUST_USE bool emitInitialYield(UnaryNode* yieldNode); + MOZ_MUST_USE bool emitYield(UnaryNode* yieldNode); + MOZ_MUST_USE bool emitYieldOp(JSOp op); + MOZ_MUST_USE bool emitYieldStar(ParseNode* iter); + MOZ_MUST_USE bool emitAwaitInInnermostScope() { + return emitAwaitInScope(*innermostEmitterScope()); + } + MOZ_MUST_USE bool emitAwaitInInnermostScope(UnaryNode* awaitNode); + MOZ_MUST_USE bool emitAwaitInScope(EmitterScope& currentScope); + + MOZ_MUST_USE bool emitPushResumeKind(GeneratorResumeKind kind); + + MOZ_MUST_USE bool emitPropLHS(PropertyAccess* prop); + MOZ_MUST_USE bool emitPropIncDec(UnaryNode* incDec); + + MOZ_MUST_USE bool emitComputedPropertyName(UnaryNode* computedPropName); + + // 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 }; + MOZ_MUST_USE bool emitElemOperands(PropertyByValue* elem, + EmitElemOption opts); + + MOZ_MUST_USE bool emitElemObjAndKey(PropertyByValue* elem, bool isSuper, + ElemOpEmitter& eoe); + MOZ_MUST_USE bool emitElemOpBase( + JSOp op, ShouldInstrument shouldInstrument = ShouldInstrument::No); + MOZ_MUST_USE bool emitElemOp(PropertyByValue* elem, JSOp op); + MOZ_MUST_USE bool emitElemIncDec(UnaryNode* incDec); + + MOZ_MUST_USE bool emitCatch(BinaryNode* catchClause); + MOZ_MUST_USE bool emitIf(TernaryNode* ifNode); + MOZ_MUST_USE bool emitWith(BinaryNode* withNode); + + MOZ_NEVER_INLINE MOZ_MUST_USE bool emitLabeledStatement( + const LabeledStatement* labeledStmt); + MOZ_NEVER_INLINE MOZ_MUST_USE bool emitLexicalScope( + LexicalScopeNode* lexicalScope); + MOZ_MUST_USE bool emitLexicalScopeBody( + ParseNode* body, EmitLineNumberNote emitLineNote = EMIT_LINENOTE); + MOZ_NEVER_INLINE MOZ_MUST_USE bool emitSwitch(SwitchStatement* switchStmt); + MOZ_NEVER_INLINE MOZ_MUST_USE bool emitTry(TryNode* tryNode); + + MOZ_MUST_USE bool emitGoSub(JumpList* jump); + + // 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. + MOZ_MUST_USE 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). + MOZ_MUST_USE bool emitSetOrInitializeDestructuring(ParseNode* target, + DestructuringFlavor flav); + + // emitDestructuringObjRestExclusionSet emits the property exclusion set + // for the rest-property in an object pattern. + MOZ_MUST_USE 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. + MOZ_MUST_USE bool emitDestructuringOps(ListNode* pattern, + DestructuringFlavor flav); + MOZ_MUST_USE bool emitDestructuringOpsArray(ListNode* pattern, + DestructuringFlavor flav); + MOZ_MUST_USE 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. + MOZ_MUST_USE bool emitCopyDataProperties(CopyOption option); + + // emitIterator expects the iterable to already be on the stack. + // It will replace that stack value with the corresponding iterator + MOZ_MUST_USE bool emitIterator(); + + MOZ_MUST_USE bool emitAsyncIterator(); + + // Pops iterator from the top of the stack. Pushes the result of |.next()| + // onto the stack. + MOZ_MUST_USE bool emitIteratorNext( + const mozilla::Maybe<uint32_t>& callSourceCoordOffset, + IteratorKind kind = IteratorKind::Sync, bool allowSelfHosted = false); + MOZ_MUST_USE bool emitIteratorCloseInScope( + EmitterScope& currentScope, IteratorKind iterKind = IteratorKind::Sync, + CompletionKind completionKind = CompletionKind::Normal, + bool allowSelfHosted = false); + MOZ_MUST_USE bool emitIteratorCloseInInnermostScope( + IteratorKind iterKind = IteratorKind::Sync, + CompletionKind completionKind = CompletionKind::Normal, + bool allowSelfHosted = false) { + return emitIteratorCloseInScope(*innermostEmitterScope(), iterKind, + completionKind, allowSelfHosted); + } + + template <typename InnerEmitter> + MOZ_MUST_USE bool wrapWithDestructuringTryNote(int32_t iterDepth, + InnerEmitter emitter); + + MOZ_MUST_USE 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. + MOZ_MUST_USE bool emitDefault(ParseNode* defaultExpr, ParseNode* pattern); + + MOZ_MUST_USE bool emitAnonymousFunctionWithName(ParseNode* node, + const ParserAtom* name); + + MOZ_MUST_USE bool emitAnonymousFunctionWithComputedName( + ParseNode* node, FunctionPrefixKind prefixKind); + + MOZ_MUST_USE bool setFunName(FunctionBox* fun, const ParserAtom* name); + MOZ_MUST_USE bool emitInitializer(ParseNode* initializer, ParseNode* pattern); + + MOZ_MUST_USE bool emitCallSiteObjectArray(ListNode* cookedOrRaw, + GCThingIndex* outArrayIndex); + MOZ_MUST_USE bool emitCallSiteObject(CallSiteNode* callSiteObj); + MOZ_MUST_USE bool emitTemplateString(ListNode* templateString); + MOZ_MUST_USE bool emitAssignmentOrInit(ParseNodeKind kind, ParseNode* lhs, + ParseNode* rhs); + MOZ_MUST_USE bool emitShortCircuitAssignment(AssignmentNode* node); + + MOZ_MUST_USE bool emitReturn(UnaryNode* returnNode); + MOZ_MUST_USE bool emitExpressionStatement(UnaryNode* exprStmt); + MOZ_MUST_USE bool emitStatementList(ListNode* stmtList); + + MOZ_MUST_USE bool emitDeleteName(UnaryNode* deleteNode); + MOZ_MUST_USE bool emitDeleteProperty(UnaryNode* deleteNode); + MOZ_MUST_USE bool emitDeleteElement(UnaryNode* deleteNode); + MOZ_MUST_USE bool emitDeleteExpression(UnaryNode* deleteNode); + + // Optional methods which emit Optional Jump Target + MOZ_MUST_USE bool emitOptionalChain(UnaryNode* expr, ValueUsage valueUsage); + MOZ_MUST_USE bool emitCalleeAndThisForOptionalChain(UnaryNode* expr, + CallNode* callNode, + CallOrNewEmitter& cone); + MOZ_MUST_USE 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. + MOZ_MUST_USE bool emitOptionalDotExpression(PropertyAccessBase* expr, + PropOpEmitter& poe, bool isSuper, + OptionalEmitter& oe); + MOZ_MUST_USE bool emitOptionalElemExpression(PropertyByValueBase* elem, + ElemOpEmitter& poe, bool isSuper, + OptionalEmitter& oe); + MOZ_MUST_USE bool emitOptionalCall(CallNode* callNode, OptionalEmitter& oe, + ValueUsage valueUsage); + MOZ_MUST_USE bool emitDeletePropertyInOptChain(PropertyAccessBase* propExpr, + OptionalEmitter& oe); + MOZ_MUST_USE bool emitDeleteElementInOptChain(PropertyByValueBase* elemExpr, + OptionalEmitter& oe); + + // |op| must be JSOp::Typeof or JSOp::TypeofExpr. + MOZ_MUST_USE bool emitTypeof(UnaryNode* typeofNode, JSOp op); + + MOZ_MUST_USE bool emitUnary(UnaryNode* unaryNode); + MOZ_MUST_USE bool emitRightAssociative(ListNode* node); + MOZ_MUST_USE bool emitLeftAssociative(ListNode* node); + MOZ_MUST_USE bool emitShortCircuit(ListNode* node); + MOZ_MUST_USE bool emitSequenceExpr( + ListNode* node, ValueUsage valueUsage = ValueUsage::WantValue); + + MOZ_NEVER_INLINE MOZ_MUST_USE bool emitIncOrDec(UnaryNode* incDec); + + MOZ_MUST_USE bool emitConditionalExpression( + ConditionalExpression& conditional, + ValueUsage valueUsage = ValueUsage::WantValue); + + bool isRestParameter(ParseNode* expr); + + MOZ_MUST_USE ParseNode* getCoordNode(ParseNode* callNode, + ParseNode* calleeNode, JSOp op, + ListNode* argsList); + + MOZ_MUST_USE bool emitArguments(ListNode* argsList, bool isCall, + bool isSpread, CallOrNewEmitter& cone); + MOZ_MUST_USE bool emitCallOrNew( + CallNode* callNode, ValueUsage valueUsage = ValueUsage::WantValue); + MOZ_MUST_USE bool emitSelfHostedCallFunction(CallNode* callNode); + MOZ_MUST_USE bool emitSelfHostedResumeGenerator(BinaryNode* callNode); + MOZ_MUST_USE bool emitSelfHostedForceInterpreter(); + MOZ_MUST_USE bool emitSelfHostedAllowContentIter(BinaryNode* callNode); + MOZ_MUST_USE bool emitSelfHostedDefineDataProperty(BinaryNode* callNode); + MOZ_MUST_USE bool emitSelfHostedGetPropertySuper(BinaryNode* callNode); + MOZ_MUST_USE bool emitSelfHostedHasOwn(BinaryNode* callNode); + MOZ_MUST_USE bool emitSelfHostedToNumeric(BinaryNode* callNode); + MOZ_MUST_USE bool emitSelfHostedToString(BinaryNode* callNode); + MOZ_MUST_USE bool emitSelfHostedGetBuiltinConstructor(BinaryNode* callNode); + MOZ_MUST_USE bool emitSelfHostedGetBuiltinPrototype(BinaryNode* callNode); +#ifdef DEBUG + MOZ_MUST_USE bool checkSelfHostedUnsafeGetReservedSlot(BinaryNode* callNode); + MOZ_MUST_USE bool checkSelfHostedUnsafeSetReservedSlot(BinaryNode* callNode); +#endif + + MOZ_MUST_USE bool emitDo(BinaryNode* doNode); + MOZ_MUST_USE bool emitWhile(BinaryNode* whileNode); + + MOZ_MUST_USE bool emitFor( + ForNode* forNode, const EmitterScope* headLexicalEmitterScope = nullptr); + MOZ_MUST_USE bool emitCStyleFor(ForNode* forNode, + const EmitterScope* headLexicalEmitterScope); + MOZ_MUST_USE bool emitForIn(ForNode* forNode, + const EmitterScope* headLexicalEmitterScope); + MOZ_MUST_USE bool emitForOf(ForNode* forNode, + const EmitterScope* headLexicalEmitterScope); + + MOZ_MUST_USE bool emitInitializeForInOrOfTarget(TernaryNode* forHead); + + MOZ_MUST_USE bool emitBreak(const ParserName* label); + MOZ_MUST_USE bool emitContinue(const ParserName* label); + + MOZ_MUST_USE bool emitFunctionFormalParameters(ListNode* paramsBody); + MOZ_MUST_USE bool emitInitializeFunctionSpecialNames(); + MOZ_MUST_USE bool emitLexicalInitialization(NameNode* name); + MOZ_MUST_USE bool emitLexicalInitialization(const ParserAtom* name); + + // Emit bytecode for the spread operator. + // + // emitSpread expects the current index (I) of the array, the array itself + // and the iterator to be on the stack in that order (iterator on the bottom). + // It will pop the iterator and I, then iterate over the iterator by calling + // |.next()| and put the results into the I-th element of array with + // incrementing I, then push the result I (it will be original I + + // iteration count). The stack after iteration will look like |ARRAY INDEX|. + MOZ_MUST_USE bool emitSpread(bool allowSelfHosted = false); + + 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 + }; + + MOZ_MUST_USE bool emitClass( + ClassNode* classNode, ClassNameKind nameKind = ClassNameKind::BindingName, + const ParserAtom* nameForAnonymousClass = nullptr); + + MOZ_MUST_USE bool emitSuperElemOperands( + PropertyByValue* elem, EmitElemOption opts = EmitElemOption::Get); + MOZ_MUST_USE bool emitSuperGetElem(PropertyByValue* elem, + bool isCall = false); + + MOZ_MUST_USE bool emitCalleeAndThis(ParseNode* callee, ParseNode* call, + CallOrNewEmitter& cone); + + MOZ_MUST_USE bool emitOptionalCalleeAndThis(ParseNode* callee, CallNode* call, + CallOrNewEmitter& cone, + OptionalEmitter& oe); + + MOZ_MUST_USE bool emitPipeline(ListNode* node); + + MOZ_MUST_USE bool emitExportDefault(BinaryNode* exportNode); + + MOZ_MUST_USE bool emitReturnRval() { + return emitInstrumentation(InstrumentationKind::Exit) && + emit1(JSOp::RetRval); + } + + MOZ_MUST_USE bool emitCheckPrivateField(ThrowCondition throwCondition, + ThrowMsgKind msgKind) { + return emit3(JSOp::CheckPrivateField, uint8_t(throwCondition), + uint8_t(msgKind)); + } + + template <class ClassMemberType> + MOZ_MUST_USE bool emitNewPrivateNames(ListNode* classMembers); + + MOZ_MUST_USE bool emitInstrumentation(InstrumentationKind kind, + uint32_t npopped = 0) { + return MOZ_LIKELY(!instrumentationKinds) || + emitInstrumentationSlow(kind, std::function<bool(uint32_t)>()); + } + + MOZ_MUST_USE bool emitInstrumentationForOpcode(JSOp op, + GCThingIndex atomIndex) { + return MOZ_LIKELY(!instrumentationKinds) || + emitInstrumentationForOpcodeSlow(op, atomIndex); + } + + MOZ_MUST_USE js::UniquePtr<ImmutableScriptData> createImmutableScriptData( + JSContext* cx); + + private: + MOZ_MUST_USE bool emitInstrumentationSlow( + InstrumentationKind kind, + const std::function<bool(uint32_t)>& pushOperandsCallback); + MOZ_MUST_USE bool emitInstrumentationForOpcodeSlow(JSOp op, + GCThingIndex atomIndex); + + MOZ_MUST_USE bool allowSelfHostedIter(ParseNode* parseNode); + + MOZ_MUST_USE bool emitSelfHostedGetBuiltinConstructorOrPrototype( + BinaryNode* callNode, bool isConstructor); +}; + +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 <stddef.h> // 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<ptrdiff_t> 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<ptrdiff_t> result = value_; + result -= rhs.value_; + return BytecodeOffsetDiff(result.value()); +} + +inline BytecodeOffset BytecodeOffset::operator+( + const BytecodeOffsetDiff& diff) const { + MOZ_ASSERT(valid()); + mozilla::CheckedInt<ptrdiff_t> result = value_; + result += diff.value_; + return BytecodeOffset(result.value()); +} + +inline BytecodeOffset& BytecodeOffset::operator+=( + const BytecodeOffsetDiff& diff) { + MOZ_ASSERT(valid()); + mozilla::CheckedInt<ptrdiff_t> result = value_; + result += diff.value_; + value_ = result.value(); + return *this; +} + +inline BytecodeOffset& BytecodeOffset::operator-=( + const BytecodeOffsetDiff& diff) { + MOZ_ASSERT(valid()); + mozilla::CheckedInt<ptrdiff_t> 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..2391656405 --- /dev/null +++ b/js/src/frontend/BytecodeSection.cpp @@ -0,0 +1,197 @@ +/* -*- 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 "mozilla/ReverseIterator.h" // mozilla::Reversed + +#include "frontend/AbstractScopePtr.h" // ScopeIndex +#include "frontend/CompilationInfo.h" +#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. + MOZ_ASSERT(stencil.input.enclosingScope); + MOZ_ASSERT(!stencil.input.enclosingScope->as<GlobalScope>().hasBindings()); + return AbstractScopePtr(stencil.input.enclosingScope); + } + return AbstractScopePtr(compilationState, elem.toScope()); +} + +mozilla::Maybe<ScopeIndex> GCThingList::getScopeIndex(size_t index) const { + const TaggedScriptThingIndex& elem = vector[index]; + if (elem.isEmptyGlobalScope()) { + return mozilla::Nothing(); + } + return mozilla::Some(vector[index].toScope()); +} + +bool js::frontend::EmitScriptThingsVector( + JSContext* cx, CompilationInput& input, BaseCompilationStencil& stencil, + CompilationGCOutput& gcOutput, + mozilla::Span<const TaggedScriptThingIndex> things, + mozilla::Span<JS::GCCellPtr> output) { + MOZ_ASSERT(things.size() <= INDEX_LIMIT); + MOZ_ASSERT(things.size() == output.size()); + + auto& atomCache = input.atomCache; + + 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: { + JSAtom* atom = atomCache.getExistingAtomAt(cx, thing.toAtom()); + MOZ_ASSERT(atom); + output[i] = JS::GCCellPtr(atom); + break; + } + case TaggedScriptThingIndex::Kind::Null: + output[i] = JS::GCCellPtr(nullptr); + break; + case TaggedScriptThingIndex::Kind::BigInt: { + 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: { + ObjLiteralStencil& data = stencil.objLiteralData[thing.toObjLiteral()]; + JSObject* obj = data.create(cx, atomCache); + if (!obj) { + return false; + } + output[i] = JS::GCCellPtr(obj); + 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.scopes[thing.toScope()]); + break; + case TaggedScriptThingIndex::Kind::Function: + output[i] = JS::GCCellPtr(gcOutput.functions[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; +} + +JSObject* ObjLiteralStencil::create(JSContext* cx, + CompilationAtomCache& atomCache) const { + return InterpretObjLiteral(cx, atomCache, code_, flags_); +} + +BytecodeSection::BytecodeSection(JSContext* cx, uint32_t lineNum, + uint32_t column) + : code_(cx), + notes_(cx), + lastNoteOffset_(0), + tryNoteList_(cx), + scopeNoteList_(cx), + resumeOffsetList_(cx), + currentLine_(lineNum), + lastColumn_(column) {} + +void BytecodeSection::updateDepth(BytecodeOffset target) { + jsbytecode* pc = code(target); + + int nuses = StackUses(pc); + int ndefs = StackDefs(pc); + + stackDepth_ -= nuses; + MOZ_ASSERT(stackDepth_ >= 0); + stackDepth_ += ndefs; + + if (uint32_t(stackDepth_) > maxStackDepth_) { + maxStackDepth_ = stackDepth_; + } +} + +PerScriptData::PerScriptData(JSContext* cx, + frontend::CompilationStencil& stencil, + frontend::CompilationState& compilationState) + : gcThingList_(cx, stencil, compilationState), + atomIndices_(cx->frontendCollectionPool()) {} + +bool PerScriptData::init(JSContext* cx) { return atomIndices_.acquire(cx); } diff --git a/js/src/frontend/BytecodeSection.h b/js/src/frontend/BytecodeSection.h new file mode 100644 index 0000000000..d68561c783 --- /dev/null +++ b/js/src/frontend/BytecodeSection.h @@ -0,0 +1,418 @@ +/* -*- 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_MUST_USE, MOZ_STACK_CLASS +#include "mozilla/Maybe.h" // mozilla::Maybe +#include "mozilla/Span.h" // mozilla::Span + +#include <stddef.h> // ptrdiff_t, size_t +#include <stdint.h> // uint16_t, int32_t, uint32_t + +#include "jstypes.h" // JS_PUBLIC_API +#include "NamespaceImports.h" // ValueVector + +#include "frontend/AbstractScopePtr.h" // AbstractScopePtr, ScopeIndex +#include "frontend/BytecodeOffset.h" // BytecodeOffset +#include "frontend/CompilationInfo.h" // CompilationStencil, CompilationGCOutput +#include "frontend/JumpList.h" // JumpTarget +#include "frontend/NameCollections.h" // AtomIndexMap, PooledMapPtr +#include "frontend/ObjLiteral.h" // ObjLiteralStencil +#include "frontend/ParseNode.h" // BigIntLiteral +#include "frontend/SourceNotes.h" // SrcNote +#include "frontend/Stencil.h" // Stencils +#include "gc/Rooting.h" // JS::Rooted +#include "js/GCVariant.h" // GCPolicy<mozilla::Variant> +#include "js/GCVector.h" // GCVector +#include "js/TypeDecls.h" // jsbytecode, JSContext +#include "js/Value.h" // JS::Vector +#include "js/Vector.h" // Vector +#include "vm/Opcodes.h" // JSOpLength_JumpTarget +#include "vm/SharedStencil.h" // TryNote, ScopeNote, GCThingIndex +#include "vm/StencilEnums.h" // TryNoteKind + +namespace js { + +class Scope; + +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<TaggedScriptThingIndex, 8>; + + CompilationStencil& stencil; + CompilationState& compilationState; + ScriptThingsStackVector vector; + + // Index of the first scope in the vector. + mozilla::Maybe<GCThingIndex> firstScopeIndex; + + explicit GCThingList(JSContext* cx, CompilationStencil& stencil, + CompilationState& compilationState) + : stencil(stencil), compilationState(compilationState), vector(cx) {} + + MOZ_MUST_USE bool append(const ParserAtom* atom, GCThingIndex* index) { + *index = GCThingIndex(vector.length()); + atom->markUsedByStencil(); + if (!vector.emplaceBack(atom->toIndex())) { + return false; + } + return true; + } + MOZ_MUST_USE bool append(ScopeIndex scope, GCThingIndex* index) { + *index = GCThingIndex(vector.length()); + if (!vector.emplaceBack(scope)) { + return false; + } + if (!firstScopeIndex) { + firstScopeIndex.emplace(*index); + } + return true; + } + MOZ_MUST_USE bool append(BigIntLiteral* literal, GCThingIndex* index) { + *index = GCThingIndex(vector.length()); + if (!vector.emplaceBack(literal->index())) { + return false; + } + return true; + } + MOZ_MUST_USE bool append(RegExpLiteral* literal, GCThingIndex* index) { + *index = GCThingIndex(vector.length()); + if (!vector.emplaceBack(literal->index())) { + return false; + } + return true; + } + MOZ_MUST_USE bool append(ObjLiteralIndex objlit, GCThingIndex* index) { + *index = GCThingIndex(vector.length()); + if (!vector.emplaceBack(objlit)) { + return false; + } + return true; + } + MOZ_MUST_USE bool append(FunctionBox* funbox, GCThingIndex* index); + + MOZ_MUST_USE 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<ScopeIndex> getScopeIndex(size_t index) const; + + AbstractScopePtr firstScope() const { + MOZ_ASSERT(firstScopeIndex.isSome()); + return getScope(*firstScopeIndex); + } +}; + +MOZ_MUST_USE bool EmitScriptThingsVector( + JSContext* cx, CompilationInput& input, BaseCompilationStencil& stencil, + CompilationGCOutput& gcOutput, + mozilla::Span<const TaggedScriptThingIndex> things, + mozilla::Span<JS::GCCellPtr> output); + +struct CGTryNoteList { + Vector<TryNote, 0> list; + explicit CGTryNoteList(JSContext* cx) : list(cx) {} + + MOZ_MUST_USE bool append(TryNoteKind kind, uint32_t stackDepth, + BytecodeOffset start, BytecodeOffset end); + mozilla::Span<const TryNote> span() const { + return {list.begin(), list.length()}; + } + size_t length() const { return list.length(); } +}; + +struct CGScopeNoteList { + Vector<ScopeNote, 0> list; + explicit CGScopeNoteList(JSContext* cx) : list(cx) {} + + MOZ_MUST_USE bool append(GCThingIndex scopeIndex, BytecodeOffset offset, + uint32_t parent); + void recordEnd(uint32_t index, BytecodeOffset offset); + void recordEndFunctionBodyVar(uint32_t index); + mozilla::Span<const ScopeNote> 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<uint32_t, 0> list; + explicit CGResumeOffsetList(JSContext* cx) : list(cx) {} + + MOZ_MUST_USE bool append(uint32_t offset) { return list.append(offset); } + mozilla::Span<const uint32_t> 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<jsbytecode, 64> BytecodeVector; +typedef Vector<js::SrcNote, 64> SrcNotesVector; + +// Bytecode and all data directly associated with specific opcode/index inside +// bytecode is stored in this class. +class BytecodeSection { + public: + BytecodeSection(JSContext* cx, 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; + } + + // Check if the last emitted opcode is a jump target. + bool lastOpcodeIsJumpTarget() const { + return lastTarget_.offset.valid() && + offset() - lastTarget_.offset == + BytecodeOffsetDiff(JSOpLength_JumpTarget); + } + + // JumpTarget should not be part of the emitted statement, as they can be + // aliased by multiple statements. If we included the jump target as part of + // the statement we might have issues where the enclosing statement might + // not contain all the opcodes of the enclosed statements. + BytecodeOffset lastNonJumpTargetOffset() const { + return lastOpcodeIsJumpTarget() ? 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(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, gosub) 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: + explicit PerScriptData(JSContext* cx, frontend::CompilationStencil& stencil, + frontend::CompilationState& compilationState); + + MOZ_MUST_USE bool init(JSContext* cx); + + GCThingList& gcThingList() { return gcThingList_; } + const GCThingList& gcThingList() const { return gcThingList_; } + + PooledMapPtr<AtomIndexMap>& atomIndices() { return atomIndices_; } + const PooledMapPtr<AtomIndexMap>& atomIndices() const { return atomIndices_; } + + private: + // List of emitted scopes/objects/bigints. + GCThingList gcThingList_; + + // Map from atom to index. + PooledMapPtr<AtomIndexMap> 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..338b4fe0f1 --- /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/Scope.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<uint32_t>& 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<uint32_t>& 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_->emit1(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::IfEq, &loopInfo_->breaks)) { + return false; + } + } + + tdzCache_.emplace(bce_); + +#ifdef DEBUG + state_ = State::Body; +#endif + return true; +} + +bool CForEmitter::emitUpdate(Update update, const Maybe<uint32_t>& 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_->emit1(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(const Maybe<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 (forPos) { + 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..3c51506914 --- /dev/null +++ b/js/src/frontend/CForEmitter.h @@ -0,0 +1,176 @@ +/* -*- 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, MOZ_MUST_USE +#include "mozilla/Maybe.h" // mozilla::Maybe + +#include <stdint.h> // uint32_t + +#include "frontend/BytecodeControlStructures.h" // LoopControl +#include "frontend/BytecodeOffset.h" // BytecodeOffset +#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, Some(offset_of_body)); +// emit(body); +// cfor.emitUpdate(CForEmitter::Update::Present, Some(offset_of_update))); +// emit(update); +// cfor.emitEnd(Some(offset_of_for)); +// +// `for (;;) body` +// CForEmitter cfor(this, nullptr); +// cfor.emitInit(Nothing()); +// cfor.emitCond(Nothing()); +// cfor.emitBody(CForEmitter::Cond::Missing, Some(offset_of_body)); +// emit(body); +// cfor.emitUpdate(CForEmitter::Update::Missing, Nothing()); +// cfor.emitEnd(Some(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::IfEq 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<LoopControl> 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<TDZCheckCache> 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. + MOZ_MUST_USE bool emitInit(const mozilla::Maybe<uint32_t>& initPos); + MOZ_MUST_USE bool emitCond(const mozilla::Maybe<uint32_t>& condPos); + MOZ_MUST_USE bool emitBody(Cond cond); + MOZ_MUST_USE bool emitUpdate(Update update, + const mozilla::Maybe<uint32_t>& updatePos); + MOZ_MUST_USE bool emitEnd(const mozilla::Maybe<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..1d82d93dba --- /dev/null +++ b/js/src/frontend/CallOrNewEmitter.cpp @@ -0,0 +1,277 @@ +/* -*- 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 "frontend/SharedContext.h" +#include "vm/Opcodes.h" +#include "vm/StringType.h" + +using namespace js; +using namespace js::frontend; + +using mozilla::Maybe; + +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(const ParserAtom* name) { + MOZ_ASSERT(state_ == State::Start); + + NameOpEmitter noe( + bce_, name, + isCall() ? NameOpEmitter::Kind::Call : NameOpEmitter::Kind::Get); + if (!noe.emitGet()) { + // [stack] CALLEE THIS + return false; + } + + state_ = State::NameCallee; + return true; +} + +MOZ_MUST_USE PropOpEmitter& CallOrNewEmitter::prepareForPropCallee( + bool isSuperProp) { + MOZ_ASSERT(state_ == State::Start); + + poe_.emplace(bce_, + isCall() ? PropOpEmitter::Kind::Call : PropOpEmitter::Kind::Get, + isSuperProp ? PropOpEmitter::ObjKind::Super + : PropOpEmitter::ObjKind::Other); + + state_ = State::PropCallee; + return *poe_; +} + +MOZ_MUST_USE ElemOpEmitter& CallOrNewEmitter::prepareForElemCallee( + bool isSuperElem, bool isPrivate) { + MOZ_ASSERT(state_ == State::Start); + + eoe_.emplace(bce_, + isCall() ? ElemOpEmitter::Kind::Call : ElemOpEmitter::Kind::Get, + isSuperElem ? ElemOpEmitter::ObjKind::Super + : ElemOpEmitter::ObjKind::Other, + isPrivate ? NameVisibility::Private : NameVisibility::Public); + + state_ = State::ElemCallee; + return *eoe_; +} + +bool CallOrNewEmitter::prepareForFunctionCallee() { + MOZ_ASSERT(state_ == State::Start); + + state_ = State::FunctionCallee; + return true; +} + +bool CallOrNewEmitter::emitSuperCallee() { + MOZ_ASSERT(state_ == State::Start); + + if (!bce_->emitThisEnvironmentCallee()) { + // [stack] CALLEE + return false; + } + if (!bce_->emit1(JSOp::SuperFun)) { + // [stack] CALLEE + return false; + } + if (!bce_->emit1(JSOp::IsConstructing)) { + // [stack] CALLEE THIS + return false; + } + + state_ = State::SuperCallee; + return true; +} + +bool CallOrNewEmitter::prepareForOtherCallee() { + MOZ_ASSERT(state_ == State::Start); + + state_ = State::OtherCallee; + return true; +} + +bool CallOrNewEmitter::emitThis() { + MOZ_ASSERT(state_ == State::NameCallee || state_ == State::PropCallee || + state_ == State::ElemCallee || state_ == State::FunctionCallee || + state_ == State::SuperCallee || state_ == State::OtherCallee); + + 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::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 THIS + return false; + } + } else { + if (!bce_->emit1(JSOp::Undefined)) { + // [stack] CALLEE THIS + return false; + } + } + } + + state_ = State::This; + return true; +} + +// Used by BytecodeEmitter::emitPipeline to reuse CallOrNewEmitter instance +// across multiple chained calls. +void CallOrNewEmitter::reset() { + MOZ_ASSERT(state_ == State::End); + state_ = State::Start; +} + +bool CallOrNewEmitter::prepareForNonSpreadArguments() { + MOZ_ASSERT(state_ == State::This); + MOZ_ASSERT(!isSpread()); + + 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()); + + state_ = State::WantSpreadOperand; + return isSingleSpreadRest(); +} + +bool CallOrNewEmitter::emitSpreadArgumentsTest() { + // Caller should check wantSpreadOperand before this. + MOZ_ASSERT(state_ == State::WantSpreadOperand); + MOZ_ASSERT(isSpread()); + + if (isSingleSpreadRest()) { + // Emit a preparation code to optimize the spread call with a rest + // parameter: + // + // function f(...args) { + // g(...args); + // } + // + // If the spread operand is a rest parameter and it's optimizable + // array, skip spread operation and pass it directly to spread call + // operation. See the comment in OptimizeSpreadCall in + // Interpreter.cpp for the optimizable conditons. + + // [stack] CALLEE THIS ARG0 + + ifNotOptimizable_.emplace(bce_); + if (!bce_->emit1(JSOp::OptimizeSpreadCall)) { + // [stack] CALLEE THIS ARG0 OPTIMIZED + return false; + } + if (!ifNotOptimizable_->emitThen(IfEmitter::ConditionKind::Negative)) { + // [stack] CALLEE THIS ARG0 + return false; + } + if (!bce_->emit1(JSOp::Pop)) { + // [stack] CALLEE THIS + return false; + } + } + + state_ = State::Arguments; + return true; +} + +bool CallOrNewEmitter::emitEnd(uint32_t argc, const Maybe<uint32_t>& beginPos) { + MOZ_ASSERT(state_ == State::Arguments); + + if (isSingleSpreadRest()) { + if (!ifNotOptimizable_->emitEnd()) { + // [stack] CALLEE THIS ARR + return false; + } + + ifNotOptimizable_.reset(); + } + if (isNew() || isSuperCall()) { + if (isSuperCall()) { + if (!bce_->emit1(JSOp::NewTarget)) { + // [stack] CALLEE THIS ARG.. NEW.TARGET + return false; + } + } else { + // Repush the callee as new.target + uint32_t effectiveArgc = isSpread() ? 1 : argc; + if (!bce_->emitDupAt(effectiveArgc + 1)) { + // [stack] CALLEE THIS ARR CALLEE + return false; + } + } + } + if (beginPos) { + 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() && beginPos) { + uint32_t lineNum = bce_->parser->errorReporter().lineAt(*beginPos); + if (!bce_->emitUint32Operand(JSOp::Lineno, lineNum)) { + 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..9469a751a2 --- /dev/null +++ b/js/src/frontend/CallOrNewEmitter.h @@ -0,0 +1,318 @@ +/* -*- 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 <stdint.h> + +#include "frontend/ElemOpEmitter.h" +#include "frontend/IfEmitter.h" +#include "frontend/PropOpEmitter.h" +#include "frontend/ValueUsage.h" +#include "js/TypeDecls.h" +#include "vm/BytecodeUtil.h" +#include "vm/Opcodes.h" + +namespace js { +namespace frontend { + +struct BytecodeEmitter; + +// 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, Some(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, Some(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, Some(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, Some(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, Some(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, Some(offset_of_callee)); +// +// `print(...arg);` +// CallOrNewEmitter cone(this, JSOp::SpreadCall, +// CallOrNewEmitter::ArgumentsKind::Other, +// ValueUsage::WantValue); +// cone.emitNameCallee(print); +// cone.emitThis(); +// if (cone.wantSpreadOperand()) { +// emit(arg) +// } +// cone.emitSpreadArgumentsTest(); +// emit([...arg]); +// cone.emitEnd(1, Some(offset_of_callee)); +// +// `print(...rest);` +// where `rest` is rest parameter +// CallOrNewEmitter cone(this, JSOp::SpreadCall, +// CallOrNewEmitter::ArgumentsKind::SingleSpreadRest, +// ValueUsage::WantValue); +// cone.emitNameCallee(print); +// cone.emitThis(); +// if (cone.wantSpreadOperand()) { +// emit(arg) +// } +// cone.emitSpreadArgumentsTest(); +// emit([...arg]); +// cone.emitEnd(1, Some(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, Some(offset_of_callee)); +// +class MOZ_STACK_CLASS CallOrNewEmitter { + public: + enum class ArgumentsKind { + Other, + + // Specify this for the following case: + // + // function f(...rest) { + // g(...rest); + // } + // + // This enables optimization to avoid allocating an intermediate array + // for spread operation. + // + // wantSpreadOperand() returns true when this is specified. + SingleSpreadRest + }; + + private: + BytecodeEmitter* bce_; + + // The opcode for the call or new. + JSOp op_; + + // Whether the call is a spread call with single rest parameter or not. + // See the comment in emitSpreadArgumentsTest for more details. + ArgumentsKind argumentsKind_; + + // The branch for spread call optimization. + mozilla::Maybe<InternalIfEmitter> ifNotOptimizable_; + + mozilla::Maybe<PropOpEmitter> poe_; + mozilla::Maybe<ElemOpEmitter> eoe_; + + // The state of this emitter. + // + // +-------+ emitNameCallee +------------+ + // | Start |-+------------------------->| NameCallee |------+ + // +-------+ | +------------+ | + // | | + // | prepareForPropCallee +------------+ v + // +------------------------->| PropCallee |----->+ + // | +------------+ | + // | | + // | prepareForElemCallee +------------+ v + // +------------------------->| ElemCallee |----->+ + // | +------------+ | + // | | + // | prepareForFunctionCallee +----------------+ v + // +------------------------->| FunctionCallee |->+ + // | +----------------+ | + // | | + // | emitSuperCallee +-------------+ v + // +------------------------->| SuperCallee |---->+ + // | +-------------+ | + // | | + // | prepareForOtherCallee +-------------+ v + // +------------------------->| OtherCallee |---->+ + // +-------------+ | + // | + // +--------------------------------------------------------+ + // | + // | emitThis +------+ + // +--------->| This |-+ + // +------+ | + // | + // +-------------------+ + // | + // | [!isSpread] + // | prepareForNonSpreadArguments +-----------+ emitEnd +-----+ + // +------------------------------->+->| Arguments |-------->| End | + // | ^ +-----------+ +-----+ + // | | + // | +----------------------------------+ + // | | + // | [isSpread] | + // | wantSpreadOperand +-------------------+ emitSpreadArgumentsTest | + // +-------------------->| WantSpreadOperand |-------------------------+ + // +-------------------+ + enum class State { + // The initial state. + Start, + + // After calling emitNameCallee. + NameCallee, + + // After calling prepareForPropCallee. + PropCallee, + + // After calling prepareForElemCallee. + ElemCallee, + + // After calling prepareForFunctionCallee. + FunctionCallee, + + // After calling emitSuperCallee. + SuperCallee, + + // After calling prepareForOtherCallee. + OtherCallee, + + // After calling emitThis. + This, + + // After calling wantSpreadOperand. + WantSpreadOperand, + + // After calling prepareForNonSpreadArguments. + Arguments, + + // After calling emitEnd. + End + }; + State state_ = State::Start; + + public: + CallOrNewEmitter(BytecodeEmitter* bce, JSOp op, ArgumentsKind argumentsKind, + ValueUsage valueUsage); + + private: + MOZ_MUST_USE bool isCall() const { + return op_ == JSOp::Call || op_ == JSOp::CallIgnoresRv || + op_ == JSOp::SpreadCall || isEval() || isFunApply() || isFunCall(); + } + + MOZ_MUST_USE bool isNew() const { + return op_ == JSOp::New || op_ == JSOp::SpreadNew; + } + + MOZ_MUST_USE bool isSuperCall() const { + return op_ == JSOp::SuperCall || op_ == JSOp::SpreadSuperCall; + } + + MOZ_MUST_USE bool isEval() const { + return op_ == JSOp::Eval || op_ == JSOp::StrictEval || + op_ == JSOp::SpreadEval || op_ == JSOp::StrictSpreadEval; + } + + MOZ_MUST_USE bool isFunApply() const { return op_ == JSOp::FunApply; } + + MOZ_MUST_USE bool isFunCall() const { return op_ == JSOp::FunCall; } + + MOZ_MUST_USE bool isSpread() const { return JOF_OPTYPE(op_) == JOF_BYTE; } + + MOZ_MUST_USE bool isSingleSpreadRest() const { + return argumentsKind_ == ArgumentsKind::SingleSpreadRest; + } + + public: + MOZ_MUST_USE bool emitNameCallee(const ParserAtom* name); + MOZ_MUST_USE PropOpEmitter& prepareForPropCallee(bool isSuperProp); + MOZ_MUST_USE ElemOpEmitter& prepareForElemCallee(bool isSuperElem, + bool isPrivateElem); + MOZ_MUST_USE bool prepareForFunctionCallee(); + MOZ_MUST_USE bool emitSuperCallee(); + MOZ_MUST_USE bool prepareForOtherCallee(); + + MOZ_MUST_USE bool emitThis(); + + // Used by BytecodeEmitter::emitPipeline to reuse CallOrNewEmitter instance + // across multiple chained calls. + void reset(); + + MOZ_MUST_USE bool prepareForNonSpreadArguments(); + + // See the usage in the comment at the top of the class. + MOZ_MUST_USE bool wantSpreadOperand(); + MOZ_MUST_USE bool emitSpreadArgumentsTest(); + + // Parameters are the offset in the source code for each character below: + // + // callee(arg); + // ^ + // | + // beginPos + // + // Can be Nothing() if not available. + MOZ_MUST_USE bool emitEnd(uint32_t argc, + const mozilla::Maybe<uint32_t>& beginPos); +}; + +} /* namespace frontend */ +} /* namespace js */ + +#endif /* frontend_CallOrNewEmitter_h */ diff --git a/js/src/frontend/CompilationInfo.h b/js/src/frontend/CompilationInfo.h new file mode 100644 index 0000000000..88bb6612b5 --- /dev/null +++ b/js/src/frontend/CompilationInfo.h @@ -0,0 +1,706 @@ +/* -*- 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_CompilationInfo_h +#define frontend_CompilationInfo_h + +#include "mozilla/AlreadyAddRefed.h" // already_AddRefed +#include "mozilla/Assertions.h" // MOZ_ASSERT +#include "mozilla/Attributes.h" +#include "mozilla/RefPtr.h" // RefPtr +#include "mozilla/Span.h" + +#include "builtin/ModuleObject.h" +#include "ds/LifoAlloc.h" +#include "frontend/ParserAtom.h" +#include "frontend/ScriptIndex.h" // ScriptIndex +#include "frontend/SharedContext.h" +#include "frontend/Stencil.h" +#include "frontend/UsedNameTracker.h" +#include "js/GCVector.h" +#include "js/HashTable.h" +#include "js/RealmOptions.h" +#include "js/SourceText.h" +#include "js/Transcoding.h" +#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/SharedStencil.h" // SharedImmutableScriptData + +namespace js { + +class JSONPrinter; + +namespace frontend { + +// ScopeContext hold information derivied from the scope and environment chains +// to try to avoid the parser needing to traverse VM structures directly. +struct ScopeContext { + // 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, while other contextual information only uses the + // actual scope passed to the compile. + JS::Rooted<Scope*> effectiveScope; + + // 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; + + // 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; + + // 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> memberInitializers = {}; + + // Indicates there is a 'class' or 'with' scope on enclosing scope chain. + bool inClass = false; + bool inWith = false; + + explicit ScopeContext(JSContext* cx, InheritThis inheritThis, Scope* scope, + JSObject* enclosingEnv = nullptr) + : effectiveScope(cx, determineEffectiveScope(scope, enclosingEnv)) { + if (inheritThis == InheritThis::Yes) { + computeThisBinding(effectiveScope); + computeThisEnvironment(scope); + } + computeInScope(scope); + } + + private: + void computeThisBinding(Scope* scope); + void computeThisEnvironment(Scope* scope); + void computeInScope(Scope* scope); + + static Scope* determineEffectiveScope(Scope* scope, JSObject* environment); +}; + +struct CompilationAtomCache { + public: + using AtomCacheVector = JS::GCVector<JSAtom*, 0, js::SystemAllocPolicy>; + + private: + // Atoms lowered into or converted from BaseCompilationStencil.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: + JSAtom* getExistingAtomAt(ParserAtomIndex index) const; + JSAtom* getExistingAtomAt(JSContext* cx, + TaggedParserAtomIndex taggedIndex) const; + JSAtom* getAtomAt(ParserAtomIndex index) const; + bool hasAtomAt(ParserAtomIndex index) const; + bool setAtomAt(JSContext* cx, ParserAtomIndex index, JSAtom* atom); + bool allocate(JSContext* cx, size_t length); + bool extendIfNecessary(JSContext* cx, size_t length); + + void stealBuffer(AtomCacheVector& atoms); + void releaseBuffer(AtomCacheVector& atoms); + + void trace(JSTracer* trc); +} JS_HAZ_GC_POINTER; + +// Input of the compilation, including source and enclosing context. +struct CompilationInput { + const JS::ReadOnlyCompileOptions& options; + + CompilationAtomCache atomCache; + + BaseScript* lazy = nullptr; + + ScriptSourceHolder source_; + + // * If we're compiling standalone function, the non-null enclosing scope of + // the function + // * If we're compiling eval, the non-null enclosing scope of the `eval`. + // * If we're compiling module, null that means empty global scope + // (See EmitterScope::checkEnvironmentChainLength) + // * If we're compiling self-hosted JS, an empty global scope. + // This scope is also used for EmptyGlobalScopeType in + // BaseCompilationStencil.gcThings. + // See the comment in initForSelfHostingGlobal. + // * Null otherwise + Scope* enclosingScope = nullptr; + + explicit CompilationInput(const JS::ReadOnlyCompileOptions& options) + : options(options) {} + + private: + bool initScriptSource(JSContext* cx); + + public: + bool initForGlobal(JSContext* cx) { return initScriptSource(cx); } + + bool initForSelfHostingGlobal(JSContext* cx) { + if (!initScriptSource(cx)) { + return false; + } + + // This enclosing scope is also recorded as EmptyGlobalScopeType in + // BaseCompilationStencil.gcThings even though corresponding ScopeStencil + // isn't generated. + // + // Store the enclosing scope here in order to access it from + // inner scopes' ScopeStencil::enclosing. + enclosingScope = &cx->global()->emptyGlobalScope(); + return true; + } + + bool initForStandaloneFunction(JSContext* cx, + HandleScope functionEnclosingScope) { + if (!initScriptSource(cx)) { + return false; + } + enclosingScope = functionEnclosingScope; + return true; + } + + bool initForEval(JSContext* cx, HandleScope evalEnclosingScope) { + if (!initScriptSource(cx)) { + return false; + } + enclosingScope = evalEnclosingScope; + return true; + } + + bool initForModule(JSContext* cx) { + if (!initScriptSource(cx)) { + return false; + } + // The `enclosingScope` is the emptyGlobalScope. + return true; + } + + void initFromLazy(BaseScript* lazyScript) { + lazy = lazyScript; + enclosingScope = lazy->function()->enclosingScope(); + } + + ScriptSource* source() { return source_.get(); } + + void setSource(ScriptSource* ss) { return source_.reset(ss); } + + template <typename Unit> + MOZ_MUST_USE bool assignSource(JSContext* cx, + JS::SourceText<Unit>& sourceBuffer) { + return source()->assignSource(cx, options, sourceBuffer); + } + + void trace(JSTracer* trc); +} JS_HAZ_GC_POINTER; + +struct CompilationStencil; + +struct MOZ_RAII CompilationState { + // Until we have dealt with Atoms in the front end, we need to hold + // onto them. + Directives directives; + + ScopeContext scopeContext; + + UsedNameTracker usedNames; + LifoAllocScope& allocScope; + + CompilationInput& input; + + // Temporary space to accumulate stencil data. + // Copied to BaseCompilationStencil by `finish` method. + // + // See corresponding BaseCompilationStencil fields for desription. + Vector<RegExpStencil, 0, js::SystemAllocPolicy> regExpData; + Vector<ScriptStencil, 0, js::SystemAllocPolicy> scriptData; + Vector<ScriptStencilExtra, 0, js::SystemAllocPolicy> scriptExtra; + Vector<ScopeStencil, 0, js::SystemAllocPolicy> scopeData; + Vector<BaseParserScopeData*, 0, js::SystemAllocPolicy> scopeNames; + Vector<TaggedScriptThingIndex, 0, js::SystemAllocPolicy> gcThingData; + + // Table of parser atoms for this compilation. + ParserAtomsTable parserAtoms; + + // 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 + // BaseCompilationStencil.prepareStorageFor *before* start emitting bytecode. + size_t nonLazyFunctionCount = 0; + + CompilationState(JSContext* cx, LifoAllocScope& frontendAllocScope, + const JS::ReadOnlyCompileOptions& options, + CompilationStencil& stencil, + InheritThis inheritThis = InheritThis::No, + Scope* enclosingScope = nullptr, + JSObject* enclosingEnv = nullptr); + + bool finish(JSContext* cx, CompilationStencil& stencil); + + const ParserAtom* getParserAtomAt(JSContext* cx, + TaggedParserAtomIndex taggedIndex) const; + + // Allocate space for `length` gcthings, and return the address of the + // first element to `cursor` to initialize on the caller. + bool allocateGCThingsUninitialized(JSContext* cx, ScriptIndex scriptIndex, + size_t length, + TaggedScriptThingIndex** cursor); + + bool appendGCThings(JSContext* cx, ScriptIndex scriptIndex, + mozilla::Span<const TaggedScriptThingIndex> things); +}; + +// 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<RefPtr<js::SharedImmutableScriptData>, 0, js::SystemAllocPolicy>; + using SharedDataVectorPtr = SharedDataVector*; + + using SharedDataMap = + HashMap<ScriptIndex, RefPtr<js::SharedImmutableScriptData>, + mozilla::DefaultHasher<ScriptIndex>, js::SystemAllocPolicy>; + using SharedDataMapPtr = SharedDataMap*; + + private: + enum { + SingleTag = 0, + VectorTag = 1, + MapTag = 2, + + TagMask = 3, + }; + + uintptr_t data_ = 0; + + public: + // Defaults to SingleSharedData for delazification vector. + SharedDataContainer() = default; + + ~SharedDataContainer(); + + bool initVector(JSContext* cx); + bool initMap(JSContext* cx); + + 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; } + + void setSingle(already_AddRefed<SharedImmutableScriptData>&& data) { + MOZ_ASSERT(isEmpty()); + data_ = reinterpret_cast<uintptr_t>(data.take()); + MOZ_ASSERT(isSingle()); + } + + SingleSharedDataPtr asSingle() { + MOZ_ASSERT(isSingle()); + MOZ_ASSERT(!isEmpty()); + static_assert(SingleTag == 0); + return reinterpret_cast<SingleSharedDataPtr>(data_); + } + SharedDataVectorPtr asVector() { + MOZ_ASSERT(isVector()); + return reinterpret_cast<SharedDataVectorPtr>(data_ & ~TagMask); + } + SharedDataMapPtr asMap() { + MOZ_ASSERT(isMap()); + return reinterpret_cast<SharedDataMapPtr>(data_ & ~TagMask); + } + + bool prepareStorageFor(JSContext* cx, size_t nonLazyScriptCount, + size_t allScriptCount); + + // Returns index-th script's shared data, or nullptr if it doesn't have. + js::SharedImmutableScriptData* get(ScriptIndex index); + + // Add data for index-th script and share it with VM. + bool addAndShare(JSContext* cx, ScriptIndex index, + js::SharedImmutableScriptData* data); + +#if defined(DEBUG) || defined(JS_JITSPEW) + void dump(); + void dump(js::JSONPrinter& json); + void dumpFields(js::JSONPrinter& json); +#endif +}; + +// The top level struct of stencil. +struct BaseCompilationStencil { + // Hold onto the RegExpStencil, BigIntStencil, and ObjLiteralStencil that are + // allocated during parse to ensure correct destruction. + mozilla::Span<RegExpStencil> regExpData; + Vector<BigIntStencil, 0, js::SystemAllocPolicy> bigIntData; + Vector<ObjLiteralStencil, 0, js::SystemAllocPolicy> objLiteralData; + + // 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<ScriptStencil> scriptData; + SharedDataContainer sharedData; + mozilla::Span<TaggedScriptThingIndex> 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<ScopeStencil> scopeData; + mozilla::Span<BaseParserScopeData*> scopeNames; + + // 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; + + // FunctionKey is an identifier that identifies a function within the source + // next in a reproducible way. It allows us to match delazification data with + // initial parse data, even across different runs. This is only used for + // delazification stencils. + using FunctionKey = uint32_t; + + static constexpr FunctionKey NullFunctionKey = 0; + + FunctionKey functionKey = NullFunctionKey; + + BaseCompilationStencil() = default; + + // We need a move-constructor to work with Rooted. + BaseCompilationStencil(BaseCompilationStencil&& other) = default; + + const ParserAtom* getParserAtomAt(JSContext* cx, + TaggedParserAtomIndex taggedIndex) const; + + bool prepareStorageFor(JSContext* cx, CompilationState& compilationState) { + // NOTE: At this point CompilationState shouldn't be finished, and + // BaseCompilationStencil.scriptData field should be empty. + // Use CompilationState.scriptData as data source. + MOZ_ASSERT(scriptData.empty()); + size_t allScriptCount = compilationState.scriptData.length(); + size_t nonLazyScriptCount = compilationState.nonLazyFunctionCount; + if (!compilationState.scriptData[0].isFunction()) { + nonLazyScriptCount++; + } + return sharedData.prepareStorageFor(cx, nonLazyScriptCount, allScriptCount); + } + + static FunctionKey toFunctionKey(const SourceExtent& extent) { + // In eval("x=>1"), the arrow function will have a sourceStart of 0 which + // conflicts with the NullFunctionKey, so shift all keys by 1 instead. + auto result = extent.sourceStart + 1; + MOZ_ASSERT(result != NullFunctionKey); + return result; + } + + bool isInitialStencil() const { return functionKey == NullFunctionKey; } + + bool isCompilationStencil() const { return isInitialStencil(); } + inline CompilationStencil& asCompilationStencil(); + inline const CompilationStencil& asCompilationStencil() const; + +#if defined(DEBUG) || defined(JS_JITSPEW) + void dump(); + void dump(js::JSONPrinter& json); + void dumpFields(js::JSONPrinter& json); +#endif +}; + +// 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<JSFunction*, 0, js::SystemAllocPolicy> functions; + + // References to scopes are controlled via AbstractScopePtr, which holds onto + // an index (and CompilationStencil reference). + JS::GCVector<js::Scope*, 0, js::SystemAllocPolicy> scopes; + + // The result ScriptSourceObject. This is unused in delazifying parses. + ScriptSourceObject* sourceObject = nullptr; + + CompilationGCOutput() = default; + + void trace(JSTracer* trc); +} JS_HAZ_GC_POINTER; + +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 BaseCompilationStencil& stencil_; + CompilationGCOutput& gcOutput_; + + Iterator(const BaseCompilationStencil& stencil, + CompilationGCOutput& gcOutput, size_t index) + : index_(index), stencil_(stencil), gcOutput_(gcOutput) { + MOZ_ASSERT(index == stencil.scriptData.size()); + } + + public: + explicit Iterator(const BaseCompilationStencil& 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_; + } + + inline ScriptAndFunction operator*(); + + static Iterator end(const BaseCompilationStencil& stencil, + CompilationGCOutput& gcOutput) { + return Iterator(stencil, gcOutput, stencil.scriptData.size()); + } + }; + + const BaseCompilationStencil& stencil_; + CompilationGCOutput& gcOutput_; + + explicit ScriptStencilIterable(const BaseCompilationStencil& stencil, + CompilationGCOutput& gcOutput) + : stencil_(stencil), gcOutput_(gcOutput) {} + + Iterator begin() const { return Iterator(stencil_, gcOutput_); } + + Iterator end() const { return Iterator::end(stencil_, gcOutput_); } +}; + +// Input and output of compilation to stencil. +struct CompilationStencil : public BaseCompilationStencil { + static constexpr ScriptIndex TopLevelIndex = ScriptIndex(0); + + // This holds allocations that do not require destructors to be run but are + // live until the stencil is released. + LifoAlloc alloc; + + // Parameterized chunk size to use for LifoAlloc. + static constexpr size_t LifoAllocChunkSize = 512; + + CompilationInput input; + + // Initial-compilation-specific data for each script. + mozilla::Span<ScriptStencilExtra> scriptExtra; + + // Module metadata if this is a module compile. + mozilla::Maybe<StencilModuleMetadata> moduleMetadata; + + // AsmJS modules generated by parsing. + HashMap<ScriptIndex, RefPtr<const JS::WasmModule>, + mozilla::DefaultHasher<ScriptIndex>, js::SystemAllocPolicy> + asmJS; + + // Set to true once prepareForInstantiate is called. + // NOTE: This field isn't XDR-encoded. + bool preparationIsPerformed = false; + + // 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 RewindToken { + // Temporarily share this token struct with CompilationState. + size_t scriptDataLength = 0; + + size_t asmJSCount = 0; + }; + + RewindToken getRewindToken(CompilationState& state); + void rewind(CompilationState& state, const RewindToken& pos); + + // Construct a CompilationStencil + CompilationStencil(JSContext* cx, const JS::ReadOnlyCompileOptions& options) + : alloc(LifoAllocChunkSize), input(options) {} + + static MOZ_MUST_USE bool prepareInputAndStencilForInstantiate( + JSContext* cx, CompilationInput& input, BaseCompilationStencil& stencil); + static MOZ_MUST_USE bool prepareGCOutputForInstantiate( + JSContext* cx, BaseCompilationStencil& stencil, + CompilationGCOutput& gcOutput); + + static MOZ_MUST_USE bool prepareForInstantiate(JSContext* cx, + CompilationStencil& stencil, + CompilationGCOutput& gcOutput); + static MOZ_MUST_USE bool instantiateStencils(JSContext* cx, + CompilationStencil& stencil, + CompilationGCOutput& gcOutput); + static MOZ_MUST_USE bool instantiateStencilsAfterPreparation( + JSContext* cx, CompilationInput& input, BaseCompilationStencil& stencil, + CompilationGCOutput& gcOutput); + + MOZ_MUST_USE bool serializeStencils(JSContext* cx, JS::TranscodeBuffer& buf, + bool* succeededOut = nullptr); + + // Move constructor is necessary to use Rooted, but must be explicit in + // order to steal the LifoAlloc data + CompilationStencil(CompilationStencil&& other) noexcept + : BaseCompilationStencil(std::move(other)), + alloc(LifoAllocChunkSize), + input(std::move(other.input)) { + // Steal the data from the LifoAlloc. + alloc.steal(&other.alloc); + } + + // To avoid any misuses, make sure this is neither copyable or assignable. + CompilationStencil(const CompilationStencil&) = delete; + CompilationStencil& operator=(const CompilationStencil&) = delete; + CompilationStencil& operator=(CompilationStencil&&) = delete; + + static ScriptStencilIterable functionScriptStencils( + BaseCompilationStencil& stencil, CompilationGCOutput& gcOutput) { + return ScriptStencilIterable(stencil, gcOutput); + } + + void trace(JSTracer* trc); + +#if defined(DEBUG) || defined(JS_JITSPEW) + void dump(); + void dump(js::JSONPrinter& json); + void dumpFields(js::JSONPrinter& json); +#endif +}; + +inline CompilationStencil& BaseCompilationStencil::asCompilationStencil() { + MOZ_ASSERT(isCompilationStencil()); + return *static_cast<CompilationStencil*>(this); +} + +inline const CompilationStencil& BaseCompilationStencil::asCompilationStencil() + const { + MOZ_ASSERT(isCompilationStencil()); + return *reinterpret_cast<const CompilationStencil*>(this); +} + +inline ScriptStencilIterable::ScriptAndFunction +ScriptStencilIterable::Iterator::operator*() { + ScriptIndex index = ScriptIndex(index_); + const ScriptStencil& script = stencil_.scriptData[index]; + const ScriptStencilExtra* scriptExtra = nullptr; + if (stencil_.isInitialStencil()) { + scriptExtra = &stencil_.asCompilationStencil().scriptExtra[index]; + } + return ScriptAndFunction(script, scriptExtra, gcOutput_.functions[index], + index); +} + +// A set of stencils, for XDR purpose. +// This contains the initial compilation, and a vector of delazification. +struct CompilationStencilSet : public CompilationStencil { + private: + using ScriptIndexVector = Vector<ScriptIndex, 0, js::SystemAllocPolicy>; + + MOZ_MUST_USE bool buildDelazificationIndices(JSContext* cx); + + public: + Vector<BaseCompilationStencil, 0, js::SystemAllocPolicy> delazifications; + ScriptIndexVector delazificationIndices; + CompilationAtomCache::AtomCacheVector delazificationAtomCache; + + CompilationStencilSet(JSContext* cx, + const JS::ReadOnlyCompileOptions& options) + : CompilationStencil(cx, options) {} + + // Move constructor is necessary to use Rooted. + CompilationStencilSet(CompilationStencilSet&& other) = default; + + // To avoid any misuses, make sure this is neither copyable or assignable. + CompilationStencilSet(const CompilationStencilSet&) = delete; + CompilationStencilSet& operator=(const CompilationStencilSet&) = delete; + CompilationStencilSet& operator=(CompilationStencilSet&&) = delete; + + MOZ_MUST_USE bool prepareForInstantiate( + JSContext* cx, CompilationGCOutput& gcOutput, + CompilationGCOutput& gcOutputForDelazification); + MOZ_MUST_USE bool instantiateStencils( + JSContext* cx, CompilationGCOutput& gcOutput, + CompilationGCOutput& gcOutputForDelazification); + MOZ_MUST_USE bool instantiateStencilsAfterPreparation( + JSContext* cx, CompilationGCOutput& gcOutput, + CompilationGCOutput& gcOutputForDelazification); + + MOZ_MUST_USE bool deserializeStencils(JSContext* cx, + const JS::TranscodeRange& range, + bool* succeededOut); +}; + +} // namespace frontend +} // namespace js + +#endif // frontend_CompilationInfo_h diff --git a/js/src/frontend/DefaultEmitter.cpp b/js/src/frontend/DefaultEmitter.cpp new file mode 100644 index 0000000000..a9e8512369 --- /dev/null +++ b/js/src/frontend/DefaultEmitter.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/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::Maybe; +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..3bba3b7c97 --- /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, MOZ_MUST_USE +#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<IfEmitter> 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); + + MOZ_MUST_USE bool prepareForDefault(); + MOZ_MUST_USE 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..def0d1945c --- /dev/null +++ b/js/src/frontend/DoWhileEmitter.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/DoWhileEmitter.h" + +#include "frontend/BytecodeEmitter.h" +#include "frontend/SourceNotes.h" +#include "vm/Opcodes.h" +#include "vm/StencilEnums.h" // TryNoteKind + +using namespace js; +using namespace js::frontend; + +using mozilla::Maybe; +using mozilla::Nothing; + +DoWhileEmitter::DoWhileEmitter(BytecodeEmitter* bce) : bce_(bce) {} + +bool DoWhileEmitter::emitBody(const Maybe<uint32_t>& doPos, + const Maybe<uint32_t>& bodyPos) { + MOZ_ASSERT(state_ == State::Start); + + // Ensure that the column of the 'do' is set properly. + if (doPos) { + 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_, 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::IfNe, 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..7bea693444 --- /dev/null +++ b/js/src/frontend/DoWhileEmitter.h @@ -0,0 +1,83 @@ +/* -*- 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 <stdint.h> + +#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(Some(offset_of_do), Some(offset_of_body)); +// emit(body); +// doWhile.emitCond(); +// emit(cond); +// doWhile.emitEnd(); +// +class MOZ_STACK_CLASS DoWhileEmitter { + BytecodeEmitter* bce_; + + mozilla::Maybe<LoopControl> 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 + // + // Can be Nothing() if not available. + MOZ_MUST_USE bool emitBody(const mozilla::Maybe<uint32_t>& doPos, + const mozilla::Maybe<uint32_t>& bodyPos); + MOZ_MUST_USE bool emitCond(); + MOZ_MUST_USE 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..7c8b8548cc --- /dev/null +++ b/js/src/frontend/EitherParser.h @@ -0,0 +1,167 @@ +/* -*- 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/Attributes.h" +#include "mozilla/Tuple.h" +#include "mozilla/Utf8.h" +#include "mozilla/Variant.h" + +#include <type_traits> +#include <utility> + +#include "frontend/BCEParserHandle.h" +#include "frontend/Parser.h" +#include "frontend/TokenStream.h" + +namespace js { + +namespace detail { + +template <template <class Parser> class GetThis, + template <class This> class MemberFunction, typename... Args> +struct InvokeMemberFunction { + mozilla::Tuple<std::decay_t<Args>...> args; + + template <class This, size_t... Indices> + auto matchInternal(This* obj, std::index_sequence<Indices...>) -> decltype( + ((*obj).*(MemberFunction<This>::get()))(mozilla::Get<Indices>(args)...)) { + return ((*obj).* + (MemberFunction<This>::get()))(mozilla::Get<Indices>(args)...); + } + + public: + template <typename... ActualArgs> + explicit InvokeMemberFunction(ActualArgs&&... actualArgs) + : args{std::forward<ActualArgs>(actualArgs)...} {} + + template <class Parser> + auto operator()(Parser* parser) + -> decltype(this->matchInternal(GetThis<Parser>::get(parser), + std::index_sequence_for<Args...>{})) { + return this->matchInternal(GetThis<Parser>::get(parser), + std::index_sequence_for<Args...>{}); + } +}; + +// |this|-computing templates. + +template <class Parser> +struct GetParser { + static Parser* get(Parser* parser) { return parser; } +}; + +template <class Parser> +struct GetTokenStream { + static auto get(Parser* parser) { return &parser->tokenStream; } +}; + +// Member function-computing templates. + +template <class Parser> +struct ParserOptions { + static constexpr auto get() { return &Parser::options; } +}; + +template <class TokenStream> +struct TokenStreamComputeLineAndColumn { + static constexpr auto get() { return &TokenStream::computeLineAndColumn; } +}; + +// Generic matchers. + +struct ParseHandlerMatcher { + template <class Parser> + frontend::FullParseHandler& operator()(Parser* parser) { + return parser->handler_; + } +}; + +struct ParserSharedBaseMatcher { + template <class Parser> + frontend::ParserSharedBase& operator()(Parser* parser) { + return *static_cast<frontend::ParserSharedBase*>(parser); + } +}; + +struct ErrorReporterMatcher { + template <class Parser> + frontend::ErrorReporter& operator()(Parser* parser) { + return parser->tokenStream; + } +}; + +} // namespace detail + +namespace frontend { + +class EitherParser : public BCEParserHandle { + // Leave this as a variant, to promote good form until 8-bit parser + // integration. + mozilla::Variant<Parser<FullParseHandler, char16_t>* const, + Parser<FullParseHandler, mozilla::Utf8Unit>* const> + parser; + + using Node = typename FullParseHandler::Node; + + template <template <class Parser> class GetThis, + template <class This> class GetMemberFunction, + typename... StoredArgs> + using InvokeMemberFunction = + detail::InvokeMemberFunction<GetThis, GetMemberFunction, StoredArgs...>; + + public: + template <class Parser> + explicit EitherParser(Parser* parser) : parser(parser) {} + + FullParseHandler& astGenerator() final { + return parser.match(detail::ParseHandlerMatcher()); + } + + ErrorReporter& errorReporter() final { + return parser.match(detail::ErrorReporterMatcher()); + } + const ErrorReporter& errorReporter() const final { + return parser.match(detail::ErrorReporterMatcher()); + } + + const JS::ReadOnlyCompileOptions& options() const final { + InvokeMemberFunction<detail::GetParser, detail::ParserOptions> + optionsMatcher; + return parser.match(std::move(optionsMatcher)); + } + + void computeLineAndColumn(uint32_t offset, uint32_t* line, + uint32_t* column) const { + InvokeMemberFunction<detail::GetTokenStream, + detail::TokenStreamComputeLineAndColumn, uint32_t, + uint32_t*, uint32_t*> + matcher{offset, line, column}; + return parser.match(std::move(matcher)); + } + + JSAtom* liftParserAtomToJSAtom(const ParserAtom* parserAtom) { + ParserSharedBase& base = parser.match(detail::ParserSharedBaseMatcher()); + return base.liftParserAtomToJSAtom(parserAtom); + } + + CompilationStencil& getCompilationStencil() { + ParserSharedBase& base = parser.match(detail::ParserSharedBaseMatcher()); + return base.getCompilationStencil(); + } +}; + +} /* namespace frontend */ +} /* namespace js */ + +#endif /* frontend_EitherParser_h */ diff --git a/js/src/frontend/ElemOpEmitter.cpp b/js/src/frontend/ElemOpEmitter.cpp new file mode 100644 index 0000000000..53bf366325 --- /dev/null +++ b/js/src/frontend/ElemOpEmitter.cpp @@ -0,0 +1,304 @@ +/* -*- 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, + NameVisibility visibility) + : bce_(bce), kind_(kind), objKind_(objKind), visibility_(visibility) { + // Can't access private names of super! + MOZ_ASSERT_IF(visibility == NameVisibility::Private, + objKind != ObjKind::Super); +} + +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 (!isSuper() && isIncDec()) { + if (!bce_->emit1(JSOp::CheckObjCoercible)) { + // [stack] OBJ + return false; + } + } + 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::emitPrivateGuard() { + MOZ_ASSERT(state_ == State::Key); + + if (!isPrivate()) { + return true; + } + + if (isPropInit()) { + // [stack] OBJ KEY + if (!bce_->emitCheckPrivateField(ThrowCondition::ThrowHas, + ThrowMsgKind::PrivateDoubleInit)) { + // [stack] OBJ KEY BOOL + return false; + } + } else { + if (!bce_->emitCheckPrivateField(ThrowCondition::ThrowHasNot, + isPrivateGet() + ? ThrowMsgKind::MissingPrivateOnGet + : ThrowMsgKind::MissingPrivateOnSet)) { + // [stack] OBJ KEY BOOL + return false; + } + } + + // CheckPrivate leaves the result of the HasOwnCheck on the stack. Pop it off. + return bce_->emit1(JSOp::Pop); + // [stack] OBJ KEY +} + +bool ElemOpEmitter::emitGet() { + MOZ_ASSERT(state_ == State::Key); + + if (isIncDec() || isCompoundAssignment()) { + if (!bce_->emit1(JSOp::ToPropertyKey)) { + // [stack] # if Super + // [stack] THIS KEY + // [stack] # otherwise + // [stack] OBJ KEY + return false; + } + } + + if (!emitPrivateGuard()) { + 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, ShouldInstrument::Yes)) { + // [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()) { + if (!emitPrivateGuard()) { + return false; + } + // 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()); + MOZ_ASSERT(!isPrivate()); + + 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 { + MOZ_ASSERT(!isPrivate()); + 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, ShouldInstrument::Yes)) { + // [stack] ELEM + return false; + } + +#ifdef DEBUG + state_ = State::Assignment; +#endif + return true; +} + +bool ElemOpEmitter::emitIncDec() { + 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()) { + // [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, ShouldInstrument::Yes)) { + // [stack] N? N+1 + return false; + } + if (isPostIncDec()) { + 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..d6f21315ee --- /dev/null +++ b/js/src/frontend/ElemOpEmitter.h @@ -0,0 +1,279 @@ +/* -*- 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" +#include "frontend/Token.h" + +namespace js { +namespace frontend { + +struct BytecodeEmitter; + +// 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, + Set, + Delete, + PostIncrement, + PreIncrement, + PostDecrement, + PreDecrement, + SimpleAssignment, + PropInit, + CompoundAssignment + }; + enum class ObjKind { Super, Other }; + + private: + BytecodeEmitter* bce_; + + Kind kind_; + ObjKind objKind_; + NameVisibility visibility_ = NameVisibility::Public; + +#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, + NameVisibility visibility); + + private: + MOZ_MUST_USE bool isCall() const { return kind_ == Kind::Call; } + + MOZ_MUST_USE bool isSimpleAssignment() const { + return kind_ == Kind::SimpleAssignment; + } + + bool isPrivate() { return visibility_ == NameVisibility::Private; } + + MOZ_MUST_USE bool isPropInit() const { return kind_ == Kind::PropInit; } + + MOZ_MUST_USE bool isPrivateGet() const { + return visibility_ == NameVisibility::Private && kind_ == Kind::Get; + } + + MOZ_MUST_USE bool isDelete() const { return kind_ == Kind::Delete; } + + MOZ_MUST_USE bool isCompoundAssignment() const { + return kind_ == Kind::CompoundAssignment; + } + + MOZ_MUST_USE bool isIncDec() const { return isPostIncDec() || isPreIncDec(); } + + MOZ_MUST_USE bool isPostIncDec() const { + return kind_ == Kind::PostIncrement || kind_ == Kind::PostDecrement; + } + + MOZ_MUST_USE bool isPreIncDec() const { + return kind_ == Kind::PreIncrement || kind_ == Kind::PreDecrement; + } + + MOZ_MUST_USE bool isInc() const { + return kind_ == Kind::PostIncrement || kind_ == Kind::PreIncrement; + } + + MOZ_MUST_USE bool isSuper() const { return objKind_ == ObjKind::Super; } + + public: + MOZ_MUST_USE bool prepareForObj(); + MOZ_MUST_USE bool prepareForKey(); + + MOZ_MUST_USE bool emitGet(); + + MOZ_MUST_USE bool prepareForRhs(); + MOZ_MUST_USE bool skipObjAndKeyAndRhs(); + + MOZ_MUST_USE bool emitDelete(); + + MOZ_MUST_USE bool emitAssignment(); + + MOZ_MUST_USE bool emitIncDec(); + + private: + // When we have private names, we may need to emit a CheckPrivateField + // op to potentially throw errors where required. + MOZ_MUST_USE bool emitPrivateGuard(); +}; + +} /* 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..b27bc1cf2a --- /dev/null +++ b/js/src/frontend/EmitterScope.cpp @@ -0,0 +1,1119 @@ +/* -*- 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/GlobalObject.h" + +using namespace js; +using namespace js::frontend; + +using mozilla::DebugOnly; +using mozilla::Maybe; +using mozilla::Nothing; +using mozilla::Some; + +EmitterScope::EmitterScope(BytecodeEmitter* bce) + : Nestable<EmitterScope>(&bce->innermostEmitterScope_), + nameCache_(bce->cx->frontendCollectionPool()), + hasEnvironment_(false), + environmentChainLength_(0), + nextFrameSlot_(0), + scopeIndex_(ScopeNote::NoScopeIndex), + noteIndex_(ScopeNote::NoScopeNoteIndex) {} + +bool EmitterScope::ensureCache(BytecodeEmitter* bce) { + return nameCache_.acquire(bce->cx); +} + +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->stencil.input.enclosingScope) { + hops = bce->stencil.input.enclosingScope->environmentChainLength(); + } 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<uint8_t>(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, const ParserAtom* name, + NameLocation loc) { + NameLocationMap& cache = *nameCache_; + NameLocationMap::AddPtr p = cache.lookupForAdd(name); + MOZ_ASSERT(!p); + if (!cache.add(p, name, loc)) { + ReportOutOfMemory(bce->cx); + return false; + } + return true; +} + +Maybe<NameLocation> EmitterScope::lookupInCache(BytecodeEmitter* bce, + const ParserAtom* 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<ScopeIndex> 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, const ParserAtom* name) { + // '.generator' cannot be accessed by name. + return name != bce->cx->parserNames().dotGenerator; +} + +#ifdef DEBUG +static bool NameIsOnEnvironment(Scope* scope, JSAtom* name) { + for (BindingIter bi(scope); 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. + if (bi.name() == name) { + BindingLocation::Kind kind = bi.location().kind(); + + if (bi.hasArgumentSlot()) { + JSScript* script = scope->as<FunctionScope>().script(); + if (script->functionAllowsParameterRedeclaration()) { + // Check for duplicate positional formal parameters. + for (BindingIter bi2(bi); bi2 && bi2.hasArgumentSlot(); bi2++) { + if (bi2.name() == name) { + 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 + +/* static */ +NameLocation EmitterScope::searchInEnclosingScope(JSAtom* name, Scope* scope, + uint8_t hops) { + MOZ_ASSERT(scope); + // TODO-Stencil + // This needs to be handled properly by snapshotting enclosing scopes. + + for (ScopeIter si(scope); si; si++) { + MOZ_ASSERT(NameIsOnEnvironment(si.scope(), name)); + + bool hasEnv = si.hasSyntacticEnvironment(); + + switch (si.kind()) { + case ScopeKind::Function: + if (hasEnv) { + JSScript* script = si.scope()->as<FunctionScope>().script(); + if (script->funHasExtensibleScope()) { + return NameLocation::Dynamic(); + } + + for (BindingIter bi(si.scope()); bi; bi++) { + if (bi.name() != name) { + continue; + } + + BindingLocation bindLoc = bi.location(); + if (bi.hasArgumentSlot() && + script->functionAllowsParameterRedeclaration()) { + // Check for duplicate positional formal parameters. + for (BindingIter bi2(bi); bi2 && bi2.hasArgumentSlot(); bi2++) { + if (bi2.name() == name) { + bindLoc = bi2.location(); + } + } + } + + MOZ_ASSERT(bindLoc.kind() == BindingLocation::Kind::Environment); + return NameLocation::EnvironmentCoordinate(bi.kind(), hops, + bindLoc.slot()); + } + } + break; + + 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) { + for (BindingIter bi(si.scope()); bi; bi++) { + if (bi.name() != name) { + 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); + return NameLocation::EnvironmentCoordinate(bi.kind(), hops, + bindLoc.slot()); + } + } + break; + + case ScopeKind::Module: + if (hasEnv) { + for (BindingIter bi(si.scope()); bi; bi++) { + if (bi.name() != name) { + 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); + return NameLocation::Import(); + } + + MOZ_ASSERT(bindLoc.kind() == BindingLocation::Kind::Environment); + return NameLocation::EnvironmentCoordinate(bi.kind(), hops, + bindLoc.slot()); + } + } + break; + + case ScopeKind::Eval: + case ScopeKind::StrictEval: + // 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 && si.scope()->enclosing()->is<GlobalScope>()) { + 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 (hasEnv) { + MOZ_ASSERT(hops < ENVCOORD_HOPS_LIMIT - 1); + hops++; + } + } + + MOZ_CRASH("Malformed scope chain"); +} + +NameLocation EmitterScope::searchAndCache(BytecodeEmitter* bce, + const ParserAtom* name) { + Maybe<NameLocation> loc; + uint8_t hops = hasEnvironment() ? 1 : 0; + DebugOnly<bool> 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) { + // 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 parser scopes are snapshotted, and + // `searchInEnclosingScope` changes to accepting a `const ParserAtom*` + // instead of a `JSAtom*`. + // + // See bug 1660275. + AutoEnterOOMUnsafeRegion oomUnsafe; + JSAtom* jsname = name->toJSAtom(bce->cx, bce->stencil.input.atomCache); + if (!jsname) { + oomUnsafe.crash("EmitterScope::searchAndCache"); + } + + inCurrentScript = false; + loc = Some(searchInEnclosingScope(jsname, bce->stencil.input.enclosingScope, + 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->cx->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); + + Scope* scope = &bce->cx->global()->emptyGlobalScope(); + hasEnvironment_ = scope->hasEnvironment(); + + bce->bodyScopeIndex = + GCThingIndex(bce->perScriptData().gcThingList().length()); + return bce->perScriptData().gcThingList().appendEmptyGlobalScope( + &scopeIndex_); +} + +template <typename ScopeCreator> +bool EmitterScope::internScopeCreationData(BytecodeEmitter* bce, + ScopeCreator createScope) { + ScopeIndex index; + if (!createScope(bce->cx, enclosingScopeIndex(bce), &index)) { + return false; + } + ScopeStencil& scope = bce->compilationState.scopeData[index.index]; + hasEnvironment_ = scope.hasEnvironment(); + return bce->perScriptData().gcThingList().append(index, &scopeIndex_); +} + +template <typename ScopeCreator> +bool EmitterScope::internBodyScopeCreationData(BytecodeEmitter* bce, + ScopeCreator createScope) { + MOZ_ASSERT(bce->bodyScopeIndex == ScopeNote::NoScopeIndex, + "There can be only one body scope"); + bce->bodyScopeIndex = + GCThingIndex(bce->perScriptData().gcThingList().length()); + return internScopeCreationData(bce, createScope); +} + +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(); + + UniqueChars bytes = ParserAtomToPrintableString(bce->cx, r.front().key()); + if (!bytes) { + 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::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 = NameLocation::fromBinding(bi.kind(), bi.location()); + if (!putNameInCache( + bce, bce->compilationState.getParserAtomAt(bce->cx, bi.name()), + loc)) { + return false; + } + + if (!tdzCache->noteTDZCheck( + bce, bce->compilationState.getParserAtomAt(bce->cx, bi.name()), + CheckTDZ)) { + return false; + } + } + + updateFrameFixedSlots(bce, bi); + + auto createScope = [kind, bindings, firstFrameSlot, bce]( + JSContext* cx, mozilla::Maybe<ScopeIndex> enclosing, + ScopeIndex* index) { + return ScopeStencil::createForLexicalScope( + cx, bce->stencil, bce->compilationState, kind, bindings, firstFrameSlot, + enclosing, index); + }; + if (!internScopeCreationData(bce, createScope)) { + 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::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 = NameLocation::fromBinding(bi.kind(), bi.location()); + if (!putNameInCache(bce, + bce->compilationState.getParserAtomAt(bce->cx, 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; + + auto createScope = [funbox, scopeKind, bce]( + JSContext* cx, mozilla::Maybe<ScopeIndex> enclosing, + ScopeIndex* index) { + return ScopeStencil::createForLexicalScope( + cx, bce->stencil, bce->compilationState, scopeKind, + funbox->namedLambdaBindings(), LOCALNO_LIMIT, enclosing, index); + }; + if (!internScopeCreationData(bce, createScope)) { + 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 = NameLocation::fromBinding(bi.kind(), bi.location()); + NameLocationMap::AddPtr p = cache.lookupForAdd( + bce->compilationState.getParserAtomAt(bce->cx, 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, + bce->compilationState.getParserAtomAt(bce->cx, bi.name()), + loc)) { + ReportOutOfMemory(bce->cx); + 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()); + } + + // 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 = NameLocation::fromBinding(bi.kind(), bi.location()); + if (loc.kind() == NameLocation::Kind::FrameSlot) { + MOZ_ASSERT(paramFrameSlotEnd <= loc.frameSlot()); + paramFrameSlotEnd = loc.frameSlot() + 1; + } + } + + if (!deadZoneFrameSlotRange(bce, 0, paramFrameSlotEnd)) { + return false; + } + } + + auto createScope = [funbox, bce](JSContext* cx, + mozilla::Maybe<ScopeIndex> enclosing, + ScopeIndex* index) { + return ScopeStencil::createForFunctionScope( + cx, bce->stencil, bce->compilationState, + funbox->functionScopeBindings(), funbox->hasParameterExprs, + funbox->needsCallObjectRegardlessOfBindings(), funbox->index(), + funbox->isArrow(), enclosing, index); + }; + if (!internBodyScopeCreationData(bce, createScope)) { + 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 = NameLocation::fromBinding(bi.kind(), bi.location()); + MOZ_ASSERT(bi.kind() == BindingKind::Var); + if (!putNameInCache( + bce, bce->compilationState.getParserAtomAt(bce->cx, 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. + auto createScope = [funbox, firstFrameSlot, bce]( + JSContext* cx, mozilla::Maybe<ScopeIndex> enclosing, + ScopeIndex* index) { + return ScopeStencil::createForVarScope( + cx, bce->stencil, bce->compilationState, ScopeKind::FunctionBodyVar, + funbox->extraVarScopeBindings(), firstFrameSlot, + funbox->needsExtraBodyVarEnvironmentRegardlessOfBindings(), enclosing, + index); + }; + if (!internScopeCreationData(bce, createScope)) { + 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); + } + + auto createScope = [globalsc, bce](JSContext* cx, + mozilla::Maybe<ScopeIndex> enclosing, + ScopeIndex* index) { + MOZ_ASSERT(enclosing.isNothing()); + return ScopeStencil::createForGlobalScope( + cx, bce->stencil, bce->compilationState, globalsc->scopeKind(), + globalsc->bindings, index); + }; + if (!internBodyScopeCreationData(bce, createScope)) { + 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 = NameLocation::fromBinding(bi.kind(), bi.location()); + const ParserAtom* name = + bce->compilationState.getParserAtomAt(bce->cx, bi.name()); + if (!putNameInCache(bce, 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; + } + + // For simplicity, treat all free name lookups in eval scripts as dynamic. + fallbackFreeNameLocation_ = Some(NameLocation::Dynamic()); + + // Create the `var` scope. Note that there is also a lexical scope, created + // separately in emitScript(). + auto createScope = [evalsc, bce](JSContext* cx, + mozilla::Maybe<ScopeIndex> enclosing, + ScopeIndex* index) { + ScopeKind scopeKind = + evalsc->strict() ? ScopeKind::StrictEval : ScopeKind::Eval; + return ScopeStencil::createForEvalScope(cx, bce->stencil, + bce->compilationState, scopeKind, + evalsc->bindings, enclosing, index); + }; + if (!internBodyScopeCreationData(bce, createScope)) { + return false; + } + + 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<GlobalScope>()) { + fallbackFreeNameLocation_ = Some(NameLocation::Global(BindingKind::Var)); + } + } + + return true; +} + +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<uint32_t> firstLexicalFrameSlot; + if (ModuleScope::ParserData* bindings = modulesc->bindings) { + ParserBindingIter bi(*bindings); + for (; bi; bi++) { + if (!checkSlotLimits(bce, bi)) { + return false; + } + + NameLocation loc = NameLocation::fromBinding(bi.kind(), bi.location()); + if (!putNameInCache( + bce, bce->compilationState.getParserAtomAt(bce->cx, bi.name()), + loc)) { + return false; + } + + if (BindingKindIsLexical(bi.kind())) { + if (loc.kind() == NameLocation::Kind::FrameSlot && + !firstLexicalFrameSlot) { + firstLexicalFrameSlot = Some(loc.frameSlot()); + } + + if (!tdzCache->noteTDZCheck( + bce, bce->compilationState.getParserAtomAt(bce->cx, 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. + auto createScope = [modulesc, bce](JSContext* cx, + mozilla::Maybe<ScopeIndex> enclosing, + ScopeIndex* index) { + return ScopeStencil::createForModuleScope( + cx, bce->stencil, bce->compilationState, modulesc->bindings, enclosing, + index); + }; + if (!internBodyScopeCreationData(bce, createScope)) { + 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()); + + auto createScope = [bce](JSContext* cx, mozilla::Maybe<ScopeIndex> enclosing, + ScopeIndex* index) { + return ScopeStencil::createForWithScope( + cx, bce->stencil, bce->compilationState, enclosing, index); + }; + if (!internScopeCreationData(bce, createScope)) { + 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<ScopeIndex> EmitterScope::scopeIndex( + const BytecodeEmitter* bce) const { + return bce->perScriptData().gcThingList().getScopeIndex(index()); +} + +NameLocation EmitterScope::lookup(BytecodeEmitter* bce, + const ParserAtom* name) { + if (Maybe<NameLocation> loc = lookupInCache(bce, name)) { + return *loc; + } + return searchAndCache(bce, name); +} + +Maybe<NameLocation> EmitterScope::locationBoundInScope(const ParserAtom* 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<NameLocation> 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..fe726cd0e7 --- /dev/null +++ b/js/src/frontend/EmitterScope.h @@ -0,0 +1,171 @@ +/* -*- 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/Attributes.h" +#include "mozilla/Maybe.h" + +#include <stdint.h> + +#include "ds/Nestable.h" +#include "frontend/AbstractScopePtr.h" +#include "frontend/NameAnalysisTypes.h" +#include "frontend/NameCollections.h" +#include "frontend/ParseContext.h" +#include "frontend/SharedContext.h" +#include "js/TypeDecls.h" +#include "vm/BytecodeUtil.h" // JSOp +#include "vm/SharedStencil.h" // GCThingIndex + +namespace js { + +class Scope; + +namespace frontend { + +struct BytecodeEmitter; + +// A scope that introduces bindings. +class EmitterScope : public Nestable<EmitterScope> { + // 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<NameLocationMap> nameCache_; + + // If this scope's cache does not include free names, such as the + // global scope, the NameLocation to return. + mozilla::Maybe<NameLocation> 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_; + + MOZ_MUST_USE bool ensureCache(BytecodeEmitter* bce); + + MOZ_MUST_USE bool checkSlotLimits(BytecodeEmitter* bce, + const ParserBindingIter& bi); + + MOZ_MUST_USE bool checkEnvironmentChainLength(BytecodeEmitter* bce); + + void updateFrameFixedSlots(BytecodeEmitter* bce, const ParserBindingIter& bi); + + MOZ_MUST_USE bool putNameInCache(BytecodeEmitter* bce, const ParserAtom* name, + NameLocation loc); + + mozilla::Maybe<NameLocation> lookupInCache(BytecodeEmitter* bce, + const ParserAtom* name); + + EmitterScope* enclosing(BytecodeEmitter** bce) const; + + mozilla::Maybe<ScopeIndex> enclosingScopeIndex(BytecodeEmitter* bce) const; + + static bool nameCanBeFree(BytecodeEmitter* bce, const ParserAtom* name); + + static NameLocation searchInEnclosingScope(JSAtom* name, Scope* scope, + uint8_t hops); + NameLocation searchAndCache(BytecodeEmitter* bce, const ParserAtom* name); + + MOZ_MUST_USE bool internEmptyGlobalScopeAsBody(BytecodeEmitter* bce); + + template <typename ScopeCreator> + MOZ_MUST_USE bool internScopeCreationData(BytecodeEmitter* bce, + ScopeCreator createScope); + + template <typename ScopeCreator> + MOZ_MUST_USE bool internBodyScopeCreationData(BytecodeEmitter* bce, + ScopeCreator createScope); + MOZ_MUST_USE bool appendScopeNote(BytecodeEmitter* bce); + + MOZ_MUST_USE bool clearFrameSlotRange(BytecodeEmitter* bce, JSOp opcode, + uint32_t slotStart, + uint32_t slotEnd) const; + + MOZ_MUST_USE 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); + + MOZ_MUST_USE bool enterLexical(BytecodeEmitter* bce, ScopeKind kind, + LexicalScope::ParserData* bindings); + MOZ_MUST_USE bool enterNamedLambda(BytecodeEmitter* bce, FunctionBox* funbox); + MOZ_MUST_USE bool enterFunction(BytecodeEmitter* bce, FunctionBox* funbox); + MOZ_MUST_USE bool enterFunctionExtraBodyVar(BytecodeEmitter* bce, + FunctionBox* funbox); + MOZ_MUST_USE bool enterGlobal(BytecodeEmitter* bce, + GlobalSharedContext* globalsc); + MOZ_MUST_USE bool enterEval(BytecodeEmitter* bce, EvalSharedContext* evalsc); + MOZ_MUST_USE bool enterModule(BytecodeEmitter* module, + ModuleSharedContext* modulesc); + MOZ_MUST_USE bool enterWith(BytecodeEmitter* bce); + MOZ_MUST_USE bool deadZoneFrameSlots(BytecodeEmitter* bce) const; + + MOZ_MUST_USE 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> 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<EmitterScope>::enclosing(); + } + + NameLocation lookup(BytecodeEmitter* bce, const ParserAtom* name); + + mozilla::Maybe<NameLocation> locationBoundInScope(const ParserAtom* name, + EmitterScope* target); +}; + +} /* 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..879095d24f --- /dev/null +++ b/js/src/frontend/ErrorReporter.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_ErrorReporter_h +#define frontend_ErrorReporter_h + +#include "mozilla/Variant.h" + +#include <stdarg.h> // for va_list +#include <stddef.h> // for size_t +#include <stdint.h> // for uint32_t + +#include "js/CompileOptions.h" +#include "js/UniquePtr.h" +#include "vm/ErrorReporting.h" // ErrorMetadata, ReportCompile{Error,Warning} + +class JSErrorNotes; + +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 JSContext* getContext() const = 0; + + // A variant class for the offset of the error or warning. + struct Current {}; + struct NoOffset {}; + using ErrorOffset = mozilla::Variant<uint32_t, Current, NoOffset>; + + // 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 + virtual MOZ_MUST_USE bool computeErrorMetadata(ErrorMetadata* err, + const ErrorOffset& offset) = 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, ...) { + va_list args; + va_start(args, errorNumber); + + errorWithNotesAtVA(nullptr, mozilla::AsVariant(Current()), errorNumber, + &args); + + va_end(args); + } + void errorWithNotes(UniquePtr<JSErrorNotes> notes, unsigned errorNumber, + ...) { + 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, ...) { + va_list args; + va_start(args, errorNumber); + + errorWithNotesAtVA(nullptr, mozilla::AsVariant(offset), errorNumber, &args); + + va_end(args); + } + void errorWithNotesAt(UniquePtr<JSErrorNotes> notes, uint32_t offset, + unsigned errorNumber, ...) { + va_list args; + va_start(args, errorNumber); + + errorWithNotesAtVA(std::move(notes), mozilla::AsVariant(offset), + errorNumber, &args); + + va_end(args); + } + void errorNoOffset(unsigned errorNumber, ...) { + va_list args; + va_start(args, errorNumber); + + errorWithNotesAtVA(nullptr, mozilla::AsVariant(NoOffset()), errorNumber, + &args); + + va_end(args); + } + void errorWithNotesNoOffset(UniquePtr<JSErrorNotes> notes, + unsigned errorNumber, ...) { + va_list args; + va_start(args, errorNumber); + + errorWithNotesAtVA(std::move(notes), mozilla::AsVariant(NoOffset()), + errorNumber, &args); + + va_end(args); + } + void errorWithNotesAtVA(UniquePtr<JSErrorNotes> notes, + const ErrorOffset& offset, unsigned errorNumber, + va_list* args) { + 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. + + MOZ_MUST_USE bool warning(unsigned errorNumber, ...) { + va_list args; + va_start(args, errorNumber); + + bool result = warningWithNotesAtVA(nullptr, mozilla::AsVariant(Current()), + errorNumber, &args); + + va_end(args); + + return result; + } + MOZ_MUST_USE bool warningAt(uint32_t offset, unsigned errorNumber, ...) { + va_list args; + va_start(args, errorNumber); + + bool result = warningWithNotesAtVA(nullptr, mozilla::AsVariant(offset), + errorNumber, &args); + + va_end(args); + + return result; + } + MOZ_MUST_USE bool warningNoOffset(unsigned errorNumber, ...) { + va_list args; + va_start(args, errorNumber); + + bool result = warningWithNotesAtVA(nullptr, mozilla::AsVariant(NoOffset()), + errorNumber, &args); + + va_end(args); + + return result; + } + MOZ_MUST_USE bool warningWithNotesAtVA(UniquePtr<JSErrorNotes> notes, + const ErrorOffset& offset, + unsigned errorNumber, va_list* args) { + 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. + + MOZ_MUST_USE bool strictModeError(unsigned errorNumber, ...) { + va_list args; + va_start(args, errorNumber); + + bool result = strictModeErrorWithNotesAtVA( + nullptr, mozilla::AsVariant(Current()), errorNumber, &args); + + va_end(args); + + return result; + } + MOZ_MUST_USE bool strictModeErrorWithNotes(UniquePtr<JSErrorNotes> notes, + unsigned errorNumber, ...) { + va_list args; + va_start(args, errorNumber); + + bool result = strictModeErrorWithNotesAtVA( + std::move(notes), mozilla::AsVariant(Current()), errorNumber, &args); + + va_end(args); + + return result; + } + MOZ_MUST_USE bool strictModeErrorAt(uint32_t offset, unsigned errorNumber, + ...) { + va_list args; + va_start(args, errorNumber); + + bool result = strictModeErrorWithNotesAtVA( + nullptr, mozilla::AsVariant(offset), errorNumber, &args); + + va_end(args); + + return result; + } + MOZ_MUST_USE bool strictModeErrorWithNotesAt(UniquePtr<JSErrorNotes> notes, + uint32_t offset, + unsigned errorNumber, ...) { + va_list args; + va_start(args, errorNumber); + + bool result = strictModeErrorWithNotesAtVA( + std::move(notes), mozilla::AsVariant(offset), errorNumber, &args); + + va_end(args); + + return result; + } + MOZ_MUST_USE bool strictModeErrorNoOffset(unsigned errorNumber, ...) { + va_list args; + va_start(args, errorNumber); + + bool result = strictModeErrorWithNotesAtVA( + nullptr, mozilla::AsVariant(NoOffset()), errorNumber, &args); + + va_end(args); + + return result; + } + MOZ_MUST_USE bool strictModeErrorWithNotesNoOffset( + UniquePtr<JSErrorNotes> notes, unsigned errorNumber, ...) { + va_list args; + va_start(args, errorNumber); + + bool result = strictModeErrorWithNotesAtVA( + std::move(notes), mozilla::AsVariant(NoOffset()), errorNumber, &args); + + va_end(args); + + return result; + } + MOZ_MUST_USE bool strictModeErrorWithNotesAtVA(UniquePtr<JSErrorNotes> notes, + const ErrorOffset& offset, + unsigned errorNumber, + va_list* args) { + 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. + MOZ_MUST_USE bool compileWarning(ErrorMetadata&& metadata, + UniquePtr<JSErrorNotes> notes, + unsigned errorNumber, va_list* args) { + 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: + // Returns the line and column numbers for given offset. + virtual void lineAndColumnAt(size_t offset, uint32_t* line, + uint32_t* column) const = 0; + + // Returns the line and column numbers for current offset. + virtual void currentLineAndColumn(uint32_t* line, uint32_t* column) const = 0; + + // 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; + + // Returns true if tokenization is already started and hasn't yet finished. + // currentLineAndColumn returns meaningful value only if this is true. + virtual bool hasTokenizationStarted() const = 0; + + // Returns the filename which is currently being compiled. + virtual const char* getFilename() 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..1c3ba12a02 --- /dev/null +++ b/js/src/frontend/ExpressionStatementEmitter.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/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( + const Maybe<uint32_t>& beginPos) { + MOZ_ASSERT(state_ == State::Start); + + if (beginPos) { + 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..8b89e41cc0 --- /dev/null +++ b/js/src/frontend/ExpressionStatementEmitter.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_ExpressionStatementEmitter_h +#define frontend_ExpressionStatementEmitter_h + +#include "mozilla/Attributes.h" +#include "mozilla/Maybe.h" + +#include <stdint.h> + +#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(Some(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 + // + // Can be Nothing() if not available. + MOZ_MUST_USE bool prepareForExpr(const mozilla::Maybe<uint32_t>& beginPos); + MOZ_MUST_USE 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..f39e3ac6c3 --- /dev/null +++ b/js/src/frontend/FoldConstants.cpp @@ -0,0 +1,1571 @@ +/* -*- 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/Range.h" + +#include "jslibmath.h" +#include "jsmath.h" +#include "jsnum.h" + +#include "frontend/ParseNode.h" +#include "frontend/ParseNodeVisitor.h" +#include "frontend/Parser.h" +#include "js/Conversions.h" +#include "js/friend/StackLimits.h" // js::CheckRecursionLimit +#include "js/Vector.h" +#include "vm/StringType.h" + +using namespace js; +using namespace js::frontend; + +using JS::GenericNaN; +using JS::ToInt32; +using JS::ToUint32; +using mozilla::IsNaN; +using mozilla::IsNegative; +using mozilla::NegativeInfinity; +using mozilla::PositiveInfinity; + +struct FoldInfo { + JSContext* cx; + 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. +inline MOZ_MUST_USE 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(JSContext* cx, ParseNode* node, + bool* result); + +static bool ListContainsHoistedDeclaration(JSContext* cx, ListNode* list, + bool* result) { + for (ParseNode* node : list->contents()) { + if (!ContainsHoistedDeclaration(cx, 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(JSContext* cx, ParseNode* node, + bool* result) { + if (!CheckRecursionLimit(cx)) { + 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<ListNode>()); + *result = false; + return true; + + // Similarly to the lexical declarations above, classes cannot add hoisted + // declarations + case ParseNodeKind::ClassDecl: + MOZ_ASSERT(node->is<ClassNode>()); + *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<NullaryNode>()); + *result = false; + return true; + + case ParseNodeKind::DebuggerStmt: + MOZ_ASSERT(node->is<DebuggerStatement>()); + *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<UnaryNode>()); + *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<UnaryNode>()); + *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::ExportFromStmt: + case ParseNodeKind::ExportDefaultStmt: + case ParseNodeKind::ExportSpecList: + case ParseNodeKind::ExportSpec: + case ParseNodeKind::ExportStmt: + case ParseNodeKind::ExportBatchSpecStmt: + case ParseNodeKind::CallImportExpr: + *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(cx, node->as<BinaryNode>().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(cx, node->as<BinaryNode>().right(), + result); + + case ParseNodeKind::LabelStmt: + return ContainsHoistedDeclaration( + cx, node->as<LabeledStatement>().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<TernaryNode>(); + ParseNode* consequent = ifNode->kid2(); + if (!ContainsHoistedDeclaration(cx, 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<TernaryNode>(); + + MOZ_ASSERT(tryNode->kid2() || tryNode->kid3(), + "must have either catch or finally"); + + ParseNode* tryBlock = tryNode->kid1(); + if (!ContainsHoistedDeclaration(cx, tryBlock, result)) { + return false; + } + if (*result) { + return true; + } + + if (ParseNode* catchScope = tryNode->kid2()) { + BinaryNode* catchNode = + &catchScope->as<LexicalScopeNode>().scopeBody()->as<BinaryNode>(); + MOZ_ASSERT(catchNode->isKind(ParseNodeKind::Catch)); + + ParseNode* catchStatements = catchNode->right(); + if (!ContainsHoistedDeclaration(cx, catchStatements, result)) { + return false; + } + if (*result) { + return true; + } + } + + if (ParseNode* finallyBlock = tryNode->kid3()) { + return ContainsHoistedDeclaration(cx, 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<SwitchStatement>(); + return ContainsHoistedDeclaration(cx, &switchNode->lexicalForCaseList(), + result); + } + + case ParseNodeKind::Case: { + CaseClause* caseClause = &node->as<CaseClause>(); + return ContainsHoistedDeclaration(cx, caseClause->statementList(), + result); + } + + case ParseNodeKind::ForStmt: { + ForNode* forNode = &node->as<ForNode>(); + 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(cx, loopBody, result); + } + + case ParseNodeKind::LexicalScope: { + LexicalScopeNode* scope = &node->as<LexicalScopeNode>(); + ParseNode* expr = scope->scopeBody(); + + if (expr->isKind(ParseNodeKind::ForStmt) || expr->is<FunctionNode>()) { + return ContainsHoistedDeclaration(cx, expr, result); + } + + MOZ_ASSERT(expr->isKind(ParseNodeKind::StatementList)); + return ListContainsHoistedDeclaration( + cx, &scope->scopeBody()->as<ListNode>(), result); + } + + // List nodes with all non-null children. + case ParseNodeKind::StatementList: + return ListContainsHoistedDeclaration(cx, &node->as<ListNode>(), 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::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::OptionalChain: + case ParseNodeKind::OptionalDotExpr: + case ParseNodeKind::OptionalElemExpr: + case ParseNodeKind::OptionalCallExpr: + 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::ClassMethod: + case ParseNodeKind::ClassField: + case ParseNodeKind::ClassMemberList: + case ParseNodeKind::ClassNames: + case ParseNodeKind::NewTargetExpr: + case ParseNodeKind::ImportMetaExpr: + case ParseNodeKind::PosHolder: + case ParseNodeKind::SuperCallExpr: + case ParseNodeKind::SuperBase: + case ParseNodeKind::SetThis: + MOZ_CRASH( + "ContainsHoistedDeclaration should have indicated false on " + "some parent node without recurring to test this node"); + + case ParseNodeKind::PipelineExpr: + MOZ_ASSERT(node->is<ListNode>()); + *result = false; + return true; + + case ParseNodeKind::LastUnused: + case ParseNodeKind::Limit: + MOZ_CRASH("unexpected sentinel ParseNodeKind in node"); + } + + 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)) { + double d; + if (!pn->as<NameNode>().atom()->toNumber(info.cx, &d)) { + return false; + } + if (!TryReplaceNode( + pnp, info.handler->newNumber(d, NoDecimal, pn->pn_pos))) { + return false; + } + } + break; + + case ParseNodeKind::StringExpr: + if (pn->isKind(ParseNodeKind::NumberExpr)) { + const ParserAtom* atom = + pn->as<NumericLiteral>().toAtom(info.cx, 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<NumericLiteral>().value() != 0 && + !IsNaN(pn->as<NumericLiteral>().value())) + ? Truthy + : Falsy; + + case ParseNodeKind::BigIntExpr: + return (pn->as<BigIntLiteral>().isZero()) ? Falsy : Truthy; + + case ParseNodeKind::StringExpr: + case ParseNodeKind::TemplateStringExpr: + return (pn->as<NameNode>().atom()->length() > 0) ? Truthy : Falsy; + + case ParseNodeKind::TrueExpr: + case ParseNodeKind::Function: + return Truthy; + + case ParseNodeKind::FalseExpr: + case ParseNodeKind::NullExpr: + case ParseNodeKind::RawUndefinedExpr: + return Falsy; + + case ParseNodeKind::VoidExpr: { + // |void <foo>| 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<UnaryNode>().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<UnaryNode>(); + MOZ_ASSERT(node->isKind(ParseNodeKind::TypeOfExpr)); + ParseNode* expr = node->kid(); + + // Constant-fold the entire |typeof| if given a constant with known type. + const ParserName* result = nullptr; + if (expr->isKind(ParseNodeKind::StringExpr) || + expr->isKind(ParseNodeKind::TemplateStringExpr)) { + result = info.cx->parserNames().string; + } else if (expr->isKind(ParseNodeKind::NumberExpr)) { + result = info.cx->parserNames().number; + } else if (expr->isKind(ParseNodeKind::BigIntExpr)) { + result = info.cx->parserNames().bigint; + } else if (expr->isKind(ParseNodeKind::NullExpr)) { + result = info.cx->parserNames().object; + } else if (expr->isKind(ParseNodeKind::TrueExpr) || + expr->isKind(ParseNodeKind::FalseExpr)) { + result = info.cx->parserNames().boolean; + } else if (expr->is<FunctionNode>()) { + result = info.cx->parserNames().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<UnaryNode>(); + + 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<UnaryNode>(); + 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<UnaryNode>(); + 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<UnaryNode>(); + 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<NumericLiteral>().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<ListNode>(); + + 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<TernaryNode>(); + 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<TernaryNode>()); + 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<TernaryNode>(); + 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<TernaryNode>()); + 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.cx, 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<ListNode>(); + 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<NumericLiteral>().value(), + (*next)->as<NumericLiteral>().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<ListNode>(); + 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<NumericLiteral>().value(); + double d2 = exponent->as<NumericLiteral>().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<PropertyByValue>(); + + ParseNode* expr = &elem->expression(); + ParseNode* key = &elem->key(); + const ParserName* name = nullptr; + if (key->isKind(ParseNodeKind::StringExpr)) { + const ParserAtom* atom = key->as<NameNode>().atom(); + uint32_t index; + + if (atom->isIndex(&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 = atom->asName(); + } + } else if (key->isKind(ParseNodeKind::NumberExpr)) { + auto* numeric = &key->as<NumericLiteral>(); + 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. + const ParserAtom* atom = numeric->toAtom(info.cx, info.parserAtoms); + if (!atom) { + return false; + } + name = atom->asName(); + } + } + + // 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<ListNode>(); + + 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<NumericLiteral>().value(); + double right = (*next)->as<NumericLiteral>().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; + } + + Vector<const ParserAtom*, 8> accum(info.cx); + do { + // Create a vector of all the folded strings and concatenate them. + MOZ_ASSERT((*current)->isKind(ParseNodeKind::StringExpr)); + + accum.clear(); + const ParserAtom* atom = (*current)->as<NameNode>().atom(); + if (!accum.append(atom)) { + return false; + } + + 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; + } + + // Add this string to the accumulator and remove the node. + const ParserAtom* nextAtom = (*next)->as<NameNode>().atom(); + if (!accum.append(nextAtom)) { + 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.length() > 1) { + // Construct the concatenated atom. + const ParserAtom* combination = info.parserAtoms.concatAtoms( + info.cx, mozilla::Range(accum.begin(), accum.length())); + if (!combination) { + return false; + } + + // Replace |current|'s string with the entire combination. + MOZ_ASSERT((*current)->isKind(ParseNodeKind::StringExpr)); + (*current)->as<NameNode>().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<FoldVisitor> { + using Base = RewritingParseNodeVisitor; + + JSContext* cx; + ParserAtomsTable& parserAtoms; + FullParseHandler* handler; + + FoldInfo info() const { return FoldInfo{cx, parserAtoms, handler}; } + + public: + explicit FoldVisitor(JSContext* cx, ParserAtomsTable& parserAtoms, + FullParseHandler* handler) + : RewritingParseNodeVisitor(cx), + cx(cx), + 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<FunctionNode>()) { + if (!visit(*node->unsafeLeftReference())) { + return false; + } + } + + if (!visit(*node->unsafeRightReference())) { + return false; + } + + return true; + } + + public: + bool visitCallExpr(ParseNode*& pn) { + return internalVisitCall(&pn->as<BinaryNode>()); + } + + bool visitOptionalCallExpr(ParseNode*& pn) { + return internalVisitCall(&pn->as<BinaryNode>()); + } + + bool visitNewExpr(ParseNode*& pn) { + return internalVisitCall(&pn->as<BinaryNode>()); + } + + bool visitSuperCallExpr(ParseNode*& pn) { + return internalVisitCall(&pn->as<BinaryNode>()); + } + + bool visitTaggedTemplateExpr(ParseNode*& pn) { + return internalVisitCall(&pn->as<BinaryNode>()); + } + + 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<ForNode>(); + if (stmt.left()->isKind(ParseNodeKind::ForHead)) { + TernaryNode& head = stmt.left()->as<TernaryNode>(); + 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<BinaryNode>(); + return Base::visitWhileStmt(pn) && + SimplifyCondition(info(), node.unsafeLeftReference()); + } + + bool visitDoWhileStmt(ParseNode*& pn) { + BinaryNode& node = pn->as<BinaryNode>(); + return Base::visitDoWhileStmt(pn) && + SimplifyCondition(info(), node.unsafeRightReference()); + } + + bool visitFunction(ParseNode*& pn) { + FunctionNode& node = pn->as<FunctionNode>(); + + // 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<ListNode>(); + // 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<ListNode>(); + if (list->hasNonConstInitializer()) { + for (ParseNode* node : list->contents()) { + if (node->getKind() != ParseNodeKind::PropertyDefinition) { + return true; + } + BinaryNode* binary = &node->as<BinaryNode>(); + if (binary->left()->isKind(ParseNodeKind::ComputedName)) { + return true; + } + if (!binary->right()->isConstant()) { + return true; + } + } + list->unsetHasNonConstInitializer(); + } + return true; + } +}; + +static bool Fold(JSContext* cx, ParserAtomsTable& parserAtoms, + FullParseHandler* handler, ParseNode** pnp) { + FoldVisitor visitor(cx, parserAtoms, handler); + return visitor.visit(*pnp); +} +static bool Fold(FoldInfo info, ParseNode** pnp) { + return Fold(info.cx, info.parserAtoms, info.handler, pnp); +} + +bool frontend::FoldConstants(JSContext* cx, ParserAtomsTable& parserAtoms, + ParseNode** pnp, FullParseHandler* handler) { + AutoTraceLog traceLog(TraceLoggerForCurrentThread(cx), + TraceLogger_BytecodeFoldConstants); + + return Fold(cx, parserAtoms, handler, pnp); +} diff --git a/js/src/frontend/FoldConstants.h b/js/src/frontend/FoldConstants.h new file mode 100644 index 0000000000..382f2196ff --- /dev/null +++ b/js/src/frontend/FoldConstants.h @@ -0,0 +1,49 @@ +/* -*- 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 { +namespace frontend { + +class FullParseHandler; +template <class ParseHandler> +class PerHandlerParser; + +// 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(cx, parserAtoms, &pn, parser)) { +// return false; +// } +extern MOZ_MUST_USE bool FoldConstants(JSContext* cx, + ParserAtomsTable& parserAtoms, + ParseNode** pnp, + FullParseHandler* handler); + +inline MOZ_MUST_USE bool FoldConstants(JSContext* cx, + 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..0381e5ba14 --- /dev/null +++ b/js/src/frontend/ForInEmitter.cpp @@ -0,0 +1,158 @@ +/* -*- 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 "frontend/SourceNotes.h" +#include "vm/Opcodes.h" +#include "vm/Scope.h" +#include "vm/StencilEnums.h" // TryNoteKind + +using namespace js; +using namespace js::frontend; + +using mozilla::Maybe; +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::IfNe, &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_->emit1(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(const Maybe<uint32_t>& forPos) { + MOZ_ASSERT(state_ == State::Body); + + if (forPos) { + // 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..b03dc202f5 --- /dev/null +++ b/js/src/frontend/ForInEmitter.h @@ -0,0 +1,119 @@ +/* -*- 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 <stdint.h> + +#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(Some(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<LoopControl> 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<TDZCheckCache> 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 + // + // Can be Nothing() if not available. + MOZ_MUST_USE bool emitIterated(); + MOZ_MUST_USE bool emitInitialize(); + MOZ_MUST_USE bool emitBody(); + MOZ_MUST_USE bool emitEnd(const mozilla::Maybe<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..4e05c4e3a1 --- /dev/null +++ b/js/src/frontend/ForOfEmitter.cpp @@ -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/. */ + +#include "frontend/ForOfEmitter.h" + +#include "frontend/BytecodeEmitter.h" +#include "frontend/EmitterScope.h" +#include "frontend/IfEmitter.h" +#include "frontend/SourceNotes.h" +#include "vm/Opcodes.h" +#include "vm/Scope.h" +#include "vm/StencilEnums.h" // TryNoteKind + +using namespace js; +using namespace js::frontend; + +using mozilla::Maybe; +using mozilla::Nothing; + +ForOfEmitter::ForOfEmitter(BytecodeEmitter* bce, + const EmitterScope* headLexicalEmitterScope, + bool allowSelfHostedIter, IteratorKind iterKind) + : bce_(bce), + allowSelfHostedIter_(allowSelfHostedIter), + 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(const Maybe<uint32_t>& forPos) { + MOZ_ASSERT(state_ == State::Iterated); + + tdzCacheForIteratedValue_.reset(); + + if (iterKind_ == IteratorKind::Async) { + if (!bce_->emitAsyncIterator()) { + // [stack] NEXT ITER + return false; + } + } else { + if (!bce_->emitIterator()) { + // [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, allowSelfHostedIter_, 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_->emit1(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 (forPos) { + if (!bce_->updateSourceCoordNotes(*forPos)) { + return false; + } + } + + if (!bce_->emit1(JSOp::Dup2)) { + // [stack] NEXT ITER NEXT ITER + return false; + } + + if (!bce_->emitIteratorNext(forPos, iterKind_, allowSelfHostedIter_)) { + // [stack] NEXT ITER RESULT + return false; + } + + if (!bce_->emit1(JSOp::Dup)) { + // [stack] NEXT ITER RESULT RESULT + return false; + } + if (!bce_->emitAtomOp(JSOp::GetProp, bce_->cx->parserNames().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::IfNe, &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, bce_->cx->parserNames().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(const Maybe<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 (iteratedPos) { + 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..9e934d9921 --- /dev/null +++ b/js/src/frontend/ForOfEmitter.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_ForOfEmitter_h +#define frontend_ForOfEmitter_h + +#include "mozilla/Attributes.h" +#include "mozilla/Maybe.h" + +#include <stdint.h> + +#include "frontend/ForOfLoopControl.h" +#include "frontend/IteratorKind.h" +#include "frontend/JumpList.h" +#include "frontend/TDZCheckCache.h" + +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(Some(offset_of_for)); +// emit(init); +// forOf.emitBody(); +// emit(body); +// forOf.emitEnd(Some(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 + + bool allowSelfHostedIter_; + IteratorKind iterKind_; + + mozilla::Maybe<ForOfLoopControl> 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<TDZCheckCache> 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, + bool allowSelfHostedIter, IteratorKind iterKind); + + // The offset in the source code for each character below: + // + // for ( var x of obj ) { ... } + // ^ ^ + // | | + // | iteratedPos + // | + // forPos + // + // Can be Nothing() if not available. + MOZ_MUST_USE bool emitIterated(); + MOZ_MUST_USE bool emitInitialize(const mozilla::Maybe<uint32_t>& forPos); + MOZ_MUST_USE bool emitBody(); + MOZ_MUST_USE bool emitEnd(const mozilla::Maybe<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..d42d8f156e --- /dev/null +++ b/js/src/frontend/ForOfLoopControl.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/ForOfLoopControl.h" + +#include "jsapi.h" // CompletionKind + +#include "frontend/BytecodeEmitter.h" // BytecodeEmitter +#include "frontend/EmitterScope.h" // EmitterScope +#include "frontend/IfEmitter.h" // InternalIfEmitter +#include "vm/JSScript.h" // TryNoteKind::ForOfIterClose +#include "vm/Opcodes.h" // JSOp + +using namespace js; +using namespace js::frontend; + +ForOfLoopControl::ForOfLoopControl(BytecodeEmitter* bce, int32_t iterDepth, + bool allowSelfHosted, IteratorKind iterKind) + : LoopControl(bce, StatementKind::ForOfLoop), + iterDepth_(iterDepth), + numYieldsAtBeginCodeNeedingIterClose_(UINT32_MAX), + allowSelfHosted_(allowSelfHosted), + 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 ITER is undefined, it means the exception is thrown by + // IteratorClose for non-local jump, and we should't perform + // IteratorClose again here. + if (!bce->emit1(JSOp::Undefined)) { + // [stack] ITER ... EXCEPTION ITER UNDEF + return false; + } + if (!bce->emit1(JSOp::StrictNe)) { + // [stack] ITER ... EXCEPTION NE + return false; + } + + InternalIfEmitter ifIteratorIsNotClosed(bce); + if (!ifIteratorIsNotClosed.emitThen()) { + // [stack] ITER ... EXCEPTION + return false; + } + + MOZ_ASSERT(slotFromTop == + unsigned(bce->bytecodeSection().stackDepth() - iterDepth_)); + if (!bce->emitDupAt(slotFromTop)) { + // [stack] ITER ... EXCEPTION ITER + return false; + } + if (!emitIteratorCloseInInnermostScopeWithTryNote(bce, + CompletionKind::Throw)) { + return false; // ITER ... EXCEPTION + } + + if (!ifIteratorIsNotClosed.emitEnd()) { + // [stack] ITER ... EXCEPTION + return false; + } + + 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. + uint32_t numYieldsEmitted = bce->bytecodeSection().numYields(); + if (numYieldsEmitted > numYieldsAtBeginCodeNeedingIterClose_) { + if (!tryCatch_->emitFinally()) { + return false; + } + + InternalIfEmitter ifGeneratorClosing(bce); + 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 (!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, + allowSelfHosted_); +} + +// 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; + } + + // Clear ITER slot on the stack to tell catch block to avoid performing + // IteratorClose again. + if (!bce->emit1(JSOp::Undefined)) { + // [stack] ITER UNDEF + return false; + } + if (!bce->emit1(JSOp::Swap)) { + // [stack] UNDEF ITER + return false; + } + + *tryNoteStart = bce->bytecodeSection().offset(); + if (!emitIteratorCloseInScope(bce, currentScope, CompletionKind::Normal)) { + // [stack] UNDEF + 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] UNDEF UNDEF + return false; + } + if (!bce->emit1(JSOp::Undefined)) { + // [stack] UNDEF 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..dec7737fd6 --- /dev/null +++ b/js/src/frontend/ForOfLoopControl.h @@ -0,0 +1,98 @@ +/* -*- 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/Attributes.h" // MOZ_MUST_USE +#include "mozilla/Maybe.h" // mozilla::Maybe + +#include <stdint.h> // int32_t, uint32_t + +#include "jsapi.h" // CompletionKind + +#include "frontend/BytecodeControlStructures.h" // NestableControl, LoopControl +#include "frontend/BytecodeOffset.h" // BytecodeOffset +#include "frontend/IteratorKind.h" // IteratorKind +#include "frontend/TryEmitter.h" // TryEmitter + +namespace js { +namespace frontend { + +struct BytecodeEmitter; +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 non-iterator code with + // try-catch and call IteratorClose in `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 tell catch block not to perform + // IteratorClose. + // + // for (x of y) { + // // Operations for iterator (IteratorNext etc) are outside of + // // try-block. + // try { + // ... + // if (...) { + // // Before non-local jump, clear iterator on the stack to tell + // // catch block not to perform IteratorClose. + // tmpIterator = iterator; + // iterator = undefined; + // IteratorClose(tmpIterator, { break }); + // break; + // } + // ... + // } catch (e) { + // // Just throw again when iterator is cleared by non-local jump. + // if (iterator === undefined) + // throw e; + // IteratorClose(iterator, { throw, e }); + // } + // } + mozilla::Maybe<TryEmitter> tryCatch_; + + // Used to track if any yields were emitted between calls to to + // emitBeginCodeNeedingIteratorClose and emitEndCodeNeedingIteratorClose. + uint32_t numYieldsAtBeginCodeNeedingIterClose_; + + bool allowSelfHosted_; + + IteratorKind iterKind_; + + public: + ForOfLoopControl(BytecodeEmitter* bce, int32_t iterDepth, + bool allowSelfHosted, IteratorKind iterKind); + + MOZ_MUST_USE bool emitBeginCodeNeedingIteratorClose(BytecodeEmitter* bce); + MOZ_MUST_USE bool emitEndCodeNeedingIteratorClose(BytecodeEmitter* bce); + + MOZ_MUST_USE bool emitIteratorCloseInInnermostScopeWithTryNote( + BytecodeEmitter* bce, + CompletionKind completionKind = CompletionKind::Normal); + MOZ_MUST_USE bool emitIteratorCloseInScope( + BytecodeEmitter* bce, EmitterScope& currentScope, + CompletionKind completionKind = CompletionKind::Normal); + + MOZ_MUST_USE bool emitPrepareForNonLocalJumpFromScope( + BytecodeEmitter* bce, EmitterScope& currentScope, bool isTarget, + BytecodeOffset* tryNoteStart); +}; +template <> +inline bool NestableControl::is<ForOfLoopControl>() 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..c87efb7086 --- /dev/null +++ b/js/src/frontend/Frontend2.cpp @@ -0,0 +1,752 @@ +/* -*- 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, Span} +#include "mozilla/Variant.h" // mozilla::AsVariant + +#include <stddef.h> // size_t +#include <stdint.h> // uint8_t, uint32_t + +#include "jsapi.h" + +#include "frontend/AbstractScopePtr.h" // ScopeIndex +#include "frontend/BytecodeSection.h" // EmitScriptThingsVector +#include "frontend/CompilationInfo.h" // CompilationState, CompilationStencil +#include "frontend/Parser.h" // NewEmptyLexicalScopeData, NewEmptyGlobalScopeData, NewEmptyVarScopeData, NewEmptyFunctionScopeData +#include "frontend/ParserAtom.h" // ParserAtomsTable +#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/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, const SmooshResult& result, + CompilationState& compilationState, + Vector<const ParserAtom*>& 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<const mozilla::Utf8Unit*>( + smoosh_get_atom_at(result, i)); + auto len = smoosh_get_atom_len_at(result, i); + const ParserAtom* atom = + compilationState.parserAtoms.internUtf8(cx, s, len); + if (!atom) { + return false; + } + atom->markUsedByStencil(); + allAtoms.infallibleAppend(atom); + } + + return true; +} + +void CopyBindingNames(JSContext* cx, CVec<SmooshBindingName>& from, + Vector<const ParserAtom*>& 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]->toIndex(), name.is_closed_over, + name.is_top_level_function); + } +} + +void CopyBindingNames(JSContext* cx, CVec<COption<SmooshBindingName>>& from, + Vector<const ParserAtom*>& 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<SmooshBindingName>& maybeName = from.data[i]; + if (maybeName.IsSome()) { + SmooshBindingName& name = maybeName.AsSome(); + new (mozilla::KnownNotNull, &to[i]) + ParserBindingName(allAtoms[name.name]->toIndex(), 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, const SmooshResult& result, + Vector<const ParserAtom*>& allAtoms, + CompilationStencil& stencil, + CompilationState& compilationState) { + LifoAlloc& alloc = stencil.alloc; + + if (result.scopes.len > TaggedScriptThingIndex::IndexLimit) { + ReportAllocationOverflow(cx); + 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(cx, alloc, numBindings); + if (!data) { + return false; + } + + CopyBindingNames(cx, global.bindings, allAtoms, + data->trailingNames.start()); + + data->slotInfo.letStart = global.let_start; + data->slotInfo.constStart = global.const_start; + data->slotInfo.length = numBindings; + + if (!ScopeStencil::createForGlobalScope(cx, stencil, 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(cx, alloc, numBindings); + if (!data) { + return false; + } + + CopyBindingNames(cx, var.bindings, allAtoms, + data->trailingNames.start()); + + // NOTE: data->slotInfo.nextFrameSlot is set in + // ScopeStencil::createForVarScope. + + data->slotInfo.length = numBindings; + + uint32_t firstFrameSlot = var.first_frame_slot; + ScopeIndex enclosingIndex(var.enclosing); + if (!ScopeStencil::createForVarScope( + cx, stencil, 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(cx, alloc, numBindings); + if (!data) { + return false; + } + + CopyBindingNames(cx, lexical.bindings, allAtoms, + data->trailingNames.start()); + + // NOTE: data->slotInfo.nextFrameSlot is set in + // ScopeStencil::createForLexicalScope. + + data->slotInfo.constStart = lexical.const_start; + data->slotInfo.length = numBindings; + + uint32_t firstFrameSlot = lexical.first_frame_slot; + ScopeIndex enclosingIndex(lexical.enclosing); + if (!ScopeStencil::createForLexicalScope( + cx, stencil, 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(cx, alloc, numBindings); + if (!data) { + return false; + } + + CopyBindingNames(cx, function.bindings, allAtoms, + data->trailingNames.start()); + + // 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->slotInfo.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( + cx, stencil, 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, const SmooshResult& result, + CompilationStencil& stencil, + CompilationState& compilationState) { + auto len = result.regexps.len; + if (len == 0) { + return true; + } + + if (len > TaggedScriptThingIndex::IndexLimit) { + ReportAllocationOverflow(cx); + return false; + } + + auto* p = stencil.alloc.newArrayUninitialized<RegExpStencil>(len); + if (!p) { + js::ReportOutOfMemory(cx); + return false; + } + stencil.regExpData = mozilla::Span(p, len); + + 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<const char16_t> range(pattern.get(), length); + + TokenStreamAnyChars ts(cx, stencil.input.options, + /* smg = */ nullptr); + + // See Parser<FullParseHandler, Unit>::newRegExp. + + LifoAllocScope allocScope(&cx->tempLifoAlloc()); + if (!irregexp::CheckPatternSyntax(cx, ts, range, flags)) { + return false; + } + + const mozilla::Utf8Unit* sUtf8 = + reinterpret_cast<const mozilla::Utf8Unit*>(s); + const ParserAtom* atom = + compilationState.parserAtoms.internUtf8(cx, sUtf8, len); + if (!atom) { + return false; + } + atom->markUsedByStencil(); + + new (mozilla::KnownNotNull, &stencil.regExpData[i]) + RegExpStencil(atom->toIndex(), JS::RegExpFlags(flags)); + } + + return true; +} + +// Convert SmooshImmutableScriptData into ImmutableScriptData. +UniquePtr<ImmutableScriptData> ConvertImmutableScriptData( + JSContext* cx, const SmooshImmutableScriptData& smooshScriptData, + bool isFunction) { + Vector<ScopeNote, 0, SystemAllocPolicy> 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; + } + + return ImmutableScriptData::new_( + cx, smooshScriptData.main_offset, smooshScriptData.nfixed, + smooshScriptData.nslots, GCThingIndex(smooshScriptData.body_scope_index), + smooshScriptData.num_ic_entries, isFunction, smooshScriptData.fun_length, + mozilla::Span(smooshScriptData.bytecode.data, + smooshScriptData.bytecode.len), + mozilla::Span<const SrcNote>(), mozilla::Span<const uint32_t>(), + scopeNotes, mozilla::Span<const TryNote>()); +} + +// Given the result of SmooshMonkey's parser, convert a list of GC things +// used by a script into ScriptThingsVector. +bool ConvertGCThings(JSContext* cx, const SmooshResult& result, + const SmooshScriptStencil& smooshScript, + CompilationStencil& stencil, + CompilationState& compilationState, + Vector<const ParserAtom*>& 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(cx, 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()]->toIndex()); + 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, const SmooshResult& result, + const SmooshScriptStencil& smooshScript, + Vector<const ParserAtom*>& allAtoms, + CompilationStencil& stencil, + CompilationState& compilationState, + ScriptIndex scriptIndex) { + using ImmutableFlags = js::ImmutableScriptFlagsEnum; + + const JS::ReadOnlyCompileOptions& options = stencil.input.options; + + ScriptStencil& script = stencil.scriptData[scriptIndex]; + ScriptStencilExtra& scriptExtra = stencil.scriptExtra[scriptIndex]; + + scriptExtra.immutableFlags = 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( + cx, std::move(immutableScriptData)); + if (!sharedData) { + return false; + } + + if (!stencil.sharedData.addAndShare(cx, 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()]->toIndex(); + } + 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.setWasFunctionEmitted(); + } + } + + if (!ConvertGCThings(cx, result, smooshScript, stencil, 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, ErrorMetadata&& metadata, + int errorNumber, ...) { + va_list args; + va_start(args, errorNumber); + ReportCompileErrorUTF8(cx, std::move(metadata), /* notes = */ nullptr, + errorNumber, &args); + va_end(args); +} + +/* static */ +bool Smoosh::compileGlobalScriptToStencil(JSContext* cx, + CompilationStencil& stencil, + JS::SourceText<Utf8Unit>& srcBuf, + bool* unimplemented) { + // FIXME: check info members and return with *unimplemented = true + // if any field doesn't match to smoosh_run. + + auto bytes = reinterpret_cast<const uint8_t*>(srcBuf.get()); + size_t length = srcBuf.length(); + + const auto& options = stencil.input.options; + SmooshCompileOptions compileOptions; + compileOptions.no_script_rval = options.noScriptRval; + + SmooshResult result = smoosh_run(bytes, length, &compileOptions); + AutoFreeSmooshResult afsr(&result); + + if (result.error.data) { + *unimplemented = false; + ErrorMetadata metadata; + metadata.filename = "<unknown>"; + metadata.lineNumber = 1; + metadata.columnNumber = 0; + metadata.isMuted = false; + ReportSmooshCompileError(cx, std::move(metadata), + JSMSG_SMOOSH_COMPILE_ERROR, + reinterpret_cast<const char*>(result.error.data)); + return false; + } + + if (result.unimplemented) { + *unimplemented = true; + return false; + } + + *unimplemented = false; + + LifoAllocScope allocScope(&cx->tempLifoAlloc()); + + Vector<const ParserAtom*> allAtoms(cx); + CompilationState compilationState(cx, allocScope, stencil.input.options, + stencil); + if (!ConvertAtoms(cx, result, compilationState, allAtoms)) { + return false; + } + + if (!ConvertScopeStencil(cx, result, allAtoms, stencil, compilationState)) { + return false; + } + + if (!ConvertRegExpData(cx, result, stencil, compilationState)) { + return false; + } + + auto len = result.scripts.len; + if (len == 0) { + return true; + } + + if (len > TaggedScriptThingIndex::IndexLimit) { + ReportAllocationOverflow(cx); + return false; + } + + auto* pscript = stencil.alloc.newArrayUninitialized<ScriptStencil>(len); + if (!pscript) { + js::ReportOutOfMemory(cx); + return false; + } + stencil.scriptData = mozilla::Span(pscript, len); + + auto* pextra = stencil.alloc.newArrayUninitialized<ScriptStencilExtra>(len); + if (!pextra) { + js::ReportOutOfMemory(cx); + return false; + } + stencil.scriptExtra = mozilla::Span(pextra, len); + + // 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++; + } + } + + stencil.prepareStorageFor(cx, compilationState); + + for (size_t i = 0; i < len; i++) { + new (mozilla::KnownNotNull, &stencil.scriptData[i]) + BaseCompilationStencil(); + + if (!ConvertScriptStencil(cx, result, result.scripts.data[i], allAtoms, + stencil, compilationState, ScriptIndex(i))) { + return false; + } + } + + if (!compilationState.finish(cx, stencil)) { + return true; + } + + return true; +} + +/* static */ +UniquePtr<CompilationStencil> Smoosh::compileGlobalScriptToStencil( + JSContext* cx, const JS::ReadOnlyCompileOptions& options, + JS::SourceText<Utf8Unit>& srcBuf, bool* unimplemented) { + Rooted<UniquePtr<frontend::CompilationStencil>> stencil( + cx, js_new<frontend::CompilationStencil>(cx, options)); + if (!stencil) { + ReportOutOfMemory(cx); + return nullptr; + } + + if (!stencil.get()->input.initForGlobal(cx)) { + return nullptr; + } + + if (!compileGlobalScriptToStencil(cx, *stencil.get().get(), srcBuf, + unimplemented)) { + return nullptr; + } + + return std::move(stencil.get()); +} + +/* static */ +bool Smoosh::compileGlobalScript(JSContext* cx, CompilationStencil& stencil, + JS::SourceText<Utf8Unit>& srcBuf, + CompilationGCOutput& gcOutput, + bool* unimplemented) { + if (!compileGlobalScriptToStencil(cx, stencil, srcBuf, unimplemented)) { + return false; + } + + if (!CompilationStencil::instantiateStencils(cx, stencil, gcOutput)) { + return false; + } + +#if defined(DEBUG) || defined(JS_JITSPEW) + Sprinter sprinter(cx); + Rooted<JSScript*> script(cx, gcOutput.script); + if (!sprinter.init()) { + return false; + } + if (!Disassemble(cx, script, true, &sprinter, DisassembleSkeptically::Yes)) { + return false; + } + printf("%s\n", sprinter.string()); + if (!Disassemble(cx, script, true, &sprinter, DisassembleSkeptically::No)) { + return false; + } + // (don't bother printing it) +#endif + + 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<const char*>(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<const char*>(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..287b52ebde --- /dev/null +++ b/js/src/frontend/Frontend2.h @@ -0,0 +1,67 @@ +/* -*- 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 <stddef.h> // size_t +#include <stdint.h> // 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; + +namespace frontend { + +struct CompilationStencil; +struct CompilationGCOutput; +struct CompilationState; + +// This is declarated as a class mostly to solve dependency around `friend` +// declarations in the simple way. +class Smoosh { + public: + static bool compileGlobalScript(JSContext* cx, CompilationStencil& stencil, + JS::SourceText<mozilla::Utf8Unit>& srcBuf, + CompilationGCOutput& gcOutput, + bool* unimplemented); + + static bool compileGlobalScriptToStencil( + JSContext* cx, CompilationStencil& stencil, + JS::SourceText<mozilla::Utf8Unit>& srcBuf, bool* unimplemented); + + static UniquePtr<CompilationStencil> compileGlobalScriptToStencil( + JSContext* cx, const JS::ReadOnlyCompileOptions& options, + JS::SourceText<mozilla::Utf8Unit>& srcBuf, bool* unimplemented); +}; + +// 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. +MOZ_MUST_USE bool SmooshParseScript(JSContext* cx, const uint8_t* bytes, + size_t length); +MOZ_MUST_USE 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/FullParseHandler.h b/js/src/frontend/FullParseHandler.h new file mode 100644 index 0000000000..5fba2d86ed --- /dev/null +++ b/js/src/frontend/FullParseHandler.h @@ -0,0 +1,1136 @@ +/* -*- 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/Attributes.h" +#include "mozilla/Maybe.h" // mozilla::Maybe +#include "mozilla/PodOperations.h" + +#include <cstddef> // std::nullptr_t +#include <string.h> + +#include "frontend/FunctionSyntaxKind.h" // FunctionSyntaxKind +#include "frontend/NameAnalysisTypes.h" // PrivateNameKind +#include "frontend/ParseNode.h" +#include "frontend/SharedContext.h" +#include "frontend/Stencil.h" +#include "vm/JSContext.h" + +namespace js { + +class RegExpObject; + +namespace frontend { + +class TokenStreamAnyChars; + +enum class SourceKind { + // We are parsing from a text source (Parser.h) + Text, +}; + +// 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<ParseNode*>(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: + * + * - lazyOuterFunction_ holds the lazyScript for this current parse + * - lazyInnerFunctionIndex is used as we skip over inner functions + * (see skipLazyInnerFunction), + * + * TODO-Stencil: We probably need to snapshot the atoms from the + * lazyOuterFunction here. + */ + const Rooted<BaseScript*> lazyOuterFunction_; + size_t lazyInnerFunctionIndex; + + size_t lazyClosedOverBindingIndex; + + const SourceKind sourceKind_; + + public: + /* new_ methods for creating parse nodes. These report OOM on context. */ + JS_DECLARE_NEW_METHODS(new_, allocParseNode, inline) + + // 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 isPropertyAccess(Node node) { + return node->isKind(ParseNodeKind::DotExpr) || + node->isKind(ParseNodeKind::ElemExpr); + } + + bool isOptionalPropertyAccess(Node node) { + return node->isKind(ParseNodeKind::OptionalDotExpr) || + node->isKind(ParseNodeKind::OptionalElemExpr); + } + + 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(JSContext* cx, LifoAlloc& alloc, + BaseScript* lazyOuterFunction, + SourceKind kind = SourceKind::Text) + : allocator(cx, alloc), + lazyOuterFunction_(cx, lazyOuterFunction), + lazyInnerFunctionIndex(0), + lazyClosedOverBindingIndex(0), + sourceKind_(kind) { + // The BaseScript::gcthings() array contains the inner function list + // followed by the closed-over bindings data. Advance the index for + // closed-over bindings to the end of the inner functions. The + // nextLazyInnerFunction / nextLazyClosedOverBinding accessors confirm we + // have the expected types. See also: BaseScript::CreateLazy. + if (lazyOuterFunction) { + for (JS::GCCellPtr gcThing : lazyOuterFunction->gcthings()) { + if (gcThing.is<JSObject>()) { + lazyClosedOverBindingIndex++; + } else { + break; + } + } + } + } + + static NullNode null() { return NullNode(); } + +#define DECLARE_AS(typeName, longTypeName, asMethodName) \ + static longTypeName asMethodName(Node node) { return &node->as<typeName>(); } + FOR_EACH_PARSENODE_SUBCLASS(DECLARE_AS) +#undef DECLARE_AS + + // The FullParseHandler may be used to create nodes for text sources (from + // Parser.h). With previous binary source formats, some common assumptions on + // offsets are incorrect, e.g. in `a + b`, `a`, `b` and `+` may be stored in + // any order. We use `sourceKind()` to determine whether we need to check + // these assumptions. + SourceKind sourceKind() const { return sourceKind_; } + + NameNodeType newName(const ParserName* name, const TokenPos& pos, + JSContext* cx) { + return new_<NameNode>(ParseNodeKind::Name, name, pos); + } + + UnaryNodeType newComputedName(Node expr, uint32_t begin, uint32_t end) { + TokenPos pos(begin, end); + return new_<UnaryNode>(ParseNodeKind::ComputedName, pos, expr); + } + + UnaryNodeType newSyntheticComputedName(Node expr, uint32_t begin, + uint32_t end) { + TokenPos pos(begin, end); + UnaryNode* node = new_<UnaryNode>(ParseNodeKind::ComputedName, pos, expr); + if (!node) { + return nullptr; + } + node->setSyntheticComputedName(); + return node; + } + + NameNodeType newObjectLiteralPropertyName(const ParserAtom* atom, + const TokenPos& pos) { + return new_<NameNode>(ParseNodeKind::ObjectPropertyName, atom, pos); + } + + NameNodeType newPrivateName(const ParserAtom* atom, const TokenPos& pos) { + return new_<NameNode>(ParseNodeKind::PrivateName, atom, pos); + } + + NumericLiteralType newNumber(double value, DecimalPoint decimalPoint, + const TokenPos& pos) { + return new_<NumericLiteral>(value, decimalPoint, pos); + } + + BigIntLiteralType newBigInt(BigIntIndex index, + BaseCompilationStencil& stencil, + const TokenPos& pos) { + return new_<BigIntLiteral>(index, stencil, pos); + } + + BooleanLiteralType newBooleanLiteral(bool cond, const TokenPos& pos) { + return new_<BooleanLiteral>(cond, pos); + } + + NameNodeType newStringLiteral(const ParserAtom* atom, const TokenPos& pos) { + return new_<NameNode>(ParseNodeKind::StringExpr, atom, pos); + } + + NameNodeType newTemplateStringLiteral(const ParserAtom* atom, + const TokenPos& pos) { + return new_<NameNode>(ParseNodeKind::TemplateStringExpr, atom, pos); + } + + CallSiteNodeType newCallSiteObject(uint32_t begin) { + CallSiteNode* callSiteObj = new_<CallSiteNode>(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_<ThisLiteral>(pos, thisName); + } + + NullLiteralType newNullLiteral(const TokenPos& pos) { + return new_<NullLiteral>(pos); + } + + RawUndefinedLiteralType newRawUndefinedLiteral(const TokenPos& pos) { + return new_<RawUndefinedLiteral>(pos); + } + + RegExpLiteralType newRegExp(RegExpIndex index, const TokenPos& pos) { + return new_<RegExpLiteral>(index, pos); + } + + ConditionalExpressionType newConditional(Node cond, Node thenExpr, + Node elseExpr) { + return new_<ConditionalExpression>(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<UnaryNode>().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_<UnaryNode>(kind, pos, kid); + } + + UnaryNodeType newUpdate(ParseNodeKind kind, uint32_t begin, Node kid) { + TokenPos pos(begin, kid->pn_pos.end); + return new_<UnaryNode>(kind, pos, kid); + } + + UnaryNodeType newSpread(uint32_t begin, Node kid) { + TokenPos pos(begin, kid->pn_pos.end); + return new_<UnaryNode>(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_<BinaryNode>(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_<ListNode>(ParseNodeKind::ArrayExpr, TokenPos(begin, begin + 1)); + } + + MOZ_MUST_USE bool addElision(ListNodeType literal, const TokenPos& pos) { + MOZ_ASSERT(literal->isKind(ParseNodeKind::ArrayExpr)); + + NullaryNode* elision = new_<NullaryNode>(ParseNodeKind::Elision, pos); + if (!elision) { + return false; + } + addList(/* list = */ literal, /* kid = */ elision); + literal->setHasNonConstInitializer(); + return true; + } + + MOZ_MUST_USE bool addSpreadElement(ListNodeType literal, uint32_t begin, + Node inner) { + MOZ_ASSERT(literal->isKind(ParseNodeKind::ArrayExpr)); + + UnaryNodeType spread = newSpread(begin, inner); + if (!spread) { + return false; + } + addList(/* list = */ literal, /* kid = */ spread); + literal->setHasNonConstInitializer(); + return true; + } + + void addArrayElement(ListNodeType literal, Node element) { + if (!element->isConstant()) { + literal->setHasNonConstInitializer(); + } + addList(/* list = */ literal, /* kid = */ element); + } + + CallNodeType newCall(Node callee, Node args, JSOp callOp) { + return new_<CallNode>(ParseNodeKind::CallExpr, callOp, callee, args); + } + + OptionalCallNodeType newOptionalCall(Node callee, Node args, JSOp callOp) { + return new_<CallNode>(ParseNodeKind::OptionalCallExpr, callOp, callee, + args); + } + + ListNodeType newArguments(const TokenPos& pos) { + return new_<ListNode>(ParseNodeKind::Arguments, pos); + } + + CallNodeType newSuperCall(Node callee, Node args, bool isSpread) { + return new_<CallNode>(ParseNodeKind::SuperCallExpr, + isSpread ? JSOp::SpreadSuperCall : JSOp::SuperCall, + callee, args); + } + + CallNodeType newTaggedTemplate(Node tag, Node args, JSOp callOp) { + return new_<CallNode>(ParseNodeKind::TaggedTemplateExpr, callOp, tag, args); + } + + ListNodeType newObjectLiteral(uint32_t begin) { + return new_<ListNode>(ParseNodeKind::ObjectExpr, + TokenPos(begin, begin + 1)); + } + + ClassNodeType newClass(Node name, Node heritage, + LexicalScopeNodeType memberBlock, + const TokenPos& pos) { + return new_<ClassNode>(name, heritage, memberBlock, pos); + } + ListNodeType newClassMemberList(uint32_t begin) { + return new_<ListNode>(ParseNodeKind::ClassMemberList, + TokenPos(begin, begin + 1)); + } + ClassNamesType newClassNames(Node outer, Node inner, const TokenPos& pos) { + return new_<ClassNames>(outer, inner, pos); + } + BinaryNodeType newNewTarget(NullaryNodeType newHolder, + NullaryNodeType targetHolder) { + return new_<BinaryNode>(ParseNodeKind::NewTargetExpr, newHolder, + targetHolder); + } + NullaryNodeType newPosHolder(const TokenPos& pos) { + return new_<NullaryNode>(ParseNodeKind::PosHolder, pos); + } + UnaryNodeType newSuperBase(Node thisName, const TokenPos& pos) { + return new_<UnaryNode>(ParseNodeKind::SuperBase, pos, thisName); + } + MOZ_MUST_USE 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_<PropertyDefinition>(key, val, AccessorType::None); + } + + void addPropertyDefinition(ListNodeType literal, BinaryNodeType propdef) { + MOZ_ASSERT(literal->isKind(ParseNodeKind::ObjectExpr)); + MOZ_ASSERT(propdef->isKind(ParseNodeKind::PropertyDefinition)); + + if (!propdef->right()->isConstant()) { + literal->setHasNonConstInitializer(); + } + + addList(/* list = */ literal, /* kid = */ propdef); + } + + MOZ_MUST_USE bool addPropertyDefinition(ListNodeType literal, Node key, + Node val) { + BinaryNode* propdef = newPropertyDefinition(key, val); + if (!propdef) { + return false; + } + addPropertyDefinition(literal, propdef); + return true; + } + + MOZ_MUST_USE bool addShorthand(ListNodeType literal, NameNodeType name, + NameNodeType expr) { + MOZ_ASSERT(literal->isKind(ParseNodeKind::ObjectExpr)); + 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; + } + + MOZ_MUST_USE bool addSpreadProperty(ListNodeType literal, uint32_t begin, + Node inner) { + MOZ_ASSERT(literal->isKind(ParseNodeKind::ObjectExpr)); + + literal->setHasNonConstInitializer(); + ParseNode* spread = newSpread(begin, inner); + if (!spread) { + return false; + } + addList(/* list = */ literal, /* kid = */ spread); + return true; + } + + MOZ_MUST_USE 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; + } + + MOZ_MUST_USE ClassMethod* newClassMethodDefinition( + Node key, FunctionNodeType funNode, AccessorType atype, bool isStatic, + mozilla::Maybe<FunctionNodeType> initializerIfPrivate) { + MOZ_ASSERT(isUsableAsObjectPropertyName(key)); + + checkAndSetIsDirectRHSAnonFunction(funNode); + + if (initializerIfPrivate.isSome()) { + return new_<ClassMethod>(key, funNode, atype, isStatic, + initializerIfPrivate.value()); + } + return new_<ClassMethod>(key, funNode, atype, isStatic, nullptr); + } + + MOZ_MUST_USE ClassField* newClassFieldDefinition(Node name, + FunctionNodeType initializer, + bool isStatic) { + MOZ_ASSERT(isUsableAsObjectPropertyName(name)); + + return new_<ClassField>(name, initializer, isStatic); + } + + MOZ_MUST_USE bool addClassMemberDefinition(ListNodeType memberList, + Node member) { + MOZ_ASSERT(memberList->isKind(ParseNodeKind::ClassMemberList)); + // Constructors can be surrounded by LexicalScopes. + MOZ_ASSERT(member->isKind(ParseNodeKind::ClassMethod) || + member->isKind(ParseNodeKind::ClassField) || + (member->isKind(ParseNodeKind::LexicalScope) && + member->as<LexicalScopeNode>().scopeBody()->isKind( + ParseNodeKind::ClassMethod))); + + addList(/* list = */ memberList, /* kid = */ member); + return true; + } + + UnaryNodeType newInitialYieldExpression(uint32_t begin, Node gen) { + TokenPos pos(begin, begin + 1); + return new_<UnaryNode>(ParseNodeKind::InitialYield, pos, gen); + } + + UnaryNodeType newYieldExpression(uint32_t begin, Node value) { + TokenPos pos(begin, value ? value->pn_pos.end : begin + 1); + return new_<UnaryNode>(ParseNodeKind::YieldExpr, pos, value); + } + + UnaryNodeType newYieldStarExpression(uint32_t begin, Node value) { + TokenPos pos(begin, value->pn_pos.end); + return new_<UnaryNode>(ParseNodeKind::YieldStarExpr, pos, value); + } + + UnaryNodeType newAwaitExpression(uint32_t begin, Node value) { + TokenPos pos(begin, value ? value->pn_pos.end : begin + 1); + return new_<UnaryNode>(ParseNodeKind::AwaitExpr, pos, value); + } + + UnaryNodeType newOptionalChain(uint32_t begin, Node value) { + TokenPos pos(begin, value->pn_pos.end); + return new_<UnaryNode>(ParseNodeKind::OptionalChain, pos, value); + } + + // Statements + + ListNodeType newStatementList(const TokenPos& pos) { + return new_<ListNode>(ParseNodeKind::StatementList, pos); + } + + MOZ_MUST_USE bool isFunctionStmt(Node stmt) { + while (stmt->isKind(ParseNodeKind::LabelStmt)) { + stmt = stmt->as<LabeledStatement>().statement(); + } + return stmt->is<FunctionNode>(); + } + + 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(); + } + } + + MOZ_MUST_USE 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_<NullaryNode>(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_<NullaryNode>(ParseNodeKind::EmptyStmt, pos); + } + + BinaryNodeType newImportDeclaration(Node importSpecSet, Node moduleSpec, + const TokenPos& pos) { + return new_<BinaryNode>(ParseNodeKind::ImportDecl, pos, importSpecSet, + moduleSpec); + } + + BinaryNodeType newImportSpec(Node importNameNode, Node bindingName) { + return newBinary(ParseNodeKind::ImportSpec, importNameNode, bindingName); + } + + UnaryNodeType newExportDeclaration(Node kid, const TokenPos& pos) { + return new_<UnaryNode>(ParseNodeKind::ExportStmt, pos, kid); + } + + BinaryNodeType newExportFromDeclaration(uint32_t begin, Node exportSpecSet, + Node moduleSpec) { + BinaryNode* decl = new_<BinaryNode>(ParseNodeKind::ExportFromStmt, + exportSpecSet, moduleSpec); + 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_<BinaryNode>(ParseNodeKind::ExportDefaultStmt, pos, kid, + maybeBinding); + } + + BinaryNodeType newExportSpec(Node bindingName, Node exportName) { + return newBinary(ParseNodeKind::ExportSpec, bindingName, exportName); + } + + NullaryNodeType newExportBatchSpec(const TokenPos& pos) { + return new_<NullaryNode>(ParseNodeKind::ExportBatchSpecStmt, pos); + } + + BinaryNodeType newImportMeta(NullaryNodeType importHolder, + NullaryNodeType metaHolder) { + return new_<BinaryNode>(ParseNodeKind::ImportMetaExpr, importHolder, + metaHolder); + } + + BinaryNodeType newCallImport(NullaryNodeType importHolder, Node singleArg) { + return new_<BinaryNode>(ParseNodeKind::CallImportExpr, importHolder, + singleArg); + } + + UnaryNodeType newExprStatement(Node expr, uint32_t end) { + MOZ_ASSERT_IF(sourceKind() == SourceKind::Text, expr->pn_pos.end <= end); + return new_<UnaryNode>(ParseNodeKind::ExpressionStmt, + TokenPos(expr->pn_pos.begin, end), expr); + } + + UnaryNodeType newExprStatement(Node expr) { + return newExprStatement(expr, expr->pn_pos.end); + } + + TernaryNodeType newIfStatement(uint32_t begin, Node cond, Node thenBranch, + Node elseBranch) { + TernaryNode* node = + new_<TernaryNode>(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_<BinaryNode>(ParseNodeKind::DoWhileStmt, pos, body, cond); + } + + BinaryNodeType newWhileStatement(uint32_t begin, Node cond, Node body) { + TokenPos pos(begin, body->pn_pos.end); + return new_<BinaryNode>(ParseNodeKind::WhileStmt, pos, cond, body); + } + + ForNodeType newForStatement(uint32_t begin, TernaryNodeType forHead, + Node body, unsigned iflags) { + return new_<ForNode>(TokenPos(begin, body->pn_pos.end), forHead, body, + iflags); + } + + TernaryNodeType newForHead(Node init, Node test, Node update, + const TokenPos& pos) { + return new_<TernaryNode>(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_<TernaryNode>(kind, target, nullptr, iteratedExpr, pos); + } + + SwitchStatementType newSwitchStatement( + uint32_t begin, Node discriminant, + LexicalScopeNodeType lexicalForCaseList, bool hasDefault) { + return new_<SwitchStatement>(begin, discriminant, lexicalForCaseList, + hasDefault); + } + + CaseClauseType newCaseOrDefault(uint32_t begin, Node expr, Node body) { + return new_<CaseClause>(expr, body, begin); + } + + ContinueStatementType newContinueStatement(const ParserName* label, + const TokenPos& pos) { + return new_<ContinueStatement>(label, pos); + } + + BreakStatementType newBreakStatement(const ParserName* label, + const TokenPos& pos) { + return new_<BreakStatement>(label, pos); + } + + UnaryNodeType newReturnStatement(Node expr, const TokenPos& pos) { + MOZ_ASSERT_IF(expr && sourceKind() == SourceKind::Text, + pos.encloses(expr->pn_pos)); + return new_<UnaryNode>(ParseNodeKind::ReturnStmt, pos, expr); + } + + UnaryNodeType newExpressionBody(Node expr) { + return new_<UnaryNode>(ParseNodeKind::ReturnStmt, expr->pn_pos, expr); + } + + BinaryNodeType newWithStatement(uint32_t begin, Node expr, Node body) { + return new_<BinaryNode>(ParseNodeKind::WithStmt, + TokenPos(begin, body->pn_pos.end), expr, body); + } + + LabeledStatementType newLabeledStatement(const ParserName* label, Node stmt, + uint32_t begin) { + return new_<LabeledStatement>(label, stmt, begin); + } + + UnaryNodeType newThrowStatement(Node expr, const TokenPos& pos) { + MOZ_ASSERT_IF(sourceKind() == SourceKind::Text, pos.encloses(expr->pn_pos)); + return new_<UnaryNode>(ParseNodeKind::ThrowStmt, pos, expr); + } + + TernaryNodeType newTryStatement(uint32_t begin, Node body, + LexicalScopeNodeType catchScope, + Node finallyBlock) { + return new_<TryNode>(begin, body, catchScope, finallyBlock); + } + + DebuggerStatementType newDebuggerStatement(const TokenPos& pos) { + return new_<DebuggerStatement>(pos); + } + + NameNodeType newPropertyName(const ParserName* name, const TokenPos& pos) { + return new_<NameNode>(ParseNodeKind::PropertyNameExpr, name, pos); + } + + PropertyAccessType newPropertyAccess(Node expr, NameNodeType key) { + return new_<PropertyAccess>(expr, key, expr->pn_pos.begin, key->pn_pos.end); + } + + PropertyByValueType newPropertyByValue(Node lhs, Node index, uint32_t end) { + return new_<PropertyByValue>(lhs, index, lhs->pn_pos.begin, end); + } + + OptionalPropertyAccessType newOptionalPropertyAccess(Node expr, + NameNodeType key) { + return new_<OptionalPropertyAccess>(expr, key, expr->pn_pos.begin, + key->pn_pos.end); + } + + OptionalPropertyByValueType newOptionalPropertyByValue(Node lhs, Node index, + uint32_t end) { + return new_<OptionalPropertyByValue>(lhs, index, lhs->pn_pos.begin, end); + } + + bool setupCatchScope(LexicalScopeNodeType lexicalScope, Node catchName, + Node catchBody) { + BinaryNode* catchClause; + if (catchName) { + catchClause = + new_<BinaryNode>(ParseNodeKind::Catch, catchName, catchBody); + } else { + catchClause = new_<BinaryNode>(ParseNodeKind::Catch, catchBody->pn_pos, + catchName, catchBody); + } + if (!catchClause) { + return false; + } + lexicalScope->setScopeBody(catchClause); + return true; + } + + inline MOZ_MUST_USE bool setLastFunctionFormalParameterDefault( + FunctionNodeType funNode, Node defaultValue); + + void checkAndSetIsDirectRHSAnonFunction(Node pn) { + if (IsAnonymousFunctionDefinition(pn)) { + pn->setDirectRHSAnonFunction(true); + } + } + + FunctionNodeType newFunction(FunctionSyntaxKind syntaxKind, + const TokenPos& pos) { + return new_<FunctionNode>(syntaxKind, pos); + } + + BinaryNodeType newObjectMethodOrPropertyDefinition(Node key, Node value, + AccessorType atype) { + MOZ_ASSERT(isUsableAsObjectPropertyName(key)); + + return new_<PropertyDefinition>(key, value, atype); + } + + BinaryNodeType newShorthandPropertyDefinition(Node key, Node value) { + MOZ_ASSERT(isUsableAsObjectPropertyName(key)); + + return newBinary(ParseNodeKind::Shorthand, key, value); + } + + ListNodeType newParamsBody(const TokenPos& pos) { + return new_<ListNode>(ParseNodeKind::ParamsBody, pos); + } + + void setFunctionFormalParametersAndBody(FunctionNodeType funNode, + ListNodeType paramsBody) { + MOZ_ASSERT_IF(paramsBody, paramsBody->isKind(ParseNodeKind::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) { + MOZ_ASSERT(funNode->body()->isKind(ParseNodeKind::ParamsBody)); + addList(/* list = */ funNode->body(), /* kid = */ body); + } + + ModuleNodeType newModule(const TokenPos& pos) { + return new_<ModuleNode>(pos); + } + + LexicalScopeNodeType newLexicalScope(LexicalScope::ParserData* bindings, + Node body, + ScopeKind kind = ScopeKind::Lexical) { + return new_<LexicalScopeNode>(bindings, body, kind); + } + + CallNodeType newNewExpression(uint32_t begin, Node ctor, Node args, + bool isSpread) { + return new_<CallNode>(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 || + kind == ParseNodeKind::InitExpr) && + lhs->isKind(ParseNodeKind::Name) && !lhs->isInParens()) { + checkAndSetIsDirectRHSAnonFunction(rhs); + } + + return new_<AssignmentNode>(kind, 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_IF(sourceKind() == SourceKind::Text, + 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_IF(sourceKind() == SourceKind::Text, + pn->pn_pos.begin <= pn->pn_pos.end); + } + + uint32_t getFunctionNameOffset(Node func, TokenStreamAnyChars& ts) { + return func->pn_pos.begin; + } + + bool isDeclarationKind(ParseNodeKind kind) { + return kind == ParseNodeKind::VarStmt || kind == ParseNodeKind::LetDecl || + kind == ParseNodeKind::ConstDecl; + } + + ListNodeType newList(ParseNodeKind kind, const TokenPos& pos) { + MOZ_ASSERT(!isDeclarationKind(kind)); + return new_<ListNode>(kind, pos); + } + + public: + ListNodeType newList(ParseNodeKind kind, Node kid) { + MOZ_ASSERT(!isDeclarationKind(kind)); + return new_<ListNode>(kind, kid); + } + + ListNodeType newDeclarationList(ParseNodeKind kind, const TokenPos& pos) { + MOZ_ASSERT(isDeclarationKind(kind)); + return new_<ListNode>(kind, pos); + } + + bool isDeclarationList(Node node) { + return isDeclarationKind(node->getKind()); + } + + Node singleBindingFromDeclaration(ListNodeType decl) { + MOZ_ASSERT(isDeclarationList(decl)); + MOZ_ASSERT(decl->count() == 1); + return decl->head(); + } + + ListNodeType newCommaExpressionList(Node kid) { + return new_<ListNode>(ParseNodeKind::CommaExpr, kid); + } + + void addList(ListNodeType list, Node kid) { + if (sourceKind_ == SourceKind::Text) { + list->append(kid); + } else { + list->appendWithoutOrderAssumption(kid); + } + } + + void setListHasNonConstInitializer(ListNodeType literal) { + literal->setHasNonConstInitializer(); + } + template <typename NodeType> + MOZ_MUST_USE NodeType parenthesize(NodeType node) { + node->setInParens(true); + return node; + } + template <typename NodeType> + MOZ_MUST_USE NodeType setLikelyIIFE(NodeType node) { + return parenthesize(node); + } + + bool isName(Node node) { return node->isKind(ParseNodeKind::Name); } + + bool isArgumentsName(Node node, JSContext* cx) { + return node->isKind(ParseNodeKind::Name) && + node->as<NameNode>().atom() == cx->parserNames().arguments; + } + + bool isEvalName(Node node, JSContext* cx) { + return node->isKind(ParseNodeKind::Name) && + node->as<NameNode>().atom() == cx->parserNames().eval; + } + + bool isAsyncKeyword(Node node, JSContext* cx) { + return node->isKind(ParseNodeKind::Name) && + node->pn_pos.begin + strlen("async") == node->pn_pos.end && + node->as<NameNode>().atom() == cx->parserNames().async; + } + + bool isPrivateName(Node node) { + return node->isKind(ParseNodeKind::PrivateName); + } + + bool isPrivateField(Node node) { + if (node->isKind(ParseNodeKind::ElemExpr) || + node->isKind(ParseNodeKind::OptionalElemExpr)) { + PropertyByValueBase& pbv = node->as<PropertyByValueBase>(); + if (isPrivateName(&pbv.key())) { + return true; + } + } + if (node->isKind(ParseNodeKind::OptionalChain)) { + return isPrivateField(node->as<UnaryNode>().kid()); + } + return false; + } + + const ParserName* maybeDottedProperty(Node pn) { + return pn->is<PropertyAccessBase>() ? pn->as<PropertyAccessBase>().name() + : nullptr; + } + const ParserAtom* isStringExprStatement(Node pn, TokenPos* pos) { + if (pn->is<UnaryNode>()) { + UnaryNode* unary = &pn->as<UnaryNode>(); + if (const ParserAtom* atom = unary->isStringExprStatement()) { + *pos = unary->kid()->pn_pos; + return atom; + } + } + return nullptr; + } + + bool canSkipLazyInnerFunctions() { return !!lazyOuterFunction_; } + bool canSkipLazyClosedOverBindings() { return !!lazyOuterFunction_; } + bool canSkipRegexpSyntaxParse() { return !!lazyOuterFunction_; } + JSFunction* nextLazyInnerFunction() { + return &lazyOuterFunction_->gcthings()[lazyInnerFunctionIndex++] + .as<JSObject>() + .as<JSFunction>(); + } + JSAtom* nextLazyClosedOverBinding() { + auto gcthings = lazyOuterFunction_->gcthings(); + + // Trailing nullptrs were elided in PerHandlerParser::finishFunction(). + if (lazyClosedOverBindingIndex >= gcthings.Length()) { + return nullptr; + } + + // These entries are either JSAtom* or nullptr, so use the 'asCell()' + // accessor which is faster. + gc::Cell* cell = gcthings[lazyClosedOverBindingIndex++].asCell(); + MOZ_ASSERT_IF(cell, cell->as<JSString>()->isAtom()); + return static_cast<JSAtom*>(cell); + } + + void setPrivateNameKind(Node node, PrivateNameKind kind) { + MOZ_ASSERT(node->is<NameNode>()); + node->as<NameNode>().setPrivateNameKind(kind); + } +}; + +inline bool FullParseHandler::setLastFunctionFormalParameterDefault( + FunctionNodeType funNode, Node defaultValue) { + ListNode* 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..66d78fa721 --- /dev/null +++ b/js/src/frontend/FunctionEmitter.cpp @@ -0,0 +1,954 @@ +/* -*- 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 "mozilla/Unused.h" + +#include "builtin/ModuleObject.h" // ModuleObject +#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/ParseContext.h" // BindingIter +#include "frontend/PropOpEmitter.h" // PropOpEmitter +#include "frontend/SharedContext.h" // SharedContext +#include "vm/AsyncFunctionResolveKind.h" // AsyncFunctionResolveKind +#include "vm/JSScript.h" // JSScript +#include "vm/ModuleBuilder.h" // ModuleBuilder +#include "vm/Opcodes.h" // JSOp +#include "vm/Scope.h" // BindingKind +#include "wasm/AsmJS.h" // IsAsmJSModule + +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_->wasEmitted()); + + // [stack] + + funbox_->setWasEmitted(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_->wasEmitted()); + + // [stack] + + funbox_->setWasEmitted(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_->wasEmitted()); + + // [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<NameLocation> 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_->wasEmitted()); + MOZ_ASSERT(funbox_->isAsmJSModule()); + + // [stack] + + funbox_->setWasEmitted(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] + + // JSOp::LambdaArrow is always preceded by a opcode that pushes new.target. + // See below. + MOZ_ASSERT(funbox_->isArrow() == (syntaxKind_ == FunctionSyntaxKind::Arrow)); + + if (funbox_->isArrow()) { + if (!emitNewTargetForArrow()) { + // [stack] NEW.TARGET/NULL + return false; + } + } + + 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). + JSOp op = syntaxKind_ == FunctionSyntaxKind::Arrow ? JSOp::LambdaArrow + : JSOp::Lambda; + if (!bce_->emitGCIndexOp(op, 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_->cx, 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. + mozilla::Unused << index; + + return true; +} + +bool FunctionEmitter::emitNewTargetForArrow() { + // [stack] + + if (bce_->sc->allowNewTarget()) { + if (!bce_->emit1(JSOp::NewTarget)) { + // [stack] NEW.TARGET + return false; + } + } else { + if (!bce_->emit1(JSOp::Null)) { + // [stack] NULL + return false; + } + } + + 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 (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. + if (!bce_->switchToMain()) { + return false; + } + } + + if (!functionEmitterScope_->enterFunction(bce_, funbox_)) { + return false; + } + + if (!bce_->emitInitializeFunctionSpecialNames()) { + // [stack] + return false; + } + + if (!funbox_->hasParameterExprs) { + if (!bce_->switchToMain()) { + return false; + } + } + + if (funbox_->needsPromiseResult()) { + if (funbox_->hasParameterExprs) { + if (!asyncEmitter_->prepareForParamsWithExpression()) { + return false; + } + } else { + if (!asyncEmitter_->prepareForParamsWithoutExpression()) { + return false; + } + } + } + +#ifdef DEBUG + state_ = State::Parameters; +#endif + 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; + } + } + + if (funbox_->isClassConstructor()) { + if (!funbox_->isDerivedClassConstructor()) { + if (!bce_->emitInitializeInstanceMembers()) { + // [stack] + 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; } + // + const ParserAtom* name = nullptr; + for (ParserBindingIter bi(*funbox_->functionScopeBindings(), true); bi; + bi++) { + name = bce_->compilationState.getParserAtomAt(bce_->cx, bi.name()); + + // There may not be a var binding of the same name. + if (!bce_->locationOfNameBoundInScope(name, + extraBodyVarEmitterScope_.ptr())) { + continue; + } + + // The '.this' and '.generator' function special + // bindings should never appear in the extra var + // scope. 'arguments', however, may. + MOZ_ASSERT(name != bce_->cx->parserNames().dotThis && + name != bce_->cx->parserNames().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 (funbox_->needsFinalYield()) { + // If we fall off the end of a generator, do a final yield. + 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::Undefined)) { + // [stack] RESULT? UNDEF + return false; + } + + if (!bce_->emitFinishIteratorResult(true)) { + // [stack] RESULT + return false; + } + + if (!bce_->emit1(JSOp::SetRval)) { + // [stack] + return false; + } + + if (!bce_->emitGetDotGeneratorInInnermostScope()) { + // [stack] GEN + return false; + } + + // No need to check for finally blocks, etc as in EmitReturn. + if (!bce_->emitYieldOp(JSOp::FinalYieldRval)) { + // [stack] + return false; + } + } else if (funbox_->needsPromiseResult()) { + // Emit final yield bytecode for async functions, for example: + // async function deferred() { ... } + if (!asyncEmitter_->emitEnd()) { + return false; + } + } else { + // Emit final yield bytecode for async generators, for example: + // async function asyncgen * () { ... } + if (!bce_->emit1(JSOp::Undefined)) { + // [stack] RESULT? UNDEF + return false; + } + + if (!bce_->emit1(JSOp::SetRval)) { + // [stack] + return false; + } + + if (!bce_->emitGetDotGeneratorInInnermostScope()) { + // [stack] GEN + return false; + } + + // No need to check for finally blocks, etc as in EmitReturn. + if (!bce_->emitYieldOp(JSOp::FinalYieldRval)) { + // [stack] + 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; + } + } + } + + if (funbox_->isDerivedClassConstructor()) { + 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(); + + if (bodyEnd_) { + if (!bce_->updateSourceCoordNotes(*bodyEnd_)) { + return false; + } + } + + // 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; + } + } + + // Always end the script with a JSOp::RetRval. Some other parts of the + // codebase depend on this opcode, + // e.g. InterpreterRegs::setToEndOfScript. + 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(const ParserAtom* 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(const ParserAtom* 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(const ParserAtom* 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(const ParserAtom* 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..e77c3f4162 --- /dev/null +++ b/js/src/frontend/FunctionEmitter.h @@ -0,0 +1,437 @@ +/* -*- 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, MOZ_MUST_USE + +#include <stdint.h> // 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/SharedContext.h" // FunctionBox, TopLevelFunction +#include "frontend/TDZCheckCache.h" // TDZCheckCache +#include "gc/Rooting.h" // JS::Rooted, JS::Handle +#include "vm/BytecodeUtil.h" // JSOp +#include "vm/JSAtom.h" // JSAtom +#include "vm/JSFunction.h" // JSFunction +#include "vm/SharedStencil.h" // GCThingIndex + +namespace js { +namespace frontend { + +struct BytecodeEmitter; + +// 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. + const ParserAtom* 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); + + MOZ_MUST_USE bool prepareForNonLazy(); + MOZ_MUST_USE bool emitNonLazyEnd(); + + MOZ_MUST_USE bool emitLazy(); + + MOZ_MUST_USE bool emitAgain(); + + MOZ_MUST_USE 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. + MOZ_MUST_USE bool emitFunction(); + + // Helper methods used by emitFunction for each case. + // `index` is the object index of the function. + MOZ_MUST_USE bool emitNonHoisted(GCThingIndex index); + MOZ_MUST_USE bool emitHoisted(GCThingIndex index); + MOZ_MUST_USE bool emitTopLevelFunction(GCThingIndex index); + MOZ_MUST_USE bool emitNewTargetForArrow(); +}; + +// 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<EmitterScope> namedLambdaEmitterScope_; + + // Scope for function body. + mozilla::Maybe<EmitterScope> functionEmitterScope_; + + // Scope for the extra body var. + // None if `funbox_->hasExtraBodyVarScope() == false`. + mozilla::Maybe<EmitterScope> extraBodyVarEmitterScope_; + + mozilla::Maybe<TDZCheckCache> tdzCache_; + + // try-catch block for async function parameter and body. + mozilla::Maybe<AsyncEmitter> asyncEmitter_; + + // See the comment for constructor. + mozilla::Maybe<uint32_t> paramStart_; + mozilla::Maybe<uint32_t> 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<uint32_t>& paramStart, + const mozilla::Maybe<uint32_t>& bodyEnd) + : bce_(bce), + funbox_(funbox), + paramStart_(paramStart), + bodyEnd_(bodyEnd) {} + + MOZ_MUST_USE bool prepareForParameters(); + MOZ_MUST_USE bool prepareForBody(); + MOZ_MUST_USE bool emitEndBody(); + + // Generate the ScriptStencil using the bytecode emitter data. + MOZ_MUST_USE bool intoStencil(); + + private: + MOZ_MUST_USE bool emitExtraBodyVarScope(); +}; + +// 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<DefaultEmitter> 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). + MOZ_MUST_USE bool emitSimple(const ParserAtom* paramName); + + MOZ_MUST_USE bool prepareForDefault(); + MOZ_MUST_USE bool emitDefaultEnd(const ParserAtom* paramName); + + MOZ_MUST_USE bool prepareForDestructuring(); + MOZ_MUST_USE bool emitDestructuringEnd(); + + MOZ_MUST_USE bool prepareForDestructuringDefaultInitializer(); + MOZ_MUST_USE bool prepareForDestructuringDefault(); + MOZ_MUST_USE bool emitDestructuringDefaultEnd(); + + MOZ_MUST_USE bool emitRest(const ParserAtom* paramName); + + MOZ_MUST_USE bool prepareForDestructuringRest(); + MOZ_MUST_USE bool emitDestructuringRestEnd(); + + private: + MOZ_MUST_USE bool prepareForInitializer(); + MOZ_MUST_USE bool emitInitializerEnd(); + + MOZ_MUST_USE bool emitRestArray(); + + MOZ_MUST_USE bool emitAssignment(const ParserAtom* 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..c76b41b51c --- /dev/null +++ b/js/src/frontend/FunctionSyntaxKind.h @@ -0,0 +1,37 @@ +/* -*- 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 <stdint.h> // 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, + + 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..dc5641884f --- /dev/null +++ b/js/src/frontend/GenerateReservedWords.py @@ -0,0 +1,229 @@ +# 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): + macro_pat = re.compile(r"^\s*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_list.append((index, m.group(1))) + 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): + reserved_word_list = read_reserved_word_list(reserved_words_h) + + 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..39423ea512 --- /dev/null +++ b/js/src/frontend/IfEmitter.cpp @@ -0,0 +1,285 @@ +/* -*- 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 branch-if-false around the then part. + JSOp op = conditionKind == ConditionKind::Positive ? JSOp::IfEq : JSOp::IfNe; + 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<uint32_t>& 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<uint32_t>& 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..72d35447b2 --- /dev/null +++ b/js/src/frontend/IfEmitter.h @@ -0,0 +1,312 @@ +/* -*- 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 <stdint.h> + +#include "frontend/JumpList.h" +#include "frontend/SourceNotes.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<TDZCheckCache> 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); + + MOZ_MUST_USE bool emitThenInternal(ConditionKind conditionKind); + void calculateOrCheckPushed(); + MOZ_MUST_USE bool emitElseInternal(); + MOZ_MUST_USE 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. + MOZ_MUST_USE bool emitIf(const mozilla::Maybe<uint32_t>& ifPos); + + MOZ_MUST_USE bool emitThen( + ConditionKind conditionKind = ConditionKind::Positive); + MOZ_MUST_USE bool emitThenElse( + ConditionKind conditionKind = ConditionKind::Positive); + + MOZ_MUST_USE bool emitElseIf(const mozilla::Maybe<uint32_t>& ifPos); + MOZ_MUST_USE bool emitElse(); + + MOZ_MUST_USE 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); + + MOZ_MUST_USE bool emitCond(); + MOZ_MUST_USE bool emitThenElse( + ConditionKind conditionKind = ConditionKind::Positive); + MOZ_MUST_USE bool emitElse(); + MOZ_MUST_USE 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..a5cb4ef6ad --- /dev/null +++ b/js/src/frontend/JumpList.cpp @@ -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/. */ + +#include "frontend/JumpList.h" + +#include "mozilla/Assertions.h" // MOZ_ASSERT + +#include <stddef.h> // ptrdiff_t + +#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..7a6a4d74b2 --- /dev/null +++ b/js/src/frontend/JumpList.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_JumpList_h +#define frontend_JumpList_h + +#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::IfEq, &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) +// | +// | +// ifeq .. <+ + +-+ ifeq .. +// .. | | .. +// 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..23f780579e --- /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(const ParserAtom* 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..0fc64a28af --- /dev/null +++ b/js/src/frontend/LabelEmitter.h @@ -0,0 +1,67 @@ +/* -*- 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_MUST_USE, MOZ_STACK_CLASS +#include "mozilla/Maybe.h" // Maybe + +#include "frontend/BytecodeControlStructures.h" // LabelControl +#include "frontend/BytecodeOffset.h" // BytecodeOffset +#include "frontend/JumpList.h" // JumpList +#include "js/TypeDecls.h" // JSAtom + +namespace js { +namespace frontend { + +struct BytecodeEmitter; + +// 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<LabelControl> 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(const ParserAtom* name); + MOZ_MUST_USE 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..231e5edc02 --- /dev/null +++ b/js/src/frontend/LexicalScopeEmitter.cpp @@ -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/. */ + +#include "frontend/LexicalScopeEmitter.h" + +#include "frontend/BytecodeEmitter.h" // BytecodeEmitter + +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..2a3b03a07e --- /dev/null +++ b/js/src/frontend/LexicalScopeEmitter.h @@ -0,0 +1,96 @@ +/* -*- 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/Assertions.h" // MOZ_ASSERT +#include "mozilla/Attributes.h" // MOZ_STACK_CLASS, MOZ_MUST_USE +#include "mozilla/Maybe.h" // Maybe + +#include "frontend/EmitterScope.h" // EmitterScope +#include "frontend/TDZCheckCache.h" // TDZCheckCache +#include "gc/Rooting.h" // JS::Handle +#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<TDZCheckCache> tdzCache_; + mozilla::Maybe<EmitterScope> 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_; } + + MOZ_MUST_USE bool emitScope(ScopeKind kind, + LexicalScope::ParserData* bindings); + MOZ_MUST_USE bool emitEmptyScope(); + + MOZ_MUST_USE 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..15ef0e9bff --- /dev/null +++ b/js/src/frontend/ModuleSharedContext.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_ModuleSharedContext_h +#define frontend_ModuleSharedContext_h + +#include "mozilla/Assertions.h" // MOZ_ASSERT +#include "mozilla/Attributes.h" // MOZ_STACK_CLASS + +#include "builtin/ModuleObject.h" // js::ModuleObject +#include "frontend/SharedContext.h" // js::frontend::SharedContext +#include "js/RootingAPI.h" // JS::Handle, JS::Rooted +#include "vm/Scope.h" // js::{Module,}Scope +#include "vm/StencilEnums.h" // ImmutableScriptFlagsEnum + +namespace js { + +class ModuleBuilder; + +namespace frontend { + +struct CompilationStencil; + +class MOZ_STACK_CLASS ModuleSharedContext : public SuspendableContext { + public: + ModuleScope::ParserData* bindings; + ModuleBuilder& builder; + + ModuleSharedContext(JSContext* cx, CompilationStencil& stencil, + ModuleBuilder& builder, SourceExtent extent); +}; + +inline ModuleSharedContext* SharedContext::asModuleContext() { + MOZ_ASSERT(isModuleContext()); + return static_cast<ModuleSharedContext*>(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..8cb0895729 --- /dev/null +++ b/js/src/frontend/NameAnalysisTypes.h @@ -0,0 +1,377 @@ +/* -*- 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 <stdint.h> // uint8_t, uint16_t, uint32_t +#include <type_traits> + +#include "frontend/ParserAtom.h" // ParserAtom +#include "js/AllocPolicy.h" // SystemAllocPolicy +#include "js/Vector.h" // Vector +#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); + } + + 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, +}; + +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: + case DeclarationKind::PrivateName: + return BindingKind::Const; + + case DeclarationKind::Import: + return BindingKind::Import; + } + + 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_; + + public: + explicit DeclaredNameInfo(DeclarationKind kind, uint32_t pos, + ClosedOver closedOver = ClosedOver::No) + : pos_(pos), + kind_(kind), + closedOver_(bool(closedOver)), + privateNameKind_(PrivateNameKind::None) {} + + // 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; + } + + PrivateNameKind privateNameKind() const { return privateNameKind_; } +}; + +// 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, + + // 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 LOCALNO_LIMIT/ENVCOORD_SLOT_LIMIT. + 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 = ENVCOORD_SLOT_LIMIT) + : kind_(kind), bindingKind_(bindingKind), hops_(hops), slot_(slot) {} + + public: + // Default constructor for InlineMap. + NameLocation() = default; + + static NameLocation Dynamic() { return NameLocation(); } + + 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 Import() { + return NameLocation(Kind::Import, BindingKind::Import); + } + + static NameLocation DynamicAnnexBVar() { + return NameLocation(Kind::DynamicAnnexBVar, BindingKind::Var); + } + + static NameLocation fromBinding(BindingKind bindKind, + const BindingLocation& bl) { + switch (bl.kind()) { + case BindingLocation::Kind::Global: + return Global(bindKind); + case BindingLocation::Kind::Argument: + return ArgumentSlot(bl.argumentSlot()); + case BindingLocation::Kind::Frame: + return FrameSlot(bindKind, bl.slot()); + case BindingLocation::Kind::Environment: + return EnvironmentCoordinate(bindKind, 0, bl.slot()); + case BindingLocation::Kind::Import: + return Import(); + case BindingLocation::Kind::NamedLambdaCallee: + return NamedLambdaCallee(); + } + MOZ_CRASH("Bad BindingKind"); + } + + 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<uint16_t>(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); + 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 hasKnownSlot() const { + return kind_ == Kind::ArgumentSlot || kind_ == Kind::FrameSlot || + kind_ == Kind::EnvironmentCoordinate; + } +}; + +// These types are declared here for BaseScript::CreateLazy. +using AtomVector = Vector<const ParserAtom*, 24, SystemAllocPolicy>; + +class FunctionBox; +// FunctionBoxes stored in this type are required to be rooted +// by the parser +using FunctionBoxVector = Vector<const FunctionBox*, 8>; + +} // 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..9d4909467a --- /dev/null +++ b/js/src/frontend/NameCollections.h @@ -0,0 +1,378 @@ +/* -*- 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 <type_traits> + +#include "ds/InlineTable.h" +#include "frontend/NameAnalysisTypes.h" +#include "js/Vector.h" + +namespace js { +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 <typename RepresentativeCollection, typename ConcreteCollectionPool> +class CollectionPool { + using RecyclableCollections = Vector<void*, 32, SystemAllocPolicy>; + + RecyclableCollections all_; + RecyclableCollections recyclable_; + + static RepresentativeCollection* asRepresentative(void* p) { + return reinterpret_cast<RepresentativeCollection*>(p); + } + + RepresentativeCollection* allocate() { + size_t newAllLength = all_.length() + 1; + if (!all_.reserve(newAllLength) || !recyclable_.reserve(newAllLength)) { + return nullptr; + } + + RepresentativeCollection* collection = js_new<RepresentativeCollection>(); + 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 <typename Collection> + Collection* acquire(JSContext* cx) { + ConcreteCollectionPool::template assertInvariants<Collection>(); + + RepresentativeCollection* collection; + if (recyclable_.empty()) { + collection = allocate(); + if (!collection) { + ReportOutOfMemory(cx); + } + } else { + collection = asRepresentative(recyclable_.popCopy()); + collection->clear(); + } + return reinterpret_cast<Collection*>(collection); + } + + // Release a collection back to the pool. + template <typename Collection> + void release(Collection** collection) { + ConcreteCollectionPool::template assertInvariants<Collection>(); + 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 <typename Wrapped> +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; } +}; + +struct NameMapHasher : public DefaultHasher<const ParserAtom*> { + static inline HashNumber hash(const Lookup& l) { + // Name maps use the atom's precomputed hash code, which is based on + // the atom's contents rather than its pointer value. This is necessary + // to preserve iteration order while recording/replaying: iteration can + // affect generated script bytecode and the order in which e.g. lookup + // property hooks are performed on the associated global. + return l->hash(); + } +}; + +template <typename MapValue> +using RecyclableNameMap = + InlineMap<const ParserAtom*, RecyclableAtomMapValueWrapper<MapValue>, 24, + NameMapHasher, SystemAllocPolicy>; + +using DeclaredNameMap = RecyclableNameMap<DeclaredNameInfo>; +using NameLocationMap = RecyclableNameMap<NameLocation>; +// Cannot use GCThingIndex here because it's not trivial type. +using AtomIndexMap = RecyclableNameMap<uint32_t>; + +template <typename RepresentativeTable> +class InlineTablePool + : public CollectionPool<RepresentativeTable, + InlineTablePool<RepresentativeTable>> { + template <typename> + struct IsRecyclableAtomMapValueWrapper : std::false_type {}; + + template <typename T> + struct IsRecyclableAtomMapValueWrapper<RecyclableAtomMapValueWrapper<T>> + : std::true_type {}; + + public: + template <typename Table> + 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<ValueType>::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<EntryType>|, 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<KeyType>, + "Only tables with trivial keys are usable in the pool."); + static_assert(std::is_trivial_v<WrappedType>, + "Only tables with trivial values are usable in the pool."); + + static_assert( + std::is_trivially_copyable_v<EntryType>, + "Only tables with trivially copyable entries are usable in the pool."); + static_assert(std::is_trivially_destructible_v<EntryType>, + "Only tables with trivially destructible entries are usable " + "in the pool."); + } +}; + +template <typename RepresentativeVector> +class VectorPool : public CollectionPool<RepresentativeVector, + VectorPool<RepresentativeVector>> { + public: + template <typename Vector> + 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<ElementType>, + "Only vectors of trivial values are usable in the pool."); + static_assert(std::is_trivially_destructible_v<ElementType>, + "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."); + } +}; + +class NameCollectionPool { + InlineTablePool<AtomIndexMap> mapPool_; + VectorPool<AtomVector> vectorPool_; + uint32_t activeCompilations_; + + public: + NameCollectionPool() : activeCompilations_(0) {} + + bool hasActiveCompilation() const { return activeCompilations_ != 0; } + + void addActiveCompilation() { activeCompilations_++; } + + void removeActiveCompilation() { + MOZ_ASSERT(hasActiveCompilation()); + activeCompilations_--; + } + + template <typename Map> + Map* acquireMap(JSContext* cx) { + MOZ_ASSERT(hasActiveCompilation()); + return mapPool_.acquire<Map>(cx); + } + + template <typename Map> + void releaseMap(Map** map) { + MOZ_ASSERT(hasActiveCompilation()); + MOZ_ASSERT(map); + if (*map) { + mapPool_.release(map); + } + } + + template <typename Vector> + Vector* acquireVector(JSContext* cx) { + MOZ_ASSERT(hasActiveCompilation()); + return vectorPool_.acquire<Vector>(cx); + } + + template <typename Vector> + void releaseVector(Vector** vec) { + MOZ_ASSERT(hasActiveCompilation()); + MOZ_ASSERT(vec); + if (*vec) { + vectorPool_.release(vec); + } + } + + void purge() { + if (!hasActiveCompilation()) { + mapPool_.purgeAll(); + vectorPool_.purgeAll(); + } + } +}; + +template <typename T, template <typename> typename Impl> +class PooledCollectionPtr { + NameCollectionPool& pool_; + T* collection_ = nullptr; + + protected: + ~PooledCollectionPtr() { Impl<T>::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(JSContext* cx) { + MOZ_ASSERT(!collection_); + collection_ = Impl<T>::acquireCollection(cx, 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 <typename Map> +class PooledMapPtr : public PooledCollectionPtr<Map, PooledMapPtr> { + friend class PooledCollectionPtr<Map, PooledMapPtr>; + + static Map* acquireCollection(JSContext* cx, NameCollectionPool& pool) { + return pool.acquireMap<Map>(cx); + } + + static void releaseCollection(NameCollectionPool& pool, Map** ptr) { + pool.releaseMap(ptr); + } + + using Base = PooledCollectionPtr<Map, PooledMapPtr>; + + public: + using Base::Base; + + ~PooledMapPtr() = default; +}; + +template <typename Vector> +class PooledVectorPtr : public PooledCollectionPtr<Vector, PooledVectorPtr> { + friend class PooledCollectionPtr<Vector, PooledVectorPtr>; + + static Vector* acquireCollection(JSContext* cx, NameCollectionPool& pool) { + return pool.acquireVector<Vector>(cx); + } + + static void releaseCollection(NameCollectionPool& pool, Vector** ptr) { + pool.releaseVector(ptr); + } + + using Base = PooledCollectionPtr<Vector, PooledVectorPtr>; + 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..82f4283c52 --- /dev/null +++ b/js/src/frontend/NameFunctions.cpp @@ -0,0 +1,485 @@ +/* -*- 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/MemoryChecking.h" +#include "mozilla/ScopeExit.h" +#include "mozilla/Sprintf.h" + +#include "frontend/BytecodeCompiler.h" +#include "frontend/ParseNode.h" +#include "frontend/ParseNodeVisitor.h" +#include "frontend/ParserAtom.h" +#include "frontend/SharedContext.h" +#include "util/Poison.h" +#include "util/StringBuffer.h" +#include "vm/JSFunction.h" + +using namespace js; +using namespace js::frontend; + +namespace { + +class NameResolver : public ParseNodeVisitor<NameResolver> { + using Base = ParseNodeVisitor; + + static const size_t MaxParents = 100; + + ParserAtomsTable& parserAtoms_; + const ParserAtom* 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(const ParserAtom* name) { + if (IsIdentifier(name)) { + return buf_.append('.') && buf_.append(name); + } + + /* Quote the string as needed. */ + UniqueChars source = QuoteString(cx_, name, '"'); + return source && 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 "[<n>]" 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 respresents 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<PropertyAccess>(); + if (!nameExpression(&prop->expression(), foundName)) { + return false; + } + if (!*foundName) { + return true; + } + return appendPropertyReference(prop->right()->as<NameNode>().atom()); + } + + case ParseNodeKind::Name: + case ParseNodeKind::PrivateName: + *foundName = true; + return buf_.append(n->as<NameNode>().atom()); + + case ParseNodeKind::ThisExpr: + *foundName = true; + return buf_.append("this"); + + case ParseNodeKind::ElemExpr: { + PropertyByValue* elem = &n->as<PropertyByValue>(); + 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<NumericLiteral>().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<FunctionNode>()); + + *size = 0; + + for (int pos = nparents_ - 2; pos >= 0; pos--) { + ParseNode* cur = parents_[pos]; + if (cur->is<AssignmentNode>()) { + 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. + */ + MOZ_MUST_USE bool resolveFun(FunctionNode* funNode, + const ParserAtom** retId) { + MOZ_ASSERT(funNode != nullptr); + + FunctionBox* funbox = funNode->funbox(); + + MOZ_ASSERT(buf_.empty()); + auto resetBuf = mozilla::MakeScopeExit([&] { buf_.clear(); }); + + *retId = nullptr; + + // If the function already has a name, use that. + if (funbox->displayAtom()) { + if (!prefix_) { + *retId = funbox->displayAtom(); + return true; + } + if (!buf_.append(prefix_) || !buf_.append('/') || + !buf_.append(funbox->displayAtom())) { + return false; + } + *retId = buf_.finishParserAtom(parserAtoms_); + return !!*retId; + } + + // If a prefix is specified, then it is a form of namespace. + if (prefix_) { + if (!buf_.append(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) { + if (assignment->is<AssignmentNode>()) { + assignment = assignment->as<AssignmentNode>().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<BinaryNode>().left(); + if (left->isKind(ParseNodeKind::ObjectPropertyName) || + left->isKind(ParseNodeKind::StringExpr)) { + if (!appendPropertyReference(left->as<NameNode>().atom())) { + return false; + } + } else if (left->isKind(ParseNodeKind::NumberExpr)) { + if (!appendNumericPropertyReference( + left->as<NumericLiteral>().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_); + 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<BinaryNode>().left() == cur; + } + + public: + MOZ_MUST_USE bool visitFunction(FunctionNode* pn) { + const ParserAtom* savedPrefix = prefix_; + const ParserAtom* newPrefix = nullptr; + 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. + MOZ_MUST_USE 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. + MOZ_MUST_USE 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<ListNode>().head()->as<CallSiteNode>(); +#ifdef DEBUG + { + ListNode* rawNodes = &element->head()->as<ListNode>(); + 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. + MOZ_MUST_USE bool internalVisitSpecList(ListNode* pn) { + // Import/export spec lists contain import/export specs containing + // only pairs of names. 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<NullaryNode>()); + } else { + for (ParseNode* item : pn->contents()) { + BinaryNode* spec = &item->as<BinaryNode>(); + MOZ_ASSERT(spec->isKind(isImport ? ParseNodeKind::ImportSpec + : ParseNodeKind::ExportSpec)); + MOZ_ASSERT(spec->left()->isKind(ParseNodeKind::Name)); + MOZ_ASSERT(spec->right()->isKind(ParseNodeKind::Name)); + } + } +#endif + return true; + } + + public: + MOZ_MUST_USE bool visitImportSpecList(ListNode* pn) { + return internalVisitSpecList(pn); + } + + MOZ_MUST_USE bool visitExportSpecList(ListNode* pn) { + return internalVisitSpecList(pn); + } + + explicit NameResolver(JSContext* cx, ParserAtomsTable& parserAtoms) + : ParseNodeVisitor(cx), + parserAtoms_(parserAtoms), + prefix_(nullptr), + nparents_(0), + buf_(cx) {} + + /* + * Resolve names for all anonymous functions in the given ParseNode tree. + */ + MOZ_MUST_USE 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(JSContext* cx, ParserAtomsTable& parserAtoms, + ParseNode* pn) { + AutoTraceLog traceLog(TraceLoggerForCurrentThread(cx), + TraceLogger_BytecodeNameFunctions); + NameResolver nr(cx, 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..5b1f8a6e37 --- /dev/null +++ b/js/src/frontend/NameFunctions.h @@ -0,0 +1,26 @@ +/* -*- 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 "mozilla/Attributes.h" + +#include "js/TypeDecls.h" + +namespace js { +namespace frontend { + +class ParseNode; +class ParserAtomsTable; + +MOZ_MUST_USE bool NameFunctions(JSContext* cx, 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..19d4433b82 --- /dev/null +++ b/js/src/frontend/NameOpEmitter.cpp @@ -0,0 +1,403 @@ +/* -*- 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/SharedContext.h" +#include "frontend/TDZCheckCache.h" +#include "vm/Opcodes.h" +#include "vm/Scope.h" +#include "vm/StringType.h" + +using namespace js; +using namespace js::frontend; + +NameOpEmitter::NameOpEmitter(BytecodeEmitter* bce, const ParserAtom* name, + Kind kind) + : bce_(bce), kind_(kind), name_(name), loc_(bce_->lookupName(name_)) {} + +NameOpEmitter::NameOpEmitter(BytecodeEmitter* bce, const ParserAtom* 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: + if (!bce_->emitAtomOp(JSOp::GetGName, name_)) { + // [stack] VAL + return false; + } + break; + case NameLocation::Kind::Intrinsic: + 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: + if (!bce_->emitEnvCoordOp(JSOp::GetAliasedVar, + 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()) { + switch (loc_.kind()) { + case NameLocation::Kind::Dynamic: { + JSOp thisOp = bce_->needsImplicitThis() ? JSOp::ImplicitThis + : JSOp::GImplicitThis; + if (!bce_->emitAtomOp(thisOp, name_)) { + // [stack] CALLEE THIS + return false; + } + break; + } + case NameLocation::Kind::Global: + if (!bce_->emitAtomOp(JSOp::GImplicitThis, name_)) { + // [stack] CALLEE THIS + 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_->emit1(JSOp::Undefined)) { + // [stack] CALLEE UNDEF + return false; + } + 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_, &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_, &atomIndex_)) { + return false; + } + if (loc_.isLexical() && isInitialize()) { + // InitGLexical always gets the global lexical scope. It doesn't + // need a BindGName. + MOZ_ASSERT(bce_->innermostScope().is<GlobalScope>()); + } 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::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_) { + 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; + if (loc_.isLexical()) { + 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; + if (loc_.isLexical()) { + 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; + } + } + +#ifdef DEBUG + state_ = State::Assignment; +#endif + return true; +} + +bool NameOpEmitter::emitIncDec() { + 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()) { + 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()) { + 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()) { + 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..07692b63d2 --- /dev/null +++ b/js/src/frontend/NameOpEmitter.h @@ -0,0 +1,181 @@ +/* -*- 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 <stdint.h> + +#include "frontend/NameAnalysisTypes.h" +#include "js/TypeDecls.h" +#include "vm/SharedStencil.h" // GCThingIndex + +namespace js { +namespace frontend { + +struct BytecodeEmitter; + +// Class for emitting bytecode for name operation. +// +// Usage: (check for the return value is omitted for simplicity) +// +// `name;` +// NameOpEmitter noe(this, atom_of_name +// ElemOpEmitter::Kind::Get); +// noe.emitGet(); +// +// `name();` +// this is handled in CallOrNewEmitter +// +// `name++;` +// NameOpEmitter noe(this, atom_of_name +// ElemOpEmitter::Kind::PostIncrement); +// noe.emitIncDec(); +// +// `name = 10;` +// NameOpEmitter noe(this, atom_of_name +// ElemOpEmitter::Kind::SimpleAssignment); +// noe.prepareForRhs(); +// emit(10); +// noe.emitAssignment(); +// +// `name += 10;` +// NameOpEmitter noe(this, atom_of_name +// ElemOpEmitter::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 +// ElemOpEmitter::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; + + const ParserAtom* 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, const ParserAtom* name, Kind kind); + NameOpEmitter(BytecodeEmitter* bce, const ParserAtom* name, + const NameLocation& loc, Kind kind); + + private: + MOZ_MUST_USE bool isCall() const { return kind_ == Kind::Call; } + + MOZ_MUST_USE bool isSimpleAssignment() const { + return kind_ == Kind::SimpleAssignment; + } + + MOZ_MUST_USE bool isCompoundAssignment() const { + return kind_ == Kind::CompoundAssignment; + } + + MOZ_MUST_USE bool isIncDec() const { return isPostIncDec() || isPreIncDec(); } + + MOZ_MUST_USE bool isPostIncDec() const { + return kind_ == Kind::PostIncrement || kind_ == Kind::PostDecrement; + } + + MOZ_MUST_USE bool isPreIncDec() const { + return kind_ == Kind::PreIncrement || kind_ == Kind::PreDecrement; + } + + MOZ_MUST_USE bool isInc() const { + return kind_ == Kind::PostIncrement || kind_ == Kind::PreIncrement; + } + + MOZ_MUST_USE bool isInitialize() const { return kind_ == Kind::Initialize; } + + public: + MOZ_MUST_USE bool emittedBindOp() const { return emittedBindOp_; } + + MOZ_MUST_USE const NameLocation& loc() const { return loc_; } + + MOZ_MUST_USE bool emitGet(); + MOZ_MUST_USE bool prepareForRhs(); + MOZ_MUST_USE bool emitAssignment(); + MOZ_MUST_USE bool emitIncDec(); +}; + +} /* 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..0c9546680c --- /dev/null +++ b/js/src/frontend/ObjLiteral.cpp @@ -0,0 +1,246 @@ +/* -*- 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" + +#include "frontend/CompilationInfo.h" // frontend::CompilationAtomCache +#include "frontend/ParserAtom.h" // frontend::ParserAtom, frontend::ParserAtomTable +#include "js/RootingAPI.h" +#include "vm/JSAtom.h" +#include "vm/JSObject.h" +#include "vm/JSONPrinter.h" // js::JSONPrinter +#include "vm/ObjectGroup.h" +#include "vm/Printer.h" // js::Fprinter + +#include "gc/ObjectKind-inl.h" +#include "vm/JSAtom-inl.h" +#include "vm/JSObject-inl.h" + +namespace js { + +static void InterpretObjLiteralValue(JSContext* cx, + frontend::CompilationAtomCache& atomCache, + const ObjLiteralInsn& insn, + JS::Value* valOut) { + switch (insn.getOp()) { + case ObjLiteralOpcode::ConstValue: + *valOut = insn.getConstValue(); + return; + case ObjLiteralOpcode::ConstAtom: { + frontend::TaggedParserAtomIndex index = insn.getAtomIndex(); + JSAtom* jsatom = atomCache.getExistingAtomAt(cx, index); + MOZ_ASSERT(jsatom); + *valOut = StringValue(jsatom); + return; + } + case ObjLiteralOpcode::Null: + *valOut = NullValue(); + return; + case ObjLiteralOpcode::Undefined: + *valOut = UndefinedValue(); + return; + case ObjLiteralOpcode::True: + *valOut = BooleanValue(true); + return; + case ObjLiteralOpcode::False: + *valOut = BooleanValue(false); + return; + default: + MOZ_CRASH("Unexpected object-literal instruction opcode"); + } +} + +static JSObject* InterpretObjLiteralObj( + JSContext* cx, frontend::CompilationAtomCache& atomCache, + const mozilla::Span<const uint8_t> literalInsns, ObjLiteralFlags flags) { + bool singleton = flags.contains(ObjLiteralFlag::Singleton); + + ObjLiteralReader reader(literalInsns); + ObjLiteralInsn insn; + + Rooted<IdValueVector> properties(cx, IdValueVector(cx)); + + // Compute property values and build the key/value-pair list. + while (reader.readInsn(&insn)) { + MOZ_ASSERT(insn.isValid()); + + jsid propId; + if (insn.getKey().isArrayIndex()) { + propId = INT_TO_JSID(insn.getKey().getArrayIndex()); + } else { + JSAtom* jsatom = + atomCache.getExistingAtomAt(cx, insn.getKey().getAtomIndex()); + MOZ_ASSERT(jsatom); + propId = AtomToId(jsatom); + } + + JS::Value propVal; + if (singleton) { + InterpretObjLiteralValue(cx, atomCache, insn, &propVal); + } + + if (!properties.emplaceBack(propId, propVal)) { + return nullptr; + } + } + + return NewPlainObjectWithProperties(cx, properties.begin(), + properties.length(), TenuredObject); +} + +static JSObject* InterpretObjLiteralArray( + JSContext* cx, frontend::CompilationAtomCache& atomCache, + const mozilla::Span<const uint8_t> literalInsns, ObjLiteralFlags flags) { + ObjLiteralReader reader(literalInsns); + ObjLiteralInsn insn; + + Rooted<ValueVector> elements(cx, ValueVector(cx)); + + while (reader.readInsn(&insn)) { + MOZ_ASSERT(insn.isValid()); + + JS::Value propVal; + InterpretObjLiteralValue(cx, atomCache, insn, &propVal); + if (!elements.append(propVal)) { + return nullptr; + } + } + + return NewDenseCopiedArray(cx, elements.length(), elements.begin(), + /* proto = */ nullptr, + NewObjectKind::TenuredObject); +} + +JSObject* InterpretObjLiteral(JSContext* cx, + frontend::CompilationAtomCache& atomCache, + const mozilla::Span<const uint8_t> literalInsns, + ObjLiteralFlags flags) { + return flags.contains(ObjLiteralFlag::Array) + ? InterpretObjLiteralArray(cx, atomCache, literalInsns, flags) + : InterpretObjLiteralObj(cx, atomCache, literalInsns, flags); +} + +#if defined(DEBUG) || defined(JS_JITSPEW) + +static void DumpObjLiteralFlagsItems(js::JSONPrinter& json, + ObjLiteralFlags flags) { + if (flags.contains(ObjLiteralFlag::Array)) { + json.value("Array"); + flags -= ObjLiteralFlag::Array; + } + if (flags.contains(ObjLiteralFlag::Singleton)) { + json.value("Singleton"); + flags -= ObjLiteralFlag::Singleton; + } + + if (!flags.isEmpty()) { + json.value("Unknown(%x)", flags.serialize()); + } +} + +static void DumpObjLiteral(js::JSONPrinter& json, + frontend::BaseCompilationStencil* stencil, + mozilla::Span<const uint8_t> code, + const ObjLiteralFlags& flags) { + 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::ConstAtom: { + 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(); +} + +void ObjLiteralWriter::dump() { + js::Fprinter out(stderr); + js::JSONPrinter json(out); + dump(json, nullptr); +} + +void ObjLiteralWriter::dump(js::JSONPrinter& json, + frontend::BaseCompilationStencil* stencil) { + json.beginObject(); + dumpFields(json, stencil); + json.endObject(); +} + +void ObjLiteralWriter::dumpFields(js::JSONPrinter& json, + frontend::BaseCompilationStencil* stencil) { + DumpObjLiteral(json, stencil, getCode(), flags_); +} + +void ObjLiteralStencil::dump() { + js::Fprinter out(stderr); + js::JSONPrinter json(out); + dump(json, nullptr); +} + +void ObjLiteralStencil::dump(js::JSONPrinter& json, + frontend::BaseCompilationStencil* stencil) { + json.beginObject(); + dumpFields(json, stencil); + json.endObject(); +} + +void ObjLiteralStencil::dumpFields(js::JSONPrinter& json, + frontend::BaseCompilationStencil* stencil) { + DumpObjLiteral(json, stencil, code_, flags_); +} + +#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..bc51bdd262 --- /dev/null +++ b/js/src/frontend/ObjLiteral.h @@ -0,0 +1,562 @@ +/* -*- 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/EndianUtils.h" +#include "mozilla/EnumSet.h" +#include "mozilla/Span.h" + +#include "frontend/ParserAtom.h" +#include "js/AllocPolicy.h" +#include "js/GCPolicyAPI.h" +#include "js/Value.h" +#include "js/Vector.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 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 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. 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 objects 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 objects. + * + * (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 object, when we can support the properties but not the + * keys. + * - To build the actual result object, when we support the properties and the + * keys 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 an object as an argument (held by the script data + * side-tables), allocates a new object with `undefined` property values but + * with a defined set of properties. The given object is used as a + * *template*. + * + * - 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 JSONPrinter; + +namespace frontend { +struct CompilationAtomCache; +struct BaseCompilationStencil; +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. + ConstAtom = 2, + Null = 3, + Undefined = 4, + True = 5, + False = 6, + + MAX = False, +}; + +// 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 is an array. + Array = 1, + + // If set, this is an object literal in a singleton context and property + // values are included. See also JSOp::Object. + Singleton = 2, +}; + +using ObjLiteralFlags = mozilla::EnumSet<ObjLiteralFlag>; + +inline bool ObjLiteralOpcodeHasValueArg(ObjLiteralOpcode op) { + return op == ObjLiteralOpcode::ConstValue; +} + +inline bool ObjLiteralOpcodeHasAtomArg(ObjLiteralOpcode op) { + return op == ObjLiteralOpcode::ConstAtom; +} + +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<uint8_t, 64, js::SystemAllocPolicy>; + + protected: + CodeVector code_; + + public: + ObjLiteralWriterBase() = default; + + uint32_t curOffset() const { return code_.length(); } + + private: + MOZ_MUST_USE bool pushByte(JSContext* cx, uint8_t data) { + if (!code_.append(data)) { + js::ReportOutOfMemory(cx); + return false; + } + return true; + } + + MOZ_MUST_USE bool prepareBytes(JSContext* cx, size_t len, uint8_t** p) { + size_t offset = code_.length(); + if (!code_.growByUninitialized(len)) { + js::ReportOutOfMemory(cx); + return false; + } + *p = &code_[offset]; + return true; + } + + template <typename T> + MOZ_MUST_USE bool pushRawData(JSContext* cx, T data) { + uint8_t* p = nullptr; + if (!prepareBytes(cx, sizeof(T), &p)) { + return false; + } + mozilla::NativeEndian::copyAndSwapToLittleEndian(reinterpret_cast<void*>(p), + &data, 1); + return true; + } + + public: + MOZ_MUST_USE bool pushOpAndName(JSContext* cx, ObjLiteralOpcode op, + ObjLiteralKey key) { + uint8_t opdata = static_cast<uint8_t>(op); + uint32_t data = key.rawIndex() | (key.isArrayIndex() ? INDEXED_PROP : 0); + return pushByte(cx, opdata) && pushRawData(cx, data); + } + + MOZ_MUST_USE bool pushValueArg(JSContext* cx, const JS::Value& value) { + MOZ_ASSERT(value.isNumber() || value.isNullOrUndefined() || + value.isBoolean()); + uint64_t data = value.asRawBits(); + return pushRawData(cx, data); + } + + MOZ_MUST_USE bool pushAtomArg(JSContext* cx, + frontend::TaggedParserAtomIndex atomIndex) { + return pushRawData(cx, *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; + + mozilla::Span<const uint8_t> getCode() const { return code_; } + ObjLiteralFlags getFlags() const { return flags_; } + + void beginObject(ObjLiteralFlags flags) { flags_ = flags; } + void setPropName(const frontend::ParserAtom* propName) { + // Only valid in object-mode. + MOZ_ASSERT(!flags_.contains(ObjLiteralFlag::Array)); + propName->markUsedByStencil(); + nextKey_ = ObjLiteralKey::fromPropName(propName->toIndex()); + } + void setPropIndex(uint32_t propIndex) { + // Only valid in object-mode. + MOZ_ASSERT(!flags_.contains(ObjLiteralFlag::Array)); + MOZ_ASSERT(propIndex <= ATOM_INDEX_MASK); + nextKey_ = ObjLiteralKey::fromArrayIndex(propIndex); + } + void beginDenseArrayElements() { + // Only valid in array-mode. + MOZ_ASSERT(flags_.contains(ObjLiteralFlag::Array)); + // Dense array element sequences do not use the keys; the indices are + // implicit. + nextKey_ = ObjLiteralKey::none(); + } + + MOZ_MUST_USE bool propWithConstNumericValue(JSContext* cx, + const JS::Value& value) { + MOZ_ASSERT(value.isNumber()); + return pushOpAndName(cx, ObjLiteralOpcode::ConstValue, nextKey_) && + pushValueArg(cx, value); + } + MOZ_MUST_USE bool propWithAtomValue(JSContext* cx, + const frontend::ParserAtom* value) { + value->markUsedByStencil(); + return pushOpAndName(cx, ObjLiteralOpcode::ConstAtom, nextKey_) && + pushAtomArg(cx, value->toIndex()); + } + MOZ_MUST_USE bool propWithNullValue(JSContext* cx) { + return pushOpAndName(cx, ObjLiteralOpcode::Null, nextKey_); + } + MOZ_MUST_USE bool propWithUndefinedValue(JSContext* cx) { + return pushOpAndName(cx, ObjLiteralOpcode::Undefined, nextKey_); + } + MOZ_MUST_USE bool propWithTrueValue(JSContext* cx) { + return pushOpAndName(cx, ObjLiteralOpcode::True, nextKey_); + } + MOZ_MUST_USE bool propWithFalseValue(JSContext* cx) { + return pushOpAndName(cx, ObjLiteralOpcode::False, nextKey_); + } + + static bool arrayIndexInRange(int32_t i) { + return i >= 0 && static_cast<uint32_t>(i) <= ATOM_INDEX_MASK; + } + +#if defined(DEBUG) || defined(JS_JITSPEW) + void dump(); + void dump(JSONPrinter& json, frontend::BaseCompilationStencil* stencil); + void dumpFields(JSONPrinter& json, frontend::BaseCompilationStencil* stencil); +#endif + + private: + ObjLiteralFlags flags_; + ObjLiteralKey nextKey_; +}; + +struct ObjLiteralReaderBase { + private: + mozilla::Span<const uint8_t> data_; + size_t cursor_; + + MOZ_MUST_USE bool readByte(uint8_t* b) { + if (cursor_ + 1 > data_.Length()) { + return false; + } + *b = *data_.From(cursor_).data(); + cursor_ += 1; + return true; + } + + MOZ_MUST_USE 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 <typename T> + MOZ_MUST_USE bool readRawData(T* data) { + const uint8_t* p = nullptr; + if (!readBytes(sizeof(T), &p)) { + return false; + } + mozilla::NativeEndian::copyAndSwapFromLittleEndian( + data, reinterpret_cast<const void*>(p), 1); + return true; + } + + public: + explicit ObjLiteralReaderBase(mozilla::Span<const uint8_t> data) + : data_(data), cursor_(0) {} + + MOZ_MUST_USE bool readOpAndKey(ObjLiteralOpcode* op, ObjLiteralKey* key) { + uint8_t opbyte; + if (!readByte(&opbyte)) { + return false; + } + if (MOZ_UNLIKELY(opbyte > static_cast<uint8_t>(ObjLiteralOpcode::MAX))) { + return false; + } + *op = static_cast<ObjLiteralOpcode>(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; + } + + MOZ_MUST_USE bool readValueArg(JS::Value* value) { + uint64_t data; + if (!readRawData(&data)) { + return false; + } + *value = JS::Value::fromRawBits(data); + return true; + } + + MOZ_MUST_USE bool readAtomArg(frontend::TaggedParserAtomIndex* atomIndex) { + return readRawData(atomIndex->rawData()); + } +}; + +// 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<const uint8_t> data) + : ObjLiteralReaderBase(data) {} + + MOZ_MUST_USE 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; + } +}; + +JSObject* InterpretObjLiteral(JSContext* cx, + frontend::CompilationAtomCache& atomCache, + const mozilla::Span<const uint8_t> insns, + ObjLiteralFlags flags); + +class ObjLiteralStencil { + friend class frontend::StencilXDR; + + mozilla::Span<uint8_t> code_; + ObjLiteralFlags flags_; + + public: + ObjLiteralStencil() = default; + + ObjLiteralStencil(uint8_t* code, size_t length, const ObjLiteralFlags& flags) + : code_(mozilla::Span(code, length)), flags_(flags) {} + + JSObject* create(JSContext* cx, + frontend::CompilationAtomCache& atomCache) const; + +#if defined(DEBUG) || defined(JS_JITSPEW) + void dump(); + void dump(JSONPrinter& json, frontend::BaseCompilationStencil* stencil); + void dumpFields(JSONPrinter& json, frontend::BaseCompilationStencil* stencil); + +#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..96bcad8dc6 --- /dev/null +++ b/js/src/frontend/ObjectEmitter.cpp @@ -0,0 +1,913 @@ +/* -*- 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 "gc/AllocKind.h" // AllocKind +#include "js/Id.h" // jsid +#include "js/Value.h" // UndefinedHandleValue +#include "vm/BytecodeUtil.h" // IsHiddenInitOp +#include "vm/FunctionPrefixKind.h" // FunctionPrefixKind +#include "vm/JSContext.h" // JSContext +#include "vm/NativeObject.h" // NativeDefineDataProperty +#include "vm/ObjectGroup.h" // TenuredObject +#include "vm/Opcodes.h" // JSOp +#include "vm/Runtime.h" // cx->parserNames() +#include "vm/SharedStencil.h" // GCThingIndex + +#include "gc/ObjectKind-inl.h" // GetGCObjectKind +#include "vm/JSAtom-inl.h" // AtomToId +#include "vm/JSObject-inl.h" // NewBuiltinClassInstance + +using namespace js; +using namespace js::frontend; + +using mozilla::Maybe; + +PropertyEmitter::PropertyEmitter(BytecodeEmitter* bce) : bce_(bce) {} + +bool PropertyEmitter::prepareForProtoValue(const Maybe<uint32_t>& keyPos) { + MOZ_ASSERT(propertyState_ == PropertyState::Start || + propertyState_ == PropertyState::Init); + + // [stack] CTOR? OBJ CTOR? + + if (keyPos) { + 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( + const Maybe<uint32_t>& spreadPos) { + MOZ_ASSERT(propertyState_ == PropertyState::Start || + propertyState_ == PropertyState::Init); + + // [stack] OBJ + + if (spreadPos) { + 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( + const Maybe<uint32_t>& keyPos, bool isStatic, bool isIndexOrComputed) { + isStatic_ = isStatic; + isIndexOrComputed_ = isIndexOrComputed; + + // [stack] CTOR? OBJ + + if (keyPos) { + 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::prepareForPropValue(const Maybe<uint32_t>& keyPos, + Kind kind /* = Kind::Prototype */) { + 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( + const Maybe<uint32_t>& keyPos, Kind kind /* = Kind::Prototype */) { + 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( + const Maybe<uint32_t>& keyPos, Kind kind /* = Kind::Prototype */) { + 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::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::IndexValue) { + propertyState_ = PropertyState::InitHomeObjForIndex; + } else { + propertyState_ = PropertyState::InitHomeObjForComputed; + } +#endif + return true; +} + +bool PropertyEmitter::emitInit(AccessorType accessorType, + const ParserAtom* 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); + default: + 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); + default: + MOZ_CRASH("Invalid op"); + } +} + +bool PropertyEmitter::emitInit(JSOp op, const ParserAtom* 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::emitInitIndexOrComputed(JSOp op) { + MOZ_ASSERT(propertyState_ == PropertyState::IndexValue || + propertyState_ == PropertyState::InitHomeObjForIndex || + propertyState_ == PropertyState::ComputedValue || + propertyState_ == PropertyState::InitHomeObjForComputed); + + MOZ_ASSERT(op == JSOp::InitElem || op == JSOp::InitHiddenElem || + 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), + name_(nullptr), + nameForAnonymousClass_(nullptr) { + 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(LexicalScope::ParserData* scopeBindings) { + MOZ_ASSERT(propertyState_ == PropertyState::Start); + MOZ_ASSERT(classState_ == ClassState::Start || + classState_ == ClassState::Scope); + + bodyTdzCache_.emplace(bce_); + + bodyScope_.emplace(bce_); + if (!bodyScope_->enterLexical(bce_, ScopeKind::ClassBody, scopeBindings)) { + return false; + } + +#ifdef DEBUG + classState_ = ClassState::BodyScope; +#endif + + return true; +} + +bool ClassEmitter::emitClass(const ParserAtom* name, + const ParserAtom* 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(const ParserAtom* name, + const ParserAtom* 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, bce_->cx->parserNames().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::emitInitDefaultConstructor(uint32_t classStart, + uint32_t classEnd) { + MOZ_ASSERT(propertyState_ == PropertyState::Start); + MOZ_ASSERT(classState_ == ClassState::Class); + + const ParserAtom* className = name_; + if (!className) { + if (nameForAnonymousClass_) { + className = nameForAnonymousClass_; + } else { + className = bce_->cx->parserNames().empty; + } + } + + GCThingIndex atomIndex; + if (!bce_->makeAtomIndex(className, &atomIndex)) { + return false; + } + + // The default constructor opcodes below will synthesize new scripts with + // line/column at start of class definition. + if (!bce_->updateSourceCoordNotes(classStart)) { + return false; + } + + // In the case of default class constructors, emit the start and end + // offsets in the source buffer as source notes so that when we + // actually make the constructor during execution, we can give it the + // correct toString output. + BytecodeOffset off; + if (isDerived_) { + // [stack] HERITAGE PROTO + if (!bce_->emitN(JSOp::DerivedConstructor, 12, &off)) { + // [stack] HOMEOBJ CTOR + return false; + } + } else { + // [stack] HOMEOBJ + if (!bce_->emitN(JSOp::ClassConstructor, 12, &off)) { + // [stack] HOMEOBJ CTOR + return false; + } + } + SetClassConstructorOperands(bce_->bytecodeSection().code(off), atomIndex, + classStart, classEnd); + + 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, + bce_->cx->parserNames().prototype)) { + // [stack] NAME? CTOR HOMEOBJ CTOR + return false; + } + if (!bce_->emitAtomOp(JSOp::InitHiddenProp, + bce_->cx->parserNames().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. + const ParserName* initializers = + isStatic ? bce_->cx->parserNames().dotStaticInitializers + : bce_->cx->parserNames().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..9af261461d --- /dev/null +++ b/js/src/frontend/ObjectEmitter.h @@ -0,0 +1,815 @@ +/* -*- 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_MUST_USE, MOZ_STACK_CLASS, MOZ_ALWAYS_INLINE, MOZ_RAII +#include "mozilla/Maybe.h" // Maybe + +#include <stddef.h> // size_t +#include <stdint.h> // uint32_t + +#include "frontend/BytecodeOffset.h" // BytecodeOffset +#include "frontend/EmitterScope.h" // EmitterScope +#include "frontend/NameOpEmitter.h" // NameOpEmitter +#include "frontend/ParseNode.h" // AccessorType +#include "frontend/TDZCheckCache.h" // TDZCheckCache +#include "vm/BytecodeUtil.h" // JSOp +#include "vm/NativeObject.h" // PlainObject +#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 | + // | +------------------------------------------------------>+ + // | ^ + // | [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 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 + MOZ_MUST_USE bool prepareForProtoValue( + const mozilla::Maybe<uint32_t>& keyPos); + MOZ_MUST_USE bool emitMutateProto(); + + // { ...obj } + // ^ + // | + // spreadPos + MOZ_MUST_USE bool prepareForSpreadOperand( + const mozilla::Maybe<uint32_t>& spreadPos); + MOZ_MUST_USE bool emitSpread(); + + // { key: value } + // ^ + // | + // keyPos + MOZ_MUST_USE bool prepareForPropValue(const mozilla::Maybe<uint32_t>& keyPos, + Kind kind = Kind::Prototype); + + // { 1: value } + // ^ + // | + // keyPos + MOZ_MUST_USE bool prepareForIndexPropKey( + const mozilla::Maybe<uint32_t>& keyPos, Kind kind = Kind::Prototype); + MOZ_MUST_USE bool prepareForIndexPropValue(); + + // { [ key ]: value } + // ^ + // | + // keyPos + MOZ_MUST_USE bool prepareForComputedPropKey( + const mozilla::Maybe<uint32_t>& keyPos, Kind kind = Kind::Prototype); + MOZ_MUST_USE bool prepareForComputedPropValue(); + + MOZ_MUST_USE bool emitInitHomeObject(); + + // @param key + // Property key + MOZ_MUST_USE bool emitInit(AccessorType accessorType, const ParserAtom* key); + + MOZ_MUST_USE bool emitInitIndexOrComputed(AccessorType accessorType); + + private: + MOZ_MUST_USE MOZ_ALWAYS_INLINE bool prepareForProp( + const mozilla::Maybe<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 + MOZ_MUST_USE bool emitInit(JSOp op, const ParserAtom* key); + MOZ_MUST_USE bool emitInitIndexOrComputed(JSOp op); + + MOZ_MUST_USE 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(Some(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(Some(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(Some(offset_of_prop)); +// emit(function_for_getter); +// oe.emitInitGetter(atom_of_prop); +// +// oe.prepareForPropValue(Some(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(Some(offset_of_prop)); +// emit(1); +// oe.prepareForIndexPropValue(); +// emit(10); +// oe.emitInitIndexedProp(); +// +// oe.prepareForIndexPropKey(Some(offset_of_opening_bracket)); +// emit(2); +// oe.prepareForIndexPropValue(); +// emit(function_for_getter); +// oe.emitInitIndexGetter(); +// +// oe.prepareForIndexPropKey(Some(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(Some(offset_of_opening_bracket)); +// emit(prop1); +// oe.prepareForComputedPropValue(); +// emit(10); +// oe.emitInitComputedProp(); +// +// oe.prepareForComputedPropKey(Some(offset_of_opening_bracket)); +// emit(prop2); +// oe.prepareForComputedPropValue(); +// emit(function_for_getter); +// oe.emitInitComputedGetter(); +// +// oe.prepareForComputedPropKey(Some(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(Some(offset_of___proto__)); +// emit(obj); +// oe.emitMutateProto(); +// oe.emitEnd(); +// +// `{ ...obj }` +// ObjectEmitter oe(this); +// oe.emitObject(1); +// oe.prepareForSpreadOperand(Some(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); + + MOZ_MUST_USE bool emitObject(size_t propertyCount); + // Same as `emitObject()`, but start with an empty template object already on + // the stack. + MOZ_MUST_USE bool emitObjectWithTemplateOnStack(); + MOZ_MUST_USE 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 {}` +// ClassEmitter ce(this); +// ce.emitScope(scopeBindings); +// ce.emitClass(nullptr, nullptr, false); +// +// ce.emitInitDefaultConstructor(offset_of_class, +// offset_of_closing_bracket); +// +// ce.emitEnd(ClassEmitter::Kind::Expression); +// +// `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/emitInitDefaultConstructor +// ce.prepareForPropValue(Some(offset_of_m)); +// emit(function_for_m); +// ce.emitInitProp(atom_of_m); +// +// `m() { super.f(); }` in class +// // after emitInitConstructor/emitInitDefaultConstructor +// ce.prepareForPropValue(Some(offset_of_m)); +// emit(function_for_m); +// ce.emitInitHomeObject(); +// ce.emitInitProp(atom_of_m); +// +// `async m() { super.f(); }` in class +// // after emitInitConstructor/emitInitDefaultConstructor +// ce.prepareForPropValue(Some(offset_of_m)); +// emit(function_for_m); +// ce.emitInitHomeObject(); +// ce.emitInitProp(atom_of_m); +// +// `get p() { super.f(); }` in class +// // after emitInitConstructor/emitInitDefaultConstructor +// ce.prepareForPropValue(Some(offset_of_p)); +// emit(function_for_p); +// ce.emitInitHomeObject(); +// ce.emitInitGetter(atom_of_m); +// +// `static m() {}` in class +// // after emitInitConstructor/emitInitDefaultConstructor +// ce.prepareForPropValue(Some(offset_of_m), +// PropertyEmitter::Kind::Static); +// emit(function_for_m); +// ce.emitInitProp(atom_of_m); +// +// `static get [p]() { super.f(); }` in class +// // after emitInitConstructor/emitInitDefaultConstructor +// ce.prepareForComputedPropValue(Some(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 <BaseExpression> { + // 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 <constructor> { + // if defined <BaseExpression> { + // cons = DefineMethod(<constructor>, proto=homeObject, + // funProto=funProto); + // } else { + // cons = DefineMethod(<constructor>, proto=homeObject); + // } + // } else { + // if defined <BaseExpression> { + // cons = DefaultDerivedConstructor(proto=homeObject, + // funProto=funProto); + // } else { + // cons = DefaultConstructor(proto=homeObject); + // } + // } + // + // cons.prototype = homeObject; + // homeObject.constructor = cons; + // + // EmitPropertyList(...) + + bool isDerived_ = false; + + mozilla::Maybe<TDZCheckCache> tdzCache_; + mozilla::Maybe<EmitterScope> innerScope_; + mozilla::Maybe<TDZCheckCache> bodyTdzCache_; + mozilla::Maybe<EmitterScope> 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 |-+ + // | ^ +-----------------+ | + // | emitInitDefaultConstructor | | + // +----------------------------+ | + // | + // +-----------------------------------------------------+ + // | + // | 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 or emitInitDefaultConstructor. + 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 + + const ParserAtom* name_; + const ParserAtom* nameForAnonymousClass_; + bool hasNameOnStack_ = false; + mozilla::Maybe<NameOpEmitter> initializersAssignment_; + size_t initializerIndex_ = 0; + + public: + explicit ClassEmitter(BytecodeEmitter* bce); + + bool emitScope(LexicalScope::ParserData* scopeBindings); + bool emitBodyScope(LexicalScope::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) + MOZ_MUST_USE bool emitClass(const ParserAtom* name, + const ParserAtom* nameForAnonymousClass, + bool hasNameOnStack); + MOZ_MUST_USE bool emitDerivedClass(const ParserAtom* name, + const ParserAtom* nameForAnonymousClass, + bool hasNameOnStack); + + // @param needsHomeObject + // True if the constructor contains `super.foo` + MOZ_MUST_USE bool emitInitConstructor(bool needsHomeObject); + + // Parameters are the offset in the source code for each character below: + // + // class X { foo() {} } + // ^ ^ + // | | + // | classEnd + // | + // classStart + // + MOZ_MUST_USE bool emitInitDefaultConstructor(uint32_t classStart, + uint32_t classEnd); + + MOZ_MUST_USE bool prepareForMemberInitializers(size_t numInitializers, + bool isStatic); + MOZ_MUST_USE bool prepareForMemberInitializer(); + MOZ_MUST_USE bool emitMemberInitializerHomeObject(bool isStatic); + MOZ_MUST_USE bool emitStoreMemberInitializer(); + MOZ_MUST_USE bool emitMemberInitializersEnd(); + + MOZ_MUST_USE bool emitBinding(); + + MOZ_MUST_USE bool emitEnd(Kind kind); + + private: + MOZ_MUST_USE 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..2a60d3a6ea --- /dev/null +++ b/js/src/frontend/OptionalEmitter.cpp @@ -0,0 +1,163 @@ +/* -*- 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 "frontend/SharedContext.h" +#include "vm/Opcodes.h" +#include "vm/StringType.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()); + InternalIfEmitter ifEmitter(bce_); + if (!bce_->emitPushNotUndefinedOrNull()) { + // [stack] OBJ NOT-UNDEFINED-OR-NULL + return false; + } + + if (!bce_->emit1(JSOp::Not)) { + // [stack] OBJ UNDEFINED-OR-NULL + return false; + } + + if (!ifEmitter.emitThen()) { + return false; + } + + if (!bce_->emitJump(JSOp::Goto, &jumpShortCircuit_)) { + // [stack] UNDEFINED-OR-NULL + return false; + } + + if (!ifEmitter.emitEnd()) { + 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_->emitPushNotUndefinedOrNull()) { + // [stack] THIS CALLEE NOT-UNDEFINED-OR-NULL + return false; + } + + if (!bce_->emit1(JSOp::Not)) { + // [stack] THIS CALLEE UNDEFINED-OR-NULL + return false; + } + + if (!ifEmitter.emitThen()) { + return false; + } + + if (!bce_->emit1(JSOp::Pop)) { + // [stack] THIS + return false; + } + + if (!bce_->emitJump(JSOp::Goto, &jumpShortCircuit_)) { + // [stack] UNDEFINED-OR-NULL + 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] # if call + // [stack] CALLEE THIS + // [stack] # otherwise, if defined + // [stack] VAL + // [stack] # otherwise + // [stack] UNDEFINED-OR-NULL + return false; + } + + if (!bce_->emitJumpTargetAndPatch(jumpShortCircuit_)) { + // [stack] UNDEFINED-OR-NULL + 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..1ddcef1ff2 --- /dev/null +++ b/js/src/frontend/OptionalEmitter.h @@ -0,0 +1,219 @@ +/* -*- 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/IfEmitter.h" // IfEmitter, InternalIfEmitter, CondEmitter +#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, Some(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, Some(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, Some(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, Some(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, Some(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 + }; + + MOZ_MUST_USE bool emitJumpShortCircuit(); + MOZ_MUST_USE 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) + MOZ_MUST_USE 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..2d1d404ddc --- /dev/null +++ b/js/src/frontend/ParseContext-inl.h @@ -0,0 +1,180 @@ +/* -*- 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 "mozilla/ResultVariant.h" + +#include "frontend/Parser.h" +#include "vm/JSContext.h" + +namespace js { +namespace frontend { + +template <> +inline bool ParseContext::Statement::is<ParseContext::LabelStatement>() const { + return kind_ == StatementKind::Label; +} + +template <> +inline bool ParseContext::Statement::is<ParseContext::ClassStatement>() const { + return kind_ == StatementKind::Class; +} + +template <typename T> +inline T& ParseContext::Statement::as() { + MOZ_ASSERT(is<T>()); + return static_cast<T&>(*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<Scope>(&parser->pc_->innermostScope_), + declared_(parser->cx_->frontendCollectionPool()), + possibleAnnexBFunctionBoxes_(parser->cx_->frontendCollectionPool()), + id_(parser->usedNames_.nextScopeId()) {} + +inline ParseContext::Scope::Scope(JSContext* cx, ParseContext* pc, + UsedNameTracker& usedNames) + : Nestable<Scope>(&pc->innermostScope_), + declared_(cx->frontendCollectionPool()), + possibleAnnexBFunctionBoxes_(cx->frontendCollectionPool()), + id_(usedNames.nextScopeId()) {} + +inline ParseContext::VarScope::VarScope(ParserBase* parser) : Scope(parser) { + useAsVarScope(parser->pc_); +} + +inline ParseContext::VarScope::VarScope(JSContext* cx, ParseContext* pc, + UsedNameTracker& usedNames) + : Scope(cx, pc, usedNames) { + useAsVarScope(pc); +} + +inline JS::Result<Ok, ParseContext::BreakStatementError> +ParseContext::checkBreakStatement(const ParserName* label) { + // Labeled 'break' statements target the nearest labeled statements (could + // be any kind) with the same label. Unlabeled 'break' statements target + // the innermost loop or switch statement. + if (label) { + auto hasSameLabel = [&label](ParseContext::LabelStatement* stmt) { + MOZ_ASSERT(stmt); + return stmt->label() == label; + }; + + if (!findInnermostStatement<ParseContext::LabelStatement>(hasSameLabel)) { + return mozilla::Err(ParseContext::BreakStatementError::LabelNotFound); + } + + } else { + auto isBreakTarget = [](ParseContext::Statement* stmt) { + return StatementKindIsUnlabeledBreakTarget(stmt->kind()); + }; + + if (!findInnermostStatement(isBreakTarget)) { + return mozilla::Err(ParseContext::BreakStatementError::ToughBreak); + } + } + + return Ok(); +} + +inline JS::Result<Ok, ParseContext::ContinueStatementError> +ParseContext::checkContinueStatement(const ParserName* label) { + // Labeled 'continue' statements target the nearest labeled loop + // statements with the same label. Unlabeled 'continue' statements target + // the innermost loop statement. + auto isLoop = [](ParseContext::Statement* stmt) { + MOZ_ASSERT(stmt); + return StatementKindIsLoop(stmt->kind()); + }; + + if (!label) { + // Unlabeled statement: we target the innermost loop, so make sure that + // there is an innermost loop. + if (!findInnermostStatement(isLoop)) { + return mozilla::Err(ParseContext::ContinueStatementError::NotInALoop); + } + return Ok(); + } + + // Labeled statement: targest the nearest labeled loop with the same label. + ParseContext::Statement* stmt = innermostStatement(); + bool foundLoop = false; // True if we have encountered at least one loop. + + for (;;) { + stmt = ParseContext::Statement::findNearest(stmt, isLoop); + if (!stmt) { + return foundLoop + ? mozilla::Err( + ParseContext::ContinueStatementError::LabelNotFound) + : mozilla::Err( + ParseContext::ContinueStatementError::NotInALoop); + } + + foundLoop = true; + + // Is it labeled by our label? + stmt = stmt->enclosing(); + while (stmt && stmt->is<ParseContext::LabelStatement>()) { + if (stmt->as<ParseContext::LabelStatement>().label() == label) { + return Ok(); + } + + stmt = stmt->enclosing(); + } + } +} + +template <typename DeclaredNamePtrT> +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..498dacd0c3 --- /dev/null +++ b/js/src/frontend/ParseContext.cpp @@ -0,0 +1,720 @@ +/* -*- 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 "js/friend/ErrorMessages.h" // JSMSG_* + +#include "vm/EnvironmentObject-inl.h" + +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"; + } + + 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(JSContext* cx, const ParserAtom* name, + NameVisibility visibility, uint32_t scriptId, + uint32_t scopeId, + mozilla::Maybe<TokenPos> tokenPosition) { + if (UsedNameMap::AddPtr p = map_.lookupForAdd(name)) { + 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(cx, 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<UnboundPrivateName, 8>& 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( + JSContext* cx, mozilla::Maybe<UnboundPrivateName>& maybeUnboundName) { + // We never saw any private names, so can just return early + if (!hasPrivateNames_) { + return true; + } + + Vector<UnboundPrivateName, 8> unboundPrivateNames(cx); + 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) { + JSContext* cx = pc->sc()->cx_; + + fprintf(stdout, "ParseScope %p", this); + + fprintf(stdout, "\n decls:\n"); + for (DeclaredNameMap::Range r = declared_->all(); !r.empty(); r.popFront()) { + UniqueChars bytes = QuoteString(cx, r.front().key()); + if (!bytes) { + 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()->cx_)) { + return false; + } + } + + return maybeReportOOM(pc, possibleAnnexBFunctionBoxes_->append(funbox)); +} + +bool ParseContext::Scope::propagateAndMarkAnnexBFunctionBoxes( + ParseContext* pc) { + // 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<DeclarationKind> redeclaredKind; + uint32_t unused; + for (FunctionBox* funbox : *possibleAnnexBFunctionBoxes_) { + bool annexBApplies; + if (!pc->computeAnnexBAppliesToLexicalFunctionInInnermostScope( + funbox, &annexBApplies)) { + return false; + } + if (annexBApplies) { + const ParserName* name = funbox->explicitName()->asName(); + if (!pc->tryDeclareVar( + name, 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, &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)); + const ParserAtom* 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()) { + DeclaredNamePtr p = declared_->lookup(r.front().key()); + 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(JSContext* cx, ParseContext*& parent, + SharedContext* sc, ErrorReporter& errorReporter, + CompilationState& compilationState, + Directives* newDirectives, bool isFull) + : Nestable<ParseContext>(&parent), + traceLog_(sc->cx_, + isFull ? TraceLogger_ParsingFull : TraceLogger_ParsingSyntax, + errorReporter), + sc_(sc), + errorReporter_(errorReporter), + innermostStatement_(nullptr), + innermostScope_(nullptr), + varScope_(nullptr), + positionalFormalParameterNames_(cx->frontendCollectionPool()), + closedOverBindingsForLazy_(cx->frontendCollectionPool()), + innerFunctionIndexesForLazy(cx), + newDirectives(newDirectives), + lastYieldOffset(NoYieldOffset), + lastAwaitOffset(NoAwaitOffset), + scriptId_(compilationState.usedNames.nextScriptId()), + superScopeNeedsHomeObject_(false) { + if (isFunctionBox()) { + if (functionBox()->isNamedLambda()) { + namedLambdaScope_.emplace(cx, parent, compilationState.usedNames); + } + functionScope_.emplace(cx, parent, compilationState.usedNames); + } +} + +bool ParseContext::init() { + if (scriptId_ == UINT32_MAX) { + errorReporter_.errorNoOffset(JSMSG_NEED_DIET, js_script_str); + return false; + } + + JSContext* cx = sc()->cx_; + + if (isFunctionBox()) { + // Named lambdas always need a binding for their own name. If this + // binding is closed over when we finish parsing the function in + // 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(cx)) { + return false; + } + } + + if (!closedOverBindingsForLazy_.acquire(cx)) { + return false; + } + + return true; +} + +bool ParseContext::computeAnnexBAppliesToLexicalFunctionInInnermostScope( + FunctionBox* funbox, bool* annexBApplies) { + MOZ_ASSERT(!sc()->strict()); + + const ParserName* name = funbox->explicitName()->asName(); + Maybe<DeclarationKind> redeclaredKind; + if (!isVarRedeclaredInInnermostScope( + name, 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(sc()->cx_, name->toIndex())); + } + } + } + } + + // 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( + const ParserName* name, DeclarationKind kind, + mozilla::Maybe<DeclarationKind>* out) { + uint32_t unused; + return tryDeclareVarHelper<DryRunInnermostScopeOnly>( + name, kind, DeclaredNameInfo::npos, out, &unused); +} + +bool ParseContext::isVarRedeclaredInEval(const ParserName* name, + DeclarationKind kind, + Maybe<DeclarationKind>* out) { + MOZ_ASSERT(out); + MOZ_ASSERT(DeclarationKindIsVar(kind)); + MOZ_ASSERT(sc()->isEvalContext()); + + // TODO-Stencil: After scope snapshotting, this can be done away with. + JSAtom* nameAtom = name->toJSAtom(sc()->cx_, sc()->stencil().input.atomCache); + if (!nameAtom) { + return false; + } + + // In the case of eval, we also need to check enclosing VM scopes to see + // if the var declaration is allowed in the context. + js::Scope* enclosingScope = sc()->stencil().input.enclosingScope; + js::Scope* varScope = EvalScope::nearestVarScopeForDirectEval(enclosingScope); + MOZ_ASSERT(varScope); + for (ScopeIter si(enclosingScope); si; si++) { + for (js::BindingIter bi(si.scope()); bi; bi++) { + if (bi.name() != nameAtom) { + continue; + } + + 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) { + *out = Some(ScopeKindIsCatch(si.kind()) + ? DeclarationKind::CatchParameter + : DeclarationKind::Let); + return true; + } + break; + } + + case BindingKind::Const: + *out = Some(DeclarationKind::Const); + return true; + + case BindingKind::Import: + case BindingKind::FormalParameter: + case BindingKind::Var: + case BindingKind::NamedLambdaCallee: + break; + } + } + + if (si.scope() == varScope) { + break; + } + } + + *out = Nothing(); + return true; +} + +bool ParseContext::tryDeclareVar(const ParserName* name, DeclarationKind kind, + uint32_t beginPos, + Maybe<DeclarationKind>* redeclaredKind, + uint32_t* prevPos) { + return tryDeclareVarHelper<NotDryRun>(name, kind, beginPos, redeclaredKind, + prevPos); +} + +template <ParseContext::DryRunOption dryRunOption> +bool ParseContext::tryDeclareVarHelper(const ParserName* name, + DeclarationKind kind, uint32_t beginPos, + Maybe<DeclarationKind>* 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, kind, redeclaredKind)) { + return false; + } + // We don't have position information at runtime. + *prevPos = DeclaredNameInfo::npos; + } + + return true; +} + +bool ParseContext::hasUsedName(const UsedNameTracker& usedNames, + const ParserName* name) { + if (auto p = usedNames.lookup(name)) { + return p->value().isUsedInScript(scriptId()); + } + return false; +} + +bool ParseContext::hasUsedFunctionSpecialName(const UsedNameTracker& usedNames, + const ParserName* name) { + MOZ_ASSERT(name == sc()->cx_->parserNames().arguments || + name == sc()->cx_->parserNames().dotThis); + 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. + FunctionBox* funbox = functionBox(); + const ParserName* dotThis = sc()->cx_->parserNames().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'. + const ParserName* argumentsName = sc()->cx_->parserNames().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) { + // There is an 'arguments' binding. Is the arguments object definitely + // needed? + // + // Also see the flags' comments in ContextFlags. + funbox->setArgumentsHasVarBinding(); + + // Dynamic scope access destroys all hope of optimization. + if (sc()->bindingsAccessedDynamically()) { + funbox->setAlwaysNeedsArgsObj(); + } + } + + 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(); + const ParserName* dotGenerator = sc()->cx_->parserNames().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(); + const ParserName* dotGenerator = sc()->cx_->parserNames().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..b24f66de60 --- /dev/null +++ b/js/src/frontend/ParseContext.h @@ -0,0 +1,593 @@ +/* -*- 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/BytecodeCompiler.h" +#include "frontend/CompilationInfo.h" +#include "frontend/ErrorReporter.h" +#include "frontend/ModuleSharedContext.h" +#include "frontend/NameAnalysisTypes.h" // DeclaredNameInfo +#include "frontend/NameCollections.h" +#include "frontend/ScriptIndex.h" // ScriptIndex +#include "frontend/SharedContext.h" +#include "frontend/UsedNameTracker.h" +#include "js/friend/ErrorMessages.h" // JSMSG_* +#include "vm/GeneratorAndAsyncKind.h" // js::GeneratorKind, js::FunctionAsyncKind +#include "vm/GeneratorObject.h" // js::AbstractGeneratorObject::FixedSlotLimit + +namespace js { + +namespace frontend { + +class ParserBase; + +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<ParseContext> { + 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<Statement> { + StatementKind kind_; + + public: + using Nestable<Statement>::enclosing; + using Nestable<Statement>::findNearest; + + Statement(ParseContext* pc, StatementKind kind) + : Nestable<Statement>(&pc->innermostStatement_), kind_(kind) {} + + template <typename T> + inline bool is() const; + template <typename T> + 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 { + const ParserAtom* label_; + + public: + LabelStatement(ParseContext* pc, const ParserAtom* label) + : Statement(pc, StatementKind::Label), label_(label) {} + + const ParserAtom* 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<Scope> { + // 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<DeclaredNameMap> 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. + using FunctionBoxVector = Vector<FunctionBox*, 24, SystemAllocPolicy>; + PooledVectorPtr<FunctionBoxVector> 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()->cx_); + } + return result; + } + + public: + using DeclaredNamePtr = DeclaredNameMap::Ptr; + using AddDeclaredNamePtr = DeclaredNameMap::AddPtr; + + using Nestable<Scope>::enclosing; + + explicit inline Scope(ParserBase* parser); + explicit inline Scope(JSContext* cx, ParseContext* pc, + UsedNameTracker& usedNames); + + void dump(ParseContext* pc); + + uint32_t id() const { return id_; } + + MOZ_MUST_USE bool init(ParseContext* pc) { + if (id_ == UINT32_MAX) { + pc->errorReporter_.errorNoOffset(JSMSG_NEED_DIET, js_script_str); + return false; + } + + return declared_.acquire(pc->sc()->cx_); + } + + 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(const ParserAtom* name) { + return declared_->lookup(name); + } + + AddDeclaredNamePtr lookupDeclaredNameForAdd(const ParserAtom* name) { + return declared_->lookupForAdd(name); + } + + MOZ_MUST_USE bool addDeclaredName(ParseContext* pc, AddDeclaredNamePtr& p, + const ParserAtom* 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. + MOZ_MUST_USE 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. + MOZ_MUST_USE bool propagateAndMarkAnnexBFunctionBoxes(ParseContext* pc); + + // 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; + } + + // 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 > AbstractGeneratorObject::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 for non-'var' scopes. + class BindingIter { + friend class Scope; + + DeclaredNameMap::Range declaredRange_; + mozilla::DebugOnly<uint32_t> count_; + bool isVarScope_; + + BindingIter(Scope& scope, bool isVarScope) + : declaredRange_(scope.declared_->all()), + count_(0), + isVarScope_(isVarScope) { + settle(); + } + + void settle() { + // Both var and lexically declared names are binding in a var + // scope. + if (isVarScope_) { + return; + } + + // Otherwise, pop only lexically declared names are + // binding. Pop the range until we find such a name. + while (!declaredRange_.empty()) { + if (BindingKindIsLexical(kind())) { + break; + } + declaredRange_.popFront(); + } + } + + public: + bool done() const { return declaredRange_.empty(); } + + explicit operator bool() const { return !done(); } + + const ParserAtom* 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(JSContext* cx, ParseContext* pc, + UsedNameTracker& usedNames); + }; + + private: + // Trace logging of parsing time. + AutoFrontendTraceLog traceLog_; + + // 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<Scope> 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<Scope> 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<AtomVector> positionalFormalParameterNames_; + + // Closed over binding names, in order of appearance. Null-delimited + // between scopes. Only used when syntax parsing. + PooledVectorPtr<AtomVector> 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<ScriptIndex, 4> 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(JSContext* cx, ParseContext*& parent, SharedContext* sc, + ErrorReporter& errorReporter, CompilationState& compilationState, + Directives* newDirectives, bool isFull); + + MOZ_MUST_USE bool init(); + + SharedContext* sc() { return sc_; } + + // `true` if we are in the body of a function definition. + bool isFunctionBox() const { return sc_->isFunctionBox(); } + + FunctionBox* functionBox() { return sc_->asFunctionBox(); } + + 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 <typename Predicate /* (Statement*) -> bool */> + Statement* findInnermostStatement(Predicate predicate) { + return Statement::findNearest(innermostStatement_, predicate); + } + + template <typename T, typename Predicate /* (Statement*) -> bool */> + T* findInnermostStatement(Predicate predicate) { + return Statement::findNearest<T>(innermostStatement_, predicate); + } + + template <typename T> + T* findInnermostStatement() { + return Statement::findNearest<T>(innermostStatement_); + } + + AtomVector& positionalFormalParameterNames() { + return *positionalFormalParameterNames_; + } + + AtomVector& closedOverBindingsForLazy() { + return *closedOverBindingsForLazy_; + } + + enum class BreakStatementError { + // Unlabeled break must be inside loop or switch. + ToughBreak, + LabelNotFound, + }; + + // Return Err(true) if we have encountered at least one loop, + // Err(false) otherwise. + MOZ_MUST_USE inline JS::Result<Ok, BreakStatementError> checkBreakStatement( + const ParserName* label); + + enum class ContinueStatementError { + NotInALoop, + LabelNotFound, + }; + MOZ_MUST_USE inline JS::Result<Ok, ContinueStatementError> + checkContinueStatement(const ParserName* 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(); + } + + 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()); + } + + uint32_t scriptId() const { return scriptId_; } + + bool computeAnnexBAppliesToLexicalFunctionInInnermostScope( + FunctionBox* funbox, bool* annexBApplies); + + bool tryDeclareVar(const ParserName* name, DeclarationKind kind, + uint32_t beginPos, + mozilla::Maybe<DeclarationKind>* redeclaredKind, + uint32_t* prevPos); + + bool hasUsedName(const UsedNameTracker& usedNames, const ParserName* name); + bool hasUsedFunctionSpecialName(const UsedNameTracker& usedNames, + const ParserName* name); + + bool declareFunctionThis(const UsedNameTracker& usedNames, + bool canSkipLazyClosedOverBindings); + bool declareFunctionArgumentsObject(const UsedNameTracker& usedNames, + bool canSkipLazyClosedOverBindings); + bool declareDotGeneratorName(); + bool declareTopLevelDotGeneratorName(); + + private: + MOZ_MUST_USE bool isVarRedeclaredInInnermostScope( + const ParserName* name, DeclarationKind kind, + mozilla::Maybe<DeclarationKind>* out); + + MOZ_MUST_USE bool isVarRedeclaredInEval(const ParserName* name, + DeclarationKind kind, + mozilla::Maybe<DeclarationKind>* out); + + enum DryRunOption { NotDryRun, DryRunInnermostScopeOnly }; + template <DryRunOption dryRunOption> + bool tryDeclareVarHelper(const ParserName* name, DeclarationKind kind, + uint32_t beginPos, + mozilla::Maybe<DeclarationKind>* 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..916157d1ba --- /dev/null +++ b/js/src/frontend/ParseNode.cpp @@ -0,0 +1,442 @@ +/* -*- 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/FullParseHandler.h" +#include "frontend/ParseContext.h" +#include "frontend/ParserAtom.h" +#include "frontend/SharedContext.h" +#include "vm/BigIntType.h" +#include "vm/Printer.h" +#include "vm/RegExpObject.h" + +using namespace js; +using namespace js::frontend; + +using mozilla::IsFinite; + +#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(cx); + } + 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<ListNode>(); + + list->append(right); + list->pn_pos.end = right->pn_pos.end; + + return list; + } + } + + ListNode* list = handler->new_<ListNode>(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 +}; + +void frontend::DumpParseTree(ParseNode* pn, GenericPrinter& out, int indent) { + if (pn == nullptr) { + out.put("#NULL"); + } else { + pn->dump(out, indent); + } +} + +static void IndentNewLine(GenericPrinter& out, int indent) { + out.putChar('\n'); + for (int i = 0; i < indent; ++i) { + out.putChar(' '); + } +} + +void ParseNode::dump(GenericPrinter& out) { + dump(out, 0); + out.putChar('\n'); +} + +void ParseNode::dump() { + js::Fprinter out(stderr); + dump(out); +} + +void ParseNode::dump(GenericPrinter& out, int indent) { + switch (getKind()) { +# define DUMP(K, T) \ + case ParseNodeKind::K: \ + as<T>().dumpImpl(out, indent); \ + break; + FOR_EACH_PARSE_NODE_KIND(DUMP) +# undef DUMP + default: + out.printf("#<BAD NODE %p, kind=%u>", (void*)this, unsigned(getKind())); + } +} + +void NullaryNode::dumpImpl(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(GenericPrinter& out, int indent) { + ToCStringBuf cbuf; + const char* cstr = NumberToCString(nullptr, &cbuf, value()); + if (!IsFinite(value())) { + out.put("#"); + } + if (cstr) { + out.printf("%s", cstr); + } else { + out.printf("%g", value()); + } +} + +void BigIntLiteral::dumpImpl(GenericPrinter& out, int indent) { + out.printf("(%s)", parseNodeNames[getKindAsIndex()]); +} + +void RegExpLiteral::dumpImpl(GenericPrinter& out, int indent) { + out.printf("(%s)", parseNodeNames[getKindAsIndex()]); +} + +static void DumpCharsNoNewline(const ParserAtom* atom, + js::GenericPrinter& out) { + if (atom->hasLatin1Chars()) { + out.put("[Latin 1]"); + JSString::dumpChars(atom->latin1Chars(), atom->length(), out); + } else { + out.put("[2 byte]"); + JSString::dumpChars(atom->twoByteChars(), atom->length(), out); + } +} + +void LoopControlStatement::dumpImpl(GenericPrinter& out, int indent) { + const char* name = parseNodeNames[getKindAsIndex()]; + out.printf("(%s", name); + if (label()) { + out.printf(" "); + DumpCharsNoNewline(label(), out); + } + out.printf(")"); +} + +void UnaryNode::dumpImpl(GenericPrinter& out, int indent) { + const char* name = parseNodeNames[getKindAsIndex()]; + out.printf("(%s ", name); + indent += strlen(name) + 2; + DumpParseTree(kid(), out, indent); + out.printf(")"); +} + +void BinaryNode::dumpImpl(GenericPrinter& out, int indent) { + if (isKind(ParseNodeKind::DotExpr)) { + out.put("(."); + + DumpParseTree(right(), out, indent + 2); + + out.putChar(' '); + if (as<PropertyAccess>().isSuper()) { + out.put("super"); + } else { + DumpParseTree(left(), out, indent + 2); + } + + out.printf(")"); + return; + } + + const char* name = parseNodeNames[getKindAsIndex()]; + out.printf("(%s ", name); + indent += strlen(name) + 2; + DumpParseTree(left(), out, indent); + IndentNewLine(out, indent); + DumpParseTree(right(), out, indent); + out.printf(")"); +} + +void TernaryNode::dumpImpl(GenericPrinter& out, int indent) { + const char* name = parseNodeNames[getKindAsIndex()]; + out.printf("(%s ", name); + indent += strlen(name) + 2; + DumpParseTree(kid1(), out, indent); + IndentNewLine(out, indent); + DumpParseTree(kid2(), out, indent); + IndentNewLine(out, indent); + DumpParseTree(kid3(), out, indent); + out.printf(")"); +} + +void FunctionNode::dumpImpl(GenericPrinter& out, int indent) { + const char* name = parseNodeNames[getKindAsIndex()]; + out.printf("(%s ", name); + indent += strlen(name) + 2; + DumpParseTree(body(), out, indent); + out.printf(")"); +} + +void ModuleNode::dumpImpl(GenericPrinter& out, int indent) { + const char* name = parseNodeNames[getKindAsIndex()]; + out.printf("(%s ", name); + indent += strlen(name) + 2; + DumpParseTree(body(), out, indent); + out.printf(")"); +} + +void ListNode::dumpImpl(GenericPrinter& out, int indent) { + const char* name = parseNodeNames[getKindAsIndex()]; + out.printf("(%s [", name); + if (ParseNode* listHead = head()) { + indent += strlen(name) + 3; + DumpParseTree(listHead, out, indent); + for (ParseNode* item : contentsFrom(listHead->pn_next)) { + IndentNewLine(out, indent); + DumpParseTree(item, out, indent); + } + } + out.printf("])"); +} + +template <typename CharT> +static void DumpName(GenericPrinter& out, const CharT* s, size_t len) { + if (len == 0) { + out.put("#<zero-length name>"); + } + + for (size_t i = 0; i < len; i++) { + char16_t c = s[i]; + if (c > 32 && c < 127) { + out.putChar(c); + } else if (c <= 255) { + out.printf("\\x%02x", unsigned(c)); + } else { + out.printf("\\u%04x", unsigned(c)); + } + } +} + +void NameNode::dumpImpl(GenericPrinter& out, int indent) { + switch (getKind()) { + case ParseNodeKind::StringExpr: + case ParseNodeKind::TemplateStringExpr: + case ParseNodeKind::ObjectPropertyName: + DumpCharsNoNewline(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("#<null name>"); + } else { + if (atom()->hasLatin1Chars()) { + DumpName(out, atom()->latin1Chars(), atom()->length()); + } else { + DumpName(out, atom()->twoByteChars(), atom()->length()); + } + } + return; + + case ParseNodeKind::LabelStmt: { + this->as<LabeledStatement>().dumpImpl(out, indent); + return; + } + + default: { + const char* name = parseNodeNames[getKindAsIndex()]; + out.printf("(%s)", name); + return; + } + } +} + +void LabeledStatement::dumpImpl(GenericPrinter& out, int indent) { + const char* name = parseNodeNames[getKindAsIndex()]; + out.printf("(%s ", name); + DumpCharsNoNewline(atom(), out); + out.printf(" "); + indent += strlen(name) + atom()->length() + 3; + DumpParseTree(statement(), out, indent); + out.printf(")"); +} + +void LexicalScopeNode::dumpImpl(GenericPrinter& out, int indent) { + const char* name = parseNodeNames[getKindAsIndex()]; + out.printf("(%s [", name); + int nameIndent = indent + strlen(name) + 3; + if (!isEmptyScope()) { + LexicalScope::ParserData* bindings = scopeBindings(); + for (uint32_t i = 0; i < bindings->slotInfo.length; i++) { + auto index = bindings->trailingNames[i].name(); + JSONPrinter json(out); + json.setIndentLevel((nameIndent + 1) / 2); + json.beginObject(); + DumpTaggedParserAtomIndex(json, index, nullptr); + json.endObject(); + if (i < bindings->slotInfo.length - 1) { + IndentNewLine(out, nameIndent); + } + } + } + out.putChar(']'); + indent += 2; + IndentNewLine(out, indent); + DumpParseTree(scopeBody(), out, indent); + out.printf(")"); +} +#endif + +BigInt* BigIntLiteral::create(JSContext* cx) { + return stencil_.bigIntData[index_].createBigInt(cx); +} + +bool BigIntLiteral::isZero() { return stencil_.bigIntData[index_].isZero(); } + +const ParserAtom* NumericLiteral::toAtom(JSContext* cx, + ParserAtomsTable& parserAtoms) const { + return NumberToParserAtom(cx, parserAtoms, value()); +} + +RegExpObject* RegExpStencil::createRegExp( + JSContext* cx, CompilationAtomCache& atomCache) const { + RootedAtom atom(cx, atomCache.getExistingAtomAt(cx, atom_)); + return RegExpObject::createSyntaxChecked(cx, atom, flags(), TenuredObject); +} + +RegExpObject* RegExpStencil::createRegExpAndEnsureAtom( + JSContext* cx, CompilationAtomCache& atomCache, + BaseCompilationStencil& stencil) const { + const ParserAtom* parserAtom = stencil.getParserAtomAt(cx, atom_); + MOZ_ASSERT(parserAtom); + RootedAtom atom(cx, parserAtom->toJSAtom(cx, atomCache)); + if (!atom) { + return nullptr; + } + return RegExpObject::createSyntaxChecked(cx, atom, flags(), TenuredObject); +} + +RegExpObject* RegExpLiteral::create(JSContext* cx, + CompilationAtomCache& atomCache, + BaseCompilationStencil& stencil) const { + return stencil.regExpData[index_].createRegExpAndEnsureAtom(cx, atomCache, + stencil); +} + +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<FunctionNode>() && + !pn->as<FunctionNode>().funbox()->explicitName()) { + return true; + } + + // 14.5.8 (ClassExpression) + if (pn->is<ClassNode>() && !pn->as<ClassNode>().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..63d1878019 --- /dev/null +++ b/js/src/frontend/ParseNode.h @@ -0,0 +1,2336 @@ +/* -*- 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 "mozilla/Attributes.h" + +#include <iterator> +#include <stddef.h> +#include <stdint.h> + +#include "jstypes.h" // js::Bit + +#include "frontend/FunctionSyntaxKind.h" // FunctionSyntaxKind +#include "frontend/NameAnalysisTypes.h" // PrivateNameKind +#include "frontend/ParserAtom.h" +#include "frontend/Stencil.h" +#include "frontend/Token.h" +#include "js/RootingAPI.h" +#include "vm/BytecodeUtil.h" +#include "vm/Scope.h" +#include "vm/ScopeKind.h" +#include "vm/StringType.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` 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 BigInt; +} + +namespace js { + +class GenericPrinter; +class LifoAlloc; +class RegExpObject; + +namespace frontend { + +class ParseContext; +class ParserAtomsTable; +struct BaseCompilationStencil; +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(OptionalDotExpr, OptionalPropertyAccess) \ + F(OptionalChain, UnaryNode) \ + F(OptionalElemExpr, OptionalPropertyByValue) \ + 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) \ + 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, ListNode) \ + F(ConstDecl, ListNode) \ + F(WithStmt, BinaryNode) \ + F(ReturnStmt, UnaryNode) \ + F(NewExpr, BinaryNode) \ + /* 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, ListNode) \ + F(ImportDecl, BinaryNode) \ + F(ImportSpecList, ListNode) \ + F(ImportSpec, BinaryNode) \ + F(ExportStmt, UnaryNode) \ + F(ExportFromStmt, BinaryNode) \ + F(ExportDefaultStmt, BinaryNode) \ + F(ExportSpecList, ListNode) \ + F(ExportSpec, BinaryNode) \ + F(ExportBatchSpecStmt, NullaryNode) \ + F(ForIn, TernaryNode) \ + F(ForOf, TernaryNode) \ + F(ForHead, TernaryNode) \ + F(ParamsBody, ListNode) \ + F(Spread, UnaryNode) \ + F(MutateProto, UnaryNode) \ + F(ClassDecl, ClassNode) \ + F(ClassMethod, ClassMethod) \ + F(ClassField, ClassField) \ + F(ClassMemberList, ListNode) \ + F(ClassNames, ClassNames) \ + F(NewTargetExpr, BinaryNode) \ + F(PosHolder, NullaryNode) \ + F(SuperBase, UnaryNode) \ + F(SuperCallExpr, BinaryNode) \ + F(SetThis, BinaryNode) \ + F(ImportMetaExpr, BinaryNode) \ + F(CallImportExpr, 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(PipelineExpr, ListNode) \ + 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(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 + * <A && <B && C>> so that code generation can emit a left-associative branch + * around <B && C> 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::PipelineExpr, + 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; +} + +/* + * <Definitions> + * Function (FunctionNode) + * funbox: ptr to js::FunctionBox + * body: ParamsBody or null for lazily-parsed function, ordinarily; + * ParseNodeKind::LexicalScope for implicit function in genexpr + * 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 either: + * * StatementList node for function body statements + * * ReturnStmt for expression closure + * 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 or ClassField nodes + * count: N >= 0 + * ClassMethod (ClassMethod) + * name: propertyName + * method: methodDefinition + * initializerIfPrivate: initializer to stamp private method onto instance + * Module (ModuleNode) + * body: statement list of the module + * + * <Statements> + * 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 (ListNode) + * 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 + * 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 + * ExportDefaultStmt (BinaryNode) + * left: export default declaration or expression + * right: Name node for assignment + * + * <Expressions> + * 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 + * PipelineExpr, 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 + * 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(PropertyDefinition, PropertyDefinitionType, asPropertyDefinition) \ + MACRO(ClassNames, ClassNamesType, asClassNames) \ + MACRO(ForNode, ForNodeType, asFor) \ + MACRO(PropertyAccess, PropertyAccessType, asPropertyAccess) \ + MACRO(PropertyByValue, PropertyByValueType, asPropertyByValue) \ + MACRO(OptionalPropertyAccess, OptionalPropertyAccessType, \ + asOptionalPropertyAccess) \ + MACRO(OptionalPropertyByValue, OptionalPropertyByValueType, \ + OptionalasPropertyByValue) \ + MACRO(SwitchStatement, SwitchStatementType, asSwitchStatement) \ + \ + MACRO(FunctionNode, FunctionNodeType, asFunction) \ + MACRO(ModuleNode, ModuleNodeType, asModule) \ + \ + MACRO(LexicalScopeNode, LexicalScopeNodeType, asLexicalScope) \ + \ + 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(const ParserName* 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); + } + + // True iff this is a for-in/of loop variable declaration (var/let/const). + inline bool isForLoopDeclaration() const; + + inline bool isConstant(); + + template <class NodeType> + inline bool is() const { + return NodeType::test(*this); + } + + /* Casting operations. */ + template <class NodeType> + inline NodeType& as() { + MOZ_ASSERT(NodeType::test(*this)); + return *static_cast<NodeType*>(this); + } + + template <class NodeType> + inline const NodeType& as() const { + MOZ_ASSERT(NodeType::test(*this)); + return *static_cast<const NodeType*>(this); + } + +#ifdef DEBUG + // Debugger-friendly stderr printer. + void dump(); + void dump(GenericPrinter& out); + void dump(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<NullaryNode>()); + } + + static bool test(const ParseNode& node) { + return node.typeCode() == TypeCode::Nullary; + } + + static constexpr TypeCode classTypeCode() { return TypeCode::Nullary; } + + template <typename Visitor> + bool accept(Visitor& visitor) { + return true; + } + +#ifdef DEBUG + void dumpImpl(GenericPrinter& out, int indent); +#endif +}; + +class NameNode : public ParseNode { + const ParserAtom* atom_; /* lexical name or label atom */ + PrivateNameKind privateNameKind_ = PrivateNameKind::None; + + public: + NameNode(ParseNodeKind kind, const ParserAtom* atom, const TokenPos& pos) + : ParseNode(kind, pos), atom_(atom) { + MOZ_ASSERT(is<NameNode>()); + } + + static bool test(const ParseNode& node) { + return node.typeCode() == TypeCode::Name; + } + + static constexpr TypeCode classTypeCode() { return TypeCode::Name; } + + template <typename Visitor> + bool accept(Visitor& visitor) { + return true; + } + +#ifdef DEBUG + void dumpImpl(GenericPrinter& out, int indent); +#endif + + const ParserAtom* atom() const { return atom_; } + + const ParserName* name() const { + MOZ_ASSERT(isKind(ParseNodeKind::Name) || + isKind(ParseNodeKind::PrivateName)); + return atom()->asName(); + } + + void setAtom(const ParserAtom* atom) { atom_ = atom; } + + void setPrivateNameKind(PrivateNameKind privateNameKind) { + privateNameKind_ = privateNameKind; + } + + PrivateNameKind privateNameKind() { return privateNameKind_; } +}; + +inline bool ParseNode::isName(const ParserName* name) const { + return getKind() == ParseNodeKind::Name && as<NameNode>().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<UnaryNode>()); + } + + static bool test(const ParseNode& node) { + return node.typeCode() == TypeCode::Unary; + } + + static constexpr TypeCode classTypeCode() { return TypeCode::Unary; } + + template <typename Visitor> + bool accept(Visitor& visitor) { + if (kid_) { + if (!visitor.visit(kid_)) { + return false; + } + } + return true; + } + +#ifdef DEBUG + void dumpImpl(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. + */ + const ParserAtom* isStringExprStatement() const { + if (isKind(ParseNodeKind::ExpressionStmt)) { + if (kid()->isKind(ParseNodeKind::StringExpr) && !kid()->isInParens()) { + return kid()->as<NameNode>().atom(); + } + } + return nullptr; + } + + // 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>()); + } + + BinaryNode(ParseNodeKind kind, ParseNode* left, ParseNode* right) + : ParseNode(kind, TokenPos::box(left->pn_pos, right->pn_pos)), + left_(left), + right_(right) { + MOZ_ASSERT(is<BinaryNode>()); + } + + static bool test(const ParseNode& node) { + return node.typeCode() == TypeCode::Binary; + } + + static constexpr TypeCode classTypeCode() { return TypeCode::Binary; } + + template <typename Visitor> + 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(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) {} + + static bool test(const ParseNode& node) { + ParseNodeKind kind = node.getKind(); + bool match = ParseNodeKind::AssignmentStart <= kind && + kind <= ParseNodeKind::AssignmentLast; + MOZ_ASSERT_IF(match, node.is<BinaryNode>()); + 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<BinaryNode>()); + return match; + } + + TernaryNode* head() const { return &left()->as<TernaryNode>(); } + + 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<TernaryNode>()); + } + + static bool test(const ParseNode& node) { + return node.typeCode() == TypeCode::Ternary; + } + + static constexpr TypeCode classTypeCode() { return TypeCode::Ternary; } + + template <typename Visitor> + 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(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) { + makeEmpty(); + MOZ_ASSERT(is<ListNode>()); + } + + 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<ListNode>()); + } + + static bool test(const ParseNode& node) { + return node.typeCode() == TypeCode::List; + } + + static constexpr TypeCode classTypeCode() { return TypeCode::List; } + + template <typename Visitor> + 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(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 + ; + + MOZ_MUST_USE bool hasTopLevelFunctionDeclarations() const { + MOZ_ASSERT(isKind(ParseNodeKind::StatementList)); + return xflags & hasTopLevelFunctionDeclarationsBit; + } + + MOZ_MUST_USE bool emittedTopLevelFunctionDeclarations() const { + MOZ_ASSERT(isKind(ParseNodeKind::StatementList)); + MOZ_ASSERT(hasTopLevelFunctionDeclarations()); + return xflags & emittedTopLevelFunctionDeclarationsBit; + } + + MOZ_MUST_USE 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)); + xflags |= hasNonConstInitializerBit; + } + + void unsetHasNonConstInitializer() { + MOZ_ASSERT(isKind(ParseNodeKind::ArrayExpr) || + isKind(ParseNodeKind::ObjectExpr)); + 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 makeEmpty() { + head_ = nullptr; + tail_ = &head_; + count_ = 0; + xflags = 0; + } + + void append(ParseNode* item) { + MOZ_ASSERT(item->pn_pos.begin >= pn_pos.begin); + appendWithoutOrderAssumption(item); + } + + void appendWithoutOrderAssumption(ParseNode* item) { + 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_++; + } + + void prependAndUpdatePos(ParseNode* item) { + prepend(item); + pn_pos.begin = item->pn_pos.begin; + } + + // 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 + MOZ_MUST_USE 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); + } +}; + +inline bool ParseNode::isForLoopDeclaration() const { + if (isKind(ParseNodeKind::VarStmt) || isKind(ParseNodeKind::LetDecl) || + isKind(ParseNodeKind::ConstDecl)) { + MOZ_ASSERT(!as<ListNode>().empty()); + return true; + } + + return false; +} + +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<FunctionNode>()); + } + + static bool test(const ParseNode& node) { + return node.isKind(ParseNodeKind::Function); + } + + static constexpr TypeCode classTypeCode() { return TypeCode::Other; } + + template <typename Visitor> + bool accept(Visitor& visitor) { + // Note: body is null for lazily-parsed functions. + if (body_) { + if (!visitor.visit(body_)) { + return false; + } + } + return true; + } + +#ifdef DEBUG + void dumpImpl(GenericPrinter& out, int indent); +#endif + + FunctionBox* funbox() const { return funbox_; } + + ListNode* body() const { return body_ ? &body_->as<ListNode>() : nullptr; } + + void setFunbox(FunctionBox* funbox) { funbox_ = funbox; } + + void setBody(ListNode* 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<ModuleNode>()); + } + + static bool test(const ParseNode& node) { + return node.isKind(ParseNodeKind::Module); + } + + static constexpr TypeCode classTypeCode() { return TypeCode::Other; } + + template <typename Visitor> + bool accept(Visitor& visitor) { + return visitor.visit(body_); + } + +#ifdef DEBUG + void dumpImpl(GenericPrinter& out, int indent); +#endif + + ListNode* body() const { return &body_->as<ListNode>(); } + + 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 <typename Visitor> + bool accept(Visitor& visitor) { + return true; + } + +#ifdef DEBUG + void dumpImpl(GenericPrinter& out, int indent); +#endif + + double value() const { return value_; } + + DecimalPoint decimalPoint() const { return decimalPoint_; } + + void setValue(double v) { value_ = v; } + + void setDecimalPoint(DecimalPoint d) { decimalPoint_ = d; } + + // Return the decimal string representation of this numeric literal. + const ParserAtom* toAtom(JSContext* cx, ParserAtomsTable& parserAtoms) const; +}; + +class BigIntLiteral : public ParseNode { + BaseCompilationStencil& stencil_; + BigIntIndex index_; + + public: + BigIntLiteral(BigIntIndex index, BaseCompilationStencil& stencil, + const TokenPos& pos) + : ParseNode(ParseNodeKind::BigIntExpr, pos), + stencil_(stencil), + index_(index) {} + + static bool test(const ParseNode& node) { + return node.isKind(ParseNodeKind::BigIntExpr); + } + + static constexpr TypeCode classTypeCode() { return TypeCode::Other; } + + template <typename Visitor> + bool accept(Visitor& visitor) { + return true; + } + +#ifdef DEBUG + void dumpImpl(GenericPrinter& out, int indent); +#endif + + BigIntIndex index() { return index_; } + + // Create a BigInt value of this BigInt literal. + BigInt* create(JSContext* cx); + + bool isZero(); +}; + +class LexicalScopeNode : public ParseNode { + LexicalScope::ParserData* bindings; + ParseNode* body; + ScopeKind kind_; + + public: + LexicalScopeNode(LexicalScope::ParserData* bindings, ParseNode* body, + ScopeKind kind = ScopeKind::Lexical) + : ParseNode(ParseNodeKind::LexicalScope, body->pn_pos), + bindings(bindings), + body(body), + kind_(kind) {} + + static bool test(const ParseNode& node) { + return node.isKind(ParseNodeKind::LexicalScope); + } + + static constexpr TypeCode classTypeCode() { return TypeCode::Other; } + + template <typename Visitor> + bool accept(Visitor& visitor) { + return visitor.visit(body); + } + +#ifdef DEBUG + void dumpImpl(GenericPrinter& out, int indent); +#endif + + LexicalScope::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 LabeledStatement : public NameNode { + ParseNode* statement_; + + public: + LabeledStatement(const ParserName* label, ParseNode* stmt, uint32_t begin) + : NameNode(ParseNodeKind::LabelStmt, label, + TokenPos(begin, stmt->pn_pos.end)), + statement_(stmt) {} + + const ParserName* label() const { return atom()->asName(); } + + ParseNode* statement() const { return statement_; } + + static bool test(const ParseNode& node) { + return node.isKind(ParseNodeKind::LabelStmt); + } + + template <typename Visitor> + bool accept(Visitor& visitor) { + if (statement_) { + if (!visitor.visit(statement_)) { + return false; + } + } + return true; + } + +#ifdef DEBUG + void dumpImpl(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<ListNode>(); } + + static bool test(const ParseNode& node) { + bool match = node.isKind(ParseNodeKind::Case); + MOZ_ASSERT_IF(match, node.is<BinaryNode>()); + return match; + } +}; + +class LoopControlStatement : public ParseNode { + const ParserName* label_; /* target of break/continue statement */ + + protected: + LoopControlStatement(ParseNodeKind kind, const ParserName* label, + const TokenPos& pos) + : ParseNode(kind, pos), label_(label) { + MOZ_ASSERT(kind == ParseNodeKind::BreakStmt || + kind == ParseNodeKind::ContinueStmt); + MOZ_ASSERT(is<LoopControlStatement>()); + } + + public: + /* Label associated with this break/continue statement, if any. */ + const ParserName* label() const { return label_; } + +#ifdef DEBUG + void dumpImpl(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 <typename Visitor> + bool accept(Visitor& visitor) { + return true; + } +}; + +class BreakStatement : public LoopControlStatement { + public: + BreakStatement(const ParserName* 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<LoopControlStatement>()); + return match; + } +}; + +class ContinueStatement : public LoopControlStatement { + public: + ContinueStatement(const ParserName* 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<LoopControlStatement>()); + 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<NullaryNode>()); + 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<TernaryNode>()); + 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<TernaryNode>()); + return match; + } + + ParseNode* body() const { return kid1(); } + + LexicalScopeNode* catchScope() const { + return kid2() ? &kid2()->as<LexicalScopeNode>() : 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<UnaryNode>()); + 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<NullaryNode>()); + 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<NullaryNode>()); + 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<NullaryNode>()); + 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, CompilationAtomCache& atomCache, + BaseCompilationStencil& stencil) const; + +#ifdef DEBUG + void dumpImpl(GenericPrinter& out, int indent); +#endif + + static bool test(const ParseNode& node) { + return node.isKind(ParseNodeKind::RegExpExpr); + } + + static constexpr TypeCode classTypeCode() { return TypeCode::Other; } + + template <typename Visitor> + 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<BinaryNode>()); + MOZ_ASSERT_IF(match, node.as<BinaryNode>().right()->isKind( + ParseNodeKind::PropertyNameExpr)); + return match; + } + + NameNode& key() const { return right()->as<NameNode>(); } + + // 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; } + + const ParserName* name() const { + return right()->as<NameNode>().atom()->asName(); + } +}; + +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<PropertyAccessBase>()); + 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<PropertyAccessBase>()); + 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<BinaryNode>()); + 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<PropertyByValueBase>()); + 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<PropertyByValueBase>()); + return match; + } +}; + +/* + * 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<ListNode>()); + return match; + } + + ListNode* rawNodes() const { + MOZ_ASSERT(head()); + return &head()->as<ListNode>(); + } +}; + +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<CallNode>()); + } + + 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<BinaryNode>()); + return match; + } + + JSOp callOp() { return callOp_; } +}; + +class ClassMethod : public BinaryNode { + bool isStatic_; + AccessorType accessorType_; + FunctionNode* initializerIfPrivate_; + + public: + /* + * Method definitions often keep a name and function body that overlap, + * so explicitly define the beginning and end here. + */ + ClassMethod(ParseNode* name, ParseNode* body, AccessorType accessorType, + bool isStatic, FunctionNode* initializerIfPrivate) + : BinaryNode(ParseNodeKind::ClassMethod, + TokenPos(name->pn_pos.begin, body->pn_pos.end), name, body), + isStatic_(isStatic), + accessorType_(accessorType), + initializerIfPrivate_(initializerIfPrivate) {} + + static bool test(const ParseNode& node) { + bool match = node.isKind(ParseNodeKind::ClassMethod); + MOZ_ASSERT_IF(match, node.is<BinaryNode>()); + return match; + } + + ParseNode& name() const { return *left(); } + + FunctionNode& method() const { return right()->as<FunctionNode>(); } + + bool isStatic() const { return isStatic_; } + + AccessorType accessorType() const { return accessorType_; } + + FunctionNode* initializerIfPrivate() const { return initializerIfPrivate_; } +}; + +class ClassField : public BinaryNode { + bool isStatic_; + + public: + ClassField(ParseNode* name, ParseNode* initializer, bool isStatic) + : BinaryNode(ParseNodeKind::ClassField, initializer->pn_pos, name, + initializer), + isStatic_(isStatic) {} + + static bool test(const ParseNode& node) { + bool match = node.isKind(ParseNodeKind::ClassField); + MOZ_ASSERT_IF(match, node.is<BinaryNode>()); + return match; + } + + ParseNode& name() const { return *left(); } + + FunctionNode* initializer() const { return &right()->as<FunctionNode>(); } + + bool isStatic() const { return isStatic_; } +}; + +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<BinaryNode>()); + 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<ListNode>(); + MOZ_ASSERT(cases->isKind(ParseNodeKind::StatementList)); + bool found = false; + for (ParseNode* item : cases->contents()) { + CaseClause* caseNode = &item->as<CaseClause>(); + 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<BinaryNode>()); + return match; + } + + ParseNode& discriminant() const { return *left(); } + + LexicalScopeNode& lexicalForCaseList() const { + return right()->as<LexicalScopeNode>(); + } + + 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<NameNode>().atom() == + outerBinding->as<NameNode>().atom()); + } + + static bool test(const ParseNode& node) { + bool match = node.isKind(ParseNodeKind::ClassNames); + MOZ_ASSERT_IF(match, node.is<BinaryNode>()); + 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<NameNode>(); + } + return nullptr; + } + + NameNode* innerBinding() const { return &right()->as<NameNode>(); } +}; + +class ClassNode : public TernaryNode { + private: + LexicalScopeNode* innerScope() const { + return &kid3()->as<LexicalScopeNode>(); + } + + LexicalScopeNode* bodyScope() const { + return &innerScope()->scopeBody()->as<LexicalScopeNode>(); + } + + public: + ClassNode(ParseNode* names, ParseNode* heritage, + LexicalScopeNode* memberBlock, const TokenPos& pos) + : TernaryNode(ParseNodeKind::ClassDecl, names, heritage, memberBlock, + pos) { + MOZ_ASSERT_IF(names, names->is<ClassNames>()); + } + + static bool test(const ParseNode& node) { + bool match = node.isKind(ParseNodeKind::ClassDecl); + MOZ_ASSERT_IF(match, node.is<TernaryNode>()); + return match; + } + + ClassNames* names() const { + return kid1() ? &kid1()->as<ClassNames>() : nullptr; + } + + ParseNode* heritage() const { return kid2(); } + + ListNode* memberList() const { + ListNode* list = &bodyScope()->scopeBody()->as<ListNode>(); + MOZ_ASSERT(list->isKind(ParseNodeKind::ClassMemberList)); + return list; + } + + LexicalScopeNode* scopeBindings() const { + LexicalScopeNode* scope = innerScope(); + return scope->isEmptyScope() ? nullptr : scope; + } + + LexicalScopeNode* bodyScopeBindings() const { + LexicalScopeNode* scope = bodyScope(); + return scope->isEmptyScope() ? nullptr : scope; + } +}; + +#ifdef DEBUG +void DumpParseTree(ParseNode* pn, GenericPrinter& out, int indent = 0); +#endif + +class ParseNodeAllocator { + public: + explicit ParseNodeAllocator(JSContext* cx, LifoAlloc& alloc) + : cx(cx), alloc(alloc) {} + + void* allocNode(size_t size); + + private: + JSContext* cx; + 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<ListNode>().hasNonConstInitializer(); + default: + return false; + } +} + +static inline ParseNode* FunctionFormalParametersList(ParseNode* fn, + unsigned* numFormals) { + MOZ_ASSERT(fn->isKind(ParseNodeKind::Function)); + ListNode* argsBody = fn->as<FunctionNode>().body(); + MOZ_ASSERT(argsBody->isKind(ParseNodeKind::ParamsBody)); + *numFormals = argsBody->count(); + if (*numFormals > 0 && argsBody->last()->is<LexicalScopeNode>() && + argsBody->last()->as<LexicalScopeNode>().scopeBody()->isKind( + ParseNodeKind::StatementList)) { + (*numFormals)--; + } + return argsBody->head(); +} + +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..d19392890a --- /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<ParseNodeVerifier> { + using Base = ParseNodeVisitor<ParseNodeVerifier>; + + const LifoAlloc& alloc_; + + public: + ParseNodeVerifier(JSContext* cx, const LifoAlloc& alloc) + : Base(cx), alloc_(alloc) {} + + MOZ_MUST_USE 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<ListNode>()) { + pn->as<ListNode>().checkConsistency(); + } + return Base::visit(pn); + } +}; + +} // namespace frontend +} // namespace js + +bool frontend::CheckParseTree(JSContext* cx, const LifoAlloc& alloc, + ParseNode* pn) { + ParseNodeVerifier verifier(cx, 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..5cf7c12bf8 --- /dev/null +++ b/js/src/frontend/ParseNodeVerify.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_ParseNodeVerify_h +#define frontend_ParseNodeVerify_h + +#include "ds/LifoAlloc.h" // LifoAlloc +#include "frontend/ParseNode.h" // ParseNode +#include "frontend/SyntaxParseHandler.h" // SyntaxParseHandler::Node + +namespace js { +namespace frontend { + +// 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 +extern MOZ_MUST_USE bool CheckParseTree(JSContext* cx, const LifoAlloc& alloc, + ParseNode* pn); +#else +inline MOZ_MUST_USE bool CheckParseTree(JSContext* cx, const LifoAlloc& alloc, + ParseNode* pn) { + return true; +} +#endif + +inline MOZ_MUST_USE bool CheckParseTree(JSContext* cx, 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..a347691fc0 --- /dev/null +++ b/js/src/frontend/ParseNodeVisitor.h @@ -0,0 +1,134 @@ +/* -*- 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 "mozilla/Attributes.h" + +#include "jsfriendapi.h" + +#include "frontend/ParseNode.h" +#include "js/friend/StackLimits.h" // js::CheckRecursionLimit + +namespace js { +namespace frontend { + +/** + * Utility class for walking a JS AST. + * + * Simple usage: + * + * class HowTrueVisitor : public ParseNodeVisitor<HowTrueVisitor> { + * 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 <typename Derived> +class ParseNodeVisitor { + public: + JSContext* cx_; + + explicit ParseNodeVisitor(JSContext* cx) : cx_(cx) {} + + MOZ_MUST_USE bool visit(ParseNode* pn) { + if (!CheckRecursionLimit(cx_)) { + return false; + } + + switch (pn->getKind()) { +#define VISIT_CASE(KIND, TYPE) \ + case ParseNodeKind::KIND: \ + return static_cast<Derived*>(this)->visit##KIND(&pn->as<TYPE>()); + FOR_EACH_PARSE_NODE_KIND(VISIT_CASE) +#undef VISIT_CASE + default: + MOZ_CRASH("invalid node kind"); + } + } + + // using static_cast<Derived*> here allows plain visit() to be overridden. +#define VISIT_METHOD(KIND, TYPE) \ + MOZ_MUST_USE bool visit##KIND(TYPE* pn) { /* NOLINT */ \ + return pn->accept(*static_cast<Derived*>(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 <typename Derived> +class RewritingParseNodeVisitor { + public: + JSContext* cx_; + + explicit RewritingParseNodeVisitor(JSContext* cx) : cx_(cx) {} + + MOZ_MUST_USE bool visit(ParseNode*& pn) { + if (!CheckRecursionLimit(cx_)) { + return false; + } + + switch (pn->getKind()) { +#define VISIT_CASE(KIND, _type) \ + case ParseNodeKind::KIND: \ + return static_cast<Derived*>(this)->visit##KIND(pn); + FOR_EACH_PARSE_NODE_KIND(VISIT_CASE) +#undef VISIT_CASE + default: + MOZ_CRASH("invalid node kind"); + } + } + + // using static_cast<Derived*> here allows plain visit() to be overridden. +#define VISIT_METHOD(KIND, TYPE) \ + MOZ_MUST_USE bool visit##KIND(ParseNode*& pn) { \ + MOZ_ASSERT(pn->is<TYPE>(), \ + "Node of kind " #KIND " was not of type " #TYPE); \ + return pn->as<TYPE>().accept(*static_cast<Derived*>(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..12bc1369cd --- /dev/null +++ b/js/src/frontend/Parser.cpp @@ -0,0 +1,11654 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: set ts=8 sts=2 et sw=2 tw=80: + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + * JS parser. + * + * This is a recursive-descent parser for the JavaScript language specified by + * "The ECMAScript Language Specification" (Standard ECMA-262). It uses + * lexical and semantic feedback to disambiguate non-LL(1) structures. It + * generates trees of nodes induced by the recursive parsing (not precise + * syntax trees, see Parser.h). After tree construction, it rewrites trees to + * fold constants and evaluate compile-time expressions. + * + * This parser attempts no error recovery. + */ + +#include "frontend/Parser.h" + +#include "mozilla/ArrayUtils.h" +#include "mozilla/Casting.h" +#include "mozilla/DebugOnly.h" +#include "mozilla/Range.h" +#include "mozilla/Sprintf.h" +#include "mozilla/Unused.h" +#include "mozilla/Utf8.h" +#include "mozilla/Variant.h" + +#include <memory> +#include <new> +#include <type_traits> + +#include "jsnum.h" +#include "jstypes.h" + +#include "builtin/ModuleObject.h" +#include "builtin/SelfHostingDefines.h" +#include "frontend/BytecodeCompiler.h" +#include "frontend/BytecodeSection.h" +#include "frontend/FoldConstants.h" +#include "frontend/FunctionSyntaxKind.h" // FunctionSyntaxKind +#include "frontend/ModuleSharedContext.h" +#include "frontend/ParseNode.h" +#include "frontend/ParseNodeVerify.h" +#include "frontend/ScriptIndex.h" // ScriptIndex +#include "frontend/TokenStream.h" +#include "irregexp/RegExpAPI.h" +#include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_* +#include "js/RegExpFlags.h" // JS::RegExpFlags +#include "util/StringBuffer.h" // StringBuffer +#include "vm/BigIntType.h" +#include "vm/BytecodeUtil.h" +#include "vm/FunctionFlags.h" // js::FunctionFlags +#include "vm/GeneratorAndAsyncKind.h" // js::GeneratorKind, js::FunctionAsyncKind +#include "vm/JSAtom.h" +#include "vm/JSContext.h" +#include "vm/JSFunction.h" +#include "vm/JSScript.h" +#include "vm/ModuleBuilder.h" // js::ModuleBuilder +#include "vm/RegExpObject.h" +#include "vm/SelfHosting.h" +#include "vm/StringType.h" +#include "wasm/AsmJS.h" + +#include "frontend/ParseContext-inl.h" +#include "frontend/SharedContext-inl.h" +#include "vm/EnvironmentObject-inl.h" + +using namespace js; + +using mozilla::AssertedCast; +using mozilla::AsVariant; +using mozilla::Maybe; +using mozilla::Nothing; +using mozilla::PointerRangeSize; +using mozilla::Some; +using mozilla::Utf8Unit; + +using JS::AutoGCRooter; +using JS::ReadOnlyCompileOptions; +using JS::RegExpFlags; + +namespace js::frontend { + +using DeclaredNamePtr = ParseContext::Scope::DeclaredNamePtr; +using AddDeclaredNamePtr = ParseContext::Scope::AddDeclaredNamePtr; +using BindingIter = ParseContext::Scope::BindingIter; +using UsedNamePtr = UsedNameTracker::UsedNameMap::Ptr; + +using ParserBindingNameVector = Vector<ParserBindingName, 6>; + +template <class T, class U> +static inline void PropagateTransitiveParseFlags(const T* inner, U* outer) { + if (inner->bindingsAccessedDynamically()) { + outer->setBindingsAccessedDynamically(); + } + if (inner->hasDirectEval()) { + outer->setHasDirectEval(); + } +} + +static bool StatementKindIsBraced(StatementKind kind) { + return kind == StatementKind::Block || kind == StatementKind::Switch || + kind == StatementKind::Try || kind == StatementKind::Catch || + kind == StatementKind::Finally; +} + +template <class ParseHandler, typename Unit> +inline typename GeneralParser<ParseHandler, Unit>::FinalParser* +GeneralParser<ParseHandler, Unit>::asFinalParser() { + static_assert( + std::is_base_of_v<GeneralParser<ParseHandler, Unit>, FinalParser>, + "inheritance relationship required by the static_cast<> below"); + + return static_cast<FinalParser*>(this); +} + +template <class ParseHandler, typename Unit> +inline const typename GeneralParser<ParseHandler, Unit>::FinalParser* +GeneralParser<ParseHandler, Unit>::asFinalParser() const { + static_assert( + std::is_base_of_v<GeneralParser<ParseHandler, Unit>, FinalParser>, + "inheritance relationship required by the static_cast<> below"); + + return static_cast<const FinalParser*>(this); +} + +template <class ParseHandler, typename Unit> +template <typename ConditionT, typename ErrorReportT> +bool GeneralParser<ParseHandler, Unit>::mustMatchTokenInternal( + ConditionT condition, ErrorReportT errorReport) { + MOZ_ASSERT(condition(TokenKind::Div) == false); + MOZ_ASSERT(condition(TokenKind::DivAssign) == false); + MOZ_ASSERT(condition(TokenKind::RegExp) == false); + + TokenKind actual; + if (!tokenStream.getToken(&actual, TokenStream::SlashIsInvalid)) { + return false; + } + if (!condition(actual)) { + errorReport(actual); + return false; + } + return true; +} + +ParserSharedBase::ParserSharedBase(JSContext* cx, CompilationStencil& stencil, + CompilationState& compilationState, + Kind kind) + : cx_(cx), + alloc_(compilationState.allocScope.alloc()), + stencil_(stencil), + compilationState_(compilationState), + pc_(nullptr), + usedNames_(compilationState.usedNames) { + cx->frontendCollectionPool().addActiveCompilation(); +} + +ParserSharedBase::~ParserSharedBase() { + cx_->frontendCollectionPool().removeActiveCompilation(); +} + +ParserBase::ParserBase(JSContext* cx, const ReadOnlyCompileOptions& options, + bool foldConstants, CompilationStencil& stencil, + CompilationState& compilationState) + : ParserSharedBase(cx, stencil, compilationState, + ParserSharedBase::Kind::Parser), + anyChars(cx, options, this), + ss(nullptr), + foldConstants_(foldConstants), +#ifdef DEBUG + checkOptionsCalled_(false), +#endif + isUnexpectedEOF_(false), + awaitHandling_(AwaitIsName), + inParametersOfAsyncFunction_(false) { +} + +bool ParserBase::checkOptions() { +#ifdef DEBUG + checkOptionsCalled_ = true; +#endif + + return anyChars.checkOptions(); +} + +ParserBase::~ParserBase() { MOZ_ASSERT(checkOptionsCalled_); } + +template <class ParseHandler> +PerHandlerParser<ParseHandler>::PerHandlerParser( + JSContext* cx, const ReadOnlyCompileOptions& options, bool foldConstants, + CompilationStencil& stencil, CompilationState& compilationState, + BaseScript* lazyOuterFunction, void* internalSyntaxParser) + : ParserBase(cx, options, foldConstants, stencil, compilationState), + handler_(cx, compilationState.allocScope.alloc(), lazyOuterFunction), + internalSyntaxParser_(internalSyntaxParser) {} + +template <class ParseHandler, typename Unit> +GeneralParser<ParseHandler, Unit>::GeneralParser( + JSContext* cx, const ReadOnlyCompileOptions& options, const Unit* units, + size_t length, bool foldConstants, CompilationStencil& stencil, + CompilationState& compilationState, SyntaxParser* syntaxParser, + BaseScript* lazyOuterFunction) + : Base(cx, options, foldConstants, stencil, compilationState, syntaxParser, + lazyOuterFunction), + tokenStream(cx, &compilationState.parserAtoms, options, units, length) {} + +template <typename Unit> +void Parser<SyntaxParseHandler, Unit>::setAwaitHandling( + AwaitHandling awaitHandling) { + this->awaitHandling_ = awaitHandling; +} + +template <typename Unit> +void Parser<FullParseHandler, Unit>::setAwaitHandling( + AwaitHandling awaitHandling) { + this->awaitHandling_ = awaitHandling; + if (SyntaxParser* syntaxParser = getSyntaxParser()) { + syntaxParser->setAwaitHandling(awaitHandling); + } +} + +template <class ParseHandler, typename Unit> +inline void GeneralParser<ParseHandler, Unit>::setAwaitHandling( + AwaitHandling awaitHandling) { + asFinalParser()->setAwaitHandling(awaitHandling); +} + +template <typename Unit> +void Parser<SyntaxParseHandler, Unit>::setInParametersOfAsyncFunction( + bool inParameters) { + this->inParametersOfAsyncFunction_ = inParameters; +} + +template <typename Unit> +void Parser<FullParseHandler, Unit>::setInParametersOfAsyncFunction( + bool inParameters) { + this->inParametersOfAsyncFunction_ = inParameters; + if (SyntaxParser* syntaxParser = getSyntaxParser()) { + syntaxParser->setInParametersOfAsyncFunction(inParameters); + } +} + +template <class ParseHandler, typename Unit> +inline void GeneralParser<ParseHandler, Unit>::setInParametersOfAsyncFunction( + bool inParameters) { + asFinalParser()->setInParametersOfAsyncFunction(inParameters); +} + +template <class ParseHandler> +FunctionBox* PerHandlerParser<ParseHandler>::newFunctionBox( + FunctionNodeType funNode, const ParserAtom* explicitName, + FunctionFlags flags, uint32_t toStringStart, Directives inheritedDirectives, + GeneratorKind generatorKind, FunctionAsyncKind asyncKind) { + MOZ_ASSERT(funNode); + + ScriptIndex index = ScriptIndex(compilationState_.scriptData.length()); + if (uint32_t(index) >= TaggedScriptThingIndex::IndexLimit) { + ReportAllocationOverflow(cx_); + return nullptr; + } + if (!compilationState_.scriptData.emplaceBack()) { + js::ReportOutOfMemory(cx_); + return nullptr; + } + + if (!handler_.canSkipLazyInnerFunctions()) { + if (!compilationState_.scriptExtra.emplaceBack()) { + js::ReportOutOfMemory(cx_); + return nullptr; + } + } + + // This source extent will be further filled in during the remainder of parse. + SourceExtent extent; + extent.toStringStart = toStringStart; + + /* + * We use JSContext.tempLifoAlloc to allocate parsed objects and place them + * on a list in this Parser to ensure GC safety. Thus the tempLifoAlloc + * arenas containing the entries must be alive until we are done with + * scanning, parsing and code generation for the whole script or top-level + * function. + */ + FunctionBox* funbox = alloc_.new_<FunctionBox>( + cx_, extent, stencil_, compilationState_, inheritedDirectives, + generatorKind, asyncKind, explicitName, flags, index); + if (!funbox) { + ReportOutOfMemory(cx_); + return nullptr; + } + + handler_.setFunctionBox(funNode, funbox); + + return funbox; +} + +bool ParserBase::setSourceMapInfo() { + // If support for source pragmas have been fully disabled, we can skip + // processing of all of these values. + if (!options().sourcePragmas()) { + return true; + } + + // Not all clients initialize ss. Can't update info to an object that isn't + // there. + if (!ss) { + return true; + } + + if (anyChars.hasDisplayURL()) { + if (!ss->setDisplayURL(cx_, anyChars.displayURL())) { + return false; + } + } + + if (anyChars.hasSourceMapURL()) { + MOZ_ASSERT(!ss->hasSourceMapURL()); + if (!ss->setSourceMapURL(cx_, anyChars.sourceMapURL())) { + return false; + } + } + + /* + * Source map URLs passed as a compile option (usually via a HTTP source map + * header) override any source map urls passed as comment pragmas. + */ + if (options().sourceMapURL()) { + // Warn about the replacement, but use the new one. + if (ss->hasSourceMapURL()) { + if (!warningNoOffset(JSMSG_ALREADY_HAS_PRAGMA, ss->filename(), + "//# sourceMappingURL")) { + return false; + } + } + + if (!ss->setSourceMapURL(cx_, options().sourceMapURL())) { + return false; + } + } + + return true; +} + +/* + * Parse a top-level JS script. + */ +template <class ParseHandler, typename Unit> +typename ParseHandler::ListNodeType GeneralParser<ParseHandler, Unit>::parse() { + MOZ_ASSERT(checkOptionsCalled_); + + SourceExtent extent = SourceExtent::makeGlobalExtent( + /* len = */ 0, options().lineno, options().column); + Directives directives(options().forceStrictMode()); + GlobalSharedContext globalsc(cx_, ScopeKind::Global, + this->getCompilationStencil(), directives, + extent); + SourceParseContext globalpc(this, &globalsc, /* newDirectives = */ nullptr); + if (!globalpc.init()) { + return null(); + } + + ParseContext::VarScope varScope(this); + if (!varScope.init(pc_)) { + return null(); + } + + ListNodeType stmtList = statementList(YieldIsName); + if (!stmtList) { + return null(); + } + + TokenKind tt; + if (!tokenStream.getToken(&tt, TokenStream::SlashIsRegExp)) { + return null(); + } + if (tt != TokenKind::Eof) { + error(JSMSG_GARBAGE_AFTER_INPUT, "script", TokenKindToDesc(tt)); + return null(); + } + + if (!CheckParseTree(cx_, alloc_, stmtList)) { + return null(); + } + + if (foldConstants_) { + Node node = stmtList; + // Don't constant-fold inside "use asm" code, as this could create a parse + // tree that doesn't type-check as asm.js. + if (!pc_->useAsmOrInsideUseAsm()) { + if (!FoldConstants(cx_, this->compilationState_.parserAtoms, &node, + &handler_)) { + return null(); + } + } + stmtList = handler_.asList(node); + } + + return stmtList; +} + +/* + * Strict mode forbids introducing new definitions for 'eval', 'arguments', + * 'let', 'static', 'yield', or for any strict mode reserved word. + */ +bool ParserBase::isValidStrictBinding(const ParserName* name) { + TokenKind tt = ReservedWordTokenKind(name); + if (tt == TokenKind::Name) { + return name != cx_->parserNames().eval && + name != cx_->parserNames().arguments; + } + return tt != TokenKind::Let && tt != TokenKind::Static && + tt != TokenKind::Yield && !TokenKindIsStrictReservedWord(tt); +} + +/* + * Returns true if all parameter names are valid strict mode binding names and + * no duplicate parameter names are present. + */ +bool ParserBase::hasValidSimpleStrictParameterNames() { + MOZ_ASSERT(pc_->isFunctionBox() && + pc_->functionBox()->hasSimpleParameterList()); + + if (pc_->functionBox()->hasDuplicateParameters) { + return false; + } + + for (auto name : pc_->positionalFormalParameterNames()) { + MOZ_ASSERT(name); + if (!isValidStrictBinding(name->asName())) { + return false; + } + } + return true; +} + +template <class ParseHandler, typename Unit> +void GeneralParser<ParseHandler, Unit>::reportMissingClosing( + unsigned errorNumber, unsigned noteNumber, uint32_t openedPos) { + auto notes = MakeUnique<JSErrorNotes>(); + if (!notes) { + ReportOutOfMemory(pc_->sc()->cx_); + return; + } + + uint32_t line, column; + tokenStream.computeLineAndColumn(openedPos, &line, &column); + + const size_t MaxWidth = sizeof("4294967295"); + char columnNumber[MaxWidth]; + SprintfLiteral(columnNumber, "%" PRIu32, column); + char lineNumber[MaxWidth]; + SprintfLiteral(lineNumber, "%" PRIu32, line); + + if (!notes->addNoteASCII(pc_->sc()->cx_, getFilename(), 0, line, column, + GetErrorMessage, nullptr, noteNumber, lineNumber, + columnNumber)) { + return; + } + + errorWithNotes(std::move(notes), errorNumber); +} + +template <class ParseHandler, typename Unit> +void GeneralParser<ParseHandler, Unit>::reportRedeclaration( + const ParserName* name, DeclarationKind prevKind, TokenPos pos, + uint32_t prevPos) { + UniqueChars bytes = ParserAtomToPrintableString(cx_, name); + if (!bytes) { + return; + } + + if (prevPos == DeclaredNameInfo::npos) { + errorAt(pos.begin, JSMSG_REDECLARED_VAR, DeclarationKindString(prevKind), + bytes.get()); + return; + } + + auto notes = MakeUnique<JSErrorNotes>(); + if (!notes) { + ReportOutOfMemory(pc_->sc()->cx_); + return; + } + + uint32_t line, column; + tokenStream.computeLineAndColumn(prevPos, &line, &column); + + const size_t MaxWidth = sizeof("4294967295"); + char columnNumber[MaxWidth]; + SprintfLiteral(columnNumber, "%" PRIu32, column); + char lineNumber[MaxWidth]; + SprintfLiteral(lineNumber, "%" PRIu32, line); + + if (!notes->addNoteASCII(pc_->sc()->cx_, getFilename(), 0, line, column, + GetErrorMessage, nullptr, JSMSG_REDECLARED_PREV, + lineNumber, columnNumber)) { + return; + } + + errorWithNotesAt(std::move(notes), pos.begin, JSMSG_REDECLARED_VAR, + DeclarationKindString(prevKind), bytes.get()); +} + +// notePositionalFormalParameter is called for both the arguments of a regular +// function definition and the arguments specified by the Function +// constructor. +// +// The 'disallowDuplicateParams' bool indicates whether the use of another +// feature (destructuring or default arguments) disables duplicate arguments. +// (ECMA-262 requires us to support duplicate parameter names, but, for newer +// features, we consider the code to have "opted in" to higher standards and +// forbid duplicates.) +template <class ParseHandler, typename Unit> +bool GeneralParser<ParseHandler, Unit>::notePositionalFormalParameter( + FunctionNodeType funNode, const ParserName* name, uint32_t beginPos, + bool disallowDuplicateParams, bool* duplicatedParam) { + if (AddDeclaredNamePtr p = + pc_->functionScope().lookupDeclaredNameForAdd(name)) { + if (disallowDuplicateParams) { + error(JSMSG_BAD_DUP_ARGS); + return false; + } + + // Strict-mode disallows duplicate args. We may not know whether we are + // in strict mode or not (since the function body hasn't been parsed). + // In such cases, report will queue up the potential error and return + // 'true'. + if (pc_->sc()->strict()) { + UniqueChars bytes = ParserAtomToPrintableString(cx_, name); + if (!bytes) { + return false; + } + if (!strictModeError(JSMSG_DUPLICATE_FORMAL, bytes.get())) { + return false; + } + } + + *duplicatedParam = true; + } else { + DeclarationKind kind = DeclarationKind::PositionalFormalParameter; + if (!pc_->functionScope().addDeclaredName(pc_, p, name, kind, beginPos)) { + return false; + } + } + + if (!pc_->positionalFormalParameterNames().append(name)) { + ReportOutOfMemory(cx_); + return false; + } + + NameNodeType paramNode = newName(name); + if (!paramNode) { + return false; + } + + handler_.addFunctionFormalParameter(funNode, paramNode); + return true; +} + +template <class ParseHandler> +bool PerHandlerParser<ParseHandler>::noteDestructuredPositionalFormalParameter( + FunctionNodeType funNode, Node destruct) { + // Append an empty name to the positional formals vector to keep track of + // argument slots when making FunctionScope::ParserData. + if (!pc_->positionalFormalParameterNames().append(nullptr)) { + ReportOutOfMemory(cx_); + return false; + } + + handler_.addFunctionFormalParameter(funNode, destruct); + return true; +} + +template <class ParseHandler, typename Unit> +bool GeneralParser<ParseHandler, Unit>::noteDeclaredName(const ParserName* name, + DeclarationKind kind, + TokenPos pos) { + // The asm.js validator does all its own symbol-table management so, as an + // optimization, avoid doing any work here. + if (pc_->useAsmOrInsideUseAsm()) { + return true; + } + + switch (kind) { + case DeclarationKind::Var: + case DeclarationKind::BodyLevelFunction: { + Maybe<DeclarationKind> redeclaredKind; + uint32_t prevPos; + if (!pc_->tryDeclareVar(name, kind, pos.begin, &redeclaredKind, + &prevPos)) { + return false; + } + + if (redeclaredKind) { + reportRedeclaration(name, *redeclaredKind, pos, prevPos); + return false; + } + + break; + } + + case DeclarationKind::ModuleBodyLevelFunction: { + MOZ_ASSERT(pc_->atModuleLevel()); + + AddDeclaredNamePtr p = pc_->varScope().lookupDeclaredNameForAdd(name); + if (p) { + reportRedeclaration(name, p->value()->kind(), pos, p->value()->pos()); + return false; + } + + if (!pc_->varScope().addDeclaredName(pc_, p, name, kind, pos.begin)) { + return false; + } + + // Body-level functions in modules are always closed over. + pc_->varScope().lookupDeclaredName(name)->value()->setClosedOver(); + + break; + } + + case DeclarationKind::FormalParameter: { + // It is an early error if any non-positional formal parameter name + // (e.g., destructuring formal parameter) is duplicated. + + AddDeclaredNamePtr p = + pc_->functionScope().lookupDeclaredNameForAdd(name); + if (p) { + error(JSMSG_BAD_DUP_ARGS); + return false; + } + + if (!pc_->functionScope().addDeclaredName(pc_, p, name, kind, + pos.begin)) { + return false; + } + + break; + } + + case DeclarationKind::LexicalFunction: + case DeclarationKind::PrivateName: { + ParseContext::Scope* scope = pc_->innermostScope(); + AddDeclaredNamePtr p = scope->lookupDeclaredNameForAdd(name); + if (p) { + reportRedeclaration(name, p->value()->kind(), pos, p->value()->pos()); + return false; + } + + if (!scope->addDeclaredName(pc_, p, name, kind, pos.begin)) { + return false; + } + + break; + } + + case DeclarationKind::SloppyLexicalFunction: { + // Functions in block have complex allowances in sloppy mode for being + // labelled that other lexical declarations do not have. Those checks + // are more complex than calling checkLexicalDeclarationDirectlyWithin- + // Block and are done in checkFunctionDefinition. + + ParseContext::Scope* scope = pc_->innermostScope(); + if (AddDeclaredNamePtr p = scope->lookupDeclaredNameForAdd(name)) { + // It is usually an early error if there is another declaration + // with the same name in the same scope. + // + // Sloppy lexical functions may redeclare other sloppy lexical + // functions for web compatibility reasons. + if (p->value()->kind() != DeclarationKind::SloppyLexicalFunction) { + reportRedeclaration(name, p->value()->kind(), pos, p->value()->pos()); + return false; + } + } else { + if (!scope->addDeclaredName(pc_, p, name, kind, pos.begin)) { + return false; + } + } + + break; + } + + case DeclarationKind::Let: + case DeclarationKind::Const: + case DeclarationKind::Class: + // The BoundNames of LexicalDeclaration and ForDeclaration must not + // contain 'let'. (CatchParameter is the only lexical binding form + // without this restriction.) + if (name == cx_->parserNames().let) { + errorAt(pos.begin, JSMSG_LEXICAL_DECL_DEFINES_LET); + return false; + } + + // For body-level lexically declared names in a function, it is an + // early error if there is a formal parameter of the same name. This + // needs a special check if there is an extra var scope due to + // parameter expressions. + if (pc_->isFunctionExtraBodyVarScopeInnermost()) { + DeclaredNamePtr p = pc_->functionScope().lookupDeclaredName(name); + if (p && DeclarationKindIsParameter(p->value()->kind())) { + reportRedeclaration(name, p->value()->kind(), pos, p->value()->pos()); + return false; + } + } + + [[fallthrough]]; + + case DeclarationKind::Import: + // Module code is always strict, so 'let' is always a keyword and never a + // name. + MOZ_ASSERT(name != cx_->parserNames().let); + [[fallthrough]]; + + case DeclarationKind::SimpleCatchParameter: + case DeclarationKind::CatchParameter: { + ParseContext::Scope* scope = pc_->innermostScope(); + + // It is an early error if there is another declaration with the same + // name in the same scope. + AddDeclaredNamePtr p = scope->lookupDeclaredNameForAdd(name); + if (p) { + reportRedeclaration(name, p->value()->kind(), pos, p->value()->pos()); + return false; + } + + if (!scope->addDeclaredName(pc_, p, name, kind, pos.begin)) { + return false; + } + + break; + } + + case DeclarationKind::CoverArrowParameter: + // CoverArrowParameter is only used as a placeholder declaration kind. + break; + + case DeclarationKind::PositionalFormalParameter: + MOZ_CRASH( + "Positional formal parameter names should use " + "notePositionalFormalParameter"); + break; + + case DeclarationKind::VarForAnnexBLexicalFunction: + MOZ_CRASH( + "Synthesized Annex B vars should go through " + "tryDeclareVarForAnnexBLexicalFunction"); + break; + } + + return true; +} + +template <class ParseHandler, typename Unit> +bool GeneralParser<ParseHandler, Unit>::noteDeclaredPrivateName( + Node nameNode, const ParserName* name, PropertyType propType, + TokenPos pos) { + ParseContext::Scope* scope = pc_->innermostScope(); + AddDeclaredNamePtr p = scope->lookupDeclaredNameForAdd(name); + + PrivateNameKind kind; + switch (propType) { + case PropertyType::Field: + kind = PrivateNameKind::Field; + break; + case PropertyType::Method: + case PropertyType::GeneratorMethod: + case PropertyType::AsyncMethod: + case PropertyType::AsyncGeneratorMethod: + kind = PrivateNameKind::Method; + break; + case PropertyType::Getter: + kind = PrivateNameKind::Getter; + break; + case PropertyType::Setter: + kind = PrivateNameKind::Setter; + break; + default: + kind = PrivateNameKind::None; + } + + if (p) { + PrivateNameKind prevKind = p->value()->privateNameKind(); + if ((prevKind == PrivateNameKind::Getter && + kind == PrivateNameKind::Setter) || + (prevKind == PrivateNameKind::Setter && + kind == PrivateNameKind::Getter)) { + p->value()->setPrivateNameKind(PrivateNameKind::GetterSetter); + handler_.setPrivateNameKind(nameNode, PrivateNameKind::GetterSetter); + return true; + } + + reportRedeclaration(name, p->value()->kind(), pos, p->value()->pos()); + return false; + } + + if (!scope->addDeclaredName(pc_, p, name, DeclarationKind::PrivateName, + pos.begin)) { + return false; + } + scope->lookupDeclaredName(name)->value()->setPrivateNameKind(kind); + handler_.setPrivateNameKind(nameNode, kind); + + return true; +} + +bool ParserBase::noteUsedNameInternal(const ParserName* name, + NameVisibility visibility, + mozilla::Maybe<TokenPos> tokenPosition) { + // The asm.js validator does all its own symbol-table management so, as an + // optimization, avoid doing any work here. + if (pc_->useAsmOrInsideUseAsm()) { + return true; + } + + // Global bindings are properties and not actual bindings; we don't need + // to know if they are closed over. So no need to track used name at the + // global scope. It is not incorrect to track them, this is an + // optimization. + // + // As an exception however, we continue to track private name references, + // as the used names tracker is used to provide early errors for undeclared + // private name references + ParseContext::Scope* scope = pc_->innermostScope(); + if (pc_->sc()->isGlobalContext() && scope == &pc_->varScope() && + visibility == NameVisibility::Public) { + return true; + } + + return usedNames_.noteUse(cx_, name, visibility, pc_->scriptId(), scope->id(), + tokenPosition); +} + +template <class ParseHandler> +bool PerHandlerParser<ParseHandler>:: + propagateFreeNamesAndMarkClosedOverBindings(ParseContext::Scope& scope) { + // Now that we have all the declared names in the scope, check which + // functions should exhibit Annex B semantics. + if (!scope.propagateAndMarkAnnexBFunctionBoxes(pc_)) { + return false; + } + + if (handler_.canSkipLazyClosedOverBindings()) { + // Scopes are nullptr-delimited in the BaseScript closed over bindings + // array. + uint32_t slotCount = scope.declaredCount(); + while (JSAtom* name = handler_.nextLazyClosedOverBinding()) { + // TODO-Stencil + // After closed-over-bindings are snapshotted in the handler, + // remove this. + const ParserAtom* parserAtom = + this->compilationState_.parserAtoms.internJSAtom( + cx_, this->getCompilationStencil(), name); + if (!parserAtom) { + return false; + } + + scope.lookupDeclaredName(parserAtom->asName())->value()->setClosedOver(); + MOZ_ASSERT(slotCount > 0); + slotCount--; + } + + if (pc_->isGeneratorOrAsync()) { + scope.setOwnStackSlotCount(slotCount); + } + return true; + } + + constexpr bool isSyntaxParser = + std::is_same_v<ParseHandler, SyntaxParseHandler>; + uint32_t scriptId = pc_->scriptId(); + uint32_t scopeId = scope.id(); + + uint32_t slotCount = 0; + for (BindingIter bi = scope.bindings(pc_); bi; bi++) { + 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(bi.name())) { + ReportOutOfMemory(cx_); + 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(nullptr)) { + ReportOutOfMemory(cx_); + return false; + } + } + + return true; +} + +template <typename Unit> +bool Parser<FullParseHandler, Unit>::checkStatementsEOF() { + // This is designed to be paired with parsing a statement list at the top + // level. + // + // The statementList() call breaks on TokenKind::RightCurly, so make sure + // we've reached EOF here. + TokenKind tt; + if (!tokenStream.peekToken(&tt, TokenStream::SlashIsRegExp)) { + return false; + } + if (tt != TokenKind::Eof) { + error(JSMSG_UNEXPECTED_TOKEN, "expression", TokenKindToDesc(tt)); + return false; + } + return true; +} + +template <typename ScopeT> +typename ScopeT::ParserData* NewEmptyBindingData(JSContext* cx, + LifoAlloc& alloc, + uint32_t numBindings) { + using Data = typename ScopeT::ParserData; + size_t allocSize = SizeOfScopeData<Data>(numBindings); + auto* bindings = alloc.newWithSize<Data>(allocSize, numBindings); + if (!bindings) { + ReportOutOfMemory(cx); + } + return bindings; +} + +GlobalScope::ParserData* NewEmptyGlobalScopeData(JSContext* cx, + LifoAlloc& alloc, + uint32_t numBindings) { + return NewEmptyBindingData<GlobalScope>(cx, alloc, numBindings); +} + +LexicalScope::ParserData* NewEmptyLexicalScopeData(JSContext* cx, + LifoAlloc& alloc, + uint32_t numBindings) { + return NewEmptyBindingData<LexicalScope>(cx, alloc, numBindings); +} + +FunctionScope::ParserData* NewEmptyFunctionScopeData(JSContext* cx, + LifoAlloc& alloc, + uint32_t numBindings) { + return NewEmptyBindingData<FunctionScope>(cx, alloc, numBindings); +} + +namespace detail { + +template <class SlotInfo> +static MOZ_ALWAYS_INLINE ParserBindingName* InitializeIndexedBindings( + SlotInfo& slotInfo, ParserBindingName* start, ParserBindingName* cursor) { + return cursor; +} + +template <class SlotInfo, typename UnsignedInteger, typename... Step> +static MOZ_ALWAYS_INLINE ParserBindingName* InitializeIndexedBindings( + SlotInfo& slotInfo, ParserBindingName* start, ParserBindingName* cursor, + UnsignedInteger SlotInfo::*field, const ParserBindingNameVector& bindings, + Step&&... step) { + slotInfo.*field = + AssertedCast<UnsignedInteger>(PointerRangeSize(start, cursor)); + + ParserBindingName* newCursor = + std::uninitialized_copy(bindings.begin(), bindings.end(), cursor); + + return InitializeIndexedBindings(slotInfo, start, newCursor, + std::forward<Step>(step)...); +} + +} // namespace detail + +// Initialize |data->trailingNames| bindings, then set |data->slotInfo.length| +// to the count of bindings added (which must equal |count|). +// +// First, |firstBindings| are added to |data->trailingNames|. Then any "steps" +// present are performed first to last. Each step is 1) a pointer to a member +// of |data| to be set to the current number of bindings added, and 2) a vector +// of |ParserBindingName|s to then copy into |data->trailingNames|. (Thus each +// |data| member field indicates where the corresponding vector's names start.) +template <class Data, typename... Step> +static MOZ_ALWAYS_INLINE void InitializeBindingData( + Data* data, uint32_t count, const ParserBindingNameVector& firstBindings, + Step&&... step) { + MOZ_ASSERT(data->slotInfo.length == 0, "data shouldn't be filled yet"); + + ParserBindingName* start = data->trailingNames.start(); + ParserBindingName* cursor = std::uninitialized_copy( + firstBindings.begin(), firstBindings.end(), start); + +#ifdef DEBUG + ParserBindingName* end = +#endif + detail::InitializeIndexedBindings(data->slotInfo, start, cursor, + std::forward<Step>(step)...); + + MOZ_ASSERT(PointerRangeSize(start, end) == count); + data->slotInfo.length = count; +} + +Maybe<GlobalScope::ParserData*> NewGlobalScopeData(JSContext* cx, + ParseContext::Scope& scope, + LifoAlloc& alloc, + ParseContext* pc) { + ParserBindingNameVector vars(cx); + ParserBindingNameVector lets(cx); + ParserBindingNameVector consts(cx); + + bool allBindingsClosedOver = pc->sc()->allBindingsClosedOver(); + for (BindingIter bi = scope.bindings(pc); bi; bi++) { + bool closedOver = allBindingsClosedOver || bi.closedOver(); + + switch (bi.kind()) { + case BindingKind::Var: { + bool isTopLevelFunction = + bi.declarationKind() == DeclarationKind::BodyLevelFunction; + + ParserBindingName binding(bi.name()->toIndex(), closedOver, + isTopLevelFunction); + if (!vars.append(binding)) { + return Nothing(); + } + break; + } + case BindingKind::Let: { + ParserBindingName binding(bi.name()->toIndex(), closedOver); + if (!lets.append(binding)) { + return Nothing(); + } + break; + } + case BindingKind::Const: { + ParserBindingName binding(bi.name()->toIndex(), 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<GlobalScope>(cx, 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<GlobalScope::ParserData*> ParserBase::newGlobalScopeData( + ParseContext::Scope& scope) { + return NewGlobalScopeData(cx_, scope, stencilAlloc(), pc_); +} + +Maybe<ModuleScope::ParserData*> NewModuleScopeData(JSContext* cx, + ParseContext::Scope& scope, + LifoAlloc& alloc, + ParseContext* pc) { + ParserBindingNameVector imports(cx); + ParserBindingNameVector vars(cx); + ParserBindingNameVector lets(cx); + ParserBindingNameVector consts(cx); + + bool allBindingsClosedOver = pc->sc()->allBindingsClosedOver(); + for (BindingIter bi = scope.bindings(pc); bi; bi++) { + // Imports are indirect bindings and must not be given known slots. + ParserBindingName binding(bi.name()->toIndex(), + (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<ModuleScope>(cx, 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<ModuleScope::ParserData*> ParserBase::newModuleScopeData( + ParseContext::Scope& scope) { + return NewModuleScopeData(cx_, scope, stencilAlloc(), pc_); +} + +Maybe<EvalScope::ParserData*> NewEvalScopeData(JSContext* cx, + ParseContext::Scope& scope, + LifoAlloc& alloc, + ParseContext* pc) { + ParserBindingNameVector vars(cx); + + for (BindingIter bi = scope.bindings(pc); bi; bi++) { + // Eval scopes only contain 'var' bindings. Make all bindings aliased + // for now. + MOZ_ASSERT(bi.kind() == BindingKind::Var); + bool isTopLevelFunction = + bi.declarationKind() == DeclarationKind::BodyLevelFunction; + + ParserBindingName binding(bi.name()->toIndex(), true, isTopLevelFunction); + if (!vars.append(binding)) { + return Nothing(); + } + } + + EvalScope::ParserData* bindings = nullptr; + uint32_t numBindings = vars.length(); + + if (numBindings > 0) { + bindings = NewEmptyBindingData<EvalScope>(cx, alloc, numBindings); + if (!bindings) { + return Nothing(); + } + + InitializeBindingData(bindings, numBindings, vars); + } + + return Some(bindings); +} + +Maybe<EvalScope::ParserData*> ParserBase::newEvalScopeData( + ParseContext::Scope& scope) { + return NewEvalScopeData(cx_, scope, stencilAlloc(), pc_); +} + +Maybe<FunctionScope::ParserData*> NewFunctionScopeData( + JSContext* cx, ParseContext::Scope& scope, bool hasParameterExprs, + LifoAlloc& alloc, ParseContext* pc) { + ParserBindingNameVector positionalFormals(cx); + ParserBindingNameVector formals(cx); + ParserBindingNameVector vars(cx); + + bool allBindingsClosedOver = + pc->sc()->allBindingsClosedOver() || scope.tooBigToOptimize(); + bool argumentBindingsClosedOver = + allBindingsClosedOver || pc->isGeneratorOrAsync(); + bool hasDuplicateParams = pc->functionBox()->hasDuplicateParameters; + + // Positional parameter names must be added in order of appearance as they are + // referenced using argument slots. + for (size_t i = 0; i < pc->positionalFormalParameterNames().length(); i++) { + const ParserAtom* name = pc->positionalFormalParameterNames()[i]; + + ParserBindingName bindName; + if (name) { + DeclaredNamePtr p = scope.lookupDeclaredName(name); + + // Do not consider any positional formal parameters closed over if + // there are parameter defaults. It is the binding in the defaults + // scope that is closed over instead. + bool closedOver = + argumentBindingsClosedOver || (p && p->value()->closedOver()); + + // If the parameter name has duplicates, only the final parameter + // name should be on the environment, as otherwise the environment + // object would have multiple, same-named properties. + if (hasDuplicateParams) { + for (size_t j = pc->positionalFormalParameterNames().length() - 1; + j > i; j--) { + if (pc->positionalFormalParameterNames()[j] == name) { + closedOver = false; + break; + } + } + } + + bindName = ParserBindingName(name->toIndex(), closedOver); + } + + if (!positionalFormals.append(bindName)) { + return Nothing(); + } + } + + for (BindingIter bi = scope.bindings(pc); bi; bi++) { + ParserBindingName binding(bi.name()->toIndex(), + allBindingsClosedOver || bi.closedOver()); + switch (bi.kind()) { + case BindingKind::FormalParameter: + // Positional parameter names are already handled above. + if (bi.declarationKind() == DeclarationKind::FormalParameter) { + if (!formals.append(binding)) { + return Nothing(); + } + } + break; + case BindingKind::Var: + // The only vars in the function scope when there are parameter + // exprs, which induces a separate var environment, should be the + // special bindings. + MOZ_ASSERT_IF(hasParameterExprs, + FunctionScope::isSpecialName(cx, bi.name()->toIndex())); + if (!vars.append(binding)) { + return Nothing(); + } + break; + default: + break; + } + } + + FunctionScope::ParserData* bindings = nullptr; + uint32_t numBindings = + positionalFormals.length() + formals.length() + vars.length(); + + if (numBindings > 0) { + bindings = NewEmptyBindingData<FunctionScope>(cx, alloc, numBindings); + if (!bindings) { + return Nothing(); + } + + // The ordering here is important. See comments in FunctionScope. + InitializeBindingData( + bindings, numBindings, positionalFormals, + &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<FunctionScope::ParserData*> ParserBase::newFunctionScopeData( + ParseContext::Scope& scope, bool hasParameterExprs) { + return NewFunctionScopeData(cx_, scope, hasParameterExprs, stencilAlloc(), + pc_); +} + +VarScope::ParserData* NewEmptyVarScopeData(JSContext* cx, LifoAlloc& alloc, + uint32_t numBindings) { + return NewEmptyBindingData<VarScope>(cx, alloc, numBindings); +} + +Maybe<VarScope::ParserData*> NewVarScopeData(JSContext* cx, + ParseContext::Scope& scope, + LifoAlloc& alloc, + ParseContext* pc) { + ParserBindingNameVector vars(cx); + + bool allBindingsClosedOver = + pc->sc()->allBindingsClosedOver() || scope.tooBigToOptimize(); + + for (BindingIter bi = scope.bindings(pc); bi; bi++) { + if (bi.kind() == BindingKind::Var) { + ParserBindingName binding(bi.name()->toIndex(), + allBindingsClosedOver || bi.closedOver()); + if (!vars.append(binding)) { + return Nothing(); + } + } + } + + VarScope::ParserData* bindings = nullptr; + uint32_t numBindings = vars.length(); + + if (numBindings > 0) { + bindings = NewEmptyBindingData<VarScope>(cx, alloc, numBindings); + if (!bindings) { + return Nothing(); + } + + InitializeBindingData(bindings, numBindings, vars); + } + + return Some(bindings); +} + +// Compute if `NewVarScopeData` would return any binding list. This is done +// without allocate the binding list. +static bool VarScopeHasBindings(ParseContext* pc) { + for (BindingIter bi = pc->varScope().bindings(pc); bi; bi++) { + if (bi.kind() == BindingKind::Var) { + return true; + } + } + + return false; +} + +Maybe<VarScope::ParserData*> ParserBase::newVarScopeData( + ParseContext::Scope& scope) { + return NewVarScopeData(cx_, scope, stencilAlloc(), pc_); +} + +Maybe<LexicalScope::ParserData*> NewLexicalScopeData(JSContext* cx, + ParseContext::Scope& scope, + LifoAlloc& alloc, + ParseContext* pc) { + ParserBindingNameVector lets(cx); + ParserBindingNameVector consts(cx); + + bool allBindingsClosedOver = + pc->sc()->allBindingsClosedOver() || scope.tooBigToOptimize(); + + for (BindingIter bi = scope.bindings(pc); bi; bi++) { + ParserBindingName binding(bi.name()->toIndex(), + allBindingsClosedOver || bi.closedOver()); + switch (bi.kind()) { + case BindingKind::Let: + if (!lets.append(binding)) { + return Nothing(); + } + break; + case BindingKind::Const: + if (!consts.append(binding)) { + return Nothing(); + } + break; + default: + break; + } + } + + LexicalScope::ParserData* bindings = nullptr; + uint32_t numBindings = lets.length() + consts.length(); + + if (numBindings > 0) { + bindings = NewEmptyBindingData<LexicalScope>(cx, alloc, numBindings); + if (!bindings) { + return Nothing(); + } + + // The ordering here is important. See comments in LexicalScope. + InitializeBindingData(bindings, numBindings, lets, + &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<LexicalScope::ParserData*> ParserBase::newLexicalScopeData( + ParseContext::Scope& scope) { + return NewLexicalScopeData(cx_, scope, stencilAlloc(), pc_); +} + +template <> +SyntaxParseHandler::LexicalScopeNodeType +PerHandlerParser<SyntaxParseHandler>::finishLexicalScope( + ParseContext::Scope& scope, Node body, ScopeKind kind) { + if (!propagateFreeNamesAndMarkClosedOverBindings(scope)) { + return null(); + } + + return handler_.newLexicalScope(body); +} + +template <> +LexicalScopeNode* PerHandlerParser<FullParseHandler>::finishLexicalScope( + ParseContext::Scope& scope, ParseNode* body, ScopeKind kind) { + if (!propagateFreeNamesAndMarkClosedOverBindings(scope)) { + return nullptr; + } + + Maybe<LexicalScope::ParserData*> bindings = newLexicalScopeData(scope); + if (!bindings) { + return nullptr; + } + + return handler_.newLexicalScope(*bindings, body, kind); +} + +template <class ParseHandler> +bool PerHandlerParser<ParseHandler>::checkForUndefinedPrivateFields( + EvalSharedContext* evalSc) { + if (handler_.canSkipLazyClosedOverBindings()) { + // We're delazifying -- so we already checked private names during first + // parse. + return true; + } + + Vector<UnboundPrivateName, 8> unboundPrivateNames(cx_); + if (!this->compilationState_.usedNames.getUnboundPrivateNames( + unboundPrivateNames)) { + return false; + } + + // No unbound names, let's get out of here! + if (unboundPrivateNames.empty()) { + return true; + } + + // It is an early error if there's private name references unbound, + // unless it's an eval, in which case we need to check the scope + // chain. + if (!evalSc) { + // The unbound private names are sorted, so just grab the first one. + UnboundPrivateName minimum = unboundPrivateNames[0]; + UniqueChars str = ParserAtomToPrintableString(cx_, minimum.atom); + if (!str) { + return false; + } + + errorAt(minimum.position.begin, JSMSG_MISSING_PRIVATE_DECL, str.get()); + return false; + } + + // For the given private name, search the enclosing scope chain + // to see if there's an associated binding, and if not, issue an error. + auto verifyPrivateName = [](JSContext* cx, auto* parser, + HandleScope enclosingScope, + UnboundPrivateName unboundName) { + // Walk the enclosing scope chain looking for this private name; + for (ScopeIter si(enclosingScope); si; si++) { + // Private names are only found within class body scopes. + if (si.scope()->kind() != ScopeKind::ClassBody) { + continue; + } + + // Look for a matching binding. + for (js::BindingIter bi(si.scope()); bi; bi++) { + if (unboundName.atom->equalsJSAtom(bi.name())) { + // Awesome. We found it, we're done here! + return true; + } + } + } + + // Didn't find a matching binding, so issue an error. + UniqueChars str = ParserAtomToPrintableString(cx, unboundName.atom); + if (!str) { + return false; + } + parser->errorAt(unboundName.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 (!verifyPrivateName(cx_, this, + compilationState_.scopeContext.effectiveScope, + unboundName)) { + return false; + } + } + + return true; +} + +template <typename Unit> +LexicalScopeNode* Parser<FullParseHandler, Unit>::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->getCompilationStencil().input.enclosingScope) { + // 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. + ScopeIter si(this->getCompilationStencil().input.enclosingScope); + for (; si; si++) { + if (si.kind() == ScopeKind::Function) { + JSFunction* fun = si.scope()->as<FunctionScope>().canonicalFunction(); + if (fun->isArrow()) { + continue; + } + MOZ_ASSERT(fun->allowSuperProperty()); + MOZ_ASSERT(fun->baseScript()->needsHomeObject()); + break; + } + } + MOZ_ASSERT(!si.done(), + "Eval must have found an enclosing function box scope that " + "allows super.property"); + } +#endif + + if (!CheckParseTree(cx_, 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(cx_, this->compilationState_.parserAtoms, &node, + &handler_)) { + return null(); + } + } + body = handler_.asLexicalScope(node); + + if (!this->setSourceMapInfo()) { + return nullptr; + } + + // For eval scripts, since all bindings are automatically considered + // closed over, we don't need to call propagateFreeNamesAndMarkClosed- + // OverBindings. However, Annex B.3.3 functions still need to be marked. + if (!varScope.propagateAndMarkAnnexBFunctionBoxes(pc_)) { + return nullptr; + } + + Maybe<EvalScope::ParserData*> bindings = newEvalScopeData(pc_->varScope()); + if (!bindings) { + return nullptr; + } + evalsc->bindings = *bindings; + + return body; +} + +template <typename Unit> +ListNode* Parser<FullParseHandler, Unit>::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(cx_, 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(cx_, this->compilationState_.parserAtoms, &node, + &handler_)) { + return null(); + } + } + body = &node->as<ListNode>(); + + 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_)) { + return nullptr; + } + + Maybe<GlobalScope::ParserData*> bindings = + newGlobalScopeData(pc_->varScope()); + if (!bindings) { + return nullptr; + } + globalsc->bindings = *bindings; + + return body; +} + +template <typename Unit> +ModuleNode* Parser<FullParseHandler, Unit>::moduleBody( + ModuleSharedContext* modulesc) { + MOZ_ASSERT(checkOptionsCalled_); + + this->stencil_.moduleMetadata.emplace(); + + SourceParseContext modulepc(this, modulesc, nullptr); + if (!modulepc.init()) { + return null(); + } + + ParseContext::VarScope varScope(this); + if (!varScope.init(pc_)) { + return nullptr; + } + + ModuleNodeType moduleNode = handler_.newModule(pos()); + if (!moduleNode) { + return null(); + } + + AutoAwaitIsKeyword<FullParseHandler, Unit> awaitIsKeyword( + this, AwaitIsModuleKeyword); + ListNode* stmtList = statementList(YieldIsName); + if (!stmtList) { + return null(); + } + + MOZ_ASSERT(stmtList->isKind(ParseNodeKind::StatementList)); + moduleNode->setBody(&stmtList->as<ListNode>()); + + if (pc_->isAsync()) { + if (!noteUsedName(cx_->parserNames().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->stencil_.moduleMetadata); + } + + // Generate the Import/Export tables and store in CompilationStencil. + if (!modulesc->builder.buildTables(*this->stencil_.moduleMetadata)) { + return null(); + } + + // Check exported local bindings exist and mark them as closed over. + StencilModuleMetadata& moduleMetadata = *this->stencil_.moduleMetadata; + for (auto entry : moduleMetadata.localExportEntries) { + const ParserAtom* nameId = + this->compilationState_.getParserAtomAt(cx_, entry.localName); + MOZ_ASSERT(nameId); + + DeclaredNamePtr p = modulepc.varScope().lookupDeclaredName(nameId); + if (!p) { + UniqueChars str = ParserAtomToPrintableString(cx_, nameId); + if (!str) { + 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(cx_->parserNames().starNamespaceStar, + DeclarationKind::Const, pos())) { + return nullptr; + } + modulepc.varScope() + .lookupDeclaredName(cx_->parserNames().starNamespaceStar) + ->value() + ->setClosedOver(); + + if (!CheckParseTree(cx_, 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(cx_, this->compilationState_.parserAtoms, &node, + &handler_)) { + return null(); + } + } + stmtList = &node->as<ListNode>(); + + 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<ModuleScope::ParserData*> bindings = + newModuleScopeData(modulepc.varScope()); + if (!bindings) { + return nullptr; + } + + modulesc->bindings = *bindings; + return moduleNode; +} + +template <typename Unit> +SyntaxParseHandler::ModuleNodeType Parser<SyntaxParseHandler, Unit>::moduleBody( + ModuleSharedContext* modulesc) { + MOZ_ALWAYS_FALSE(abortIfSyntaxParser()); + return SyntaxParseHandler::NodeFailure; +} + +template <class ParseHandler> +typename ParseHandler::NameNodeType +PerHandlerParser<ParseHandler>::newInternalDotName(const ParserName* name) { + NameNodeType nameNode = newName(name); + if (!nameNode) { + return null(); + } + if (!noteUsedName(name)) { + return null(); + } + return nameNode; +} + +template <class ParseHandler> +typename ParseHandler::NameNodeType +PerHandlerParser<ParseHandler>::newThisName() { + return newInternalDotName(cx_->parserNames().dotThis); +} + +template <class ParseHandler> +typename ParseHandler::NameNodeType +PerHandlerParser<ParseHandler>::newDotGeneratorName() { + return newInternalDotName(cx_->parserNames().dotGenerator); +} + +template <class ParseHandler> +bool PerHandlerParser<ParseHandler>::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<FullParseHandler>::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<VarScope::ParserData*> 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<FunctionScope::ParserData*> bindings = + newFunctionScopeData(pc_->functionScope(), hasParameterExprs); + if (!bindings) { + return false; + } + funbox->setFunctionScopeBindings(*bindings); + } + + if (funbox->isNamedLambda() && !isStandaloneFunction) { + Maybe<LexicalScope::ParserData*> bindings = + newLexicalScopeData(pc_->namedLambdaScope()); + if (!bindings) { + return false; + } + funbox->setNamedLambdaBindings(*bindings); + } + + funbox->finishScriptFlags(); + funbox->copyFunctionFields(script); + funbox->copyScriptFields(script); + + if (!handler_.canSkipLazyInnerFunctions()) { + ScriptStencilExtra& scriptExtra = funbox->functionExtraStencil(); + funbox->copyFunctionExtraFields(scriptExtra); + funbox->copyScriptExtraFields(scriptExtra); + } + + return true; +} + +template <> +bool PerHandlerParser<SyntaxParseHandler>::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); + funbox->copyScriptFields(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(cx_); + 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( + cx_, 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 (const ParserAtom* binding : pc_->closedOverBindingsForLazy()) { + void* raw = &(*cursor++); + if (binding) { + binding->markUsedByStencil(); + new (raw) TaggedScriptThingIndex(binding->toIndex()); + } 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; +} + +FunctionFlags InitialFunctionFlags(FunctionSyntaxKind kind, + GeneratorKind generatorKind, + FunctionAsyncKind asyncKind, + bool isSelfHosting, bool hasUnclonedName) { + FunctionFlags flags = {}; + gc::AllocKind allocKind = gc::AllocKind::FUNCTION; + + // The SetCanonicalName mechanism is only allowed on normal functions. + MOZ_ASSERT_IF(hasUnclonedName, kind == FunctionSyntaxKind::Statement); + + 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; + allocKind = gc::AllocKind::FUNCTION_EXTENDED; + break; + case FunctionSyntaxKind::Method: + case FunctionSyntaxKind::FieldInitializer: + flags = FunctionFlags::INTERPRETED_METHOD; + allocKind = gc::AllocKind::FUNCTION_EXTENDED; + break; + case FunctionSyntaxKind::ClassConstructor: + case FunctionSyntaxKind::DerivedClassConstructor: + flags = FunctionFlags::INTERPRETED_CLASS_CTOR; + allocKind = gc::AllocKind::FUNCTION_EXTENDED; + break; + case FunctionSyntaxKind::Getter: + flags = FunctionFlags::INTERPRETED_GETTER; + allocKind = gc::AllocKind::FUNCTION_EXTENDED; + break; + case FunctionSyntaxKind::Setter: + flags = FunctionFlags::INTERPRETED_SETTER; + allocKind = gc::AllocKind::FUNCTION_EXTENDED; + break; + default: + MOZ_ASSERT(kind == FunctionSyntaxKind::Statement); + if (hasUnclonedName) { + allocKind = gc::AllocKind::FUNCTION_EXTENDED; + } + flags = (generatorKind == GeneratorKind::NotGenerator && + asyncKind == FunctionAsyncKind::SyncFunction + ? FunctionFlags::INTERPRETED_NORMAL + : FunctionFlags::INTERPRETED_GENERATOR_OR_ASYNC); + } + + if (isSelfHosting) { + flags.setIsSelfHostedBuiltin(); + } + + if (allocKind == gc::AllocKind::FUNCTION_EXTENDED) { + flags.setIsExtended(); + } + + return flags; +} + +template <typename Unit> +FunctionNode* Parser<FullParseHandler, Unit>::standaloneFunction( + const Maybe<uint32_t>& 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. + const ParserAtom* explicitName = nullptr; + if (TokenKindIsPossibleIdentifierName(tt)) { + explicitName = anyChars.currentName(); + } else { + anyChars.ungetToken(); + } + + FunctionNodeType funNode = handler_.newFunction(syntaxKind, pos()); + if (!funNode) { + return null(); + } + + ListNodeType argsbody = handler_.newList(ParseNodeKind::ParamsBody, 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, flags, + syntaxKind); + + SourceParseContext funpc(this, funbox, newDirectives); + if (!funpc.init()) { + return null(); + } + + YieldHandling yieldHandling = GetYieldHandling(generatorKind); + AwaitHandling awaitHandling = GetAwaitHandling(asyncKind); + AutoAwaitIsKeyword<FullParseHandler, Unit> 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(cx_, 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(cx_, this->compilationState_.parserAtoms, &node, + &handler_)) { + return null(); + } + } + funNode = &node->as<FunctionNode>(); + + if (!checkForUndefinedPrivateFields(nullptr)) { + return null(); + } + + if (!this->setSourceMapInfo()) { + return null(); + } + + return funNode; +} + +template <class ParseHandler, typename Unit> +typename ParseHandler::LexicalScopeNodeType +GeneralParser<ParseHandler, Unit>::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' and 'this' 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_.canSkipLazyClosedOverBindings(); + if (!pc_->declareFunctionArgumentsObject(usedNames_, + canSkipLazyClosedOverBindings)) { + return null(); + } + if (!pc_->declareFunctionThis(usedNames_, canSkipLazyClosedOverBindings)) { + return null(); + } + } + + return finishLexicalScope(pc_->varScope(), body, ScopeKind::FunctionLexical); +} + +template <class ParseHandler, typename Unit> +bool GeneralParser<ParseHandler, Unit>::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) { + if (!options().topLevelAwait) { + error(JSMSG_AWAIT_OUTSIDE_ASYNC); + return false; + } + 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<SyntaxParseHandler>::finishFunction. + if (!outerpc->innerFunctionIndexesForLazy.append( + pc_->functionBox()->index())) { + return false; + } + + PropagateTransitiveParseFlags(pc_->functionBox(), outerpc->sc()); + + return true; +} + +const ParserAtom* ParserBase::prefixAccessorName(PropertyType propType, + const ParserAtom* propAtom) { + const ParserAtom* prefix = nullptr; + if (propType == PropertyType::Setter) { + prefix = cx_->parserNames().setPrefix; + } else { + MOZ_ASSERT(propType == PropertyType::Getter); + prefix = cx_->parserNames().getPrefix; + } + + const ParserAtom* atoms[2] = {prefix, propAtom}; + auto atomsRange = mozilla::Range(atoms, 2); + return this->compilationState_.parserAtoms.concatAtoms(cx_, atomsRange); +} + +template <class ParseHandler, typename Unit> +void GeneralParser<ParseHandler, Unit>::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 <class ParseHandler, typename Unit> +void GeneralParser<ParseHandler, Unit>::setFunctionStartAtCurrentToken( + FunctionBox* funbox) const { + setFunctionStartAtPosition(funbox, anyChars.currentToken().pos); +} + +template <class ParseHandler, typename Unit> +bool GeneralParser<ParseHandler, Unit>::functionArguments( + YieldHandling yieldHandling, FunctionSyntaxKind kind, + FunctionNodeType funNode) { + FunctionBox* funbox = pc_->functionBox(); + + bool parenFreeArrow = false; + // 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 = TokenStream::SlashIsDiv; + + // Modifier for the the first token in each argument. + // can be changed to TokenStream::SlashIsDiv for the following case: + // async a => 1 + // ^ + Modifier argModifier = TokenStream::SlashIsRegExp; + if (kind == FunctionSyntaxKind::Arrow) { + TokenKind tt; + // In async function, the first token after `async` is already gotten + // with TokenStream::SlashIsDiv. + // In sync function, the first token is already gotten with + // TokenStream::SlashIsRegExp. + firstTokenModifier = funbox->isAsync() ? TokenStream::SlashIsDiv + : TokenStream::SlashIsRegExp; + if (!tokenStream.peekToken(&tt, firstTokenModifier)) { + return false; + } + if (TokenKindIsPossibleIdentifier(tt)) { + parenFreeArrow = true; + argModifier = firstTokenModifier; + } + } + + TokenPos firstTokenPos; + if (!parenFreeArrow) { + TokenKind tt; + if (!tokenStream.getToken(&tt, firstTokenModifier)) { + return false; + } + if (tt != TokenKind::LeftParen) { + error(kind == FunctionSyntaxKind::Arrow ? JSMSG_BAD_ARROW_ARGS + : JSMSG_PAREN_BEFORE_FORMAL); + return false; + } + + firstTokenPos = pos(); + + // Record the start of function source (for FunctionToString). If we + // are parenFreeArrow, we will set this below, after consuming the NAME. + setFunctionStartAtCurrentToken(funbox); + } else { + // When delazifying, we may not have a current token and pos() is + // garbage. In that case, substitute the first token's position. + if (!tokenStream.peekTokenPos(&firstTokenPos, firstTokenModifier)) { + return false; + } + } + + ListNodeType argsbody = + handler_.newList(ParseNodeKind::ParamsBody, firstTokenPos); + if (!argsbody) { + return false; + } + handler_.setFunctionFormalParametersAndBody(funNode, argsbody); + + bool hasArguments = false; + if (parenFreeArrow) { + hasArguments = true; + } else { + bool matched; + if (!tokenStream.matchToken(&matched, TokenKind::RightParen, + TokenStream::SlashIsRegExp)) { + return false; + } + if (!matched) { + hasArguments = true; + } + } + if (hasArguments) { + 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, argModifier)) { + return false; + } + argModifier = TokenStream::SlashIsRegExp; + MOZ_ASSERT_IF(parenFreeArrow, TokenKindIsPossibleIdentifier(tt)); + + 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; + } + + if (parenFreeArrow) { + setFunctionStartAtCurrentToken(funbox); + } + + const ParserName* 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; + } + + // The next step is to detect arguments with default expressions, + // e.g. |function parseInt(str, radix = 10) {}|. But if we have a + // parentheses-free arrow function, |a => ...|, the '=' necessary + // for a default expression would really be an assignment operator: + // that is, |a = b => 42;| would parse as |a = (b => 42);|. So we + // should stop parsing arguments here. + if (parenFreeArrow) { + break; + } + + bool matched; + if (!tokenStream.matchToken(&matched, TokenKind::Assign, + TokenStream::SlashIsRegExp)) { + return false; + } + if (matched) { + // A default argument without parentheses would look like: + // a = expr => body, but both operators are right-associative, so + // that would have been parsed as a = (expr => body) instead. + // Therefore it's impossible to get here with parenFreeArrow. + MOZ_ASSERT(!parenFreeArrow); + + 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; + } + } + } + + if (!parenFreeArrow) { + 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 <typename Unit> +bool Parser<FullParseHandler, Unit>::skipLazyInnerFunction( + FunctionNode* funNode, uint32_t toStringStart, FunctionSyntaxKind kind, + 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. + + RootedFunction fun(cx_, handler_.nextLazyInnerFunction()); + + // TODO-Stencil: Consider for snapshotting. + const ParserAtom* displayAtom = nullptr; + if (fun->displayAtom()) { + displayAtom = this->compilationState_.parserAtoms.internJSAtom( + cx_, this->stencil_, fun->displayAtom()); + if (!displayAtom) { + return false; + } + } + + FunctionBox* funbox = newFunctionBox( + funNode, displayAtom, fun->flags(), toStringStart, + Directives(/* strict = */ false), fun->generatorKind(), fun->asyncKind()); + if (!funbox) { + return false; + } + + ScriptStencil& script = funbox->functionStencil(); + funbox->initFromLazyFunction(fun); + funbox->copyFunctionFields(script); + funbox->copyScriptFields(script); + + MOZ_ASSERT_IF(pc_->isFunctionBox(), + pc_->functionBox()->index() < funbox->index()); + + // 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(fun->baseScript()->hasEnclosingScript()); + MOZ_ASSERT_IF(fun->isClassConstructor(), + !fun->baseScript()->getMemberInitializers().valid); + + 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 <typename Unit> +bool Parser<SyntaxParseHandler, Unit>::skipLazyInnerFunction( + FunctionNodeType funNode, uint32_t toStringStart, FunctionSyntaxKind kind, + bool tryAnnexB) { + MOZ_CRASH("Cannot skip lazy inner functions when syntax parsing"); +} + +template <class ParseHandler, typename Unit> +bool GeneralParser<ParseHandler, Unit>::skipLazyInnerFunction( + FunctionNodeType funNode, uint32_t toStringStart, FunctionSyntaxKind kind, + bool tryAnnexB) { + return asFinalParser()->skipLazyInnerFunction(funNode, toStringStart, kind, + tryAnnexB); +} + +template <class ParseHandler, typename Unit> +bool GeneralParser<ParseHandler, Unit>::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 <class ParseHandler, typename Unit> +bool GeneralParser<ParseHandler, Unit>::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 <class ParseHandler, typename Unit> +typename ParseHandler::ListNodeType +GeneralParser<ParseHandler, Unit>::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 <class ParseHandler, typename Unit> +typename ParseHandler::FunctionNodeType +GeneralParser<ParseHandler, Unit>::functionDefinition( + FunctionNodeType funNode, uint32_t toStringStart, InHandling inHandling, + YieldHandling yieldHandling, const ParserAtom* 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_.canSkipLazyInnerFunctions()) { + if (!skipLazyInnerFunction(funNode, toStringStart, kind, tryAnnexB)) { + return null(); + } + + return funNode; + } + + bool isSelfHosting = options().selfHostingMode; + bool hasUnclonedName = isSelfHosting && funName && + IsExtendedUnclonedSelfHostedFunctionName(funName); + MOZ_ASSERT_IF(hasUnclonedName, !pc_->isFunctionBox()); + + FunctionFlags flags = InitialFunctionFlags(kind, generatorKind, asyncKind, + isSelfHosting, hasUnclonedName); + + // 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); + CompilationStencil::RewindToken startObj = + this->stencil_.getRewindToken(this->compilationState_); + + // 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->stencil_.rewind(this->compilationState_, startObj); + + // functionFormalParametersAndBody may have already set body before + // failing. + handler_.setFunctionFormalParametersAndBody(funNode, null()); + } + + return funNode; +} + +template <typename Unit> +bool Parser<FullParseHandler, Unit>::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 <typename Unit> +bool Parser<FullParseHandler, Unit>::trySyntaxParseInnerFunction( + FunctionNode** funNode, const ParserAtom* 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(); + CompilationStencil::RewindToken startObj = + this->stencil_.getRewindToken(this->compilationState_); + + // 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_, flags, 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->stencil_.rewind(this->compilationState_, startObj); + MOZ_ASSERT_IF(!syntaxParser->cx_->isHelperThreadContext(), + !syntaxParser->cx_->isExceptionPending()); + 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 <typename Unit> +bool Parser<SyntaxParseHandler, Unit>::trySyntaxParseInnerFunction( + FunctionNodeType* funNode, const ParserAtom* 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 <class ParseHandler, typename Unit> +inline bool GeneralParser<ParseHandler, Unit>::trySyntaxParseInnerFunction( + FunctionNodeType* funNode, const ParserAtom* 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 <class ParseHandler, typename Unit> +typename ParseHandler::FunctionNodeType +GeneralParser<ParseHandler, Unit>::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 <class ParseHandler, typename Unit> +typename ParseHandler::FunctionNodeType +GeneralParser<ParseHandler, Unit>::innerFunction( + FunctionNodeType funNode, ParseContext* outerpc, + const ParserAtom* 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, flags, 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 <class ParseHandler, typename Unit> +bool GeneralParser<ParseHandler, Unit>::appendToCallSiteObj( + CallSiteNodeType callSiteObj) { + Node cookedNode = noSubstitutionTaggedTemplate(); + if (!cookedNode) { + return false; + } + + const ParserAtom* 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 <typename Unit> +FunctionNode* Parser<FullParseHandler, Unit>::standaloneLazyFunction( + HandleFunction fun, uint32_t toStringStart, bool strict, + GeneratorKind generatorKind, FunctionAsyncKind asyncKind) { + MOZ_ASSERT(checkOptionsCalled_); + + FunctionSyntaxKind syntaxKind = FunctionSyntaxKind::Statement; + if (fun->isClassConstructor()) { + if (fun->isDerivedClassConstructor()) { + syntaxKind = FunctionSyntaxKind::DerivedClassConstructor; + } else { + syntaxKind = FunctionSyntaxKind::ClassConstructor; + } + } else if (fun->isMethod()) { + if (fun->isFieldInitializer()) { + syntaxKind = FunctionSyntaxKind::FieldInitializer; + } else { + syntaxKind = FunctionSyntaxKind::Method; + } + } else if (fun->isGetter()) { + syntaxKind = FunctionSyntaxKind::Getter; + } else if (fun->isSetter()) { + syntaxKind = FunctionSyntaxKind::Setter; + } else if (fun->isArrow()) { + syntaxKind = FunctionSyntaxKind::Arrow; + } + + FunctionNodeType funNode = handler_.newFunction(syntaxKind, pos()); + if (!funNode) { + return null(); + } + + // TODO-Stencil: Consider for snapshotting. + const ParserAtom* displayAtom = nullptr; + if (fun->displayAtom()) { + displayAtom = this->compilationState_.parserAtoms.internJSAtom( + cx_, this->stencil_, fun->displayAtom()); + if (!displayAtom) { + return null(); + } + } + + Directives directives(strict); + FunctionBox* funbox = + newFunctionBox(funNode, displayAtom, fun->flags(), toStringStart, + directives, generatorKind, asyncKind); + if (!funbox) { + return null(); + } + funbox->initFromLazyFunction(fun); + funbox->initStandalone(this->compilationState_.scopeContext, fun->flags(), + syntaxKind); + if (fun->isClassConstructor()) { + funbox->setMemberInitializers(fun->baseScript()->getMemberInitializers()); + } + + 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 = + (fun->isArrow() && asyncKind == FunctionAsyncKind::SyncFunction) + ? TokenStream::SlashIsRegExp + : TokenStream::SlashIsDiv; + if (!tokenStream.peekTokenPos(&funNode->pn_pos, modifier)) { + return null(); + } + + YieldHandling yieldHandling = GetYieldHandling(generatorKind); + + if (!functionFormalParametersAndBody(InAllowed, yieldHandling, &funNode, + syntaxKind)) { + MOZ_ASSERT(directives == newDirectives); + return null(); + } + + if (!CheckParseTree(cx_, 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(cx_, this->compilationState_.parserAtoms, &node, + &handler_)) { + return null(); + } + } + funNode = &node->as<FunctionNode>(); + + return funNode; +} + +void ParserBase::setFunctionEndFromCurrentToken(FunctionBox* funbox) const { + funbox->setEnd(anyChars.currentToken().pos.end); +} + +template <class ParseHandler, typename Unit> +bool GeneralParser<ParseHandler, Unit>::functionFormalParametersAndBody( + InHandling inHandling, YieldHandling yieldHandling, + FunctionNodeType* funNode, FunctionSyntaxKind kind, + const Maybe<uint32_t>& 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(cx_->parserNames().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 = + (funbox->isAsync() || + (kind == FunctionSyntaxKind::Arrow && awaitIsKeyword())) + ? AwaitIsKeyword + : AwaitIsName; + AutoAwaitIsKeyword<ParseHandler, Unit> awaitIsKeyword(this, awaitHandling); + AutoInParametersOfAsyncFunction<ParseHandler, Unit> inParameters( + this, funbox->isAsync()); + if (!functionArguments(yieldHandling, kind, *funNode)) { + return false; + } + } + + Maybe<ParseContext::VarScope> 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<ParseHandler, Unit> awaitIsKeyword(this, + bodyAwaitHandling); + AutoInParametersOfAsyncFunction<ParseHandler, Unit> 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"); + + const ParserName* propertyName = funbox->explicitName()->asName(); + 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 <class ParseHandler, typename Unit> +typename ParseHandler::FunctionNodeType +GeneralParser<ParseHandler, Unit>::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(); + } + } + + const ParserName* name = nullptr; + if (TokenKindIsPossibleIdentifier(tt)) { + name = bindingIdentifier(yieldHandling); + if (!name) { + return null(); + } + } else if (defaultHandling == AllowDefaultName) { + name = cx_->parserNames().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 <class ParseHandler, typename Unit> +typename ParseHandler::FunctionNodeType +GeneralParser<ParseHandler, Unit>::functionExpr(uint32_t toStringStart, + InvokedPrediction invoked, + FunctionAsyncKind asyncKind) { + MOZ_ASSERT(anyChars.isCurrentTokenType(TokenKind::Function)); + + AutoAwaitIsKeyword<ParseHandler, Unit> 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); + + const ParserName* name = nullptr; + 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, + * could be the string of a directive in a Directive Prologue. Directive + * strings never contain escape sequences or line continuations. + * isEscapeFreeStringLiteral, below, checks whether the node itself could be + * a directive. + */ +static inline bool IsEscapeFreeStringLiteral(const TokenPos& pos, + const ParserAtom* atom) { + /* + * If the string's length in the source code is its length as a value, + * accounting for the quotes, then it must not contain any escape + * sequences or line continuations. + */ + return pos.begin + atom->length() + 2 == pos.end; +} + +template <typename Unit> +bool Parser<SyntaxParseHandler, Unit>::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()); + + // Record that the current script source constains some AsmJS, to disable + // any incremental encoder, as AsmJS cannot be encoded with XDR at the + // moment. + if (ss) { + ss->setContainsAsmJS(); + } + return false; +} + +template <typename Unit> +bool Parser<FullParseHandler, Unit>::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; + } + + ss->setContainsAsmJS(); + 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(cx_, this->compilationState_.parserAtoms, *this, list, + &validated)) { + return false; + } + if (!validated) { + pc_->newDirectives->setAsmJS(); + return false; + } + + return true; +} + +template <class ParseHandler, typename Unit> +inline bool GeneralParser<ParseHandler, Unit>::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 <class ParseHandler, typename Unit> +bool GeneralParser<ParseHandler, Unit>::maybeParseDirective( + ListNodeType list, Node possibleDirective, bool* cont) { + TokenPos directivePos; + const ParserAtom* directive = + handler_.isStringExprStatement(possibleDirective, &directivePos); + + *cont = !!directive; + if (!*cont) { + return true; + } + + if (IsEscapeFreeStringLiteral(directivePos, directive)) { + if (directive == cx_->parserNames().useStrict) { + // 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()) { + // We keep track of the possible strict violations that could occur in + // the directive prologue -- deprecated octal syntax -- and + // complain now. + if (anyChars.sawDeprecatedOctal()) { + error(JSMSG_DEPRECATED_OCTAL); + return false; + } + pc_->sc()->setStrictScript(); + } + } else if (directive == cx_->parserNames().useAsm) { + if (pc_->isFunctionBox()) { + return asmJS(list); + } + return warningAt(directivePos.begin, JSMSG_USE_ASM_DIRECTIVE_FAIL); + } + } + return true; +} + +template <class ParseHandler, typename Unit> +typename ParseHandler::ListNodeType +GeneralParser<ParseHandler, Unit>::statementList(YieldHandling yieldHandling) { + if (!CheckRecursionLimit(cx_)) { + return null(); + } + + ListNodeType stmtList = handler_.newStatementList(pos()); + if (!stmtList) { + return null(); + } + + bool canHaveDirectives = pc_->atBodyLevel(); + if (canHaveDirectives) { + anyChars.clearSawDeprecatedOctal(); + } + + 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 <class ParseHandler, typename Unit> +typename ParseHandler::Node GeneralParser<ParseHandler, Unit>::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 <class ParseHandler, typename Unit> +bool GeneralParser<ParseHandler, Unit>::matchLabel( + YieldHandling yieldHandling, const ParserName** 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 = nullptr; + } + return true; +} + +template <class ParseHandler, typename Unit> +GeneralParser<ParseHandler, Unit>::PossibleError::PossibleError( + GeneralParser<ParseHandler, Unit>& parser) + : parser_(parser) {} + +template <class ParseHandler, typename Unit> +typename GeneralParser<ParseHandler, Unit>::PossibleError::Error& +GeneralParser<ParseHandler, Unit>::PossibleError::error(ErrorKind kind) { + if (kind == ErrorKind::Expression) { + return exprError_; + } + if (kind == ErrorKind::Destructuring) { + return destructuringError_; + } + MOZ_ASSERT(kind == ErrorKind::DestructuringWarning); + return destructuringWarning_; +} + +template <class ParseHandler, typename Unit> +void GeneralParser<ParseHandler, Unit>::PossibleError::setResolved( + ErrorKind kind) { + error(kind).state_ = ErrorState::None; +} + +template <class ParseHandler, typename Unit> +bool GeneralParser<ParseHandler, Unit>::PossibleError::hasError( + ErrorKind kind) { + return error(kind).state_ == ErrorState::Pending; +} + +template <class ParseHandler, typename Unit> +bool GeneralParser<ParseHandler, + Unit>::PossibleError::hasPendingDestructuringError() { + return hasError(ErrorKind::Destructuring); +} + +template <class ParseHandler, typename Unit> +void GeneralParser<ParseHandler, Unit>::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 <class ParseHandler, typename Unit> +void GeneralParser<ParseHandler, Unit>::PossibleError:: + setPendingDestructuringErrorAt(const TokenPos& pos, unsigned errorNumber) { + setPending(ErrorKind::Destructuring, pos, errorNumber); +} + +template <class ParseHandler, typename Unit> +void GeneralParser<ParseHandler, Unit>::PossibleError:: + setPendingDestructuringWarningAt(const TokenPos& pos, + unsigned errorNumber) { + setPending(ErrorKind::DestructuringWarning, pos, errorNumber); +} + +template <class ParseHandler, typename Unit> +void GeneralParser<ParseHandler, Unit>::PossibleError:: + setPendingExpressionErrorAt(const TokenPos& pos, unsigned errorNumber) { + setPending(ErrorKind::Expression, pos, errorNumber); +} + +template <class ParseHandler, typename Unit> +bool GeneralParser<ParseHandler, Unit>::PossibleError::checkForError( + ErrorKind kind) { + if (!hasError(kind)) { + return true; + } + + Error& err = error(kind); + parser_.errorAt(err.offset_, err.errorNumber_); + return false; +} + +template <class ParseHandler, typename Unit> +bool GeneralParser<ParseHandler, + Unit>::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 <class ParseHandler, typename Unit> +bool GeneralParser<ParseHandler, + Unit>::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 <class ParseHandler, typename Unit> +void GeneralParser<ParseHandler, Unit>::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 <class ParseHandler, typename Unit> +void GeneralParser<ParseHandler, Unit>::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 <class ParseHandler, typename Unit> +typename ParseHandler::BinaryNodeType +GeneralParser<ParseHandler, Unit>::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 <class ParseHandler, typename Unit> +typename ParseHandler::NameNodeType +GeneralParser<ParseHandler, Unit>::bindingIdentifier( + DeclarationKind kind, YieldHandling yieldHandling) { + const ParserName* name = bindingIdentifier(yieldHandling); + if (!name) { + return null(); + } + + NameNodeType binding = newName(name); + if (!binding || !noteDeclaredName(name, kind, pos())) { + return null(); + } + + return binding; +} + +template <class ParseHandler, typename Unit> +typename ParseHandler::Node +GeneralParser<ParseHandler, Unit>::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 <class ParseHandler, typename Unit> +typename ParseHandler::ListNodeType +GeneralParser<ParseHandler, Unit>::objectBindingPattern( + DeclarationKind kind, YieldHandling yieldHandling) { + MOZ_ASSERT(anyChars.isCurrentTokenType(TokenKind::LeftCurly)); + + if (!CheckRecursionLimit(cx_)) { + return null(); + } + + uint32_t begin = pos().begin; + ListNodeType literal = handler_.newObjectLiteral(begin); + if (!literal) { + return null(); + } + + Maybe<DeclarationKind> declKind = Some(kind); + const ParserAtom* propAtom = nullptr; + 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 <class ParseHandler, typename Unit> +typename ParseHandler::ListNodeType +GeneralParser<ParseHandler, Unit>::arrayBindingPattern( + DeclarationKind kind, YieldHandling yieldHandling) { + MOZ_ASSERT(anyChars.isCurrentTokenType(TokenKind::LeftBracket)); + + if (!CheckRecursionLimit(cx_)) { + 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 <class ParseHandler, typename Unit> +typename ParseHandler::Node +GeneralParser<ParseHandler, Unit>::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 <class ParseHandler, typename Unit> +typename ParseHandler::Node +GeneralParser<ParseHandler, Unit>::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 <class ParseHandler, typename Unit> +typename ParseHandler::LexicalScopeNodeType +GeneralParser<ParseHandler, Unit>::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 <class ParseHandler, typename Unit> +typename ParseHandler::Node +GeneralParser<ParseHandler, Unit>::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 <class ParseHandler, typename Unit> +typename ParseHandler::Node +GeneralParser<ParseHandler, Unit>::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 <class ParseHandler, typename Unit> +typename ParseHandler::AssignmentNodeType +GeneralParser<ParseHandler, Unit>::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 <class ParseHandler, typename Unit> +typename ParseHandler::Node GeneralParser<ParseHandler, Unit>::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(); + } + + const ParserName* 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 <class ParseHandler, typename Unit> +typename ParseHandler::ListNodeType +GeneralParser<ParseHandler, Unit>::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"); + } + + ListNodeType 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 <class ParseHandler, typename Unit> +typename ParseHandler::ListNodeType +GeneralParser<ParseHandler, Unit>::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. + */ + ListNodeType decl = declarationList( + yieldHandling, kind == DeclarationKind::Const ? ParseNodeKind::ConstDecl + : ParseNodeKind::LetDecl); + if (!decl || !matchOrInsertSemicolon()) { + return null(); + } + + return decl; +} + +template <typename Unit> +bool Parser<FullParseHandler, Unit>::namedImportsOrNamespaceImport( + TokenKind tt, ListNodeType importSpecSet) { + if (tt == TokenKind::LeftCurly) { + 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 }. + if (!tokenStream.getToken(&tt)) { + return false; + } + + if (tt == TokenKind::RightCurly) { + break; + } + + if (!TokenKindIsPossibleIdentifierName(tt)) { + error(JSMSG_NO_IMPORT_NAME); + return false; + } + + const ParserName* importName = anyChars.currentName(); + TokenPos importNamePos = pos(); + + 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 { + // 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. + if (IsKeyword(importName)) { + error(JSMSG_AS_AFTER_RESERVED_WORD, ReservedWordToCharZ(importName)); + return false; + } + } + + const ParserName* bindingAtom = importedBinding(); + if (!bindingAtom) { + return false; + } + + NameNodeType bindingName = newName(bindingAtom); + if (!bindingName) { + return false; + } + if (!noteDeclaredName(bindingAtom, DeclarationKind::Import, pos())) { + return false; + } + + NameNodeType importNameNode = newName(importName, importNamePos); + if (!importNameNode) { + 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; + } + } + } else { + MOZ_ASSERT(tt == TokenKind::Mul); + + if (!mustMatchToken(TokenKind::As, JSMSG_AS_AFTER_IMPORT_STAR)) { + return false; + } + + if (!mustMatchToken(TokenKindIsPossibleIdentifierName, + JSMSG_NO_BINDING_NAME)) { + return false; + } + + NameNodeType importName = newName(cx_->parserNames().star); + if (!importName) { + return false; + } + + // Namespace imports are 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. + const ParserName* 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(); + + BinaryNodeType importSpec = + handler_.newImportSpec(importName, bindingNameNode); + if (!importSpec) { + return false; + } + + handler_.addList(importSpecSet, importSpec); + } + + return true; +} + +template <typename Unit> +BinaryNode* Parser<FullParseHandler, Unit>::importDeclaration() { + 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'|. + importSpecSet->pn_pos.end = importSpecSet->pn_pos.begin; + } else { + if (tt == TokenKind::LeftCurly || tt == TokenKind::Mul) { + if (!namedImportsOrNamespaceImport(tt, 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(cx_->parserNames().default_); + if (!importName) { + return null(); + } + + const ParserName* 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 && tt != TokenKind::Mul) { + error(JSMSG_NAMED_IMPORTS_OR_NAMESPACE_IMPORT); + return null(); + } + + if (!namedImportsOrNamespaceImport(tt, importSpecSet)) { + 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 (!matchOrInsertSemicolon()) { + return null(); + } + + BinaryNode* node = handler_.newImportDeclaration(importSpecSet, moduleSpec, + TokenPos(begin, pos().end)); + if (!node || !pc_->sc()->asModuleContext()->builder.processImport(node)) { + return null(); + } + + return node; +} + +template <typename Unit> +inline SyntaxParseHandler::BinaryNodeType +Parser<SyntaxParseHandler, Unit>::importDeclaration() { + MOZ_ALWAYS_FALSE(abortIfSyntaxParser()); + return SyntaxParseHandler::NodeFailure; +} + +template <class ParseHandler, typename Unit> +inline typename ParseHandler::BinaryNodeType +GeneralParser<ParseHandler, Unit>::importDeclaration() { + return asFinalParser()->importDeclaration(); +} + +template <class ParseHandler, typename Unit> +inline typename ParseHandler::Node +GeneralParser<ParseHandler, Unit>::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 <typename Unit> +bool Parser<FullParseHandler, Unit>::checkExportedName( + const ParserAtom* exportName) { + if (!pc_->sc()->asModuleContext()->builder.hasExportedName(exportName)) { + return true; + } + + UniqueChars str = ParserAtomToPrintableString(cx_, exportName); + if (!str) { + return false; + } + + error(JSMSG_DUPLICATE_EXPORT_NAME, str.get()); + return false; +} + +template <typename Unit> +inline bool Parser<SyntaxParseHandler, Unit>::checkExportedName( + const ParserAtom* exportName) { + MOZ_ALWAYS_FALSE(abortIfSyntaxParser()); + return false; +} + +template <class ParseHandler, typename Unit> +inline bool GeneralParser<ParseHandler, Unit>::checkExportedName( + const ParserAtom* exportName) { + return asFinalParser()->checkExportedName(exportName); +} + +template <typename Unit> +bool Parser<FullParseHandler, Unit>::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<UnaryNode>().kid(); + } else if (node->isKind(ParseNodeKind::AssignExpr)) { + binding = node->as<AssignmentNode>().left(); + } else { + binding = node; + } + + if (!checkExportedNamesForDeclaration(binding)) { + return false; + } + } + + return true; +} + +template <typename Unit> +inline bool Parser<SyntaxParseHandler, Unit>::checkExportedNamesForArrayBinding( + ListNodeType array) { + MOZ_ALWAYS_FALSE(abortIfSyntaxParser()); + return false; +} + +template <class ParseHandler, typename Unit> +inline bool +GeneralParser<ParseHandler, Unit>::checkExportedNamesForArrayBinding( + ListNodeType array) { + return asFinalParser()->checkExportedNamesForArrayBinding(array); +} + +template <typename Unit> +bool Parser<FullParseHandler, Unit>::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<UnaryNode>().kid(); + } else { + if (node->isKind(ParseNodeKind::MutateProto)) { + target = node->as<UnaryNode>().kid(); + } else { + target = node->as<BinaryNode>().right(); + } + + if (target->isKind(ParseNodeKind::AssignExpr)) { + target = target->as<AssignmentNode>().left(); + } + } + + if (!checkExportedNamesForDeclaration(target)) { + return false; + } + } + + return true; +} + +template <typename Unit> +inline bool Parser<SyntaxParseHandler, + Unit>::checkExportedNamesForObjectBinding(ListNodeType obj) { + MOZ_ALWAYS_FALSE(abortIfSyntaxParser()); + return false; +} + +template <class ParseHandler, typename Unit> +inline bool +GeneralParser<ParseHandler, Unit>::checkExportedNamesForObjectBinding( + ListNodeType obj) { + return asFinalParser()->checkExportedNamesForObjectBinding(obj); +} + +template <typename Unit> +bool Parser<FullParseHandler, Unit>::checkExportedNamesForDeclaration( + ParseNode* node) { + if (node->isKind(ParseNodeKind::Name)) { + if (!checkExportedName(node->as<NameNode>().atom())) { + return false; + } + } else if (node->isKind(ParseNodeKind::ArrayExpr)) { + if (!checkExportedNamesForArrayBinding(&node->as<ListNode>())) { + return false; + } + } else { + MOZ_ASSERT(node->isKind(ParseNodeKind::ObjectExpr)); + if (!checkExportedNamesForObjectBinding(&node->as<ListNode>())) { + return false; + } + } + + return true; +} + +template <typename Unit> +inline bool Parser<SyntaxParseHandler, Unit>::checkExportedNamesForDeclaration( + Node node) { + MOZ_ALWAYS_FALSE(abortIfSyntaxParser()); + return false; +} + +template <class ParseHandler, typename Unit> +inline bool GeneralParser<ParseHandler, Unit>::checkExportedNamesForDeclaration( + Node node) { + return asFinalParser()->checkExportedNamesForDeclaration(node); +} + +template <typename Unit> +bool Parser<FullParseHandler, Unit>::checkExportedNamesForDeclarationList( + ListNode* node) { + for (ParseNode* binding : node->contents()) { + if (binding->isKind(ParseNodeKind::AssignExpr)) { + binding = binding->as<AssignmentNode>().left(); + } else { + MOZ_ASSERT(binding->isKind(ParseNodeKind::Name)); + } + + if (!checkExportedNamesForDeclaration(binding)) { + return false; + } + } + + return true; +} + +template <typename Unit> +inline bool +Parser<SyntaxParseHandler, Unit>::checkExportedNamesForDeclarationList( + ListNodeType node) { + MOZ_ALWAYS_FALSE(abortIfSyntaxParser()); + return false; +} + +template <class ParseHandler, typename Unit> +inline bool +GeneralParser<ParseHandler, Unit>::checkExportedNamesForDeclarationList( + ListNodeType node) { + return asFinalParser()->checkExportedNamesForDeclarationList(node); +} + +template <typename Unit> +inline bool Parser<FullParseHandler, Unit>::checkExportedNameForClause( + NameNode* nameNode) { + return checkExportedName(nameNode->atom()); +} + +template <typename Unit> +inline bool Parser<SyntaxParseHandler, Unit>::checkExportedNameForClause( + NameNodeType nameNode) { + MOZ_ALWAYS_FALSE(abortIfSyntaxParser()); + return false; +} + +template <class ParseHandler, typename Unit> +inline bool GeneralParser<ParseHandler, Unit>::checkExportedNameForClause( + NameNodeType nameNode) { + return asFinalParser()->checkExportedNameForClause(nameNode); +} + +template <typename Unit> +bool Parser<FullParseHandler, Unit>::checkExportedNameForFunction( + FunctionNode* funNode) { + return checkExportedName(funNode->funbox()->explicitName()); +} + +template <typename Unit> +inline bool Parser<SyntaxParseHandler, Unit>::checkExportedNameForFunction( + FunctionNodeType funNode) { + MOZ_ALWAYS_FALSE(abortIfSyntaxParser()); + return false; +} + +template <class ParseHandler, typename Unit> +inline bool GeneralParser<ParseHandler, Unit>::checkExportedNameForFunction( + FunctionNodeType funNode) { + return asFinalParser()->checkExportedNameForFunction(funNode); +} + +template <typename Unit> +bool Parser<FullParseHandler, Unit>::checkExportedNameForClass( + ClassNode* classNode) { + MOZ_ASSERT(classNode->names()); + return checkExportedName(classNode->names()->innerBinding()->atom()); +} + +template <typename Unit> +inline bool Parser<SyntaxParseHandler, Unit>::checkExportedNameForClass( + ClassNodeType classNode) { + MOZ_ALWAYS_FALSE(abortIfSyntaxParser()); + return false; +} + +template <class ParseHandler, typename Unit> +inline bool GeneralParser<ParseHandler, Unit>::checkExportedNameForClass( + ClassNodeType classNode) { + return asFinalParser()->checkExportedNameForClass(classNode); +} + +template <> +inline bool PerHandlerParser<FullParseHandler>::processExport(ParseNode* node) { + return pc_->sc()->asModuleContext()->builder.processExport(node); +} + +template <> +inline bool PerHandlerParser<SyntaxParseHandler>::processExport(Node node) { + MOZ_ALWAYS_FALSE(abortIfSyntaxParser()); + return false; +} + +template <> +inline bool PerHandlerParser<FullParseHandler>::processExportFrom( + BinaryNodeType node) { + return pc_->sc()->asModuleContext()->builder.processExportFrom(node); +} + +template <> +inline bool PerHandlerParser<SyntaxParseHandler>::processExportFrom( + BinaryNodeType node) { + MOZ_ALWAYS_FALSE(abortIfSyntaxParser()); + return false; +} + +template <class ParseHandler, typename Unit> +typename ParseHandler::BinaryNodeType +GeneralParser<ParseHandler, Unit>::exportFrom(uint32_t begin, Node specList) { + if (!abortIfSyntaxParser()) { + return null(); + } + + MOZ_ASSERT(anyChars.isCurrentTokenType(TokenKind::From)); + + if (!abortIfSyntaxParser()) { + return null(); + } + + if (!mustMatchToken(TokenKind::String, JSMSG_MODULE_SPEC_AFTER_FROM)) { + return null(); + } + + NameNodeType moduleSpec = stringLiteral(); + if (!moduleSpec) { + return null(); + } + + if (!matchOrInsertSemicolon()) { + return null(); + } + + BinaryNodeType node = + handler_.newExportFromDeclaration(begin, specList, moduleSpec); + if (!node) { + return null(); + } + + if (!processExportFrom(node)) { + return null(); + } + + return node; +} + +template <class ParseHandler, typename Unit> +typename ParseHandler::BinaryNodeType +GeneralParser<ParseHandler, Unit>::exportBatch(uint32_t begin) { + if (!abortIfSyntaxParser()) { + return null(); + } + + MOZ_ASSERT(anyChars.isCurrentTokenType(TokenKind::Mul)); + + ListNodeType kid = handler_.newList(ParseNodeKind::ExportSpecList, pos()); + if (!kid) { + return null(); + } + + bool foundAs; + if (!tokenStream.matchToken(&foundAs, TokenKind::As)) { + return null(); + } + + if (foundAs) { + if (!mustMatchToken(TokenKindIsPossibleIdentifierName, + JSMSG_NO_EXPORT_NAME)) { + return null(); + } + + NameNodeType exportName = newName(anyChars.currentName()); + if (!exportName) { + return null(); + } + + if (!checkExportedNameForClause(exportName)) { + return null(); + } + + NameNodeType importName = newName(cx_->parserNames().star); + if (!importName) { + return null(); + } + + BinaryNodeType exportSpec = handler_.newExportSpec(importName, 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 <typename Unit> +bool Parser<FullParseHandler, Unit>::checkLocalExportNames(ListNode* node) { + // ES 2017 draft 15.2.3.1. + for (ParseNode* next : node->contents()) { + ParseNode* name = next->as<BinaryNode>().left(); + MOZ_ASSERT(name->isKind(ParseNodeKind::Name)); + + const ParserName* ident = name->as<NameNode>().atom()->asName(); + if (!checkLocalExportName(ident, name->pn_pos.begin)) { + return false; + } + } + + return true; +} + +template <typename Unit> +bool Parser<SyntaxParseHandler, Unit>::checkLocalExportNames( + ListNodeType node) { + MOZ_ALWAYS_FALSE(abortIfSyntaxParser()); + return false; +} + +template <class ParseHandler, typename Unit> +inline bool GeneralParser<ParseHandler, Unit>::checkLocalExportNames( + ListNodeType node) { + return asFinalParser()->checkLocalExportNames(node); +} + +template <class ParseHandler, typename Unit> +typename ParseHandler::Node GeneralParser<ParseHandler, Unit>::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; + } + + if (!TokenKindIsPossibleIdentifierName(tt)) { + error(JSMSG_NO_BINDING_NAME); + return null(); + } + + NameNodeType bindingName = newName(anyChars.currentName()); + if (!bindingName) { + return null(); + } + + bool foundAs; + if (!tokenStream.matchToken(&foundAs, TokenKind::As)) { + return null(); + } + if (foundAs) { + if (!mustMatchToken(TokenKindIsPossibleIdentifierName, + JSMSG_NO_EXPORT_NAME)) { + return null(); + } + } + + NameNodeType exportName = newName(anyChars.currentName()); + 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 <class ParseHandler, typename Unit> +typename ParseHandler::UnaryNodeType +GeneralParser<ParseHandler, Unit>::exportVariableStatement(uint32_t begin) { + if (!abortIfSyntaxParser()) { + return null(); + } + + MOZ_ASSERT(anyChars.isCurrentTokenType(TokenKind::Var)); + + ListNodeType 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 <class ParseHandler, typename Unit> +typename ParseHandler::UnaryNodeType +GeneralParser<ParseHandler, Unit>::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 <class ParseHandler, typename Unit> +typename ParseHandler::UnaryNodeType +GeneralParser<ParseHandler, Unit>::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 <class ParseHandler, typename Unit> +typename ParseHandler::UnaryNodeType +GeneralParser<ParseHandler, Unit>::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)); + + ListNodeType 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 <class ParseHandler, typename Unit> +typename ParseHandler::BinaryNodeType +GeneralParser<ParseHandler, Unit>::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 <class ParseHandler, typename Unit> +typename ParseHandler::BinaryNodeType +GeneralParser<ParseHandler, Unit>::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 <class ParseHandler, typename Unit> +typename ParseHandler::BinaryNodeType +GeneralParser<ParseHandler, Unit>::exportDefaultAssignExpr(uint32_t begin) { + if (!abortIfSyntaxParser()) { + return null(); + } + + const ParserName* name = cx_->parserNames().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 <class ParseHandler, typename Unit> +typename ParseHandler::BinaryNodeType +GeneralParser<ParseHandler, Unit>::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(cx_->parserNames().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 <class ParseHandler, typename Unit> +typename ParseHandler::Node +GeneralParser<ParseHandler, Unit>::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 <class ParseHandler, typename Unit> +typename ParseHandler::UnaryNodeType +GeneralParser<ParseHandler, Unit>::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 <class ParseHandler, typename Unit> +typename ParseHandler::Node +GeneralParser<ParseHandler, Unit>::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 <class ParseHandler, typename Unit> +typename ParseHandler::TernaryNodeType +GeneralParser<ParseHandler, Unit>::ifStatement(YieldHandling yieldHandling) { + Vector<Node, 4> condList(cx_), thenList(cx_); + Vector<uint32_t, 4> posList(cx_); + 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 <class ParseHandler, typename Unit> +typename ParseHandler::BinaryNodeType +GeneralParser<ParseHandler, Unit>::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 <class ParseHandler, typename Unit> +typename ParseHandler::BinaryNodeType +GeneralParser<ParseHandler, Unit>::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 <class ParseHandler, typename Unit> +bool GeneralParser<ParseHandler, Unit>::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 <class ParseHandler, typename Unit> +bool GeneralParser<ParseHandler, Unit>::forHeadStart( + YieldHandling yieldHandling, IteratorKind iterKind, + ParseNodeKind* forHeadKind, Node* forInitialPart, + Maybe<ParseContext::Scope>& 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_.isPropertyAccess(*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 <class ParseHandler, typename Unit> +typename ParseHandler::Node GeneralParser<ParseHandler, Unit>::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() || + (options().topLevelAwait && 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()) { + 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<ParseContext::Scope> 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 <class ParseHandler, typename Unit> +typename ParseHandler::SwitchStatementType +GeneralParser<ParseHandler, Unit>::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 <class ParseHandler, typename Unit> +typename ParseHandler::ContinueStatementType +GeneralParser<ParseHandler, Unit>::continueStatement( + YieldHandling yieldHandling) { + MOZ_ASSERT(anyChars.isCurrentTokenType(TokenKind::Continue)); + uint32_t begin = pos().begin; + + const ParserName* label = nullptr; + 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 <class ParseHandler, typename Unit> +typename ParseHandler::BreakStatementType +GeneralParser<ParseHandler, Unit>::breakStatement(YieldHandling yieldHandling) { + MOZ_ASSERT(anyChars.isCurrentTokenType(TokenKind::Break)); + uint32_t begin = pos().begin; + + const ParserName* label = nullptr; + 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 <class ParseHandler, typename Unit> +typename ParseHandler::UnaryNodeType +GeneralParser<ParseHandler, Unit>::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 <class ParseHandler, typename Unit> +typename ParseHandler::UnaryNodeType +GeneralParser<ParseHandler, Unit>::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 <class ParseHandler, typename Unit> +typename ParseHandler::BinaryNodeType +GeneralParser<ParseHandler, Unit>::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 <class ParseHandler, typename Unit> +typename ParseHandler::Node GeneralParser<ParseHandler, Unit>::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 <class ParseHandler, typename Unit> +typename ParseHandler::LabeledStatementType +GeneralParser<ParseHandler, Unit>::labeledStatement( + YieldHandling yieldHandling) { + const ParserName* 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<ParseContext::LabelStatement>( + 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 <class ParseHandler, typename Unit> +typename ParseHandler::UnaryNodeType +GeneralParser<ParseHandler, Unit>::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 <class ParseHandler, typename Unit> +typename ParseHandler::TernaryNodeType +GeneralParser<ParseHandler, Unit>::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 <class ParseHandler, typename Unit> +typename ParseHandler::LexicalScopeNodeType +GeneralParser<ParseHandler, Unit>::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 <class ParseHandler, typename Unit> +typename ParseHandler::DebuggerStatementType +GeneralParser<ParseHandler, Unit>::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"); + } +} + +template <class ParseHandler, typename Unit> +bool GeneralParser<ParseHandler, Unit>::classMember( + YieldHandling yieldHandling, const ParseContext::ClassStatement& classStmt, + const ParserName* 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; + } + + bool isStatic = false; + if (tt == TokenKind::Static) { + if (!tokenStream.peekToken(&tt)) { + return false; + } + + 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; + } + + const ParserAtom* propAtom = nullptr; + PropertyType propType; + Node propName = propertyOrMethodName(yieldHandling, PropertyNameInClass, + /* maybeDecl = */ Nothing(), + classMembers, &propType, &propAtom); + if (!propName) { + return false; + } + + if (propType == PropertyType::Field) { + if (isStatic) { + if (propAtom == cx_->parserNames().prototype) { + errorAt(propNameOffset, JSMSG_BAD_METHOD_DEF); + return false; + } + } + + if (propAtom == cx_->parserNames().constructor) { + errorAt(propNameOffset, JSMSG_BAD_METHOD_DEF); + return false; + } + + if (handler_.isPrivateName(propName)) { + if (propAtom == cx_->parserNames().hashConstructor) { + errorAt(propNameOffset, JSMSG_BAD_METHOD_DEF); + return false; + } + + const ParserName* privateName = propAtom->asName(); + if (!noteDeclaredPrivateName(propName, privateName, propType, pos())) { + return false; + } + } + + if (!abortIfSyntaxParser()) { + return false; + } + + 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); + 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 == cx_->parserNames().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 == cx_->parserNames().prototype) { + errorAt(propNameOffset, JSMSG_BAD_METHOD_DEF); + return false; + } + + const ParserAtom* funName = nullptr; + 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<ParseContext::Scope> dotInitializersScope; + if (isConstructor && !options().selfHostingMode) { + dotInitializersScope.emplace(this); + if (!dotInitializersScope->init(pc_)) { + return false; + } + + if (!noteDeclaredName(cx_->parserNames().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<FunctionNodeType> initializerIfPrivate = Nothing(); + if (handler_.isPrivateName(propName)) { + if (!options().privateClassMethods) { + // Private methods are not enabled. + errorAt(propNameOffset, JSMSG_BAD_METHOD_DEF); + return false; + } + + if (propAtom == cx_->parserNames().hashConstructor) { + // #constructor is an invalid private name. + errorAt(propNameOffset, JSMSG_BAD_METHOD_DEF); + return false; + } + + if (!abortIfSyntaxParser()) { + return false; + } + + const ParserName* privateName = propAtom->asName(); + if (!noteDeclaredPrivateName(propName, privateName, propType, pos())) { + return false; + } + + // Private non-static methods are stamped onto every instance using + // initializers. Private static methods are stored directly on the + // constructor during class evaluation; see + // BytecodeEmitter::emitPropertyList. + if (!isStatic) { + classInitializedMembers.privateMethods++; + + // Synthesize a name for the lexical variable that will store the + // private method body. + StringBuffer storedMethodName(cx_); + if (!storedMethodName.append(propAtom)) { + return false; + } + switch (atype) { + case AccessorType::None: + if (!storedMethodName.append(".method")) { + return false; + } + break; + case AccessorType::Getter: + if (!storedMethodName.append(".getter")) { + return false; + } + break; + case AccessorType::Setter: + if (!storedMethodName.append(".setter")) { + return false; + } + break; + default: + MOZ_CRASH("Invalid private method accessor type"); + } + const ParserAtom* storedMethodAtom = storedMethodName.finishParserAtom( + this->compilationState_.parserAtoms); + if (!storedMethodAtom) { + return false; + } + const ParserName* storedMethodProp = storedMethodAtom->asName(); + if (!noteDeclaredName(storedMethodProp, DeclarationKind::Const, pos())) { + return false; + } + + TokenPos propNamePos(propNameOffset, pos().end); + auto initializerNode = + privateMethodInitializer(propNamePos, propAtom, storedMethodAtom); + if (!initializerNode) { + return false; + } + initializerIfPrivate = Some(initializerNode); + } + } + + Node method = handler_.newClassMethodDefinition( + propName, funNode, atype, isStatic, initializerIfPrivate); + if (!method) { + return false; + } + + if (dotInitializersScope.isSome()) { + method = finishLexicalScope(*dotInitializersScope, method); + if (!method) { + return false; + } + dotInitializersScope.reset(); + } + + return handler_.addClassMemberDefinition(classMembers, method); +} + +template <class ParseHandler, typename Unit> +bool GeneralParser<ParseHandler, Unit>::finishClassConstructor( + const ParseContext::ClassStatement& classStmt, const ParserName* className, + HasHeritage hasHeritage, uint32_t classStartOffset, uint32_t classEndOffset, + const ClassInitializedMembers& classInitializedMembers, + ListNodeType& classMembers) { + // Fields cannot re-use the constructor obtained via JSOp::ClassConstructor or + // JSOp::DerivedConstructor due to needing to emit calls to the field + // initializers in the constructor. So, synthesize a new one. + size_t numPrivateMethods = classInitializedMembers.privateMethods; + size_t numFields = classInitializedMembers.instanceFields; + + if (classStmt.constructorBox == nullptr && + numFields + numPrivateMethods > 0) { + 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(cx_->parserNames().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; + } + + MOZ_ASSERT(classStmt.constructorBox != nullptr); + + // Note: the *function* has the name of the class, but the *property* + // containing the function has the name "constructor" + Node constructorNameNode = handler_.newObjectLiteralPropertyName( + cx_->parserNames().constructor, pos()); + if (!constructorNameNode) { + return false; + } + ClassMethodType method = handler_.newClassMethodDefinition( + constructorNameNode, synthesizedCtor, AccessorType::None, + /* isStatic = */ false, Nothing()); + if (!method) { + return false; + } + LexicalScopeNodeType scope = + finishLexicalScope(dotInitializersScope, method); + if (!scope) { + return false; + } + if (!handler_.addClassMemberDefinition(classMembers, scope)) { + return false; + } + } + + if (FunctionBox* ctorbox = classStmt.constructorBox) { + // Amend the toStringEnd offset for the constructor now that we've + // finished parsing the class. + ctorbox->setCtorToStringEnd(classEndOffset); + + if (numFields + numPrivateMethods > 0) { + // Field initialization need access to `this`. + ctorbox->setCtorFunctionHasThisBinding(); + } + } + + return true; +} + +template <class ParseHandler, typename Unit> +typename ParseHandler::ClassNodeType +GeneralParser<ParseHandler, Unit>::classDefinition( + YieldHandling yieldHandling, ClassContext classContext, + DefaultHandling defaultHandling) { + MOZ_ASSERT(anyChars.isCurrentTokenType(TokenKind::Class)); + + uint32_t classStartOffset = pos().begin; + bool savedStrictness = setLocalStrictMode(true); + + TokenKind tt; + if (!tokenStream.getToken(&tt)) { + return null(); + } + + const ParserName* className = nullptr; + if (TokenKindIsPossibleIdentifier(tt)) { + className = bindingIdentifier(yieldHandling); + if (!className) { + return null(); + } + } else if (classContext == ClassStatement) { + if (defaultHandling == AllowDefaultName) { + className = cx_->parserNames().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(); + LexicalScopeNodeType 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.instanceFieldKeys > 0) { + if (!noteDeclaredName(cx_->parserNames().dotFieldKeys, + DeclarationKind::Let, namePos)) { + return null(); + } + } + + if (classInitializedMembers.staticFields > 0) { + if (!noteDeclaredName(cx_->parserNames().dotStaticInitializers, + DeclarationKind::Let, namePos)) { + return null(); + } + } + + if (classInitializedMembers.staticFieldKeys > 0) { + if (!noteDeclaredName(cx_->parserNames().dotStaticFieldKeys, + DeclarationKind::Let, namePos)) { + return null(); + } + } + + classEndOffset = pos().end; + if (!finishClassConstructor(classStmt, className, hasHeritage, + classStartOffset, classEndOffset, + classInitializedMembers, classMembers)) { + return null(); + } + + classBodyBlock = finishLexicalScope(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<UnboundPrivateName> maybeUnboundName; + if (!this->compilationState_.usedNames.hasUnboundPrivateNames( + cx_, maybeUnboundName)) { + return null(); + } + if (maybeUnboundName) { + UniqueChars str = + ParserAtomToPrintableString(cx_, maybeUnboundName->atom); + if (!str) { + return null(); + } + + errorAt(maybeUnboundName->position.begin, JSMSG_MISSING_PRIVATE_DECL, + str.get()); + return null(); + } + } + + return handler_.newClass(nameNode, classHeritage, classBlock, + TokenPos(classStartOffset, classEndOffset)); +} + +template <class ParseHandler, typename Unit> +typename ParseHandler::FunctionNodeType +GeneralParser<ParseHandler, Unit>::synthesizeConstructor( + const ParserAtom* 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(); + } + + // 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_, flags, functionSyntaxKind); + setFunctionEndFromCurrentToken(funbox); + + // Push a SourceParseContext on to the stack. + SourceParseContext funpc(this, funbox, /* newDirectives = */ nullptr); + if (!funpc.init()) { + return null(); + } + + // Create a ListNode for the parameters + body (there are no parameters). + ListNodeType argsbody = + handler_.newList(ParseNodeKind::ParamsBody, 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, cx_->parserNames().args, + 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(cx_->parserNames().dotThis)) { + return null(); + } + + if (!noteUsedName(cx_->parserNames().dotInitializers)) { + return null(); + } + + bool canSkipLazyClosedOverBindings = handler_.canSkipLazyClosedOverBindings(); + if (!pc_->declareFunctionThis(usedNames_, canSkipLazyClosedOverBindings)) { + return null(); + } + + if (hasHeritage == HasHeritage::Yes) { + 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(cx_->parserNames().args, synthesizedBodyPos); + if (!argsNameNode) { + return null(); + } + if (!noteUsedName(cx_->parserNames().args)) { + 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); + } + + 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(); + } + + // This function is asserted to set classStmt->constructorBox - however, it's + // not directly set in this function, but rather in + // initWithEnclosingParseContext. + + return funNode; +} + +template <class ParseHandler, typename Unit> +typename ParseHandler::FunctionNodeType +GeneralParser<ParseHandler, Unit>::privateMethodInitializer( + TokenPos propNamePos, const ParserAtom* propAtom, + const ParserAtom* storedMethodAtom) { + // 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, nullptr, flags, propNamePos.begin, directives, + generatorKind, asyncKind); + if (!funbox) { + return null(); + } + funbox->initWithEnclosingParseContext(pc_, flags, 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. + ListNodeType argsbody = + handler_.newList(ParseNodeKind::ParamsBody, 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. + const ParserName* storedMethodName = storedMethodAtom->asName(); + if (!noteUsedName(storedMethodName)) { + return null(); + } + const ParserName* privateName = propAtom->asName(); + NameNodeType privateNameNode = privateNameReference(privateName); + if (!privateNameNode) { + return null(); + } + + bool canSkipLazyClosedOverBindings = handler_.canSkipLazyClosedOverBindings(); + if (!pc_->declareFunctionThis(usedNames_, canSkipLazyClosedOverBindings)) { + 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(); + } + 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 <class ParseHandler, typename Unit> +typename ParseHandler::FunctionNodeType +GeneralParser<ParseHandler, Unit>::fieldInitializerOpt( + TokenPos propNamePos, Node propName, const ParserAtom* propAtom, + ClassInitializedMembers& classInitializedMembers, bool isStatic, + HasHeritage hasHeritage) { + 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, nullptr, flags, propNamePos.begin, directives, + generatorKind, asyncKind); + if (!funbox) { + return null(); + } + funbox->initWithEnclosingParseContext(pc_, flags, syntaxKind); + MOZ_ASSERT(funbox->isFieldInitializer()); + + // 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 ListNode for the parameters + body (there are no parameters). + ListNodeType argsbody = + handler_.newList(ParseNodeKind::ParamsBody, 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(cx_->parserNames().dotStaticFieldKeys); + } else { + fieldKeysName = newInternalDotName(cx_->parserNames().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. + + const ParserName* privateName = propAtom->asName(); + NameNodeType privateNameNode = privateNameReference(privateName); + if (!privateNameNode) { + return null(); + } + + propAssignFieldAccess = handler_.newPropertyByValue( + propAssignThis, privateNameNode, wholeInitializerPos.end); + if (!propAssignFieldAccess) { + return null(); + } + } else if (propAtom->isIndex(&indexValue)) { + propAssignFieldAccess = handler_.newPropertyByValue( + propAssignThis, propName, wholeInitializerPos.end); + if (!propAssignFieldAccess) { + return null(); + } + } else { + NameNodeType propAssignName = + handler_.newPropertyName(propAtom->asName(), wholeInitializerPos); + if (!propAssignName) { + return null(); + } + + propAssignFieldAccess = + handler_.newPropertyAccess(propAssignThis, propAssignName); + if (!propAssignFieldAccess) { + return null(); + } + } + + // Synthesize an property init. + AssignmentNodeType initializerPropInit = handler_.newAssignment( + ParseNodeKind::InitExpr, propAssignFieldAccess, initializerExpr); + if (!initializerPropInit) { + return null(); + } + + bool canSkipLazyClosedOverBindings = handler_.canSkipLazyClosedOverBindings(); + if (!pc_->declareFunctionThis(usedNames_, canSkipLazyClosedOverBindings)) { + 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); + + // 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; +} + +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 <class ParseHandler, typename Unit> +typename ParseHandler::ListNodeType +GeneralParser<ParseHandler, Unit>::variableStatement( + YieldHandling yieldHandling) { + ListNodeType vars = declarationList(yieldHandling, ParseNodeKind::VarStmt); + if (!vars) { + return null(); + } + if (!matchOrInsertSemicolon()) { + return null(); + } + return vars; +} + +template <class ParseHandler, typename Unit> +typename ParseHandler::Node GeneralParser<ParseHandler, Unit>::statement( + YieldHandling yieldHandling) { + MOZ_ASSERT(checkOptionsCalled_); + + if (!CheckRecursionLimit(cx_)) { + 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_->isFunctionBox()) { + 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 <class ParseHandler, typename Unit> +typename ParseHandler::Node +GeneralParser<ParseHandler, Unit>::statementListItem( + YieldHandling yieldHandling, bool canHaveDirectives /* = false */) { + MOZ_ASSERT(checkOptionsCalled_); + + if (!CheckRecursionLimit(cx_)) { + 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() == cx_->parserNames().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_->isFunctionBox()) { + 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); + + // ClassDeclaration[?Yield, ~Default] + 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 <class ParseHandler, typename Unit> +typename ParseHandler::Node GeneralParser<ParseHandler, Unit>::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::PipeLine */ + 2, /* ParseNodeKind::Coalesce */ + 3, /* ParseNodeKind::Or */ + 4, /* ParseNodeKind::And */ + 5, /* ParseNodeKind::BitOr */ + 6, /* ParseNodeKind::BitXor */ + 7, /* ParseNodeKind::BitAnd */ + 8, /* ParseNodeKind::StrictEq */ + 8, /* ParseNodeKind::Eq */ + 8, /* ParseNodeKind::StrictNe */ + 8, /* ParseNodeKind::Ne */ + 9, /* ParseNodeKind::Lt */ + 9, /* ParseNodeKind::Le */ + 9, /* ParseNodeKind::Gt */ + 9, /* ParseNodeKind::Ge */ + 9, /* ParseNodeKind::InstanceOf */ + 9, /* ParseNodeKind::In */ + 10, /* ParseNodeKind::Lsh */ + 10, /* ParseNodeKind::Rsh */ + 10, /* ParseNodeKind::Ursh */ + 11, /* ParseNodeKind::Add */ + 11, /* ParseNodeKind::Sub */ + 12, /* ParseNodeKind::Star */ + 12, /* ParseNodeKind::Div */ + 12, /* ParseNodeKind::Mod */ + 13 /* ParseNodeKind::Pow */ +}; + +static const int PRECEDENCE_CLASSES = 13; + +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 <class ParseHandler, typename Unit> +MOZ_ALWAYS_INLINE typename ParseHandler::Node +GeneralParser<ParseHandler, Unit>::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); + if (!pn) { + return null(); + } + + // If a binary operator follows, consume it and compute the + // corresponding operator. + TokenKind tok; + if (!tokenStream.getToken(&tok)) { + 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(); + } + + 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; + + default: + // do nothing in other cases + break; + } + + 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 <class ParseHandler, typename Unit> +MOZ_ALWAYS_INLINE typename ParseHandler::Node +GeneralParser<ParseHandler, Unit>::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 <class ParseHandler, typename Unit> +typename ParseHandler::Node GeneralParser<ParseHandler, Unit>::assignExpr( + InHandling inHandling, YieldHandling yieldHandling, + TripledotHandling tripledotHandling, + PossibleError* possibleError /* = nullptr */, + InvokedPrediction invoked /* = PredictUninvoked */) { + if (!CheckRecursionLimit(cx_)) { + 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) { + const ParserName* 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); + + 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. + const ParserName* 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(); + + const ParserName* 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 CompilationStencil::rewind here because parsing + // during delazification will see the same rewind and need the same sequence + // of inner functions to skip over. + tokenStream.rewind(start); + + 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, + nullptr, 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_.isPropertyAccess(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 <class ParseHandler> +const char* PerHandlerParser<ParseHandler>::nameIsArgumentsOrEval(Node node) { + MOZ_ASSERT(handler_.isName(node), + "must only call this function on known names"); + + if (handler_.isEvalName(node, cx_)) { + return js_eval_str; + } + if (handler_.isArgumentsName(node, cx_)) { + return js_arguments_str; + } + return nullptr; +} + +template <class ParseHandler, typename Unit> +bool GeneralParser<ParseHandler, Unit>::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_.isPropertyAccess(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 <class ParseHandler, typename Unit> +typename ParseHandler::UnaryNodeType +GeneralParser<ParseHandler, Unit>::unaryOpExpr(YieldHandling yieldHandling, + ParseNodeKind kind, + uint32_t begin) { + Node kid = unaryExpr(yieldHandling, TripledotProhibited); + if (!kid) { + return null(); + } + return handler_.newUnary(kind, begin, kid); +} + +template <class ParseHandler, typename Unit> +typename ParseHandler::Node GeneralParser<ParseHandler, Unit>::optionalExpr( + YieldHandling yieldHandling, TripledotHandling tripledotHandling, + TokenKind tt, PossibleError* possibleError /* = nullptr */, + InvokedPrediction invoked /* = PredictUninvoked */) { + if (!CheckRecursionLimit(cx_)) { + 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) { + 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 <class ParseHandler, typename Unit> +typename ParseHandler::Node GeneralParser<ParseHandler, Unit>::unaryExpr( + YieldHandling yieldHandling, TripledotHandling tripledotHandling, + PossibleError* possibleError /* = nullptr */, + InvokedPrediction invoked /* = PredictUninvoked */) { + if (!CheckRecursionLimit(cx_)) { + 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::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_.isPrivateField(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 <class ParseHandler, typename Unit> +typename ParseHandler::Node +GeneralParser<ParseHandler, Unit>::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 <class ParseHandler, typename Unit> +typename ParseHandler::ListNodeType +GeneralParser<ParseHandler, Unit>::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 <class ParseHandler, typename Unit> +bool GeneralParser<ParseHandler, Unit>::computeErrorMetadata( + ErrorMetadata* err, const ErrorReportMixin::ErrorOffset& offset) { + if (offset.is<ErrorReportMixin::Current>()) { + return tokenStream.computeErrorMetadata(err, AsVariant(pos().begin)); + } + return tokenStream.computeErrorMetadata(err, offset); +} + +template <class ParseHandler, typename Unit> +typename ParseHandler::Node GeneralParser<ParseHandler, Unit>::memberExpr( + YieldHandling yieldHandling, TripledotHandling tripledotHandling, + TokenKind tt, bool allowCallSyntax, PossibleError* possibleError, + InvokedPrediction invoked) { + MOZ_ASSERT(anyChars.isCurrentTokenType(tt)); + + Node lhs; + + if (!CheckRecursionLimit(cx_)) { + 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. + BinaryNodeType 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) { + 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(cx_->parserNames().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 <class ParseHandler> +inline typename ParseHandler::NameNodeType +PerHandlerParser<ParseHandler>::newName(const ParserName* name) { + return newName(name, pos()); +} + +template <class ParseHandler> +inline typename ParseHandler::NameNodeType +PerHandlerParser<ParseHandler>::newName(const ParserName* name, TokenPos pos) { + return handler_.newName(name, pos, cx_); +} + +template <class ParseHandler> +inline typename ParseHandler::NameNodeType +PerHandlerParser<ParseHandler>::newPrivateName(const ParserName* name) { + return handler_.newPrivateName(name, pos()); +} + +template <class ParseHandler, typename Unit> +typename ParseHandler::Node +GeneralParser<ParseHandler, Unit>::memberPropertyAccess( + Node lhs, OptionalKind optionalKind /* = OptionalKind::NonOptional */) { + MOZ_ASSERT(TokenKindIsPossibleIdentifierName(anyChars.currentToken().type) || + anyChars.currentToken().type == TokenKind::PrivateName); + const ParserName* 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 <class ParseHandler, typename Unit> +typename ParseHandler::Node +GeneralParser<ParseHandler, Unit>::memberPrivateAccess( + Node lhs, OptionalKind optionalKind /* = OptionalKind::NonOptional */) { + MOZ_ASSERT(anyChars.currentToken().type == TokenKind::PrivateName); + + const ParserName* 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_.newOptionalPropertyByValue(lhs, privateName, pos().end); + } + return handler_.newPropertyByValue(lhs, privateName, pos().end); +} + +template <class ParseHandler, typename Unit> +typename ParseHandler::Node GeneralParser<ParseHandler, Unit>::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 <class ParseHandler, typename Unit> +typename ParseHandler::Node GeneralParser<ParseHandler, Unit>::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(); + } + + NameNodeType thisName = newThisName(); + if (!thisName) { + return null(); + } + + return handler_.newSetThis(thisName, superCall); +} + +template <class ParseHandler, typename Unit> +typename ParseHandler::Node GeneralParser<ParseHandler, Unit>::memberCall( + TokenKind tt, Node lhs, YieldHandling yieldHandling, + PossibleError* possibleError /* = nullptr */, + OptionalKind optionalKind /* = OptionalKind::NonOptional */) { + if (options().selfHostingMode && (handler_.isPropertyAccess(lhs) || + handler_.isOptionalPropertyAccess(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 (const ParserName* prop = handler_.maybeDottedProperty(lhs)) { + // Use the JSOp::Fun{Apply,Call} optimizations given the right + // syntax. + if (prop == cx_->parserNames().apply) { + op = JSOp::FunApply; + } else if (prop == cx_->parserNames().call) { + op = JSOp::FunCall; + } + } else if (tt == TokenKind::LeftParen && + optionalKind == OptionalKind::NonOptional) { + if (handler_.isAsyncKeyword(lhs, cx_)) { + // |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, cx_)) { + // 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 <class ParseHandler, typename Unit> +bool GeneralParser<ParseHandler, Unit>::checkLabelOrIdentifierReference( + const ParserName* ident, uint32_t offset, YieldHandling yieldHandling, + TokenKind hint /* = TokenKind::Limit */) { + TokenKind tt; + if (hint == TokenKind::Limit) { + tt = ReservedWordTokenKind(ident); + } else { + MOZ_ASSERT(hint == ReservedWordTokenKind(ident), + "hint doesn't match actual token kind"); + tt = hint; + } + + if (!pc_->sc()->allowArguments() && ident == cx_->parserNames().arguments) { + error(JSMSG_BAD_ARGUMENTS); + return false; + } + + if (tt == TokenKind::Name) { + 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()) { + 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 <class ParseHandler, typename Unit> +bool GeneralParser<ParseHandler, Unit>::checkBindingIdentifier( + const ParserName* ident, uint32_t offset, YieldHandling yieldHandling, + TokenKind hint /* = TokenKind::Limit */) { + if (pc_->sc()->strict()) { + if (ident == cx_->parserNames().arguments) { + if (!strictModeErrorAt(offset, JSMSG_BAD_STRICT_ASSIGN, "arguments")) { + return false; + } + return true; + } + + if (ident == cx_->parserNames().eval) { + if (!strictModeErrorAt(offset, JSMSG_BAD_STRICT_ASSIGN, "eval")) { + return false; + } + return true; + } + } + + return checkLabelOrIdentifierReference(ident, offset, yieldHandling, hint); +} + +template <class ParseHandler, typename Unit> +const ParserName* GeneralParser<ParseHandler, Unit>::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() + ? anyChars.currentToken().type + : TokenKind::Limit; + const ParserName* ident = anyChars.currentName(); + if (!checkLabelOrIdentifierReference(ident, pos().begin, yieldHandling, + hint)) { + return nullptr; + } + return ident; +} + +template <class ParseHandler, typename Unit> +const ParserName* GeneralParser<ParseHandler, Unit>::bindingIdentifier( + YieldHandling yieldHandling) { + TokenKind hint = !anyChars.currentNameHasEscapes() + ? anyChars.currentToken().type + : TokenKind::Limit; + const ParserName* ident = anyChars.currentName(); + if (!checkBindingIdentifier(ident, pos().begin, yieldHandling, hint)) { + return nullptr; + } + return ident; +} + +template <class ParseHandler> +typename ParseHandler::NameNodeType +PerHandlerParser<ParseHandler>::identifierReference(const ParserName* name) { + NameNodeType id = newName(name); + if (!id) { + return null(); + } + + if (!noteUsedName(name)) { + return null(); + } + + return id; +} + +template <class ParseHandler> +typename ParseHandler::NameNodeType +PerHandlerParser<ParseHandler>::privateNameReference(const ParserName* name) { + NameNodeType id = newPrivateName(name); + if (!id) { + return null(); + } + + if (!noteUsedName(name, NameVisibility::Private, Some(pos()))) { + return null(); + } + + return id; +} + +template <class ParseHandler> +typename ParseHandler::NameNodeType +PerHandlerParser<ParseHandler>::stringLiteral() { + return handler_.newStringLiteral(anyChars.currentToken().atom(), pos()); +} + +template <class ParseHandler> +typename ParseHandler::Node +PerHandlerParser<ParseHandler>::noSubstitutionTaggedTemplate() { + if (anyChars.hasInvalidTemplateEscape()) { + anyChars.clearInvalidTemplateEscape(); + return handler_.newRawUndefinedLiteral(pos()); + } + + return handler_.newTemplateStringLiteral(anyChars.currentToken().atom(), + pos()); +} + +template <class ParseHandler, typename Unit> +typename ParseHandler::NameNodeType +GeneralParser<ParseHandler, Unit>::noSubstitutionUntaggedTemplate() { + if (!tokenStream.checkForInvalidTemplateEscapeError()) { + return null(); + } + + return handler_.newTemplateStringLiteral(anyChars.currentToken().atom(), + pos()); +} + +template <typename Unit> +RegExpLiteral* Parser<FullParseHandler, Unit>::newRegExp() { + MOZ_ASSERT(!options().selfHostingMode); + + // Create the regexp and check its syntax. + const auto& chars = tokenStream.getCharBuffer(); + mozilla::Range<const char16_t> 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_.canSkipRegexpSyntaxParse()) { + // 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. + LifoAllocScope allocScope(&cx_->tempLifoAlloc()); + if (!irregexp::CheckPatternSyntax(cx_, anyChars, range, flags, Some(line), + Some(column))) { + return nullptr; + } + } + + const ParserAtom* atom = this->compilationState_.parserAtoms.internChar16( + cx_, chars.begin(), chars.length()); + if (!atom) { + return nullptr; + } + atom->markUsedByStencil(); + + RegExpIndex index(this->compilationState_.regExpData.length()); + if (uint32_t(index) >= TaggedScriptThingIndex::IndexLimit) { + ReportAllocationOverflow(cx_); + return nullptr; + } + if (!this->compilationState_.regExpData.emplaceBack(atom->toIndex(), flags)) { + js::ReportOutOfMemory(cx_); + return nullptr; + } + + return handler_.newRegExp(index, pos()); +} + +template <typename Unit> +SyntaxParseHandler::RegExpLiteralType +Parser<SyntaxParseHandler, Unit>::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<const char16_t> source(chars.begin(), chars.length()); + { + LifoAllocScope scopeAlloc(&alloc_); + if (!irregexp::CheckPatternSyntax(cx_, anyChars, source, flags, Some(line), + Some(column))) { + return null(); + } + } + + return handler_.newRegExp(SyntaxParseHandler::NodeGeneric, pos()); +} + +template <class ParseHandler, typename Unit> +typename ParseHandler::RegExpLiteralType +GeneralParser<ParseHandler, Unit>::newRegExp() { + return asFinalParser()->newRegExp(); +} + +template <typename Unit> +BigIntLiteral* Parser<FullParseHandler, Unit>::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(); + + BigIntIndex index(this->getCompilationStencil().bigIntData.length()); + if (uint32_t(index) >= TaggedScriptThingIndex::IndexLimit) { + ReportAllocationOverflow(cx_); + return null(); + } + if (!this->getCompilationStencil().bigIntData.emplaceBack()) { + js::ReportOutOfMemory(cx_); + return null(); + } + + if (!this->getCompilationStencil().bigIntData[index].init(this->cx_, chars)) { + return null(); + } + + // Should the operations below fail, the buffer held by data will + // be cleaned up by the CompilationStencil destructor. + return handler_.newBigInt(index, this->getCompilationStencil(), pos()); +} + +template <typename Unit> +SyntaxParseHandler::BigIntLiteralType +Parser<SyntaxParseHandler, Unit>::newBigInt() { + // The tokenizer has already checked the syntax of the bigint. + + return handler_.newBigInt(); +} + +template <class ParseHandler, typename Unit> +typename ParseHandler::BigIntLiteralType +GeneralParser<ParseHandler, Unit>::newBigInt() { + return asFinalParser()->newBigInt(); +} + +// |exprPossibleError| is the PossibleError state within |expr|, +// |possibleError| is the surrounding PossibleError state. +template <class ParseHandler, typename Unit> +bool GeneralParser<ParseHandler, Unit>::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_.isPropertyAccess(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 <class ParseHandler, typename Unit> +void GeneralParser<ParseHandler, Unit>::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, cx_)) { + 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, cx_)) { + if (pc_->sc()->strict()) { + possibleError->setPendingDestructuringErrorAt( + namePos, JSMSG_BAD_STRICT_ASSIGN_EVAL); + } else { + possibleError->setPendingDestructuringWarningAt( + namePos, JSMSG_BAD_STRICT_ASSIGN_EVAL); + } + return; + } + } +} + +template <class ParseHandler, typename Unit> +bool GeneralParser<ParseHandler, Unit>::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 <class ParseHandler, typename Unit> +typename ParseHandler::ListNodeType +GeneralParser<ParseHandler, Unit>::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 <class ParseHandler, typename Unit> +typename ParseHandler::Node GeneralParser<ParseHandler, Unit>::propertyName( + YieldHandling yieldHandling, PropertyNameContext propertyNameContext, + const Maybe<DeclarationKind>& maybeDecl, ListNodeType propList, + const ParserAtom** propAtomOut) { + // PropertyName[Yield, Await]: + // LiteralPropertyName + // ComputedPropertyName[?Yield, ?Await] + // + // LiteralPropertyName: + // IdentifierName + // StringLiteral + // NumericLiteral + TokenKind ltok = anyChars.currentToken().type; + + *propAtomOut = nullptr; + switch (ltok) { + case TokenKind::Number: { + const ParserAtom* numAtom = + NumberToParserAtom(cx_, this->compilationState_.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: { + *propAtomOut = anyChars.currentToken().atom(); + uint32_t index; + if ((*propAtomOut)->isIndex(&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(); + } + + const ParserName* propName = anyChars.currentName()->asName(); + *propAtomOut = propName; + return privateNameReference(propName); + } + + default: { + if (!TokenKindIsPossibleIdentifierName(ltok)) { + error(JSMSG_UNEXPECTED_TOKEN, "property name", TokenKindToDesc(ltok)); + return null(); + } + + *propAtomOut = anyChars.currentName(); + return handler_.newObjectLiteralPropertyName(*propAtomOut, 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 <class ParseHandler, typename Unit> +typename ParseHandler::Node +GeneralParser<ParseHandler, Unit>::propertyOrMethodName( + YieldHandling yieldHandling, PropertyNameContext propertyNameContext, + const Maybe<DeclarationKind>& maybeDecl, ListNodeType propList, + PropertyType* propType, const ParserAtom** 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 + // 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`, indicating an accessor. + bool isGenerator = false; + bool isAsync = false; + bool isGetter = false; + bool isSetter = false; + + 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); + } + } + + 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) { + 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)) { + 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(); + + 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(); + *propType = PropertyType::Field; + return propName; + } + + error(JSMSG_COLON_AFTER_ID); + return null(); +} + +template <class ParseHandler, typename Unit> +typename ParseHandler::UnaryNodeType +GeneralParser<ParseHandler, Unit>::computedPropertyName( + YieldHandling yieldHandling, const Maybe<DeclarationKind>& 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 <class ParseHandler, typename Unit> +typename ParseHandler::ListNodeType +GeneralParser<ParseHandler, Unit>::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<DeclarationKind> declKind = Nothing(); + const ParserAtom* propAtom = nullptr; + 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 == cx_->parserNames().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}|. + */ + const ParserName* 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 + */ + const ParserName* 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 { + const ParserAtom* funName = nullptr; + 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; +} + +template <class ParseHandler, typename Unit> +typename ParseHandler::FunctionNodeType +GeneralParser<ParseHandler, Unit>::methodDefinition(uint32_t toStringStart, + PropertyType propType, + const ParserAtom* 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 <class ParseHandler, typename Unit> +bool GeneralParser<ParseHandler, Unit>::tryNewTarget( + BinaryNodeType* 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; + } + + *newTarget = handler_.newNewTarget(newHolder, targetHolder); + return !!*newTarget; +} + +template <class ParseHandler, typename Unit> +typename ParseHandler::BinaryNodeType +GeneralParser<ParseHandler, Unit>::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 (!mustMatchToken(TokenKind::RightParen, JSMSG_PAREN_AFTER_ARGS)) { + return null(); + } + + return handler_.newCallImport(importHolder, arg); + } else { + error(JSMSG_UNEXPECTED_TOKEN_NO_EXPECT, TokenKindToDesc(next)); + return null(); + } +} + +template <class ParseHandler, typename Unit> +typename ParseHandler::Node GeneralParser<ParseHandler, Unit>::primaryExpr( + YieldHandling yieldHandling, TripledotHandling tripledotHandling, + TokenKind tt, PossibleError* possibleError, InvokedPrediction invoked) { + MOZ_ASSERT(anyChars.isCurrentTokenType(tt)); + if (!CheckRecursionLimit(cx_)) { + 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); + + 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); + } + } + + const ParserName* 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 <class ParseHandler, typename Unit> +typename ParseHandler::Node GeneralParser<ParseHandler, Unit>::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<FullParseHandler>; +template class PerHandlerParser<SyntaxParseHandler>; +template class GeneralParser<FullParseHandler, Utf8Unit>; +template class GeneralParser<SyntaxParseHandler, Utf8Unit>; +template class GeneralParser<FullParseHandler, char16_t>; +template class GeneralParser<SyntaxParseHandler, char16_t>; +template class Parser<FullParseHandler, Utf8Unit>; +template class Parser<SyntaxParseHandler, Utf8Unit>; +template class Parser<FullParseHandler, char16_t>; +template class Parser<SyntaxParseHandler, char16_t>; + +CompilationStencil::RewindToken CompilationStencil::getRewindToken( + CompilationState& state) { + return RewindToken{state.scriptData.length(), asmJS.count()}; +} + +void CompilationStencil::rewind(CompilationState& state, + const CompilationStencil::RewindToken& pos) { + if (asmJS.count() != pos.asmJSCount) { + for (size_t i = pos.scriptDataLength; i < state.scriptData.length(); i++) { + asmJS.remove(ScriptIndex(i)); + } + MOZ_ASSERT(asmJS.count() == pos.asmJSCount); + } + state.scriptData.shrinkTo(pos.scriptDataLength); +} + +} // namespace js::frontend diff --git a/js/src/frontend/Parser.h b/js/src/frontend/Parser.h new file mode 100644 index 0000000000..664739f04a --- /dev/null +++ b/js/src/frontend/Parser.h @@ -0,0 +1,1878 @@ +/* -*- 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<ParseHandler> → 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<ParseHandler> (described + * further below). + * + * == GeneralParser<ParseHandler, Unit> → PerHandlerParser<ParseHandler> == + * + * 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<ParseHandler, Unit> final → GeneralParser<ParseHandler, Unit> == + * + * 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<SyntaxParseHandler or FullParseHandler, Unit> 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 ParseHandler, typename Unit> + * class GeneralParser : ... + * { + * ...parsing functions... + * }; + * + * but Parser is one undefined template class with two separate + * specializations: + * + * // Declare, but do not define. + * template<class ParseHandler, typename Unit> class Parser; + * + * // Define a syntax-parsing specialization. + * template<typename Unit> + * class Parser<SyntaxParseHandler, Unit> final + * : public GeneralParser<SyntaxParseHandler, Unit> + * { + * ...parsing functions... + * }; + * + * // Define a full-parsing specialization. + * template<typename Unit> + * class Parser<SyntaxParseHandler, Unit> final + * : public GeneralParser<SyntaxParseHandler, Unit> + * { + * ...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<typename Unit> + * void + * GeneralParser<SyntaxParseHandler, Unit>::foo() {} + * + * But if you specialize Parser *as a class*, then this is allowed: + * + * template<typename Unit> + * void + * Parser<SyntaxParseHandler, Unit>::foo() {} + * + * template<typename Unit> + * void + * Parser<FullParseHandler, Unit>::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/Array.h" +#include "mozilla/Maybe.h" + +#include <type_traits> +#include <utility> + +#include "jspubtd.h" + +#include "ds/Nestable.h" +#include "frontend/BytecodeCompiler.h" +#include "frontend/CompilationInfo.h" +#include "frontend/ErrorReporter.h" +#include "frontend/FullParseHandler.h" +#include "frontend/FunctionSyntaxKind.h" // FunctionSyntaxKind +#include "frontend/IteratorKind.h" +#include "frontend/NameAnalysisTypes.h" +#include "frontend/NameCollections.h" +#include "frontend/ParseContext.h" +#include "frontend/SharedContext.h" +#include "frontend/SyntaxParseHandler.h" +#include "frontend/TokenStream.h" +#include "js/friend/ErrorMessages.h" // JSErrNum, JSMSG_* +#include "js/Vector.h" +#include "vm/ErrorReporting.h" +#include "vm/GeneratorAndAsyncKind.h" // js::GeneratorKind, js::FunctionAsyncKind + +namespace js { + +class ModuleObject; + +namespace frontend { + +template <class ParseHandler, typename Unit> +class GeneralParser; + +class SourceParseContext : public ParseContext { + public: + template <typename ParseHandler, typename Unit> + SourceParseContext(GeneralParser<ParseHandler, Unit>* prs, SharedContext* sc, + Directives* newDirectives) + : ParseContext(prs->cx_, prs->pc_, sc, prs->tokenStream, + prs->compilationState_, newDirectives, + std::is_same_v<ParseHandler, FullParseHandler>) {} +}; + +enum VarContext { HoistVars, DontHoistVars }; +enum PropListType { ObjectLiteral, ClassBody, DerivedClassBody }; +enum class PropertyType { + Normal, + Shorthand, + CoverInitializedName, + Getter, + Setter, + Method, + GeneratorMethod, + AsyncMethod, + AsyncGeneratorMethod, + Constructor, + DerivedConstructor, + Field, +}; + +enum AwaitHandling : uint8_t { + AwaitIsName, + AwaitIsKeyword, + AwaitIsModuleKeyword +}; + +template <class ParseHandler, typename Unit> +class AutoAwaitIsKeyword; + +template <class ParseHandler, typename Unit> +class AutoInParametersOfAsyncFunction; + +class MOZ_STACK_CLASS ParserSharedBase { + public: + enum class Kind { Parser }; + + ParserSharedBase(JSContext* cx, CompilationStencil& stencil, + CompilationState& compilationState, Kind kind); + ~ParserSharedBase(); + + public: + JSContext* const cx_; + + LifoAlloc& alloc_; + + // Information for parsing with a lifetime longer than the parser itself. + CompilationStencil& stencil_; + + CompilationState& compilationState_; + + // innermost parse context (stack-allocated) + ParseContext* pc_; + + // For tracking used names in this parsing session. + UsedNameTracker& usedNames_; + + public: + CompilationStencil& getCompilationStencil() { return stencil_; } + + LifoAlloc& stencilAlloc() { return stencil_.alloc; } + + JSAtom* liftParserAtomToJSAtom(const ParserAtom* parserAtom) { + return parserAtom->toJSAtom(cx_, stencil_.input.atomCache); + } +}; + +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: + bool awaitIsKeyword() const { return awaitHandling_ != AwaitIsName; } + + bool inParametersOfAsyncFunction() const { + return inParametersOfAsyncFunction_; + } + + ParseGoal parseGoal() const { + return pc_->sc()->hasModuleGoal() ? ParseGoal::Module : ParseGoal::Script; + } + + template <class, typename> + friend class AutoAwaitIsKeyword; + template <class, typename> + friend class AutoInParametersOfAsyncFunction; + + ParserBase(JSContext* cx, const JS::ReadOnlyCompileOptions& options, + bool foldConstants, CompilationStencil& stencil, + 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. + + JSContext* getContext() const override { return cx_; } + + 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(const ParserName* name); + + bool hasValidSimpleStrictParameterNames(); + + /* + * Create a new function object given a name (which is optional if this is + * a function expression). + */ + JSFunction* newFunction(const ParserAtom* atom, FunctionSyntaxKind kind, + GeneratorKind generatorKind, + FunctionAsyncKind asyncKind); + + // 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; + CompilationStencil::RewindToken token; + }; + Mark mark() const { + Mark m; + m.mark = alloc_.mark(); + m.token = stencil_.getRewindToken(compilationState_); + return m; + } + void release(Mark m) { + alloc_.release(m.mark); + stencil_.rewind(compilationState_, m.token); + } + + public: + mozilla::Maybe<GlobalScope::ParserData*> newGlobalScopeData( + ParseContext::Scope& scope); + mozilla::Maybe<ModuleScope::ParserData*> newModuleScopeData( + ParseContext::Scope& scope); + mozilla::Maybe<EvalScope::ParserData*> newEvalScopeData( + ParseContext::Scope& scope); + mozilla::Maybe<FunctionScope::ParserData*> newFunctionScopeData( + ParseContext::Scope& scope, bool hasParameterExprs); + mozilla::Maybe<VarScope::ParserData*> newVarScopeData( + ParseContext::Scope& scope); + mozilla::Maybe<LexicalScope::ParserData*> newLexicalScopeData( + 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(const ParserName* name, NameVisibility visibility, + mozilla::Maybe<TokenPos> tokenPosition); + + bool checkAndMarkSuperScope(); + + bool leaveInnerFunction(ParseContext* outerpc); + + const ParserAtom* prefixAccessorName(PropertyType propType, + const ParserAtom* propAtom); + + MOZ_MUST_USE bool setSourceMapInfo(); + + void setFunctionEndFromCurrentToken(FunctionBox* funbox) const; +}; + +template <class ParseHandler> +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<SyntaxParseHandler, Unit>*| + // where |Unit| varies per |Parser<ParseHandler, Unit>|. But this + // template class doesn't know |Unit|, so we store a |void*| here and make + // |GeneralParser<ParseHandler, Unit>::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(JSContext* cx, const JS::ReadOnlyCompileOptions& options, + bool foldConstants, CompilationStencil& stencil, + CompilationState& compilationState, + BaseScript* lazyOuterFunction, void* internalSyntaxParser); + + protected: + template <typename Unit> + PerHandlerParser(JSContext* cx, const JS::ReadOnlyCompileOptions& options, + bool foldConstants, CompilationStencil& stencil, + CompilationState& compilationState, + GeneralParser<SyntaxParseHandler, Unit>* syntaxParser, + BaseScript* lazyOuterFunction) + : PerHandlerParser(cx, options, foldConstants, stencil, compilationState, + lazyOuterFunction, static_cast<void*>(syntaxParser)) {} + + static typename ParseHandler::NullNode null() { return ParseHandler::null(); } + + NameNodeType stringLiteral(); + + const char* nameIsArgumentsOrEval(Node node); + + bool noteDestructuredPositionalFormalParameter(FunctionNodeType funNode, + Node destruct); + + bool noteUsedName( + const ParserName* name, + NameVisibility visibility = NameVisibility::Public, + mozilla::Maybe<TokenPos> 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_.canSkipLazyClosedOverBindings()) { + 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); + bool finishFunction(bool isStandaloneFunction = false); + + inline NameNodeType newName(const ParserName* name); + inline NameNodeType newName(const ParserName* name, TokenPos pos); + + inline NameNodeType newPrivateName(const ParserName* name); + + NameNodeType newInternalDotName(const ParserName* name); + NameNodeType newThisName(); + NameNodeType newDotGeneratorName(); + + NameNodeType identifierReference(const ParserName* name); + NameNodeType privateNameReference(const ParserName* name); + + Node noSubstitutionTaggedTemplate(); + + inline bool processExport(Node node); + inline bool processExportFrom(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(const ParserName* key, const TokenPos& pos) { + return handler_.newPropertyName(key, pos); + } + + PropertyAccessType newPropertyAccess(Node expr, NameNodeType key) { + return handler_.newPropertyAccess(expr, key); + } + + FunctionBox* newFunctionBox(FunctionNodeType funNode, + const ParserAtom* explicitName, + FunctionFlags flags, uint32_t toStringStart, + Directives directives, + GeneratorKind generatorKind, + FunctionAsyncKind asyncKind); + + 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<void*>(0x1) + +template <> +inline void PerHandlerParser<SyntaxParseHandler>::disableSyntaxParser() {} + +template <> +inline bool PerHandlerParser<SyntaxParseHandler>::abortIfSyntaxParser() { + internalSyntaxParser_ = ABORTED_SYNTAX_PARSE_SENTINEL; + return false; +} + +template <> +inline bool PerHandlerParser<SyntaxParseHandler>::hadAbortedSyntaxParse() { + return internalSyntaxParser_ == ABORTED_SYNTAX_PARSE_SENTINEL; +} + +template <> +inline void PerHandlerParser<SyntaxParseHandler>::clearAbortedSyntaxParse() { + internalSyntaxParser_ = nullptr; +} + +#undef ABORTED_SYNTAX_PARSE_SENTINEL + +// Disable syntax parsing of all future inner functions during this +// full-parse. +template <> +inline void PerHandlerParser<FullParseHandler>::disableSyntaxParser() { + internalSyntaxParser_ = nullptr; +} + +template <> +inline bool PerHandlerParser<FullParseHandler>::abortIfSyntaxParser() { + disableSyntaxParser(); + return true; +} + +template <> +inline bool PerHandlerParser<FullParseHandler>::hadAbortedSyntaxParse() { + return false; +} + +template <> +inline void PerHandlerParser<FullParseHandler>::clearAbortedSyntaxParse() {} + +template <class Parser> +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 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 }; + +template <class ParseHandler, typename Unit> +class Parser; + +template <class ParseHandler, typename Unit> +class MOZ_STACK_CLASS GeneralParser : public PerHandlerParser<ParseHandler> { + public: + using TokenStream = + TokenStreamSpecific<Unit, ParserAnyCharsAccess<GeneralParser>>; + + private: + using Base = PerHandlerParser<ParseHandler>; + using FinalParser = Parser<ParseHandler, Unit>; + 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<SyntaxParseHandler, Unit>; + + protected: + using Modifier = TokenStreamShared::Modifier; + using Position = typename TokenStream::Position; + + using Base::PredictInvoked; + using Base::PredictUninvoked; + + using Base::alloc_; + using Base::awaitIsKeyword; + using Base::inParametersOfAsyncFunction; + using Base::parseGoal; +#if DEBUG + using Base::checkOptionsCalled_; +#endif + using Base::checkForUndefinedPrivateFields; + 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::newFunction; + 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. + + MOZ_MUST_USE bool computeErrorMetadata( + ErrorMetadata* err, const ErrorReportMixin::ErrorOffset& offset) 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::cx_; + 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::newThisName; + using Base::nextTokenContinuesLetDeclaration; + using Base::noSubstitutionTaggedTemplate; + using Base::noteDestructuredPositionalFormalParameter; + using Base::prefixAccessorName; + using Base::privateNameReference; + using Base::processExport; + using Base::processExportFrom; + 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<ParseHandler, Unit>& 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. + MOZ_MUST_USE bool checkForError(ErrorKind kind); + + // Transfer an existing error to another instance. + void transferErrorTo(ErrorKind kind, PossibleError* other); + + public: + explicit PossibleError(GeneralParser<ParseHandler, Unit>& 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. + MOZ_MUST_USE bool checkForDestructuringErrorOrWarning(); + + // If there is a pending expression error, report it and return false, + // otherwise return true. Clears any pending destructuring error or + // warning. + MOZ_MUST_USE 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<SyntaxParser*>(Base::internalSyntaxParser_); + } + + public: + TokenStream tokenStream; + + public: + GeneralParser(JSContext* cx, const JS::ReadOnlyCompileOptions& options, + const Unit* units, size_t length, bool foldConstants, + CompilationStencil& stencil, CompilationState& compilationState, + SyntaxParser* syntaxParser, BaseScript* lazyOuterFunction); + + 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 <typename ConditionT, typename ErrorReportT> + MOZ_MUST_USE 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. + */ + MOZ_MUST_USE bool mustMatchToken(TokenKind expected, JSErrNum errorNumber) { + return mustMatchTokenInternal( + [expected](TokenKind actual) { return actual == expected; }, + [this, errorNumber](TokenKind) { this->error(errorNumber); }); + } + + template <typename ConditionT> + MOZ_MUST_USE bool mustMatchToken(ConditionT condition, JSErrNum errorNumber) { + return mustMatchTokenInternal(condition, [this, errorNumber](TokenKind) { + this->error(errorNumber); + }); + } + + template <typename ErrorReportT> + MOZ_MUST_USE 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, const ParserAtom* 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, + FunctionSyntaxKind kind, 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. + MOZ_MUST_USE 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<uint32_t>& 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<ParseContext::Scope>& 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(); + + ListNodeType variableStatement(YieldHandling yieldHandling); + + LabeledStatementType labeledStatement(YieldHandling yieldHandling); + Node labeledItem(YieldHandling yieldHandling); + + TernaryNodeType ifStatement(YieldHandling yieldHandling); + Node consequentOrAlternative(YieldHandling yieldHandling); + + ListNodeType lexicalDeclaration(YieldHandling yieldHandling, + DeclarationKind kind); + + inline BinaryNodeType importDeclaration(); + Node importDeclarationOrImportExpr(YieldHandling yieldHandling); + + 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|. + ListNodeType 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); + 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(BinaryNodeType* newTarget); + + BinaryNodeType importExpr(YieldHandling yieldHandling, bool allowCallSyntax); + + FunctionNodeType methodDefinition(uint32_t toStringStart, + PropertyType propType, + const ParserAtom* funName); + + /* + * Additional JS parsers. + */ + bool functionArguments(YieldHandling yieldHandling, FunctionSyntaxKind kind, + FunctionNodeType funNode); + + FunctionNodeType functionDefinition( + FunctionNodeType funNode, uint32_t toStringStart, InHandling inHandling, + YieldHandling yieldHandling, const ParserAtom* 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. + 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(const ParserAtom* exportName); + inline bool checkExportedNamesForArrayBinding(ListNodeType array); + inline bool checkExportedNamesForObjectBinding(ListNodeType obj); + inline bool checkExportedNamesForDeclaration(Node node); + inline bool checkExportedNamesForDeclarationList(ListNodeType 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 class fields with computed property names. + size_t staticFieldKeys = 0; + + // The number of instance class private methods. + size_t privateMethods = 0; + }; + MOZ_MUST_USE bool classMember( + YieldHandling yieldHandling, + const ParseContext::ClassStatement& classStmt, + const ParserName* className, uint32_t classStartOffset, + HasHeritage hasHeritage, ClassInitializedMembers& classInitializedMembers, + ListNodeType& classMembers, bool* done); + MOZ_MUST_USE bool finishClassConstructor( + const ParseContext::ClassStatement& classStmt, + const ParserName* className, HasHeritage hasHeritage, + uint32_t classStartOffset, uint32_t classEndOffset, + const ClassInitializedMembers& classInitializedMembers, + ListNodeType& classMembers); + + FunctionNodeType privateMethodInitializer(TokenPos propNamePos, + const ParserAtom* propAtom, + const ParserAtom* storedMethodAtom); + FunctionNodeType fieldInitializerOpt( + TokenPos propNamePos, Node name, const ParserAtom* atom, + ClassInitializedMembers& classInitializedMembers, bool isStatic, + HasHeritage hasHeritage); + FunctionNodeType synthesizeConstructor(const ParserAtom* className, + TokenPos synthesizedBodyPos, + HasHeritage hasHeritage); + + bool checkBindingIdentifier(const ParserName* ident, uint32_t offset, + YieldHandling yieldHandling, + TokenKind hint = TokenKind::Limit); + + const ParserName* labelOrIdentifierReference(YieldHandling yieldHandling); + + const ParserName* labelIdentifier(YieldHandling yieldHandling) { + return labelOrIdentifierReference(yieldHandling); + } + + const ParserName* identifierReference(YieldHandling yieldHandling) { + return labelOrIdentifierReference(yieldHandling); + } + + bool matchLabel(YieldHandling yieldHandling, const ParserName** 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(const ParserName* name, DeclarationKind prevKind, + TokenPos pos, uint32_t prevPos); + bool notePositionalFormalParameter(FunctionNodeType funNode, + const ParserName* name, uint32_t beginPos, + bool disallowDuplicateParams, + bool* duplicatedParam); + + enum PropertyNameContext { + PropertyNameInLiteral, + PropertyNameInPattern, + PropertyNameInClass + }; + Node propertyName(YieldHandling yieldHandling, + PropertyNameContext propertyNameContext, + const mozilla::Maybe<DeclarationKind>& maybeDecl, + ListNodeType propList, const ParserAtom** propAtomOut); + Node propertyOrMethodName(YieldHandling yieldHandling, + PropertyNameContext propertyNameContext, + const mozilla::Maybe<DeclarationKind>& maybeDecl, + ListNodeType propList, PropertyType* propType, + const ParserAtom** propAtomOut); + UnaryNodeType computedPropertyName( + YieldHandling yieldHandling, + const mozilla::Maybe<DeclarationKind>& maybeDecl, + PropertyNameContext propertyNameContext, ListNodeType literal); + ListNodeType arrayInitializer(YieldHandling yieldHandling, + PossibleError* possibleError); + inline RegExpLiteralType newRegExp(); + + ListNodeType objectLiteral(YieldHandling yieldHandling, + PossibleError* possibleError); + + 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. + const ParserName* bindingIdentifier(YieldHandling yieldHandling); + + bool checkLabelOrIdentifierReference(const ParserName* ident, uint32_t offset, + YieldHandling yieldHandling, + TokenKind hint = TokenKind::Limit); + + ListNodeType statementList(YieldHandling yieldHandling); + + MOZ_MUST_USE FunctionNodeType innerFunction( + FunctionNodeType funNode, ParseContext* outerpc, + const ParserAtom* 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(const ParserName* name, DeclarationKind kind, + TokenPos pos); + + bool noteDeclaredPrivateName(Node nameNode, const ParserName* name, + PropertyType propType, TokenPos pos); + + private: + inline bool asmJS(ListNodeType list); +}; + +template <typename Unit> +class MOZ_STACK_CLASS Parser<SyntaxParseHandler, Unit> final + : public GeneralParser<SyntaxParseHandler, Unit> { + using Base = GeneralParser<SyntaxParseHandler, Unit>; + 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<SyntaxParseHandler, Unit>; + + // 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<SyntaxParseHandler, Unit>; + + 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::cx_; + 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. + + const ParserName* bindingIdentifier(YieldHandling yieldHandling) { + return Base::bindingIdentifier(yieldHandling); + } + + // Functions present in both Parser<ParseHandler, Unit> specializations. + + inline void setAwaitHandling(AwaitHandling awaitHandling); + inline void setInParametersOfAsyncFunction(bool inParameters); + + RegExpLiteralType newRegExp(); + BigIntLiteralType newBigInt(); + + // Parse a module. + ModuleNodeType moduleBody(ModuleSharedContext* modulesc); + + inline BinaryNodeType importDeclaration(); + inline bool checkLocalExportNames(ListNodeType node); + inline bool checkExportedName(const ParserAtom* exportName); + inline bool checkExportedNamesForArrayBinding(ListNodeType array); + inline bool checkExportedNamesForObjectBinding(ListNodeType obj); + inline bool checkExportedNamesForDeclaration(Node node); + inline bool checkExportedNamesForDeclarationList(ListNodeType node); + inline bool checkExportedNameForFunction(FunctionNodeType funNode); + inline bool checkExportedNameForClass(ClassNodeType classNode); + inline bool checkExportedNameForClause(NameNodeType nameNode); + + bool trySyntaxParseInnerFunction( + FunctionNodeType* funNode, const ParserAtom* 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, + FunctionSyntaxKind kind, bool tryAnnexB); + + bool asmJS(ListNodeType list); + + // Functions present only in Parser<SyntaxParseHandler, Unit>. +}; + +template <typename Unit> +class MOZ_STACK_CLASS Parser<FullParseHandler, Unit> final + : public GeneralParser<FullParseHandler, Unit> { + using Base = GeneralParser<FullParseHandler, Unit>; + 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<SyntaxParseHandler, Unit>; + + // 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<FullParseHandler, Unit>; + + 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::cx_; + 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. + + const ParserName* bindingIdentifier(YieldHandling yieldHandling) { + return Base::bindingIdentifier(yieldHandling); + } + + // Functions present in both Parser<ParseHandler, Unit> specializations. + + friend class AutoAwaitIsKeyword<SyntaxParseHandler, Unit>; + inline void setAwaitHandling(AwaitHandling awaitHandling); + + friend class AutoInParametersOfAsyncFunction<SyntaxParseHandler, Unit>; + inline void setInParametersOfAsyncFunction(bool inParameters); + + RegExpLiteralType newRegExp(); + BigIntLiteralType newBigInt(); + + // Parse a module. + ModuleNodeType moduleBody(ModuleSharedContext* modulesc); + + BinaryNodeType importDeclaration(); + bool checkLocalExportNames(ListNodeType node); + bool checkExportedName(const ParserAtom* exportName); + bool checkExportedNamesForArrayBinding(ListNodeType array); + bool checkExportedNamesForObjectBinding(ListNodeType obj); + bool checkExportedNamesForDeclaration(Node node); + bool checkExportedNamesForDeclarationList(ListNodeType node); + bool checkExportedNameForFunction(FunctionNodeType funNode); + bool checkExportedNameForClass(ClassNodeType classNode); + inline bool checkExportedNameForClause(NameNodeType nameNode); + + bool trySyntaxParseInnerFunction( + FunctionNodeType* funNode, const ParserAtom* explicitName, + FunctionFlags flags, uint32_t toStringStart, InHandling inHandling, + YieldHandling yieldHandling, FunctionSyntaxKind kind, + GeneratorKind generatorKind, FunctionAsyncKind asyncKind, bool tryAnnexB, + Directives inheritedDirectives, Directives* newDirectives); + + MOZ_MUST_USE bool advancePastSyntaxParsedFunction(SyntaxParser* syntaxParser); + + bool skipLazyInnerFunction(FunctionNodeType funNode, uint32_t toStringStart, + FunctionSyntaxKind kind, bool tryAnnexB); + + // Functions present only in Parser<FullParseHandler, Unit>. + + // 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(HandleFunction fun, + 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<uint32_t>& 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 namedImportsOrNamespaceImport(TokenKind tt, ListNodeType importSpecSet); + + const ParserName* importedBinding() { return bindingIdentifier(YieldIsName); } + + bool checkLocalExportName(const ParserName* ident, uint32_t offset) { + return checkLabelOrIdentifierReference(ident, offset, YieldIsName); + } + + bool asmJS(ListNodeType list); +}; + +template <class Parser> +/* static */ inline const TokenStreamAnyChars& +ParserAnyCharsAccess<Parser>::anyChars(const GeneralTokenStreamChars* ts) { + // The structure we're walking through looks like this: + // + // struct ParserBase + // { + // ...; + // TokenStreamAnyChars anyChars; + // ...; + // }; + // struct Parser : <class that ultimately inherits from ParserBase> + // { + // ...; + // 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<GeneralTokenStreamChars, TokenStreamSpecific>, + "the static_cast<> below assumes a base-class relationship"); + const auto* tss = static_cast<const TokenStreamSpecific*>(ts); + + auto tssAddr = reinterpret_cast<uintptr_t>(tss); + + using ActualTokenStreamType = decltype(std::declval<Parser>().tokenStream); + static_assert(std::is_same_v<ActualTokenStreamType, TokenStreamSpecific>, + "Parser::tokenStream must have type TokenStreamSpecific"); + + uintptr_t parserAddr = tssAddr - offsetof(Parser, tokenStream); + + return reinterpret_cast<const Parser*>(parserAddr)->anyChars; +} + +template <class Parser> +/* static */ inline TokenStreamAnyChars& ParserAnyCharsAccess<Parser>::anyChars( + GeneralTokenStreamChars* ts) { + const TokenStreamAnyChars& anyCharsConst = + anyChars(const_cast<const GeneralTokenStreamChars*>(ts)); + + return const_cast<TokenStreamAnyChars&>(anyCharsConst); +} + +template <class ParseHandler, typename Unit> +class MOZ_STACK_CLASS AutoAwaitIsKeyword { + using GeneralParser = frontend::GeneralParser<ParseHandler, Unit>; + + private: + GeneralParser* parser_; + AwaitHandling oldAwaitHandling_; + + public: + AutoAwaitIsKeyword(GeneralParser* parser, AwaitHandling awaitHandling) { + parser_ = parser; + oldAwaitHandling_ = static_cast<AwaitHandling>(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 ParseHandler, typename Unit> +class MOZ_STACK_CLASS AutoInParametersOfAsyncFunction { + using GeneralParser = frontend::GeneralParser<ParseHandler, Unit>; + + 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(JSContext* cx, + LifoAlloc& alloc, + uint32_t numBindings); + +VarScope::ParserData* NewEmptyVarScopeData(JSContext* cx, LifoAlloc& alloc, + uint32_t numBindings); + +LexicalScope::ParserData* NewEmptyLexicalScopeData(JSContext* cx, + LifoAlloc& alloc, + uint32_t numBindings); + +FunctionScope::ParserData* NewEmptyFunctionScopeData(JSContext* cx, + LifoAlloc& alloc, + uint32_t numBindings); + +mozilla::Maybe<GlobalScope::ParserData*> NewGlobalScopeData( + JSContext* context, ParseContext::Scope& scope, LifoAlloc& alloc, + ParseContext* pc); + +mozilla::Maybe<EvalScope::ParserData*> NewEvalScopeData( + JSContext* context, ParseContext::Scope& scope, LifoAlloc& alloc, + ParseContext* pc); + +mozilla::Maybe<FunctionScope::ParserData*> NewFunctionScopeData( + JSContext* context, ParseContext::Scope& scope, bool hasParameterExprs, + LifoAlloc& alloc, ParseContext* pc); + +mozilla::Maybe<VarScope::ParserData*> NewVarScopeData( + JSContext* context, ParseContext::Scope& scope, LifoAlloc& alloc, + ParseContext* pc); + +mozilla::Maybe<LexicalScope::ParserData*> NewLexicalScopeData( + JSContext* context, 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..e310549671 --- /dev/null +++ b/js/src/frontend/ParserAtom.cpp @@ -0,0 +1,864 @@ +/* -*- 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 <memory> // std::uninitialized_fill_n +#include <type_traits> + +#include "jsnum.h" + +#include "frontend/CompilationInfo.h" +#include "frontend/NameCollections.h" +#include "frontend/StencilXdr.h" // CanCopyDataToDisk +#include "vm/JSContext.h" +#include "vm/Printer.h" +#include "vm/Runtime.h" +#include "vm/StringType.h" + +using namespace js; +using namespace js::frontend; + +namespace js { + +// Iterates over a sequence of ParserAtoms and yield their sequence of +// characters in order. This simulates concatenation of atoms. The underlying +// ParserAtoms may be a mix of Latin1 and char16_t atoms. +template <> +class InflatedChar16Sequence<const ParserAtom*> { + private: + const ParserAtom** cur_ = nullptr; + const ParserAtom** lim_ = nullptr; + size_t index_ = 0; + + void settle() { + // Check if we are out-of-bounds for current ParserAtom. + auto outOfBounds = [this]() { return index_ >= (*cur_)->length(); }; + + while (hasMore() && outOfBounds()) { + // Advance to start of next ParserAtom. + cur_++; + index_ = 0; + } + } + + public: + explicit InflatedChar16Sequence( + const mozilla::Range<const ParserAtom*>& atoms) + : cur_(atoms.begin().get()), lim_(atoms.end().get()) { + settle(); + } + + bool hasMore() { return cur_ < lim_; } + + char16_t next() { + MOZ_ASSERT(hasMore()); + char16_t ch = (*cur_)->hasLatin1Chars() ? (*cur_)->latin1Chars()[index_] + : (*cur_)->twoByteChars()[index_]; + index_++; + settle(); + return ch; + } + + HashNumber computeHash() const { + auto copy = *this; + HashNumber hash = 0; + + while (copy.hasMore()) { + hash = mozilla::AddToHash(hash, copy.next()); + } + return hash; + } +}; + +} // namespace js + +namespace js { + +template <> +class InflatedChar16Sequence<LittleEndianChars> { + private: + LittleEndianChars chars_; + size_t idx_; + size_t len_; + + public: + InflatedChar16Sequence(LittleEndianChars chars, size_t length) + : chars_(chars), idx_(0), len_(length) {} + + bool hasMore() { return idx_ < len_; } + + char16_t next() { + MOZ_ASSERT(hasMore()); + return chars_[idx_++]; + } + + HashNumber computeHash() const { + auto copy = *this; + HashNumber hash = 0; + while (copy.hasMore()) { + hash = mozilla::AddToHash(hash, copy.next()); + } + return hash; + } +}; + +} // namespace js + +namespace js { +namespace frontend { + +JSAtom* GetWellKnownAtom(JSContext* cx, WellKnownAtomId atomId) { +#define ASSERT_OFFSET_(idpart, id, text) \ + static_assert(offsetof(JSAtomState, id) == \ + int32_t(WellKnownAtomId::id) * \ + sizeof(js::ImmutablePropertyNamePtr)); + FOR_EACH_COMMON_PROPERTYNAME(ASSERT_OFFSET_); +#undef ASSERT_OFFSET_ + +#define ASSERT_OFFSET_(name, clasp) \ + static_assert(offsetof(JSAtomState, name) == \ + int32_t(WellKnownAtomId::name) * \ + sizeof(js::ImmutablePropertyNamePtr)); + JS_FOR_EACH_PROTOTYPE(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 (isStaticParserString1()) { + MOZ_ASSERT(size_t(toStaticParserString1()) < + WellKnownParserAtoms_ROM::ASCII_STATIC_LIMIT); + } else if (isStaticParserString2()) { + MOZ_ASSERT(size_t(toStaticParserString2()) < + WellKnownParserAtoms_ROM::NUM_LENGTH2_ENTRIES); + } else { + MOZ_ASSERT(isNull()); + } +} +#endif + +template <typename CharT, typename SeqCharT> +/* static */ ParserAtomEntry* ParserAtomEntry::allocate( + JSContext* cx, LifoAlloc& alloc, InflatedChar16Sequence<SeqCharT> seq, + uint32_t length, HashNumber hash) { + constexpr size_t HeaderSize = sizeof(ParserAtomEntry); + void* raw = alloc.alloc(HeaderSize + (sizeof(CharT) * length)); + if (!raw) { + js::ReportOutOfMemory(cx); + return nullptr; + } + + constexpr bool hasTwoByteChars = (sizeof(CharT) == 2); + static_assert(sizeof(CharT) == 1 || sizeof(CharT) == 2, + "CharT should be 1 or 2 byte type"); + ParserAtomEntry* entry = + new (raw) ParserAtomEntry(length, hash, hasTwoByteChars); + CharT* entryBuf = entry->chars<CharT>(); + drainChar16Seq(entryBuf, seq, length); + return entry; +} + +/* static */ ParserAtomEntry* ParserAtomEntry::allocateRaw( + JSContext* cx, LifoAlloc& alloc, const uint8_t* srcRaw, + size_t totalLength) { + void* raw = alloc.alloc(totalLength); + if (!raw) { + js::ReportOutOfMemory(cx); + return nullptr; + } + + memcpy(raw, srcRaw, totalLength); + + return static_cast<ParserAtomEntry*>(raw); +} + +bool ParserAtomEntry::equalsJSAtom(JSAtom* other) const { + // Compare hashes and lengths first. + if (hash_ != other->hash() || length_ != other->length()) { + return false; + } + + JS::AutoCheckCannotGC nogc; + + if (hasTwoByteChars()) { + // Compare heap-allocated 16-bit chars to atom. + return other->hasLatin1Chars() + ? EqualChars(twoByteChars(), other->latin1Chars(nogc), length_) + : EqualChars(twoByteChars(), other->twoByteChars(nogc), length_); + } + + MOZ_ASSERT(hasLatin1Chars()); + return other->hasLatin1Chars() + ? EqualChars(latin1Chars(), other->latin1Chars(nogc), length_) + : EqualChars(latin1Chars(), other->twoByteChars(nogc), length_); +} + +template <typename CharT> +UniqueChars ToPrintableStringImpl(JSContext* cx, mozilla::Range<CharT> str) { + Sprinter sprinter(cx); + if (!sprinter.init()) { + return nullptr; + } + if (!QuoteString<QuoteTarget::String>(&sprinter, str)) { + return nullptr; + } + return sprinter.release(); +} + +UniqueChars ParserAtomToPrintableString(JSContext* cx, const ParserAtom* atom) { + size_t length = atom->length(); + + return atom->hasLatin1Chars() + ? ToPrintableStringImpl( + cx, mozilla::Range(atom->latin1Chars(), length)) + : ToPrintableStringImpl( + cx, mozilla::Range(atom->twoByteChars(), length)); +} + +bool ParserAtomEntry::isIndex(uint32_t* indexp) const { + size_t len = length(); + if (len == 0 || len > UINT32_CHAR_BUFFER_LENGTH) { + return false; + } + if (hasLatin1Chars()) { + return mozilla::IsAsciiDigit(*latin1Chars()) && + js::CheckStringIsIndex(latin1Chars(), len, indexp); + } + return mozilla::IsAsciiDigit(*twoByteChars()) && + js::CheckStringIsIndex(twoByteChars(), len, indexp); +} + +JSAtom* ParserAtomEntry::toJSAtom(JSContext* cx, + CompilationAtomCache& atomCache) const { + if (isParserAtomIndex()) { + JSAtom* atom = atomCache.getAtomAt(toParserAtomIndex()); + if (atom) { + return atom; + } + + return instantiate(cx, atomCache); + } + + if (isWellKnownAtomId()) { + return GetWellKnownAtom(cx, toWellKnownAtomId()); + } + + if (isStaticParserString1()) { + char16_t ch = static_cast<char16_t>(toStaticParserString1()); + return cx->staticStrings().getUnit(ch); + } + + MOZ_ASSERT(isStaticParserString2()); + size_t s = static_cast<size_t>(toStaticParserString2()); + return cx->staticStrings().getLength2FromIndex(s); +} + +JSAtom* ParserAtomEntry::toExistingJSAtom( + JSContext* cx, CompilationAtomCache& atomCache) const { + if (isParserAtomIndex()) { + JSAtom* atom = atomCache.getExistingAtomAt(toParserAtomIndex()); + MOZ_ASSERT(atom); + return atom; + } + + if (isWellKnownAtomId()) { + return GetWellKnownAtom(cx, toWellKnownAtomId()); + } + + if (isStaticParserString1()) { + char16_t ch = static_cast<char16_t>(toStaticParserString1()); + return cx->staticStrings().getUnit(ch); + } + + MOZ_ASSERT(isStaticParserString2()); + size_t s = static_cast<size_t>(toStaticParserString2()); + return cx->staticStrings().getLength2FromIndex(s); +} + +JSAtom* ParserAtomEntry::instantiate(JSContext* cx, + CompilationAtomCache& atomCache) const { + JSAtom* atom; + if (hasLatin1Chars()) { + atom = AtomizeChars(cx, hash(), latin1Chars(), length()); + } else { + atom = AtomizeChars(cx, hash(), twoByteChars(), length()); + } + if (!atom) { + js::ReportOutOfMemory(cx); + return nullptr; + } + if (!atomCache.setAtomAt(cx, toParserAtomIndex(), atom)) { + return nullptr; + } + + return atom; +} + +bool ParserAtomEntry::toNumber(JSContext* cx, double* result) const { + return hasLatin1Chars() ? CharsToNumber(cx, latin1Chars(), length(), result) + : CharsToNumber(cx, twoByteChars(), length(), result); +} + +#if defined(DEBUG) || defined(JS_JITSPEW) +void ParserAtomEntry::dump() const { + js::Fprinter out(stderr); + out.put("\""); + dumpCharsNoQuote(out); + out.put("\"\n"); +} + +void ParserAtomEntry::dumpCharsNoQuote(js::GenericPrinter& out) const { + if (hasLatin1Chars()) { + JSString::dumpCharsNoQuote<Latin1Char>(latin1Chars(), length(), out); + } else { + JSString::dumpCharsNoQuote<char16_t>(twoByteChars(), length(), out); + } +} +#endif + +ParserAtomsTable::ParserAtomsTable(JSRuntime* rt, LifoAlloc& alloc) + : wellKnownTable_(*rt->commonParserNames), alloc_(alloc) {} + +const ParserAtom* ParserAtomsTable::addEntry(JSContext* cx, + EntrySet::AddPtr& addPtr, + ParserAtomEntry* entry) { + MOZ_ASSERT(!addPtr); + ParserAtomIndex index = ParserAtomIndex(entries_.length()); + if (size_t(index) >= TaggedParserAtomIndex::IndexLimit) { + ReportAllocationOverflow(cx); + return nullptr; + } + if (!entries_.append(entry)) { + js::ReportOutOfMemory(cx); + return nullptr; + } + entry->setParserAtomIndex(index); + if (!entrySet_.add(addPtr, entry)) { + js::ReportOutOfMemory(cx); + return nullptr; + } + return entry->asAtom(); +} + +template <typename AtomCharT, typename SeqCharT> +const ParserAtom* ParserAtomsTable::internChar16Seq( + JSContext* cx, EntrySet::AddPtr& addPtr, HashNumber hash, + InflatedChar16Sequence<SeqCharT> seq, uint32_t length) { + MOZ_ASSERT(!addPtr); + + ParserAtomEntry* entry = + ParserAtomEntry::allocate<AtomCharT>(cx, alloc_, seq, length, hash); + if (!entry) { + return nullptr; + } + return addEntry(cx, addPtr, entry); +} + +static const uint16_t MAX_LATIN1_CHAR = 0xff; + +const ParserAtom* ParserAtomsTable::internAscii(JSContext* cx, + const char* asciiPtr, + uint32_t length) { + // ASCII strings are strict subsets of Latin1 strings. + const Latin1Char* latin1Ptr = reinterpret_cast<const Latin1Char*>(asciiPtr); + return internLatin1(cx, latin1Ptr, length); +} + +const ParserAtom* ParserAtomsTable::internLatin1(JSContext* cx, + const Latin1Char* latin1Ptr, + uint32_t length) { + // Check for tiny strings which are abundant in minified code. + if (const ParserAtom* tiny = wellKnownTable_.lookupTiny(latin1Ptr, length)) { + return tiny; + } + + // Check for well-known atom. + InflatedChar16Sequence<Latin1Char> seq(latin1Ptr, length); + SpecificParserAtomLookup<Latin1Char> lookup(seq); + if (const ParserAtom* wk = wellKnownTable_.lookupChar16Seq(lookup)) { + return wk; + } + + // Check for existing atom. + auto addPtr = entrySet_.lookupForAdd(lookup); + if (addPtr) { + return (*addPtr)->asAtom(); + } + + return internChar16Seq<Latin1Char>(cx, addPtr, lookup.hash(), seq, length); +} + +ParserAtomSpanBuilder::ParserAtomSpanBuilder(JSRuntime* rt, + ParserAtomSpan& entries) + : wellKnownTable_(*rt->commonParserNames), entries_(entries) {} + +bool ParserAtomSpanBuilder::allocate(JSContext* cx, LifoAlloc& alloc, + size_t count) { + if (count >= TaggedParserAtomIndex::IndexLimit) { + ReportAllocationOverflow(cx); + return false; + } + + auto* p = alloc.newArrayUninitialized<ParserAtomEntry*>(count); + if (!p) { + js::ReportOutOfMemory(cx); + return false; + } + std::uninitialized_fill_n(p, count, nullptr); + + entries_ = mozilla::Span(p, count); + return true; +} + +const ParserAtom* ParserAtomsTable::internUtf8(JSContext* cx, + const mozilla::Utf8Unit* utf8Ptr, + uint32_t nbyte) { + // Check for tiny strings which are abundant in minified code. + // NOTE: The tiny atoms are all ASCII-only so we can directly look at the + // UTF-8 data without worrying about surrogates. + if (const ParserAtom* tiny = wellKnownTable_.lookupTiny( + reinterpret_cast<const Latin1Char*>(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<const Latin1Char*>(utf8Ptr); + return internLatin1(cx, latin1Ptr, nbyte); + } + + // Check for existing. + // NOTE: Well-known are all ASCII so have been handled above. + InflatedChar16Sequence<mozilla::Utf8Unit> seq(utf8Ptr, nbyte); + SpecificParserAtomLookup<mozilla::Utf8Unit> lookup(seq); + MOZ_ASSERT(wellKnownTable_.lookupChar16Seq(lookup) == nullptr); + EntrySet::AddPtr addPtr = entrySet_.lookupForAdd(lookup); + if (addPtr) { + return (*addPtr)->asAtom(); + } + + // Compute length in code-points. + uint32_t length = 0; + InflatedChar16Sequence<mozilla::Utf8Unit> seqCopy = seq; + while (seqCopy.hasMore()) { + mozilla::Unused << seqCopy.next(); + length += 1; + } + + // Otherwise, add new entry. + bool wide = (minEncoding == JS::SmallestEncoding::UTF16); + return wide + ? internChar16Seq<char16_t>(cx, addPtr, lookup.hash(), seq, length) + : internChar16Seq<Latin1Char>(cx, addPtr, lookup.hash(), seq, + length); +} + +const ParserAtom* ParserAtomsTable::internChar16(JSContext* cx, + const char16_t* char16Ptr, + uint32_t length) { + // Check for tiny strings which are abundant in minified code. + if (const ParserAtom* tiny = wellKnownTable_.lookupTiny(char16Ptr, length)) { + return tiny; + } + + // Check against well-known. + InflatedChar16Sequence<char16_t> seq(char16Ptr, length); + SpecificParserAtomLookup<char16_t> lookup(seq); + if (const ParserAtom* wk = wellKnownTable_.lookupChar16Seq(lookup)) { + return wk; + } + + // Check for existing atom. + EntrySet::AddPtr addPtr = entrySet_.lookupForAdd(lookup); + if (addPtr) { + return (*addPtr)->asAtom(); + } + + // Compute the target encoding. + // NOTE: Length in code-points will be same, even if we deflate to Latin1. + bool wide = false; + InflatedChar16Sequence<char16_t> seqCopy = seq; + while (seqCopy.hasMore()) { + char16_t ch = seqCopy.next(); + if (ch > MAX_LATIN1_CHAR) { + wide = true; + break; + } + } + + // Otherwise, add new entry. + return wide + ? internChar16Seq<char16_t>(cx, addPtr, lookup.hash(), seq, length) + : internChar16Seq<Latin1Char>(cx, addPtr, lookup.hash(), seq, + length); +} + +const ParserAtom* ParserAtomsTable::internJSAtom(JSContext* cx, + CompilationStencil& stencil, + JSAtom* atom) { + const ParserAtom* parserAtom; + { + JS::AutoCheckCannotGC nogc; + + parserAtom = + atom->hasLatin1Chars() + ? internLatin1(cx, atom->latin1Chars(nogc), atom->length()) + : internChar16(cx, atom->twoByteChars(nogc), atom->length()); + if (!parserAtom) { + return nullptr; + } + } + + if (parserAtom->isParserAtomIndex()) { + ParserAtomIndex index = parserAtom->toParserAtomIndex(); + auto& atomCache = stencil.input.atomCache; + + if (!atomCache.hasAtomAt(index)) { + if (!atomCache.setAtomAt(cx, index, atom)) { + return nullptr; + } + } + } + + // We should (infallibly) map back to the same JSAtom. + MOZ_ASSERT(parserAtom->toJSAtom(cx, stencil.input.atomCache) == atom); + + return parserAtom; +} + +const ParserAtom* ParserAtomsTable::concatAtoms( + JSContext* cx, mozilla::Range<const ParserAtom*> atoms) { + MOZ_ASSERT(atoms.length() >= 2, + "concatAtoms should only be used for multiple inputs"); + + // Compute final length and encoding. + bool catLatin1 = true; + uint32_t catLen = 0; + for (const ParserAtom* atom : atoms) { + if (atom->hasTwoByteChars()) { + catLatin1 = false; + } + // Overflow check here, length + if (atom->length() >= (ParserAtomEntry::MAX_LENGTH - catLen)) { + js::ReportOutOfMemory(cx); + return nullptr; + } + catLen += atom->length(); + } + + // Short Latin1 strings must check for both Tiny and WellKnown atoms so simple + // concatenate onto stack and use `internLatin1`. + if (catLatin1 && (catLen <= WellKnownParserAtoms::MaxWellKnownLength)) { + Latin1Char buf[WellKnownParserAtoms::MaxWellKnownLength]; + size_t offset = 0; + for (const ParserAtom* atom : atoms) { + mozilla::PodCopy(buf + offset, atom->latin1Chars(), atom->length()); + offset += atom->length(); + } + return internLatin1(cx, buf, catLen); + } + + // NOTE: We have ruled out Tiny and WellKnown atoms and can ignore below. + + InflatedChar16Sequence<const ParserAtom*> seq(atoms); + SpecificParserAtomLookup<const ParserAtom*> lookup(seq); + + // Check for existing atom. + auto addPtr = entrySet_.lookupForAdd(lookup); + if (addPtr) { + return (*addPtr)->asAtom(); + } + + // Otherwise, add new entry. + return catLatin1 ? internChar16Seq<Latin1Char>(cx, addPtr, lookup.hash(), seq, + catLen) + : internChar16Seq<char16_t>(cx, addPtr, lookup.hash(), seq, + catLen); +} + +const ParserAtom* WellKnownParserAtoms::getWellKnown( + WellKnownAtomId atomId) const { +#define ASSERT_OFFSET_(idpart, id, text) \ + static_assert(offsetof(WellKnownParserAtoms, id) == \ + int32_t(WellKnownAtomId::id) * sizeof(ParserAtom*)); + FOR_EACH_COMMON_PROPERTYNAME(ASSERT_OFFSET_); +#undef ASSERT_OFFSET_ + +#define ASSERT_OFFSET_(name, clasp) \ + static_assert(offsetof(WellKnownParserAtoms, name) == \ + int32_t(WellKnownAtomId::name) * sizeof(ParserAtom*)); + JS_FOR_EACH_PROTOTYPE(ASSERT_OFFSET_); +#undef ASSERT_OFFSET_ + + static_assert(int32_t(WellKnownAtomId::abort) == 0, + "Unexpected order of WellKnownAtom"); + + return (&abort)[int32_t(atomId)]; +} + +/* static */ +const ParserAtom* WellKnownParserAtoms::getStatic1(StaticParserString1 s) { + return WellKnownParserAtoms::rom_.length1Table[size_t(s)].asAtom(); +} + +/* static */ +const ParserAtom* WellKnownParserAtoms::getStatic2(StaticParserString2 s) { + return WellKnownParserAtoms::rom_.length2Table[size_t(s)].asAtom(); +} + +const ParserAtom* ParserAtomSpanBuilder::getWellKnown( + WellKnownAtomId atomId) const { + return wellKnownTable_.getWellKnown(atomId); +} + +const ParserAtom* ParserAtomSpanBuilder::getStatic1( + StaticParserString1 s) const { + return WellKnownParserAtoms::getStatic1(s); +} + +const ParserAtom* ParserAtomSpanBuilder::getStatic2( + StaticParserString2 s) const { + return WellKnownParserAtoms::getStatic2(s); +} + +const ParserAtom* ParserAtomSpanBuilder::getParserAtom( + ParserAtomIndex index) const { + return entries_[index]->asAtom(); +} + +template <class T> +const ParserAtom* GetParserAtom(T self, TaggedParserAtomIndex index) { + if (index.isParserAtomIndex()) { + return self->getParserAtom(index.toParserAtomIndex()); + } + + if (index.isWellKnownAtomId()) { + return self->getWellKnown(index.toWellKnownAtomId()); + } + + if (index.isStaticParserString1()) { + return self->getStatic1(index.toStaticParserString1()); + } + + if (index.isStaticParserString2()) { + return self->getStatic2(index.toStaticParserString2()); + } + + MOZ_ASSERT(index.isNull()); + return nullptr; +} + +const ParserAtom* ParserAtomSpanBuilder::getParserAtom( + TaggedParserAtomIndex index) const { + return GetParserAtom(this, index); +} + +const ParserAtom* ParserAtomsTable::getWellKnown(WellKnownAtomId atomId) const { + return wellKnownTable_.getWellKnown(atomId); +} + +const ParserAtom* ParserAtomsTable::getStatic1(StaticParserString1 s) const { + return WellKnownParserAtoms::getStatic1(s); +} + +const ParserAtom* ParserAtomsTable::getStatic2(StaticParserString2 s) const { + return WellKnownParserAtoms::getStatic2(s); +} + +const ParserAtom* ParserAtomsTable::getParserAtom(ParserAtomIndex index) const { + return entries_[index]->asAtom(); +} + +const ParserAtom* ParserAtomsTable::getParserAtom( + TaggedParserAtomIndex index) const { + return GetParserAtom(this, index); +} + +bool InstantiateMarkedAtoms(JSContext* cx, const ParserAtomSpan& entries, + CompilationAtomCache& atomCache) { + for (const auto& entry : entries) { + if (!entry) { + continue; + } + if (entry->isUsedByStencil() && entry->isParserAtomIndex() && + !atomCache.hasAtomAt(entry->toParserAtomIndex())) { + if (!entry->instantiate(cx, atomCache)) { + return false; + } + } + } + return true; +} + +template <typename CharT> +const ParserAtom* WellKnownParserAtoms::lookupChar16Seq( + const SpecificParserAtomLookup<CharT>& lookup) const { + EntrySet::Ptr get = wellKnownSet_.readonlyThreadsafeLookup(lookup); + if (get) { + return (*get)->asAtom(); + } + return nullptr; +} + +bool WellKnownParserAtoms::initSingle(JSContext* cx, const ParserName** name, + const ParserAtomEntry& romEntry) { + MOZ_ASSERT(name != nullptr); + + unsigned int len = romEntry.length(); + const Latin1Char* str = romEntry.latin1Chars(); + + // Well-known atoms are all currently ASCII with length <= MaxWellKnownLength. + MOZ_ASSERT(len <= MaxWellKnownLength); + MOZ_ASSERT(romEntry.isAscii()); + + // Strings matched by lookupTiny are stored in static table and aliases should + // only be added using initTinyStringAlias. + MOZ_ASSERT(lookupTiny(str, len) == nullptr, + "Well-known atom matches a tiny StaticString. Did you add it to " + "the wrong CommonPropertyNames.h list?"); + + InflatedChar16Sequence<Latin1Char> seq(str, len); + SpecificParserAtomLookup<Latin1Char> lookup(seq, romEntry.hash()); + + // Save name for returning after moving entry into set. + if (!wellKnownSet_.putNew(lookup, &romEntry)) { + js::ReportOutOfMemory(cx); + return false; + } + + *name = romEntry.asName(); + return true; +} + +bool WellKnownParserAtoms::initTinyStringAlias(JSContext* cx, + const ParserName** name, + const char* str) { + MOZ_ASSERT(name != nullptr); + + unsigned int len = strlen(str); + + // Well-known atoms are all currently ASCII with length <= MaxWellKnownLength. + MOZ_ASSERT(len <= MaxWellKnownLength); + MOZ_ASSERT(FindSmallestEncoding(JS::UTF8Chars(str, len)) == + JS::SmallestEncoding::ASCII); + + // NOTE: If this assert fails, you may need to change which list is it belongs + // to in CommonPropertyNames.h. + const ParserAtom* tiny = lookupTiny(str, len); + MOZ_ASSERT(tiny, "Tiny common name was not found"); + + // Set alias to existing atom. + *name = tiny->asName(); + return true; +} + +bool WellKnownParserAtoms::init(JSContext* cx) { + // Tiny strings with a common name need a named alias to an entry in the + // WellKnownParserAtoms_ROM. +#define COMMON_NAME_INIT_(_, name, text) \ + if (!initTinyStringAlias(cx, &(name), text)) { \ + return false; \ + } + FOR_EACH_TINY_PROPERTYNAME(COMMON_NAME_INIT_) +#undef COMMON_NAME_INIT_ + + // Initialize the named fields to point to entries in the ROM. This also adds + // the atom to the lookup HashSet. The HashSet is used for dynamic lookups + // later and does not change once this init method is complete. +#define COMMON_NAME_INIT_(_, name, _2) \ + if (!initSingle(cx, &(name), rom_.name)) { \ + return false; \ + } + FOR_EACH_NONTINY_COMMON_PROPERTYNAME(COMMON_NAME_INIT_) +#undef COMMON_NAME_INIT_ +#define COMMON_NAME_INIT_(name, _) \ + if (!initSingle(cx, &(name), rom_.name)) { \ + return false; \ + } + JS_FOR_EACH_PROTOTYPE(COMMON_NAME_INIT_) +#undef COMMON_NAME_INIT_ + + return true; +} + +} /* namespace frontend */ +} /* namespace js */ + +// XDR code. +namespace js { + +template <XDRMode mode> +XDRResult XDRParserAtomEntry(XDRState<mode>* xdr, ParserAtomEntry** entryp) { + static_assert(CanCopyDataToDisk<ParserAtomEntry>::value, + "ParserAtomEntry cannot be bulk-copied to disk."); + + MOZ_TRY(xdr->align32()); + + const ParserAtomEntry* header; + if (mode == XDR_ENCODE) { + header = *entryp; + } else { + MOZ_TRY(xdr->peekData(&header)); + } + + const uint32_t CharSize = + header->hasLatin1Chars() ? sizeof(JS::Latin1Char) : sizeof(char16_t); + uint32_t totalLength = + sizeof(ParserAtomEntry) + (CharSize * header->length()); + + MOZ_TRY(xdr->borrowedData(entryp, totalLength)); + + return Ok(); +} + +template XDRResult XDRParserAtomEntry(XDRState<XDR_ENCODE>* xdr, + ParserAtomEntry** atomp); +template XDRResult XDRParserAtomEntry(XDRState<XDR_DECODE>* xdr, + ParserAtomEntry** atomp); + +} /* namespace js */ + +bool JSRuntime::initializeParserAtoms(JSContext* cx) { + MOZ_ASSERT(!commonParserNames); + + if (parentRuntime) { + commonParserNames = parentRuntime->commonParserNames; + return true; + } + + UniquePtr<js::frontend::WellKnownParserAtoms> names( + js_new<js::frontend::WellKnownParserAtoms>()); + if (!names || !names->init(cx)) { + return false; + } + + commonParserNames = names.release(); + return true; +} + +void JSRuntime::finishParserAtoms() { + if (!parentRuntime) { + js_delete(commonParserNames.ref()); + } +} diff --git a/js/src/frontend/ParserAtom.h b/js/src/frontend/ParserAtom.h new file mode 100644 index 0000000000..a1f0b2318f --- /dev/null +++ b/js/src/frontend/ParserAtom.h @@ -0,0 +1,810 @@ +/* -*- 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/DebugOnly.h" // mozilla::DebugOnly +#include "mozilla/HashFunctions.h" // HashString +#include "mozilla/Range.h" // mozilla::Range +#include "mozilla/Span.h" // mozilla::Span +#include "mozilla/Variant.h" // mozilla::Variant + +#include "ds/LifoAlloc.h" // LifoAlloc +#include "frontend/TypedIndex.h" // TypedIndex +#include "js/HashTable.h" // HashSet +#include "js/UniquePtr.h" // js::UniquePtr +#include "js/Vector.h" // Vector +#include "vm/CommonPropertyNames.h" +#include "vm/StringType.h" // CompareChars, StringEqualsAscii + +namespace js { +namespace frontend { + +struct CompilationAtomCache; +struct CompilationStencil; +class ParserAtom; +class ParserName; + +template <typename CharT> +class SpecificParserAtomLookup; + +class ParserAtomsTable; + +// An index to map WellKnownParserAtoms to cx->names(). +// This is consistent across multiple compilation. +// +// GetWellKnownAtom in ParserAtom.cpp relies on the fact that +// JSAtomState fields and this enum variants use the same order. +enum class WellKnownAtomId : uint32_t { +#define ENUM_ENTRY_(_, name, _2) name, + FOR_EACH_COMMON_PROPERTYNAME(ENUM_ENTRY_) +#undef ENUM_ENTRY_ + +#define ENUM_ENTRY_(name, _) name, + JS_FOR_EACH_PROTOTYPE(ENUM_ENTRY_) +#undef ENUM_ENTRY_ + Limit, +}; + +// These types correspond into indices in the StaticStrings arrays. +enum class StaticParserString1 : uint8_t; +enum class StaticParserString2 : uint16_t; + +class ParserAtom; +using ParserAtomIndex = TypedIndex<ParserAtom>; + +// ParserAtomIndex, WellKnownAtomId, StaticParserString1, StaticParserString2, +// or null. +// +// 0x0000_0000 Null atom +// +// 0x1YYY_YYYY 28-bit ParserAtom +// +// 0x2000_YYYY Well-known atom ID +// 0x2001_YYYY Static length-1 atom +// 0x2002_YYYY Static length-2 atom +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 Static1SubTag = 1 << SubTagShift; + static constexpr uint32_t Static2SubTag = 2 << SubTagShift; + + public: + static constexpr uint32_t IndexLimit = Bit(IndexBit); + static constexpr uint32_t SmallIndexLimit = Bit(SmallIndexBit); + + 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); + + // Static1/Static2 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(StaticParserString1 index) + : data_(uint32_t(index) | WellKnownTag | Static1SubTag) {} + explicit constexpr TaggedParserAtomIndex(StaticParserString2 index) + : data_(uint32_t(index) | WellKnownTag | Static2SubTag) {} + + static TaggedParserAtomIndex star() { + return TaggedParserAtomIndex(StaticParserString1('*')); + } + static TaggedParserAtomIndex arguments() { + return TaggedParserAtomIndex(WellKnownAtomId::arguments); + } + static TaggedParserAtomIndex dotThis() { + return TaggedParserAtomIndex(WellKnownAtomId::dotThis); + } + static TaggedParserAtomIndex dotGenerator() { + return TaggedParserAtomIndex(WellKnownAtomId::dotGenerator); + } + 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 isStaticParserString1() const { + return (data_ & (TagMask | SubTagMask)) == (WellKnownTag | Static1SubTag); + } + bool isStaticParserString2() const { + return (data_ & (TagMask | SubTagMask)) == (WellKnownTag | Static2SubTag); + } + bool isNull() const { + bool result = !data_; + MOZ_ASSERT_IF(result, (data_ & TagMask) == NullTag); + return result; + } + + ParserAtomIndex toParserAtomIndex() const { + MOZ_ASSERT(isParserAtomIndex()); + return ParserAtomIndex(data_ & IndexMask); + } + WellKnownAtomId toWellKnownAtomId() const { + MOZ_ASSERT(isWellKnownAtomId()); + return WellKnownAtomId(data_ & SmallIndexMask); + } + StaticParserString1 toStaticParserString1() const { + MOZ_ASSERT(isStaticParserString1()); + return StaticParserString1(data_ & SmallIndexMask); + } + StaticParserString2 toStaticParserString2() const { + MOZ_ASSERT(isStaticParserString2()); + return StaticParserString2(data_ & SmallIndexMask); + } + + uint32_t* rawData() { return &data_; } + + bool operator==(const TaggedParserAtomIndex& rhs) const { + return data_ == rhs.data_; + } + + explicit operator bool() const { return !isNull(); } +}; + +/** + * A ParserAtomEntry 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 ParserAtomEntry memory). + * 2. Inline char16_t storage (immediately after the ParserAtomEntry memory). + */ +class alignas(alignof(uint32_t)) ParserAtomEntry { + friend class ParserAtomsTable; + friend class WellKnownParserAtoms; + friend class WellKnownParserAtoms_ROM; + + 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; + + // 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 <typename CharT, typename SeqCharT> + static void drainChar16Seq(CharT* buf, InflatedChar16Sequence<SeqCharT> seq, + uint32_t length) { + static_assert( + std::is_same_v<CharT, char16_t> || std::is_same_v<CharT, Latin1Char>, + "Invalid target buffer type."); + CharT* cur = buf; + while (seq.hasMore()) { + char16_t ch = seq.next(); + if constexpr (std::is_same_v<CharT, Latin1Char>) { + 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; + + TaggedParserAtomIndex index_; + + uint32_t flags_ = 0; + + // End of fields. + + static const uint32_t MAX_LENGTH = JSString::MAX_LENGTH; + + ParserAtomEntry(uint32_t length, HashNumber hash, bool hasTwoByteChars) + : hash_(hash), + length_(length), + flags_(hasTwoByteChars ? HasTwoByteCharsFlag : 0) {} + + public: + // The constexpr constructor is used by StaticParserAtomEntry and XDR + constexpr ParserAtomEntry() = default; + + // ParserAtomEntries may own their content buffers in variant_, and thus + // cannot be copy-constructed - as a new chars would need to be allocated. + ParserAtomEntry(const ParserAtomEntry&) = delete; + ParserAtomEntry(ParserAtomEntry&& other) = delete; + + template <typename CharT, typename SeqCharT> + static ParserAtomEntry* allocate(JSContext* cx, LifoAlloc& alloc, + InflatedChar16Sequence<SeqCharT> seq, + uint32_t length, HashNumber hash); + + static ParserAtomEntry* allocateRaw(JSContext* cx, LifoAlloc& alloc, + const uint8_t* srcRaw, + size_t totalLength); + + ParserAtom* asAtom() { return reinterpret_cast<ParserAtom*>(this); } + const ParserAtom* asAtom() const { + return reinterpret_cast<const ParserAtom*>(this); + } + + inline ParserName* asName(); + inline const ParserName* asName() const; + + bool hasLatin1Chars() const { return !(flags_ & HasTwoByteCharsFlag); } + bool hasTwoByteChars() const { return flags_ & HasTwoByteCharsFlag; } + + template <typename CharT> + const CharT* chars() const { + MOZ_ASSERT(sizeof(CharT) == (hasTwoByteChars() ? 2 : 1)); + return reinterpret_cast<const CharT*>(this + 1); + } + + template <typename CharT> + CharT* chars() { + MOZ_ASSERT(sizeof(CharT) == (hasTwoByteChars() ? 2 : 1)); + return reinterpret_cast<CharT*>(this + 1); + } + + const Latin1Char* latin1Chars() const { return chars<Latin1Char>(); } + const char16_t* twoByteChars() const { return chars<char16_t>(); } + mozilla::Range<const Latin1Char> latin1Range() const { + return mozilla::Range(latin1Chars(), length_); + } + mozilla::Range<const char16_t> twoByteRange() const { + return mozilla::Range(twoByteChars(), length_); + } + + bool isIndex(uint32_t* indexp) const; + bool isIndex() const { + uint32_t index; + return isIndex(&index); + } + + bool isAscii() const { + if (hasTwoByteChars()) { + return false; + } + for (Latin1Char ch : latin1Range()) { + if (!mozilla::IsAscii(ch)) { + return false; + } + } + return true; + } + + HashNumber hash() const { return hash_; } + uint32_t length() const { return length_; } + + bool isUsedByStencil() const { return flags_ & UsedByStencilFlag; } + void markUsedByStencil() const { + if (isParserAtomIndex()) { + // Use const method + const_cast here to avoid marking static strings' + // field mutable. + const_cast<ParserAtomEntry*>(this)->flags_ |= UsedByStencilFlag; + } + } + + bool equalsJSAtom(JSAtom* other) const; + + template <typename CharT> + bool equalsSeq(HashNumber hash, InflatedChar16Sequence<CharT> seq) const; + + TaggedParserAtomIndex toIndex() const { return index_; } + + ParserAtomIndex toParserAtomIndex() const { + return index_.toParserAtomIndex(); + } + WellKnownAtomId toWellKnownAtomId() const { + return index_.toWellKnownAtomId(); + } + StaticParserString1 toStaticParserString1() const { + return index_.toStaticParserString1(); + } + StaticParserString2 toStaticParserString2() const { + return index_.toStaticParserString2(); + } + + bool isParserAtomIndex() const { return index_.isParserAtomIndex(); } + bool isWellKnownAtomId() const { return index_.isWellKnownAtomId(); } + bool isStaticParserString1() const { return index_.isStaticParserString1(); } + bool isStaticParserString2() const { return index_.isStaticParserString2(); } + + void setParserAtomIndex(ParserAtomIndex index) { + index_ = TaggedParserAtomIndex(index); + } + + private: + constexpr void setWellKnownAtomId(WellKnownAtomId atomId) { + index_ = TaggedParserAtomIndex(atomId); + } + constexpr void setStaticParserString1(StaticParserString1 s) { + index_ = TaggedParserAtomIndex(s); + } + constexpr void setStaticParserString2(StaticParserString2 s) { + index_ = TaggedParserAtomIndex(s); + } + constexpr void setHashAndLength(HashNumber hash, uint32_t length) { + hash_ = hash; + length_ = length; + } + + public: + // Convert this entry to a js-atom. The first time this method is called + // the entry will cache the JSAtom pointer to return later. + JSAtom* toJSAtom(JSContext* cx, CompilationAtomCache& atomCache) const; + + // Same as toJSAtom, but this is guaranteed to be instantiated. + JSAtom* toExistingJSAtom(JSContext* cx, + CompilationAtomCache& atomCache) const; + + // Convert NotInstantiated and usedByStencil entry to a js-atom. + JSAtom* instantiate(JSContext* cx, CompilationAtomCache& atomCache) const; + + // Convert this entry to a number. + bool toNumber(JSContext* cx, double* result) const; + +#if defined(DEBUG) || defined(JS_JITSPEW) + void dump() const; + void dumpCharsNoQuote(js::GenericPrinter& out) const; +#endif +}; + +class ParserAtom : public ParserAtomEntry { + ParserAtom() = delete; + ParserAtom(const ParserAtom&) = delete; +}; + +class ParserName : public ParserAtom { + ParserName() = delete; + ParserName(const ParserName&) = delete; +}; + +UniqueChars ParserAtomToPrintableString(JSContext* cx, const ParserAtom* atom); + +inline ParserName* ParserAtomEntry::asName() { + MOZ_ASSERT(!isIndex()); + return static_cast<ParserName*>(this); +} +inline const ParserName* ParserAtomEntry::asName() const { + MOZ_ASSERT(!isIndex()); + return static_cast<const ParserName*>(this); +} + +// A ParserAtomEntry with explicit inline storage. This is compatible with +// constexpr to have builtin atoms. Care must be taken to ensure these atoms are +// unique. +template <size_t Length> +class StaticParserAtomEntry : public ParserAtomEntry { + alignas(alignof(ParserAtomEntry)) char storage_[Length] = {}; + + public: + constexpr StaticParserAtomEntry() = default; + + constexpr char* storage() { + static_assert( + offsetof(StaticParserAtomEntry, storage_) == sizeof(ParserAtomEntry), + "StaticParserAtomEntry storage should follow ParserAtomEntry"); + return storage_; + } +}; + +template <> +class StaticParserAtomEntry<0> : public ParserAtomEntry { + public: + constexpr StaticParserAtomEntry() = default; +}; + +/** + * 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 ParserAtomEntry* entry) const = 0; +}; + +struct ParserAtomLookupHasher { + using Lookup = ParserAtomLookup; + + static inline HashNumber hash(const Lookup& l) { return l.hash(); } + static inline bool match(const ParserAtomEntry* entry, const Lookup& l) { + return l.equalsEntry(entry); + } +}; + +// We use this class to build a read-only constexpr table of ParserAtoms for the +// well-known atoms set. This should be resolved at compile-time (including hash +// computation) thanks to C++ constexpr. +class WellKnownParserAtoms_ROM { + // NOTE: While the well-known strings are all Latin1, we must use char16_t in + // some places in order to have constexpr mozilla::HashString. + using CharTraits = std::char_traits<char>; + using Char16Traits = std::char_traits<char16_t>; + + public: + static const size_t ASCII_STATIC_LIMIT = 128U; + static const size_t NUM_SMALL_CHARS = StaticStrings::NUM_SMALL_CHARS; + static const size_t NUM_LENGTH2_ENTRIES = NUM_SMALL_CHARS * NUM_SMALL_CHARS; + + StaticParserAtomEntry<0> emptyAtom; + StaticParserAtomEntry<1> length1Table[ASCII_STATIC_LIMIT]; + StaticParserAtomEntry<2> length2Table[NUM_LENGTH2_ENTRIES]; + +#define PROPERTYNAME_FIELD_(_, name, text) \ + StaticParserAtomEntry<CharTraits::length(text)> name; + FOR_EACH_NONTINY_COMMON_PROPERTYNAME(PROPERTYNAME_FIELD_) +#undef PROPERTYNAME_FIELD_ + +#define PROPERTYNAME_FIELD_(name, _) \ + StaticParserAtomEntry<CharTraits::length(#name)> name; + JS_FOR_EACH_PROTOTYPE(PROPERTYNAME_FIELD_) +#undef PROPERTYNAME_FIELD_ + + public: + constexpr WellKnownParserAtoms_ROM() { + // Empty atom + emptyAtom.setHashAndLength(mozilla::HashString(u""), 0); + emptyAtom.setWellKnownAtomId(WellKnownAtomId::empty); + + // Length-1 static atoms + for (size_t i = 0; i < ASCII_STATIC_LIMIT; ++i) { + init(length1Table[i], i); + } + + // Length-2 static atoms + for (size_t i = 0; i < NUM_LENGTH2_ENTRIES; ++i) { + init(length2Table[i], i); + } + + // Initialize each well-known property atoms +#define PROPERTYNAME_FIELD_(_, name, text) \ + init(name, name.storage(), u"" text, WellKnownAtomId::name); + FOR_EACH_NONTINY_COMMON_PROPERTYNAME(PROPERTYNAME_FIELD_) +#undef PROPERTYNAME_FIELD_ + + // Initialize each well-known prototype atoms +#define PROPERTYNAME_FIELD_(name, _) \ + init(name, name.storage(), u"" #name, WellKnownAtomId::name); + JS_FOR_EACH_PROTOTYPE(PROPERTYNAME_FIELD_) +#undef PROPERTYNAME_FIELD_ + } + + private: + // Initialization moved out of the constructor to workaround bug 1668238. + static constexpr void init(StaticParserAtomEntry<1>& entry, size_t i) { + size_t len = 1; + char16_t buf[] = {static_cast<char16_t>(i), + /* null-terminator */ 0}; + entry.setHashAndLength(mozilla::HashString(buf), len); + entry.setStaticParserString1(StaticParserString1(i)); + entry.storage()[0] = buf[0]; + } + + static constexpr void init(StaticParserAtomEntry<2>& entry, size_t i) { + size_t len = 2; + char16_t buf[] = {StaticStrings::fromSmallChar(i >> 6), + StaticStrings::fromSmallChar(i & 0x003F), + /* null-terminator */ 0}; + entry.setHashAndLength(mozilla::HashString(buf), len); + entry.setStaticParserString2(StaticParserString2(i)); + entry.storage()[0] = buf[0]; + entry.storage()[1] = buf[1]; + } + + static constexpr void init(ParserAtomEntry& entry, char* storage, + const char16_t* text, WellKnownAtomId id) { + size_t len = Char16Traits::length(text); + entry.setHashAndLength(mozilla::HashString(text), len); + entry.setWellKnownAtomId(id); + for (size_t i = 0; i < len; ++i) { + storage[i] = text[i]; + } + } + + public: + // Fast-path tiny strings since they are abundant in minified code. + template <typename CharsT> + const ParserAtom* lookupTiny(CharsT chars, size_t length) const { + static_assert(std::is_same_v<CharsT, const Latin1Char*> || + std::is_same_v<CharsT, const char16_t*> || + std::is_same_v<CharsT, const char*> || + std::is_same_v<CharsT, char16_t*> || + std::is_same_v<CharsT, LittleEndianChars>, + "This assert mostly explicitly documents the calling types, " + "and forces that to be updated if new types show up."); + switch (length) { + case 0: + return emptyAtom.asAtom(); + + case 1: { + if (char16_t(chars[0]) < ASCII_STATIC_LIMIT) { + size_t index = static_cast<size_t>(chars[0]); + return length1Table[index].asAtom(); + } + break; + } + + case 2: + if (StaticStrings::fitsInSmallChar(chars[0]) && + StaticStrings::fitsInSmallChar(chars[1])) { + size_t index = StaticStrings::getLength2Index(chars[0], chars[1]); + return length2Table[index].asAtom(); + } + break; + } + + // No match on tiny Atoms + return nullptr; + } +}; + +using ParserAtomVector = Vector<ParserAtomEntry*, 0, js::SystemAllocPolicy>; +using ParserAtomSpan = mozilla::Span<ParserAtomEntry*>; + +/** + * WellKnownParserAtoms reserves a set of common ParserAtoms on the JSRuntime + * in a read-only format to be used by parser. These reserved atoms can be + * translated to equivalent JSAtoms in constant time. + * + * The common-names set allows the parser to lookup up specific atoms in + * constant time. + * + * We also reserve tiny (length 1/2) parser-atoms for fast lookup similar to + * the js::StaticStrings mechanism. This speeds up parsing minified code. + */ +class WellKnownParserAtoms { + public: + // Named fields allow quickly finding an atom if it is known at compile time. + // This is particularly useful for the Parser. +#define PROPERTYNAME_FIELD_(_, name, _2) const ParserName* name{}; + FOR_EACH_COMMON_PROPERTYNAME(PROPERTYNAME_FIELD_) +#undef PROPERTYNAME_FIELD_ + +#define PROPERTYNAME_FIELD_(name, _) const ParserName* name{}; + JS_FOR_EACH_PROTOTYPE(PROPERTYNAME_FIELD_) +#undef PROPERTYNAME_FIELD_ + + // The ParserAtomEntry of all well-known and tiny ParserAtoms are generated at + // compile-time into a ROM that is computed using constexpr. This results in + // the data being in the .rodata section of binary and easily shared by + // multiple JS processes. + static constexpr WellKnownParserAtoms_ROM rom_ = {}; + + // 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 EntrySet = HashSet<const ParserAtomEntry*, ParserAtomLookupHasher, + js::SystemAllocPolicy>; + EntrySet wellKnownSet_; + + bool initTinyStringAlias(JSContext* cx, const ParserName** name, + const char* str); + bool initSingle(JSContext* cx, const ParserName** name, + const ParserAtomEntry& romEntry); + + public: + bool init(JSContext* cx); + + // Maximum length of any well known atoms. This can be increased if needed. + static constexpr size_t MaxWellKnownLength = 32; + + template <typename CharT> + const ParserAtom* lookupChar16Seq( + const SpecificParserAtomLookup<CharT>& lookup) const; + + template <typename CharsT> + const ParserAtom* lookupTiny(CharsT chars, size_t length) const { + return rom_.lookupTiny(chars, length); + } + + const ParserAtom* getWellKnown(WellKnownAtomId atomId) const; + static const ParserAtom* getStatic1(StaticParserString1 s); + static const ParserAtom* getStatic2(StaticParserString2 s); +}; + +bool InstantiateMarkedAtoms(JSContext* cx, const ParserAtomSpan& entries, + CompilationAtomCache& atomCache); + +/** + * A ParserAtomsTable owns and manages the vector of ParserAtom entries + * associated with a given compile session. + */ +class ParserAtomsTable { + private: + const WellKnownParserAtoms& wellKnownTable_; + + LifoAlloc& alloc_; + + // The ParserAtomEntry are owned by the LifoAlloc. + using EntrySet = HashSet<const ParserAtomEntry*, ParserAtomLookupHasher, + js::SystemAllocPolicy>; + EntrySet entrySet_; + ParserAtomVector entries_; + + public: + ParserAtomsTable(JSRuntime* rt, LifoAlloc& alloc); + ParserAtomsTable(ParserAtomsTable&&) = default; + + private: + // Internal APIs for interning to the table after well-known atoms cases have + // been tested. + const ParserAtom* addEntry(JSContext* cx, EntrySet::AddPtr& addPtr, + ParserAtomEntry* entry); + template <typename AtomCharT, typename SeqCharT> + const ParserAtom* internChar16Seq(JSContext* cx, EntrySet::AddPtr& addPtr, + HashNumber hash, + InflatedChar16Sequence<SeqCharT> seq, + uint32_t length); + + public: + const ParserAtom* internAscii(JSContext* cx, const char* asciiPtr, + uint32_t length); + + const ParserAtom* internLatin1(JSContext* cx, const JS::Latin1Char* latin1Ptr, + uint32_t length); + + const ParserAtom* internUtf8(JSContext* cx, const mozilla::Utf8Unit* utf8Ptr, + uint32_t nbyte); + + const ParserAtom* internChar16(JSContext* cx, const char16_t* char16Ptr, + uint32_t length); + + const ParserAtom* internJSAtom(JSContext* cx, CompilationStencil& stencil, + JSAtom* atom); + + const ParserAtom* concatAtoms(JSContext* cx, + mozilla::Range<const ParserAtom*> atoms); + + const ParserAtom* getWellKnown(WellKnownAtomId atomId) const; + const ParserAtom* getStatic1(StaticParserString1 s) const; + const ParserAtom* getStatic2(StaticParserString2 s) const; + const ParserAtom* getParserAtom(ParserAtomIndex index) const; + const ParserAtom* getParserAtom(TaggedParserAtomIndex index) const; + + const ParserAtomVector& entries() const { return entries_; } +}; + +// Lightweight version of ParserAtomsTable. +// This doesn't support deduplication. +// Used while decoding XDR. +class ParserAtomSpanBuilder { + private: + const WellKnownParserAtoms& wellKnownTable_; + ParserAtomSpan& entries_; + + public: + ParserAtomSpanBuilder(JSRuntime* rt, ParserAtomSpan& entries); + + bool allocate(JSContext* cx, LifoAlloc& alloc, size_t count); + size_t size() const { return entries_.size(); } + + void set(ParserAtomIndex index, const ParserAtomEntry* atom) { + entries_[index] = const_cast<ParserAtomEntry*>(atom); + } + + public: + const ParserAtom* getWellKnown(WellKnownAtomId atomId) const; + const ParserAtom* getStatic1(StaticParserString1 s) const; + const ParserAtom* getStatic2(StaticParserString2 s) const; + const ParserAtom* getParserAtom(ParserAtomIndex index) const; + const ParserAtom* getParserAtom(TaggedParserAtomIndex index) const; +}; + +template <typename CharT> +class SpecificParserAtomLookup : public ParserAtomLookup { + // The sequence of characters to look up. + InflatedChar16Sequence<CharT> seq_; + + public: + explicit SpecificParserAtomLookup(const InflatedChar16Sequence<CharT>& seq) + : SpecificParserAtomLookup(seq, seq.computeHash()) {} + + SpecificParserAtomLookup(const InflatedChar16Sequence<CharT>& seq, + HashNumber hash) + : ParserAtomLookup(hash), seq_(seq) { + MOZ_ASSERT(seq_.computeHash() == hash); + } + + virtual bool equalsEntry(const ParserAtomEntry* entry) const override { + return entry->equalsSeq<CharT>(hash_, seq_); + } +}; + +template <typename CharT> +inline bool ParserAtomEntry::equalsSeq( + HashNumber hash, InflatedChar16Sequence<CharT> 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/PropOpEmitter.cpp b/js/src/frontend/PropOpEmitter.cpp new file mode 100644 index 0000000000..44f9b5ad3d --- /dev/null +++ b/js/src/frontend/PropOpEmitter.cpp @@ -0,0 +1,243 @@ +/* -*- 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/SharedContext.h" +#include "vm/Opcodes.h" +#include "vm/StringType.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(const ParserAtom* prop) { + return bce_->makeAtomIndex(prop, &propAtomIndex_); +} + +bool PropOpEmitter::prepareForObj() { + MOZ_ASSERT(state_ == State::Start); + +#ifdef DEBUG + state_ = State::Obj; +#endif + return true; +} + +bool PropOpEmitter::emitGet(const ParserAtom* 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_, ShouldInstrument::Yes)) { + // [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(const ParserAtom* 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(const ParserAtom* 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_, ShouldInstrument::Yes)) { + // [stack] VAL + return false; + } + +#ifdef DEBUG + state_ = State::Assignment; +#endif + return true; +} + +bool PropOpEmitter::emitIncDec(const ParserAtom* prop) { + 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()) { + // [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_, ShouldInstrument::Yes)) { + // [stack] N? N+1 + return false; + } + if (isPostIncDec()) { + 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..a17c23da14 --- /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 <stdint.h> + +#include "js/TypeDecls.h" +#include "vm/SharedStencil.h" // GCThingIndex + +namespace js { +namespace frontend { + +struct BytecodeEmitter; +class ParserAtom; + +// 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, + Set, + 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: + MOZ_MUST_USE bool isCall() const { return kind_ == Kind::Call; } + + MOZ_MUST_USE bool isSuper() const { return objKind_ == ObjKind::Super; } + + MOZ_MUST_USE bool isSimpleAssignment() const { + return kind_ == Kind::SimpleAssignment; + } + + MOZ_MUST_USE bool isPropInit() const { return kind_ == Kind::PropInit; } + + MOZ_MUST_USE bool isDelete() const { return kind_ == Kind::Delete; } + + MOZ_MUST_USE bool isCompoundAssignment() const { + return kind_ == Kind::CompoundAssignment; + } + + MOZ_MUST_USE bool isIncDec() const { return isPostIncDec() || isPreIncDec(); } + + MOZ_MUST_USE bool isPostIncDec() const { + return kind_ == Kind::PostIncrement || kind_ == Kind::PostDecrement; + } + + MOZ_MUST_USE bool isPreIncDec() const { + return kind_ == Kind::PreIncrement || kind_ == Kind::PreDecrement; + } + + MOZ_MUST_USE bool isInc() const { + return kind_ == Kind::PostIncrement || kind_ == Kind::PreIncrement; + } + + MOZ_MUST_USE bool prepareAtomIndex(const ParserAtom* prop); + + public: + MOZ_MUST_USE bool prepareForObj(); + + MOZ_MUST_USE bool emitGet(const ParserAtom* prop); + + MOZ_MUST_USE bool prepareForRhs(); + MOZ_MUST_USE bool skipObjAndRhs(); + + MOZ_MUST_USE bool emitDelete(const ParserAtom* prop); + + // `prop` can be nullptr for CompoundAssignment. + MOZ_MUST_USE bool emitAssignment(const ParserAtom* prop); + + MOZ_MUST_USE bool emitIncDec(const ParserAtom* prop); +}; + +} /* 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..d0e234d7f7 --- /dev/null +++ b/js/src/frontend/ReservedWords.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/. */ + +/* 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) \ + 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(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) \ + /* \ + * Yield is a token inside function*. Outside of a function*, it is a \ + * future reserved word in strict mode, but a keyword in JS1.7 even \ + * when strict. Punt logic to parser. \ + */ \ + MACRO(yield, yield, TokenKind::Yield) + +#endif /* vm_ReservedWords_h */ diff --git a/js/src/frontend/ScriptIndex.h b/js/src/frontend/ScriptIndex.h new file mode 100644 index 0000000000..1cd3acbcb7 --- /dev/null +++ b/js/src/frontend/ScriptIndex.h @@ -0,0 +1,22 @@ +/* -*- 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 "frontend/TypedIndex.h" // TypedIndex + +namespace js { +namespace frontend { + +class ScriptStencil; + +using ScriptIndex = TypedIndex<ScriptStencil>; + +} /* namespace frontend */ +} /* namespace js */ + +#endif /* frontend_ScriptIndex_h */ diff --git a/js/src/frontend/SharedContext-inl.h b/js/src/frontend/SharedContext-inl.h new file mode 100644 index 0000000000..e1658c23b6 --- /dev/null +++ b/js/src/frontend/SharedContext-inl.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_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()) {} + +inline JSAtom* SharedContext::liftParserAtomToJSAtom(JSContext* cx, + const ParserAtom* atomId) { + return atomId->toJSAtom(cx, stencil_.input.atomCache); +} + +} // 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..4ebbd03361 --- /dev/null +++ b/js/src/frontend/SharedContext.cpp @@ -0,0 +1,492 @@ +/* -*- 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/AbstractScopePtr.h" +#include "frontend/FunctionSyntaxKind.h" // FunctionSyntaxKind +#include "frontend/ModuleSharedContext.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 "wasm/AsmJS.h" +#include "wasm/WasmModule.h" + +#include "frontend/ParseContext-inl.h" +#include "vm/EnvironmentObject-inl.h" + +namespace js { +namespace frontend { + +SharedContext::SharedContext(JSContext* cx, Kind kind, + CompilationStencil& stencil, Directives directives, + SourceExtent extent) + : cx_(cx), + stencil_(stencil), + extent_(extent), + allowNewTarget_(false), + allowSuperProperty_(false), + allowSuperCall_(false), + allowArguments_(true), + inWith_(false), + inClass_(false), + localStrict(false), + hasExplicitUseStrict_(false), + isScriptFieldCopiedToStencil(false) { + // Compute the script kind "input" flags. + if (kind == Kind::FunctionBox) { + setFlag(ImmutableFlags::IsFunction); + } else if (kind == Kind::Module) { + MOZ_ASSERT(!stencil.input.options.nonSyntacticScope); + setFlag(ImmutableFlags::IsModule); + } else if (kind == Kind::Eval) { + setFlag(ImmutableFlags::IsForEval); + } else { + MOZ_ASSERT(kind == Kind::Global); + } + + // Note: This is a mix of transitive and non-transitive options. + const JS::ReadOnlyCompileOptions& options = stencil.input.options; + + // 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()); +} + +void ScopeContext::computeThisEnvironment(Scope* scope) { + uint32_t envCount = 0; + for (ScopeIter si(scope); si; si++) { + if (si.kind() == ScopeKind::Function) { + JSFunction* fun = si.scope()->as<FunctionScope>().canonicalFunction(); + + // Arrow function inherit the "this" environment of the enclosing script, + // so continue ignore them. + if (!fun->isArrow()) { + allowNewTarget = true; + + if (fun->allowSuperProperty()) { + allowSuperProperty = true; + enclosingThisEnvironmentHops = envCount; + } + + if (fun->isClassConstructor()) { + memberInitializers = + mozilla::Some(fun->baseScript()->getMemberInitializers()); + MOZ_ASSERT(memberInitializers->valid); + } + + if (fun->isDerivedClassConstructor()) { + allowSuperCall = true; + } + + if (fun->isFieldInitializer()) { + allowArguments = false; + } + + // Found the effective "this" environment, so stop. + return; + } + } + + if (si.scope()->hasEnvironment()) { + envCount++; + } + } +} + +void ScopeContext::computeThisBinding(Scope* scope) { + // Inspect the scope-chain. + for (ScopeIter si(scope); si; si++) { + if (si.kind() == ScopeKind::Module) { + thisBinding = ThisBinding::Module; + return; + } + + if (si.kind() == ScopeKind::Function) { + JSFunction* fun = si.scope()->as<FunctionScope>().canonicalFunction(); + + // Arrow functions don't have their own `this` binding. + if (fun->isArrow()) { + continue; + } + + // Derived class constructors (and their nested arrow functions and evals) + // use ThisBinding::DerivedConstructor, which ensures TDZ checks happen + // when accessing |this|. + if (fun->isDerivedClassConstructor()) { + thisBinding = ThisBinding::DerivedConstructor; + } else { + thisBinding = ThisBinding::Function; + } + + return; + } + } + + thisBinding = ThisBinding::Global; +} + +void ScopeContext::computeInScope(Scope* scope) { + for (ScopeIter si(scope); si; si++) { + if (si.kind() == ScopeKind::ClassBody) { + inClass = true; + } + + if (si.kind() == ScopeKind::With) { + inWith = true; + } + } +} + +/* static */ +Scope* ScopeContext::determineEffectiveScope(Scope* scope, + JSObject* environment) { + // 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<DebugEnvironmentProxy>()) { + unwrapped = &env->as<DebugEnvironmentProxy>().environment(); + } + + if (unwrapped->is<CallObject>()) { + JSFunction* callee = &unwrapped->as<CallObject>().callee(); + return callee->nonLazyScript()->bodyScope(); + } + + env = env->enclosingEnvironment(); + } + } + + return scope; +} + +GlobalSharedContext::GlobalSharedContext(JSContext* cx, ScopeKind scopeKind, + CompilationStencil& stencil, + Directives directives, + SourceExtent extent) + : SharedContext(cx, Kind::Global, stencil, directives, extent), + scopeKind_(scopeKind), + bindings(nullptr) { + MOZ_ASSERT(scopeKind == ScopeKind::Global || + scopeKind == ScopeKind::NonSyntactic); + MOZ_ASSERT(thisBinding_ == ThisBinding::Global); +} + +EvalSharedContext::EvalSharedContext(JSContext* cx, CompilationStencil& stencil, + CompilationState& compilationState, + SourceExtent extent) + : SharedContext(cx, Kind::Eval, stencil, 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(JSContext* cx, Kind kind, + CompilationStencil& stencil, + Directives directives, + SourceExtent extent, bool isGenerator, + bool isAsync) + : SharedContext(cx, kind, stencil, directives, extent) { + setFlag(ImmutableFlags::IsGenerator, isGenerator); + setFlag(ImmutableFlags::IsAsync, isAsync); +} + +FunctionBox::FunctionBox(JSContext* cx, SourceExtent extent, + CompilationStencil& stencil, + CompilationState& compilationState, + Directives directives, GeneratorKind generatorKind, + FunctionAsyncKind asyncKind, const ParserAtom* atom, + FunctionFlags flags, ScriptIndex index) + : SuspendableContext(cx, Kind::FunctionBox, stencil, directives, extent, + generatorKind == GeneratorKind::Generator, + asyncKind == FunctionAsyncKind::AsyncFunction), + compilationState_(compilationState), + atom_(atom), + funcDataIndex_(index), + flags_(FunctionFlags::clearMutableflags(flags)), + emitBytecode(false), + wasEmitted_(false), + isAnnexB(false), + useAsm(false), + hasParameterExprs(false), + hasDestructuringArgs(false), + hasDuplicateParameters(false), + hasExprBody_(false), + isFunctionFieldCopiedToStencil(false) {} + +void FunctionBox::initFromLazyFunction(JSFunction* fun) { + BaseScript* lazy = fun->baseScript(); + immutableFlags_ = lazy->immutableFlags(); + extent_ = lazy->extent(); +} + +void FunctionBox::initWithEnclosingParseContext(ParseContext* enclosing, + FunctionFlags flags, + 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)) { + auto stmt = + enclosing->findInnermostStatement<ParseContext::ClassStatement>(); + MOZ_ASSERT(stmt); + stmt->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) { + setFieldInitializer(); + allowArguments_ = 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, + FunctionFlags flags, 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) { + setFieldInitializer(); + 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 (!stencil_.asmJS.putNew(index(), module)) { + js::ReportOutOfMemory(cx_); + return false; + } + return true; +} + +ModuleSharedContext::ModuleSharedContext(JSContext* cx, + CompilationStencil& stencil, + ModuleBuilder& builder, + SourceExtent extent) + : SuspendableContext(cx, Kind::Module, stencil, 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_]; +} + +bool FunctionBox::hasFunctionExtraStencil() const { + return funcDataIndex_ < compilationState_.scriptExtra.length(); +} + +void SharedContext::copyScriptFields(ScriptStencil& script) { + MOZ_ASSERT(!isScriptFieldCopiedToStencil); + isScriptFieldCopiedToStencil = true; +} + +void SharedContext::copyScriptExtraFields(ScriptStencilExtra& scriptExtra) { + scriptExtra.immutableFlags = immutableFlags_; + scriptExtra.extent = extent_; +} + +void FunctionBox::finishScriptFlags() { + MOZ_ASSERT(!isScriptFieldCopiedToStencil); + + using ImmutableFlags = ImmutableScriptFlagsEnum; + immutableFlags_.setFlag(ImmutableFlags::HasMappedArgsObj, hasMappedArgsObj()); +} + +void FunctionBox::copyScriptFields(ScriptStencil& script) { + MOZ_ASSERT(&script == &functionStencil()); + + SharedContext::copyScriptFields(script); + + if (memberInitializers_) { + script.setMemberInitializers(*memberInitializers_); + } + + isScriptFieldCopiedToStencil = true; +} + +void FunctionBox::copyFunctionFields(ScriptStencil& script) { + MOZ_ASSERT(&script == &functionStencil()); + MOZ_ASSERT(!isFunctionFieldCopiedToStencil); + + if (atom_) { + atom_->markUsedByStencil(); + script.functionAtom = atom_->toIndex(); + } + script.functionFlags = flags_; + if (enclosingScopeIndex_) { + script.setLazyFunctionEnclosingScopeIndex(*enclosingScopeIndex_); + } + if (wasEmitted_) { + script.setWasFunctionEmitted(); + } + + isFunctionFieldCopiedToStencil = true; +} + +void FunctionBox::copyFunctionExtraFields(ScriptStencilExtra& scriptExtra) { + scriptExtra.nargs = nargs_; +} + +void FunctionBox::copyUpdatedImmutableFlags() { + if (hasFunctionExtraStencil()) { + ScriptStencilExtra& scriptExtra = functionExtraStencil(); + scriptExtra.immutableFlags = immutableFlags_; + } +} + +void FunctionBox::copyUpdatedExtent() { + ScriptStencilExtra& scriptExtra = functionExtraStencil(); + scriptExtra.extent = extent_; +} + +void FunctionBox::copyUpdatedMemberInitializers() { + ScriptStencil& script = functionStencil(); + if (memberInitializers_) { + script.setMemberInitializers(*memberInitializers_); + } +} + +void FunctionBox::copyUpdatedEnclosingScopeIndex() { + ScriptStencil& script = functionStencil(); + if (enclosingScopeIndex_) { + script.setLazyFunctionEnclosingScopeIndex(*enclosingScopeIndex_); + } +} + +void FunctionBox::copyUpdatedAtomAndFlags() { + ScriptStencil& script = functionStencil(); + if (atom_) { + atom_->markUsedByStencil(); + script.functionAtom = atom_->toIndex(); + } + script.functionFlags = flags_; +} + +void FunctionBox::copyUpdatedWasEmitted() { + ScriptStencil& script = functionStencil(); + if (wasEmitted_) { + script.setWasFunctionEmitted(); + } +} + +} // namespace frontend +} // namespace js diff --git a/js/src/frontend/SharedContext.h b/js/src/frontend/SharedContext.h new file mode 100644 index 0000000000..6838436b95 --- /dev/null +++ b/js/src/frontend/SharedContext.h @@ -0,0 +1,712 @@ +/* -*- 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 "jstypes.h" + +#include "frontend/AbstractScopePtr.h" // ScopeIndex +#include "frontend/FunctionSyntaxKind.h" // FunctionSyntaxKind +#include "frontend/ParseNode.h" +#include "frontend/ScriptIndex.h" // ScriptIndex +#include "js/WasmModule.h" // JS::WasmModule +#include "vm/FunctionFlags.h" // js::FunctionFlags +#include "vm/GeneratorAndAsyncKind.h" // js::GeneratorKind, js::FunctionAsyncKind +#include "vm/JSFunction.h" +#include "vm/JSScript.h" +#include "vm/Scope.h" +#include "vm/SharedStencil.h" + +namespace js { +namespace frontend { + +struct CompilationStencil; +struct CompilationState; +class ParseContext; +class ScriptStencil; +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 FLAG_GETTER(enumName, enumEntry, lowerName, name) \ + public: \ + bool lowerName() const { return hasFlag(enumName::enumEntry); } + +#define FLAG_SETTER(enumName, enumEntry, lowerName, name) \ + public: \ + void set##name() { setFlag(enumName::enumEntry); } \ + void set##name(bool b) { setFlag(enumName::enumEntry, b); } + +#define IMMUTABLE_FLAG_GETTER_SETTER(lowerName, name) \ + FLAG_GETTER(ImmutableFlags, name, lowerName, name) \ + FLAG_SETTER(ImmutableFlags, name, lowerName, name) + +#define IMMUTABLE_FLAG_GETTER(lowerName, name) \ + FLAG_GETTER(ImmutableFlags, name, 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: + JSContext* const cx_; + + protected: + CompilationStencil& stencil_; + + // 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 ScriptStencil. + // + // 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 ScriptStencil by + // FunctionBox::copyUpdated* methods. + bool isScriptFieldCopiedToStencil : 1; + + // End of fields. + + enum class Kind : uint8_t { FunctionBox, Global, Eval, Module }; + + // Alias enum into SharedContext + using ImmutableFlags = ImmutableScriptFlagsEnum; + + MOZ_MUST_USE bool hasFlag(ImmutableFlags flag) const { + return immutableFlags_.hasFlag(flag); + } + void setFlag(ImmutableFlags flag, bool b = true) { + MOZ_ASSERT(!isScriptFieldCopiedToStencil); + immutableFlags_.setFlag(flag, b); + } + + public: + SharedContext(JSContext* cx, Kind kind, CompilationStencil& stencil, + 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(); } + + CompilationStencil& stencil() const { return stencil_; } + + 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; + } + + inline JSAtom* liftParserAtomToJSAtom(JSContext* cx, + const ParserAtom* atomId); + + void copyScriptFields(ScriptStencil& script); + void copyScriptExtraFields(ScriptStencilExtra& scriptExtra); +}; + +class MOZ_STACK_CLASS GlobalSharedContext : public SharedContext { + ScopeKind scopeKind_; + + public: + GlobalScope::ParserData* bindings; + + GlobalSharedContext(JSContext* cx, ScopeKind scopeKind, + CompilationStencil& stencil, Directives directives, + SourceExtent extent); + + ScopeKind scopeKind() const { return scopeKind_; } +}; + +inline GlobalSharedContext* SharedContext::asGlobalContext() { + MOZ_ASSERT(isGlobalContext()); + return static_cast<GlobalSharedContext*>(this); +} + +class MOZ_STACK_CLASS EvalSharedContext : public SharedContext { + public: + EvalScope::ParserData* bindings; + + EvalSharedContext(JSContext* cx, CompilationStencil& stencil, + CompilationState& compilationState, SourceExtent extent); +}; + +inline EvalSharedContext* SharedContext::asEvalContext() { + MOZ_ASSERT(isEvalContext()); + return static_cast<EvalSharedContext*>(this); +} + +enum class HasHeritage { No, Yes }; + +class SuspendableContext : public SharedContext { + public: + SuspendableContext(JSContext* cx, Kind kind, CompilationStencil& stencil, + 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<ScopeIndex> 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. + const ParserAtom* atom_ = nullptr; + + // Index into BaseCompilationStencil::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. + mozilla::Maybe<MemberInitializers> memberInitializers_ = {}; + + 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 wasEmitted_ : 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; + + // 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; + + // End of fields. + + FunctionBox(JSContext* cx, SourceExtent extent, CompilationStencil& stencil, + CompilationState& compilationState, Directives directives, + GeneratorKind generatorKind, FunctionAsyncKind asyncKind, + const ParserAtom* atom, FunctionFlags flags, ScriptIndex index); + + ScriptStencil& functionStencil() const; + ScriptStencilExtra& functionExtraStencil() const; + + bool hasFunctionExtraStencil() 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(JSFunction* fun); + + void initStandalone(ScopeContext& scopeContext, FunctionFlags flags, + FunctionSyntaxKind kind); + + void initWithEnclosingParseContext(ParseContext* enclosing, + FunctionFlags flags, + FunctionSyntaxKind kind); + + void setEnclosingScopeForInnerLazyFunction(ScopeIndex scopeIndex); + + bool wasEmitted() const { return wasEmitted_; } + void setWasEmitted(bool wasEmitted) { + wasEmitted_ = wasEmitted; + if (isFunctionFieldCopiedToStencil) { + copyUpdatedWasEmitted(); + } + } + + MOZ_MUST_USE 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) + // NeedsHomeObject: custom logic below. + // IsDerivedClassConstructor: custom logic below. + // IsFieldInitializer: custom logic below. + 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(argumentsHasVarBinding, ArgumentsHasVarBinding) + // AlwaysNeedsArgsObj: custom logic below. + // 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 isNamedLambda() const { + return flags_.isNamedLambda(explicitName() != nullptr); + } + 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(); } + + const ParserAtom* displayAtom() const { return atom_; } + const ParserAtom* explicitName() const { + return (hasInferredName() || hasGuessedAtom()) ? nullptr : 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(const ParserAtom* atom) { + atom_ = atom; + flags_.setInferredName(); + if (isFunctionFieldCopiedToStencil) { + copyUpdatedAtomAndFlags(); + } + } + void setGuessedAtom(const ParserAtom* atom) { + atom_ = atom; + flags_.setGuessedAtom(); + if (isFunctionFieldCopiedToStencil) { + copyUpdatedAtomAndFlags(); + } + } + + void setAlwaysNeedsArgsObj() { + MOZ_ASSERT(argumentsHasVarBinding()); + setFlag(ImmutableFlags::AlwaysNeedsArgsObj); + } + + bool needsHomeObject() const { + return hasFlag(ImmutableFlags::NeedsHomeObject); + } + void setNeedsHomeObject() { + MOZ_ASSERT(flags_.allowSuperProperty()); + setFlag(ImmutableFlags::NeedsHomeObject); + } + + bool isDerivedClassConstructor() const { + return hasFlag(ImmutableFlags::IsDerivedClassConstructor); + } + void setDerivedClassConstructor() { + MOZ_ASSERT(flags_.isClassConstructor()); + setFlag(ImmutableFlags::IsDerivedClassConstructor); + } + + bool isFieldInitializer() const { + return hasFlag(ImmutableFlags::IsFieldInitializer); + } + void setFieldInitializer() { + MOZ_ASSERT(flags_.isMethod()); + setFlag(ImmutableFlags::IsFieldInitializer); + } + + 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(!isScriptFieldCopiedToStencil); + extent_.sourceStart = offset; + extent_.lineno = line; + extent_.column = column; + } + + void setEnd(uint32_t end) { + MOZ_ASSERT(!isScriptFieldCopiedToStencil); + // 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 (isScriptFieldCopiedToStencil) { + copyUpdatedExtent(); + } + } + + void setCtorFunctionHasThisBinding() { + immutableFlags_.setFlag(ImmutableFlags::FunctionHasThisBinding, true); + if (isScriptFieldCopiedToStencil) { + 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_; } + + bool hasMemberInitializers() const { return memberInitializers_.isSome(); } + const MemberInitializers& memberInitializers() const { + return *memberInitializers_; + } + void setMemberInitializers(MemberInitializers memberInitializers) { + MOZ_ASSERT(memberInitializers_.isNothing()); + memberInitializers_ = mozilla::Some(memberInitializers); + if (isScriptFieldCopiedToStencil) { + copyUpdatedMemberInitializers(); + } + } + + ScriptIndex index() { return funcDataIndex_; } + + void finishScriptFlags(); + void copyScriptFields(ScriptStencil& script); + void copyFunctionFields(ScriptStencil& script); + void copyFunctionExtraFields(ScriptStencilExtra& scriptExtra); + + // * setCtorFunctionHasThisBinding can be called to a class constructor + // with a lazy function, while parsing enclosing class + 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<FunctionBox*>(this); +} + +inline SuspendableContext* SharedContext::asSuspendableContext() { + MOZ_ASSERT(isSuspendableContext()); + return static_cast<SuspendableContext*>(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 <algorithm> // std::min +#include <stddef.h> // ptrdiff_t, size_t +#include <stdint.h> // 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 <typename T> + 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 <typename T> + 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 <typename T> + 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..f1562d132b --- /dev/null +++ b/js/src/frontend/Stencil.cpp @@ -0,0 +1,2261 @@ +/* -*- 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/OperatorNewExtensions.h" // mozilla::KnownNotNull +#include "mozilla/RefPtr.h" // RefPtr +#include "mozilla/Sprintf.h" // SprintfLiteral + +#include "frontend/AbstractScopePtr.h" // ScopeIndex +#include "frontend/BytecodeCompilation.h" // CanLazilyParse +#include "frontend/BytecodeSection.h" // EmitScriptThingsVector +#include "frontend/CompilationInfo.h" // CompilationStencil, CompilationStencilSet, CompilationGCOutput +#include "frontend/SharedContext.h" +#include "gc/AllocKind.h" // gc::AllocKind +#include "js/CallArgs.h" // JSNative +#include "js/RootingAPI.h" // Rooted +#include "js/Transcoding.h" // JS::TranscodeBuffer +#include "js/Value.h" // ObjectValue +#include "js/WasmModule.h" // JS::WasmModule +#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 +#include "vm/JSONPrinter.h" // js::JSONPrinter +#include "vm/JSScript.h" // BaseScript, JSScript +#include "vm/ObjectGroup.h" // TenuredObject +#include "vm/Printer.h" // js::Fprinter +#include "vm/Scope.h" // Scope, ScopeKindString +#include "vm/StencilEnums.h" // ImmutableScriptFlagsEnum +#include "vm/StringType.h" // JSAtom, js::CopyChars +#include "vm/Xdr.h" // XDRMode, XDRResult, XDREncoder +#include "wasm/AsmJS.h" // InstantiateAsmJS +#include "wasm/WasmModule.h" // wasm::Module + +#include "vm/JSFunction-inl.h" // JSFunction::create + +using namespace js; +using namespace js::frontend; + +AbstractScopePtr ScopeStencil::enclosing( + CompilationState& compilationState) const { + if (hasEnclosing()) { + return AbstractScopePtr(compilationState, enclosing()); + } + + return AbstractScopePtr(compilationState.input.enclosingScope); +} + +Scope* ScopeStencil::enclosingExistingScope( + const CompilationInput& input, const CompilationGCOutput& gcOutput) const { + if (hasEnclosing()) { + Scope* result = gcOutput.scopes[enclosing()]; + MOZ_ASSERT(result, "Scope must already exist to use this method"); + return result; + } + + return input.enclosingScope; +} + +Scope* ScopeStencil::createScope(JSContext* cx, CompilationInput& input, + CompilationGCOutput& gcOutput, + BaseParserScopeData* baseScopeData) const { + Scope* scope = nullptr; + switch (kind()) { + case ScopeKind::Function: { + using ScopeType = FunctionScope; + MOZ_ASSERT(matchScopeKind<ScopeType>(kind())); + scope = createSpecificScope<ScopeType, CallObject>(cx, input, gcOutput, + baseScopeData); + break; + } + case ScopeKind::Lexical: + case ScopeKind::SimpleCatch: + case ScopeKind::Catch: + case ScopeKind::NamedLambda: + case ScopeKind::StrictNamedLambda: + case ScopeKind::FunctionLexical: + case ScopeKind::ClassBody: { + using ScopeType = LexicalScope; + MOZ_ASSERT(matchScopeKind<ScopeType>(kind())); + scope = createSpecificScope<ScopeType, LexicalEnvironmentObject>( + cx, input, gcOutput, baseScopeData); + break; + } + case ScopeKind::FunctionBodyVar: { + using ScopeType = VarScope; + MOZ_ASSERT(matchScopeKind<ScopeType>(kind())); + scope = createSpecificScope<ScopeType, VarEnvironmentObject>( + cx, input, gcOutput, baseScopeData); + break; + } + case ScopeKind::Global: + case ScopeKind::NonSyntactic: { + using ScopeType = GlobalScope; + MOZ_ASSERT(matchScopeKind<ScopeType>(kind())); + scope = createSpecificScope<ScopeType, std::nullptr_t>( + cx, input, gcOutput, baseScopeData); + break; + } + case ScopeKind::Eval: + case ScopeKind::StrictEval: { + using ScopeType = EvalScope; + MOZ_ASSERT(matchScopeKind<ScopeType>(kind())); + scope = createSpecificScope<ScopeType, VarEnvironmentObject>( + cx, input, gcOutput, baseScopeData); + break; + } + case ScopeKind::Module: { + using ScopeType = ModuleScope; + MOZ_ASSERT(matchScopeKind<ScopeType>(kind())); + scope = createSpecificScope<ScopeType, ModuleEnvironmentObject>( + cx, input, gcOutput, baseScopeData); + break; + } + case ScopeKind::With: { + using ScopeType = WithScope; + MOZ_ASSERT(matchScopeKind<ScopeType>(kind())); + scope = createSpecificScope<ScopeType, std::nullptr_t>( + cx, input, gcOutput, baseScopeData); + break; + } + case ScopeKind::WasmFunction: + case ScopeKind::WasmInstance: { + MOZ_CRASH("Unexpected deferred type"); + } + } + return scope; +} + +static bool CreateLazyScript(JSContext* cx, CompilationInput& input, + BaseCompilationStencil& stencil, + CompilationGCOutput& gcOutput, + const ScriptStencil& script, + const ScriptStencilExtra& scriptExtra, + ScriptIndex scriptIndex, HandleFunction function) { + Rooted<ScriptSourceObject*> sourceObject(cx, gcOutput.sourceObject); + + size_t ngcthings = script.gcThingsLength; + + Rooted<BaseScript*> lazy( + cx, BaseScript::CreateRawLazy(cx, ngcthings, function, sourceObject, + scriptExtra.extent, + scriptExtra.immutableFlags)); + if (!lazy) { + return false; + } + + if (ngcthings) { + if (!EmitScriptThingsVector(cx, input, stencil, gcOutput, + script.gcthings(stencil), + lazy->gcthingsForInit())) { + return false; + } + } + + function->initScript(lazy); + + return true; +} + +// Parser-generated functions with the same prototype will share the same shape +// and group. 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. +// +// This bypasses the `NewObjectCache`, but callers are expected to retrieve a +// valid group and shape from the appropriate de-duplication tables. +// +// NOTE: Keep this in sync with `js::NewFunctionWithProto`. +static JSFunction* CreateFunctionFast(JSContext* cx, CompilationInput& input, + HandleObjectGroup group, + HandleShape 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; + JS_TRY_VAR_OR_RETURN_NULL( + cx, fun, + JSFunction::create(cx, allocKind, gc::TenuredHeap, shape, group)); + + fun->setArgCount(scriptExtra.nargs); + fun->setFlags(flags); + + fun->initScript(nullptr); + fun->initEnvironment(nullptr); + + if (script.functionAtom) { + JSAtom* atom = input.atomCache.getExistingAtomAt(cx, script.functionAtom); + MOZ_ASSERT(atom); + fun->initAtom(atom); + } + + if (flags.isExtended()) { + fun->initializeExtended(); + } + + return fun; +} + +static JSFunction* CreateFunction(JSContext* cx, CompilationInput& input, + BaseCompilationStencil& 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; + + RootedAtom displayAtom(cx); + if (script.functionAtom) { + displayAtom.set(input.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<const JS::WasmModule> asmJS = + stencil.asCompilationStencil().asmJS.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, CompilationInput& input, + BaseCompilationStencil& stencil) { + return InstantiateMarkedAtoms(cx, stencil.parserAtomData, input.atomCache); +} + +static bool InstantiateScriptSourceObject(JSContext* cx, + CompilationInput& input, + CompilationGCOutput& gcOutput) { + MOZ_ASSERT(input.source()); + + gcOutput.sourceObject = ScriptSourceObject::create(cx, input.source()); + if (!gcOutput.sourceObject) { + return false; + } + + // Off-thread compilations do all their GC heap allocation, including the + // SSO, in a temporary compartment. Hence, for the SSO to refer to the + // gc-heap-allocated values in |options|, it would need cross-compartment + // wrappers from the temporary compartment to the real compartment --- which + // would then be inappropriate once we merged the temporary and real + // compartments. + // + // Instead, we put off populating those SSO slots in off-thread compilations + // until after we've merged compartments. + if (!cx->isHelperThreadContext()) { + Rooted<ScriptSourceObject*> sourceObject(cx, gcOutput.sourceObject); + if (!ScriptSourceObject::initFromOptions(cx, sourceObject, input.options)) { + return false; + } + } + + return true; +} + +// Instantiate ModuleObject. Further initialization is done after the associated +// BaseScript is instantiated in InstantiateTopLevel. +static bool InstantiateModuleObject(JSContext* cx, CompilationInput& input, + CompilationStencil& stencil, + CompilationGCOutput& gcOutput) { + MOZ_ASSERT(stencil.scriptExtra[CompilationStencil::TopLevelIndex].isModule()); + + gcOutput.module = ModuleObject::create(cx); + if (!gcOutput.module) { + return false; + } + + Rooted<ModuleObject*> module(cx, gcOutput.module); + return stencil.moduleMetadata->initModule(cx, input.atomCache, module); +} + +// Instantiate JSFunctions for each FunctionBox. +static bool InstantiateFunctions(JSContext* cx, CompilationInput& input, + BaseCompilationStencil& stencil, + CompilationGCOutput& gcOutput) { + using ImmutableFlags = ImmutableScriptFlagsEnum; + + if (!gcOutput.functions.resize(stencil.scriptData.size())) { + ReportOutOfMemory(cx); + return false; + } + + // Most JSFunctions will be have the same Shape / Group so we can compute it + // now to allow fast object creation. Generators / Async will use the slow + // path instead. + RootedObject proto(cx, + GlobalObject::getOrCreatePrototype(cx, JSProto_Function)); + if (!proto) { + return false; + } + RootedObjectGroup group(cx, ObjectGroup::defaultNewGroup( + cx, &JSFunction::class_, TaggedProto(proto))); + if (!group) { + return false; + } + RootedShape shape( + cx, EmptyShape::getInitialShape(cx, &JSFunction::class_, + TaggedProto(proto), /* nfixed = */ 0, + /* objectFlags = */ 0)); + if (!shape) { + 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 = useFastPath + ? CreateFunctionFast(cx, input, group, shape, + scriptStencil, scriptExtra) + : CreateFunction(cx, input, stencil, scriptStencil, + scriptExtra, index); + if (!fun) { + return false; + } + + gcOutput.functions[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, + BaseCompilationStencil& 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, CompilationInput& input, + CompilationStencil& stencil, + CompilationGCOutput& gcOutput) { + MOZ_ASSERT(input.lazy == nullptr); + + Rooted<JSFunction*> 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(){});`. + // + // `wasFunctionEmitted` is false also for standalone functions. They are + // handled in InstantiateTopLevel. + if (!scriptStencil.wasFunctionEmitted()) { + continue; + } + + RootedScript script( + cx, JSScript::fromStencil(cx, input, stencil, gcOutput, index)); + if (!script) { + return false; + } + + // NOTE: Inner functions can be marked `allowRelazify` after merging + // a stencil for delazification into the top-level stencil. + 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, input, 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, + BaseCompilationStencil& 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 (input.lazy) { + RootedScript script(cx, JSScript::CastFromLazy(input.lazy)); + if (!JSScript::fullyInitFromStencil(cx, input, 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, stencil.asCompilationStencil(), gcOutput, + CompilationStencil::TopLevelIndex); + if (!gcOutput.script) { + return false; + } + + if (scriptStencil.allowRelazify()) { + MOZ_ASSERT(gcOutput.script->isRelazifiable()); + gcOutput.script->setAllowRelazify(); + } + + const ScriptStencilExtra& scriptExtra = + stencil.asCompilationStencil() + .scriptExtra[CompilationStencil::TopLevelIndex]; + + // Finish initializing the ModuleObject if needed. + if (scriptExtra.isModule()) { + RootedScript script(cx, gcOutput.script); + + gcOutput.module->initScriptSlots(script); + gcOutput.module->initStatusSlot(); + + RootedModuleObject module(cx, gcOutput.module); + if (!ModuleObject::createEnvironment(cx, module)) { + return false; + } + + // Off-thread compilation with parseGlobal will freeze the module object + // in GlobalHelperThreadState::finishSingleParseTask instead. + if (!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, CompilationInput& input, + BaseCompilationStencil& stencil, + CompilationGCOutput& gcOutput) { + for (auto item : + CompilationStencil::functionScriptStencils(stencil, gcOutput)) { + auto& scriptStencil = item.script; + auto& fun = item.function; + if (!scriptStencil.wasFunctionEmitted()) { + 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.scopes[index]; + script->setEnclosingScope(scope); + + if (scriptStencil.hasMemberInitializers()) { + script->setMemberInitializers(scriptStencil.memberInitializers()); + } + + // 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 = + input.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(BaseCompilationStencil& 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()->hasBytecode()) { + continue; + } + + BaseScript* script = fun->baseScript(); + MOZ_ASSERT(!script->hasBytecode()); + + for (auto inner : script->gcthings()) { + if (!inner.is<JSObject>()) { + continue; + } + inner.as<JSObject>().as<JSFunction>().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(BaseCompilationStencil& 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.lazy->function()); + + for (JS::GCCellPtr elem : input.lazy->gcthings()) { + if (!elem.is<JSObject>()) { + continue; + } + JSFunction* fun = &elem.as<JSObject>().as<JSFunction>(); + gcOutput.functions.infallibleAppend(fun); + } +} + +/* static */ +bool CompilationStencil::instantiateStencils(JSContext* cx, + CompilationStencil& stencil, + CompilationGCOutput& gcOutput) { + if (!stencil.preparationIsPerformed) { + if (!prepareForInstantiate(cx, stencil, gcOutput)) { + return false; + } + } + + return instantiateStencilsAfterPreparation(cx, stencil.input, stencil, + gcOutput); +} + +/* static */ +bool CompilationStencil::instantiateStencilsAfterPreparation( + JSContext* cx, CompilationInput& input, BaseCompilationStencil& stencil, + CompilationGCOutput& gcOutput) { + // Distinguish between the initial (possibly lazy) compile and any subsequent + // delazification compiles. Delazification will update existing GC things. + bool isInitialParse = (input.lazy == nullptr); + + // Phase 1: Instantate JSAtoms. + if (!InstantiateAtoms(cx, input, stencil)) { + return false; + } + + // Phase 2: Instantiate ScriptSourceObject, ModuleObject, JSFunctions. + if (isInitialParse) { + CompilationStencil& initialStencil = stencil.asCompilationStencil(); + + if (!InstantiateScriptSourceObject(cx, input, gcOutput)) { + return false; + } + + if (initialStencil.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 == nullptr); + input.enclosingScope = &cx->global()->emptyGlobalScope(); + MOZ_ASSERT(input.enclosingScope->environmentChainLength() == + ModuleScope::EnclosingEnvironmentChainLength); + + if (!InstantiateModuleObject(cx, input, initialStencil, gcOutput)) { + return false; + } + } + + if (!InstantiateFunctions(cx, input, 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 == + BaseCompilationStencil::toFunctionKey(input.lazy->extent())); + + 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, input, stencil.asCompilationStencil(), + 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 (CanLazilyParse(input.options)) { + UpdateEmittedInnerFunctions(cx, input, stencil, gcOutput); + + if (isInitialParse) { + LinkEnclosingLazyScript(stencil, gcOutput); + } + } + + return true; +} + +bool CompilationStencilSet::buildDelazificationIndices(JSContext* cx) { + // Standalone-functions are not supported by XDR. + MOZ_ASSERT(!scriptData[0].isFunction()); + + // If no delazifications, we are done. + if (delazifications.empty()) { + return true; + } + + if (!delazificationIndices.resize(delazifications.length())) { + ReportOutOfMemory(cx); + return false; + } + + HashMap<BaseCompilationStencil::FunctionKey, size_t> keyToIndex(cx); + if (!keyToIndex.reserve(delazifications.length())) { + return false; + } + + for (size_t i = 0; i < delazifications.length(); i++) { + const auto& delazification = delazifications[i]; + auto key = delazification.functionKey; + keyToIndex.putNewInfallible(key, i); + } + + MOZ_ASSERT(keyToIndex.count() == delazifications.length()); + + for (size_t i = 1; i < scriptData.size(); i++) { + auto key = BaseCompilationStencil::toFunctionKey(scriptExtra[i].extent); + auto ptr = keyToIndex.lookup(key); + if (!ptr) { + continue; + } + delazificationIndices[ptr->value()] = ScriptIndex(i); + } + + return true; +} + +bool CompilationStencilSet::instantiateStencils( + JSContext* cx, CompilationGCOutput& gcOutput, + CompilationGCOutput& gcOutputForDelazification) { + if (!prepareForInstantiate(cx, gcOutput, gcOutputForDelazification)) { + return false; + } + + return instantiateStencilsAfterPreparation(cx, gcOutput, + gcOutputForDelazification); +} + +bool CompilationStencilSet::instantiateStencilsAfterPreparation( + JSContext* cx, CompilationGCOutput& gcOutput, + CompilationGCOutput& gcOutputForDelazification) { + if (!CompilationStencil::instantiateStencilsAfterPreparation(cx, input, *this, + gcOutput)) { + return false; + } + + CompilationAtomCache::AtomCacheVector reusableAtomCache; + input.atomCache.releaseBuffer(reusableAtomCache); + + for (size_t i = 0; i < delazifications.length(); i++) { + auto& delazification = delazifications[i]; + auto index = delazificationIndices[i]; + + JSFunction* fun = gcOutput.functions[index]; + MOZ_ASSERT(fun); + + BaseScript* lazy = fun->baseScript(); + MOZ_ASSERT(!lazy->hasBytecode()); + + if (!lazy->isReadyForDelazification()) { + MOZ_ASSERT(false, "Delazification target is not ready. Bad XDR?"); + continue; + } + + Rooted<CompilationInput> delazificationInput( + cx, CompilationInput(input.options)); + delazificationInput.get().initFromLazy(lazy); + + delazificationInput.get().atomCache.stealBuffer(reusableAtomCache); + + if (!CompilationStencil::prepareGCOutputForInstantiate( + cx, delazification, gcOutputForDelazification)) { + return false; + } + if (!CompilationStencil::instantiateStencilsAfterPreparation( + cx, delazificationInput.get(), delazification, + gcOutputForDelazification)) { + return false; + } + + // Destroy elements, without unreserving. + gcOutputForDelazification.functions.clear(); + gcOutputForDelazification.scopes.clear(); + + delazificationInput.get().atomCache.releaseBuffer(reusableAtomCache); + } + + input.atomCache.stealBuffer(reusableAtomCache); + + return true; +} + +/* static */ +bool CompilationStencil::prepareInputAndStencilForInstantiate( + JSContext* cx, CompilationInput& input, BaseCompilationStencil& stencil) { + if (!input.atomCache.allocate(cx, stencil.parserAtomData.size())) { + return false; + } + + return true; +} + +/* static */ +bool CompilationStencil::prepareGCOutputForInstantiate( + JSContext* cx, BaseCompilationStencil& stencil, + CompilationGCOutput& gcOutput) { + if (!gcOutput.functions.reserve(stencil.scriptData.size())) { + ReportOutOfMemory(cx); + return false; + } + if (!gcOutput.scopes.reserve(stencil.scopeData.size())) { + ReportOutOfMemory(cx); + return false; + } + + return true; +} + +/* static */ +bool CompilationStencil::prepareForInstantiate(JSContext* cx, + CompilationStencil& stencil, + CompilationGCOutput& gcOutput) { + auto& input = stencil.input; + + if (!prepareInputAndStencilForInstantiate(cx, input, stencil)) { + return false; + } + if (!prepareGCOutputForInstantiate(cx, stencil, gcOutput)) { + return false; + } + + stencil.preparationIsPerformed = true; + return true; +} + +bool CompilationStencilSet::prepareForInstantiate( + JSContext* cx, CompilationGCOutput& gcOutput, + CompilationGCOutput& gcOutputForDelazification) { + if (!CompilationStencil::prepareForInstantiate(cx, *this, gcOutput)) { + return false; + } + + size_t maxScriptDataLength = 0; + size_t maxScopeDataLength = 0; + size_t maxParserAtomDataLength = 0; + for (auto& delazification : delazifications) { + if (maxParserAtomDataLength < delazification.parserAtomData.size()) { + maxParserAtomDataLength = delazification.parserAtomData.size(); + } + if (maxScriptDataLength < delazification.scriptData.size()) { + maxScriptDataLength = delazification.scriptData.size(); + } + if (maxScopeDataLength < delazification.scopeData.size()) { + maxScopeDataLength = delazification.scopeData.size(); + } + } + + if (!input.atomCache.extendIfNecessary(cx, maxParserAtomDataLength)) { + return false; + } + if (!gcOutput.functions.reserve(maxScriptDataLength)) { + ReportOutOfMemory(cx); + return false; + } + if (!gcOutput.scopes.reserve(maxScopeDataLength)) { + ReportOutOfMemory(cx); + return false; + } + + if (!buildDelazificationIndices(cx)) { + return false; + } + + return true; +} + +bool CompilationStencil::serializeStencils(JSContext* cx, + JS::TranscodeBuffer& buf, + bool* succeededOut) { + if (succeededOut) { + *succeededOut = false; + } + XDRIncrementalStencilEncoder encoder(cx); + + XDRResult res = encoder.codeStencil(*this); + if (res.isErr()) { + if (res.unwrapErr() & JS::TranscodeResult_Failure) { + buf.clear(); + return true; + } + MOZ_ASSERT(res.unwrapErr() == JS::TranscodeResult_Throw); + + return false; + } + + // Linearize the encoder, return empty buffer on failure. + res = encoder.linearize(buf, input.source()); + if (res.isErr()) { + MOZ_ASSERT(cx->isThrowingOutOfMemory()); + buf.clear(); + return false; + } + + if (succeededOut) { + *succeededOut = true; + } + return true; +} + +bool CompilationStencilSet::deserializeStencils(JSContext* cx, + const JS::TranscodeRange& range, + bool* succeededOut) { + if (succeededOut) { + *succeededOut = false; + } + MOZ_ASSERT(parserAtomData.empty()); + XDRStencilDecoder decoder(cx, &input.options, range); + + XDRResult res = decoder.codeStencils(*this); + if (res.isErr()) { + if (res.unwrapErr() & JS::TranscodeResult_Failure) { + return true; + } + MOZ_ASSERT(res.unwrapErr() == JS::TranscodeResult_Throw); + + return false; + } + + if (succeededOut) { + *succeededOut = true; + } + return true; +} + +CompilationState::CompilationState( + JSContext* cx, LifoAllocScope& frontendAllocScope, + const JS::ReadOnlyCompileOptions& options, CompilationStencil& stencil, + InheritThis inheritThis /* = InheritThis::No */, + Scope* enclosingScope /* = nullptr */, + JSObject* enclosingEnv /* = nullptr */) + : directives(options.forceStrictMode()), + scopeContext(cx, inheritThis, enclosingScope, enclosingEnv), + usedNames(cx), + allocScope(frontendAllocScope), + input(stencil.input), + parserAtoms(cx->runtime(), stencil.alloc) {} + +SharedDataContainer::~SharedDataContainer() { + if (isEmpty()) { + // Nothing to do. + } else if (isSingle()) { + asSingle()->Release(); + } else if (isVector()) { + js_delete(asVector()); + } else { + MOZ_ASSERT(isMap()); + js_delete(asMap()); + } +} + +bool SharedDataContainer::initVector(JSContext* cx) { + auto* vec = js_new<SharedDataVector>(); + if (!vec) { + ReportOutOfMemory(cx); + return false; + } + data_ = uintptr_t(vec) | VectorTag; + return true; +} + +bool SharedDataContainer::initMap(JSContext* cx) { + auto* map = js_new<SharedDataMap>(); + if (!map) { + ReportOutOfMemory(cx); + return false; + } + data_ = uintptr_t(map) | MapTag; + return true; +} + +bool SharedDataContainer::prepareStorageFor(JSContext* cx, + size_t nonLazyScriptCount, + size_t allScriptCount) { + 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(cx)) { + return false; + } + if (!asMap()->reserve(nonLazyScriptCount)) { + ReportOutOfMemory(cx); + return false; + } + } else { + if (!initVector(cx)) { + return false; + } + if (!asVector()->resize(allScriptCount)) { + ReportOutOfMemory(cx); + return false; + } + } + + return true; +} + +js::SharedImmutableScriptData* SharedDataContainer::get(ScriptIndex index) { + 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; + } + + MOZ_ASSERT(isMap()); + auto& map = *asMap(); + auto p = map.lookup(index); + if (p) { + return p->value(); + } + return nullptr; +} + +bool SharedDataContainer::addAndShare(JSContext* cx, ScriptIndex index, + js::SharedImmutableScriptData* data) { + if (isSingle()) { + MOZ_ASSERT(index == CompilationStencil::TopLevelIndex); + RefPtr<SharedImmutableScriptData> ref(data); + if (!SharedImmutableScriptData::shareScriptData(cx, ref)) { + return false; + } + setSingle(ref.forget()); + return true; + } + + if (isVector()) { + auto& vec = *asVector(); + // Resized by SharedDataContainer::prepareStorageFor. + vec[index] = data; + return SharedImmutableScriptData::shareScriptData(cx, 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(cx, p->value()); +} + +template <typename T, typename VectorT> +bool CopyVectorToSpan(JSContext* cx, LifoAlloc& alloc, mozilla::Span<T>& span, + VectorT& vec) { + auto len = vec.length(); + if (len == 0) { + return true; + } + + auto* p = alloc.newArrayUninitialized<T>(len); + if (!p) { + js::ReportOutOfMemory(cx); + return false; + } + span = mozilla::Span(p, len); + memcpy(span.data(), vec.begin(), sizeof(T) * len); + return true; +} + +bool CompilationState::finish(JSContext* cx, CompilationStencil& stencil) { + if (!CopyVectorToSpan(cx, stencil.alloc, stencil.regExpData, regExpData)) { + return false; + } + + if (!CopyVectorToSpan(cx, stencil.alloc, stencil.scriptData, scriptData)) { + return false; + } + + if (!CopyVectorToSpan(cx, stencil.alloc, stencil.scriptExtra, scriptExtra)) { + return false; + } + + if (!CopyVectorToSpan(cx, stencil.alloc, stencil.scopeData, scopeData)) { + return false; + } + + if (!CopyVectorToSpan(cx, stencil.alloc, stencil.scopeNames, scopeNames)) { + return false; + } + + if (!CopyVectorToSpan(cx, stencil.alloc, stencil.parserAtomData, + parserAtoms.entries())) { + return false; + } + + if (!CopyVectorToSpan(cx, stencil.alloc, stencil.gcThingData, gcThingData)) { + return false; + } + + return true; +} + +mozilla::Span<TaggedScriptThingIndex> ScriptStencil::gcthings( + BaseCompilationStencil& stencil) const { + return stencil.gcThingData.Subspan(gcThingsOffset, gcThingsLength); +} + +#if defined(DEBUG) || defined(JS_JITSPEW) + +void frontend::DumpTaggedParserAtomIndex(js::JSONPrinter& json, + TaggedParserAtomIndex taggedIndex, + BaseCompilationStencil* 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: { \ + GenericPrinter& out = json.beginStringProperty("atom"); \ + WellKnownParserAtoms::rom_.name.dumpCharsNoQuote(out); \ + json.endString(); \ + break; \ + } + FOR_EACH_NONTINY_COMMON_PROPERTYNAME(CASE_) +# undef CASE_ + +# define CASE_(name, _) \ + case WellKnownAtomId::name: { \ + GenericPrinter& out = json.beginStringProperty("atom"); \ + WellKnownParserAtoms::rom_.name.dumpCharsNoQuote(out); \ + json.endString(); \ + break; \ + } + JS_FOR_EACH_PROTOTYPE(CASE_) +# undef CASE_ + + default: + // This includes tiny WellKnownAtomId atoms, which is invalid. + json.property("index", size_t(index)); + break; + } + return; + } + + if (taggedIndex.isStaticParserString1()) { + json.property("tag", "Static1"); + auto index = taggedIndex.toStaticParserString1(); + GenericPrinter& out = json.beginStringProperty("atom"); + WellKnownParserAtoms::getStatic1(index)->dumpCharsNoQuote(out); + json.endString(); + return; + } + + if (taggedIndex.isStaticParserString2()) { + json.property("tag", "Static2"); + auto index = taggedIndex.toStaticParserString2(); + GenericPrinter& out = json.beginStringProperty("atom"); + WellKnownParserAtoms::getStatic2(index)->dumpCharsNoQuote(out); + json.endString(); + return; + } + + MOZ_ASSERT(taggedIndex.isNull()); + json.property("tag", "null"); +} + +void RegExpStencil::dump() { + js::Fprinter out(stderr); + js::JSONPrinter json(out); + dump(json, nullptr); +} + +void RegExpStencil::dump(js::JSONPrinter& json, + BaseCompilationStencil* stencil) { + json.beginObject(); + dumpFields(json, stencil); + json.endObject(); +} + +void RegExpStencil::dumpFields(js::JSONPrinter& json, + BaseCompilationStencil* stencil) { + 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() { + js::Fprinter out(stderr); + js::JSONPrinter json(out); + dump(json); +} + +void BigIntStencil::dump(js::JSONPrinter& json) { + GenericPrinter& out = json.beginString(); + dumpCharsNoQuote(out); + json.endString(); +} + +void BigIntStencil::dumpCharsNoQuote(GenericPrinter& out) { + for (size_t i = 0; i < length_; i++) { + out.putChar(char(buf_[i])); + } +} + +void ScopeStencil::dump() { + js::Fprinter out(stderr); + js::JSONPrinter json(out); + dump(json, nullptr, nullptr); +} + +void ScopeStencil::dump(js::JSONPrinter& json, + BaseParserScopeData* baseScopeData, + BaseCompilationStencil* stencil) { + json.beginObject(); + dumpFields(json, baseScopeData, stencil); + json.endObject(); +} + +void ScopeStencil::dumpFields(js::JSONPrinter& json, + BaseParserScopeData* baseScopeData, + BaseCompilationStencil* stencil) { + 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"); + + AbstractTrailingNamesArray<TaggedParserAtomIndex>* trailingNames = nullptr; + uint32_t length = 0; + + switch (kind_) { + case ScopeKind::Function: { + auto* data = static_cast<FunctionScope::ParserData*>(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 = &data->trailingNames; + length = data->slotInfo.length; + break; + } + + case ScopeKind::FunctionBodyVar: { + auto* data = static_cast<VarScope::ParserData*>(baseScopeData); + json.property("nextFrameSlot", data->slotInfo.nextFrameSlot); + + trailingNames = &data->trailingNames; + length = data->slotInfo.length; + break; + } + + case ScopeKind::Lexical: + case ScopeKind::SimpleCatch: + case ScopeKind::Catch: + case ScopeKind::NamedLambda: + case ScopeKind::StrictNamedLambda: + case ScopeKind::FunctionLexical: + case ScopeKind::ClassBody: { + auto* data = static_cast<LexicalScope::ParserData*>(baseScopeData); + json.property("nextFrameSlot", data->slotInfo.nextFrameSlot); + json.property("constStart", data->slotInfo.constStart); + + trailingNames = &data->trailingNames; + length = data->slotInfo.length; + break; + } + + case ScopeKind::With: { + break; + } + + case ScopeKind::Eval: + case ScopeKind::StrictEval: { + auto* data = static_cast<EvalScope::ParserData*>(baseScopeData); + json.property("nextFrameSlot", data->slotInfo.nextFrameSlot); + + trailingNames = &data->trailingNames; + length = data->slotInfo.length; + break; + } + + case ScopeKind::Global: + case ScopeKind::NonSyntactic: { + auto* data = static_cast<GlobalScope::ParserData*>(baseScopeData); + json.property("letStart", data->slotInfo.letStart); + json.property("constStart", data->slotInfo.constStart); + + trailingNames = &data->trailingNames; + length = data->slotInfo.length; + break; + } + + case ScopeKind::Module: { + auto* data = static_cast<ModuleScope::ParserData*>(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 = &data->trailingNames; + length = data->slotInfo.length; + break; + } + + case ScopeKind::WasmInstance: { + auto* data = static_cast<WasmInstanceScope::ParserData*>(baseScopeData); + json.property("nextFrameSlot", data->slotInfo.nextFrameSlot); + json.property("globalsStart", data->slotInfo.globalsStart); + + trailingNames = &data->trailingNames; + length = data->slotInfo.length; + break; + } + + case ScopeKind::WasmFunction: { + auto* data = static_cast<WasmFunctionScope::ParserData*>(baseScopeData); + json.property("nextFrameSlot", data->slotInfo.nextFrameSlot); + + trailingNames = &data->trailingNames; + length = data->slotInfo.length; + break; + } + + default: { + MOZ_CRASH("Unexpected ScopeKind"); + break; + } + } + + if (trailingNames) { + char index[64]; + json.beginObjectProperty("trailingNames"); + for (size_t i = 0; i < length; i++) { + 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 DumpModuleEntryVectorItems( + js::JSONPrinter& json, const StencilModuleMetadata::EntryVector& entries, + BaseCompilationStencil* stencil) { + for (const auto& entry : entries) { + json.beginObject(); + if (entry.specifier) { + json.beginObjectProperty("specifier"); + DumpTaggedParserAtomIndex(json, entry.specifier, stencil); + json.endObject(); + } + 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(); + } + json.endObject(); + } +} + +void StencilModuleMetadata::dump() { + js::Fprinter out(stderr); + js::JSONPrinter json(out); + dump(json, nullptr); +} + +void StencilModuleMetadata::dump(js::JSONPrinter& json, + BaseCompilationStencil* stencil) { + json.beginObject(); + dumpFields(json, stencil); + json.endObject(); +} + +void StencilModuleMetadata::dumpFields(js::JSONPrinter& json, + BaseCompilationStencil* stencil) { + 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 (auto& index : functionDecls) { + json.value("ScriptIndex(%zu)", size_t(index)); + } + json.endList(); + + json.boolProperty("isAsync", isAsync); +} + +static void 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::IsFieldInitializer: + json.value("IsFieldInitializer"); + 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::ArgumentsHasVarBinding: + json.value("ArgumentsHasVarBinding"); + break; + case ImmutableScriptFlagsEnum::AlwaysNeedsArgsObj: + json.value("AlwaysNeedsArgsObj"); + break; + case ImmutableScriptFlagsEnum::HasMappedArgsObj: + json.value("HasMappedArgsObj"); + break; + default: + json.value("Unknown(%x)", i); + break; + } + } + } +} + +static void 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::BOUND_FUN: + json.value("BOUND_FUN"); + 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::ATOM_EXTRA_FLAG: + json.value("ATOM_EXTRA_FLAG"); + break; + case FunctionFlags::Flags::RESOLVED_NAME: + json.value("RESOLVED_NAME"); + break; + case FunctionFlags::Flags::RESOLVED_LENGTH: + json.value("RESOLVED_LENGTH"); + break; + default: + json.value("Unknown(%x)", i); + break; + } + } + } +} + +static void DumpScriptThing(js::JSONPrinter& json, + BaseCompilationStencil* 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() { + js::Fprinter out(stderr); + js::JSONPrinter json(out); + dump(json, nullptr); +} + +void ScriptStencil::dump(js::JSONPrinter& json, + BaseCompilationStencil* stencil) { + json.beginObject(); + dumpFields(json, stencil); + json.endObject(); +} + +void ScriptStencil::dumpFields(js::JSONPrinter& json, + BaseCompilationStencil* stencil) { + if (hasMemberInitializers()) { + json.property("memberInitializers", memberInitializers_); + } + + json.formatProperty("gcThingsOffset", "CompilationGCThingIndex(%u)", + gcThingsOffset.index); + json.property("gcThingsLength", gcThingsLength); + + if (stencil) { + json.beginListProperty("gcThings"); + for (auto& thing : gcthings(*stencil)) { + DumpScriptThing(json, stencil, thing); + } + json.endList(); + } + + json.beginListProperty("flags"); + if (flags_ & WasFunctionEmittedFlag) { + json.value("WasFunctionEmittedFlag"); + } + if (flags_ & AllowRelazifyFlag) { + json.value("AllowRelazifyFlag"); + } + if (flags_ & HasSharedDataFlag) { + json.value("HasSharedDataFlag"); + } + if (flags_ & HasMemberInitializersFlag) { + json.value("HasMemberInitializersFlag"); + } + 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_)); + } + } +} + +void ScriptStencilExtra::dump() { + js::Fprinter out(stderr); + js::JSONPrinter json(out); + dump(json); +} + +void ScriptStencilExtra::dump(js::JSONPrinter& json) { + json.beginObject(); + dumpFields(json); + json.endObject(); +} + +void ScriptStencilExtra::dumpFields(js::JSONPrinter& json) { + 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("nargs", nargs); +} + +void SharedDataContainer::dump() { + js::Fprinter out(stderr); + js::JSONPrinter json(out); + dump(json); +} + +void SharedDataContainer::dump(js::JSONPrinter& json) { + json.beginObject(); + dumpFields(json); + json.endObject(); +} + +void SharedDataContainer::dumpFields(js::JSONPrinter& json) { + 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; + } + + MOZ_ASSERT(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()); + } +} + +void BaseCompilationStencil::dump() { + js::Fprinter out(stderr); + js::JSONPrinter json(out); + dump(json); + out.put("\n"); +} + +void BaseCompilationStencil::dump(js::JSONPrinter& json) { + json.beginObject(); + dumpFields(json); + json.endObject(); +} + +void BaseCompilationStencil::dumpFields(js::JSONPrinter& json) { + 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("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.length(); 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.length(); i++) { + SprintfLiteral(index, "ObjLiteralIndex(%zu)", i); + json.beginObjectProperty(index); + objLiteralData[i].dumpFields(json, this); + 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(); +} + +void CompilationStencil::dump() { + js::Fprinter out(stderr); + js::JSONPrinter json(out); + dump(json); + out.put("\n"); +} + +void CompilationStencil::dump(js::JSONPrinter& json) { + json.beginObject(); + dumpFields(json); + json.endObject(); +} + +void CompilationStencil::dumpFields(js::JSONPrinter& json) { + BaseCompilationStencil::dumpFields(json); + + char index[64]; + 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(); + + if (moduleMetadata) { + json.beginObjectProperty("moduleMetadata"); + moduleMetadata->dumpFields(json, this); + json.endObject(); + } + + json.beginObjectProperty("asmJS"); + for (auto iter = asmJS.iter(); !iter.done(); iter.next()) { + SprintfLiteral(index, "ScriptIndex(%u)", iter.get().key().index); + json.formatProperty(index, "asm.js"); + } + json.endObject(); +} + +#endif // defined(DEBUG) || defined(JS_JITSPEW) + +JSAtom* CompilationAtomCache::getExistingAtomAt(ParserAtomIndex index) const { + return atoms_[index]; +} + +JSAtom* CompilationAtomCache::getExistingAtomAt( + JSContext* cx, TaggedParserAtomIndex taggedIndex) const { + if (taggedIndex.isParserAtomIndex()) { + auto index = taggedIndex.toParserAtomIndex(); + return getExistingAtomAt(index); + } + + if (taggedIndex.isWellKnownAtomId()) { + auto index = taggedIndex.toWellKnownAtomId(); + return GetWellKnownAtom(cx, index); + } + + if (taggedIndex.isStaticParserString1()) { + auto index = taggedIndex.toStaticParserString1(); + return cx->staticStrings().getUnit(char16_t(index)); + } + + MOZ_ASSERT(taggedIndex.isStaticParserString2()); + auto index = taggedIndex.toStaticParserString2(); + return cx->staticStrings().getLength2FromIndex(size_t(index)); +} + +JSAtom* CompilationAtomCache::getAtomAt(ParserAtomIndex index) const { + if (size_t(index) >= atoms_.length()) { + return nullptr; + } + return atoms_[index]; +} + +bool CompilationAtomCache::hasAtomAt(ParserAtomIndex index) const { + if (size_t(index) >= atoms_.length()) { + return false; + } + return !!atoms_[index]; +} + +bool CompilationAtomCache::setAtomAt(JSContext* cx, ParserAtomIndex index, + JSAtom* atom) { + if (size_t(index) < atoms_.length()) { + atoms_[index] = atom; + return true; + } + + if (!atoms_.resize(size_t(index) + 1)) { + ReportOutOfMemory(cx); + return false; + } + + atoms_[index] = atom; + return true; +} + +bool CompilationAtomCache::allocate(JSContext* cx, size_t length) { + MOZ_ASSERT(length >= atoms_.length()); + if (length == atoms_.length()) { + return true; + } + + if (!atoms_.resize(length)) { + ReportOutOfMemory(cx); + return false; + } + + return true; +} + +bool CompilationAtomCache::extendIfNecessary(JSContext* cx, size_t length) { + if (length <= atoms_.length()) { + return true; + } + + if (!atoms_.resize(length)) { + ReportOutOfMemory(cx); + 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_); +} + +const ParserAtom* GetWellKnownParserAtomAt(JSContext* cx, + TaggedParserAtomIndex taggedIndex) { + MOZ_ASSERT(!taggedIndex.isParserAtomIndex()); + + if (taggedIndex.isWellKnownAtomId()) { + auto index = taggedIndex.toWellKnownAtomId(); + return cx->runtime()->commonParserNames->getWellKnown(index); + } + + if (taggedIndex.isStaticParserString1()) { + auto index = taggedIndex.toStaticParserString1(); + return WellKnownParserAtoms::getStatic1(index); + } + + MOZ_ASSERT(taggedIndex.isStaticParserString2()); + auto index = taggedIndex.toStaticParserString2(); + return WellKnownParserAtoms::getStatic2(index); +} + +const ParserAtom* CompilationState::getParserAtomAt( + JSContext* cx, TaggedParserAtomIndex taggedIndex) const { + if (taggedIndex.isParserAtomIndex()) { + auto index = taggedIndex.toParserAtomIndex(); + MOZ_ASSERT(index < parserAtoms.entries().length()); + return parserAtoms.entries()[index]->asAtom(); + } + + return GetWellKnownParserAtomAt(cx, taggedIndex); +} + +bool CompilationState::allocateGCThingsUninitialized( + JSContext* cx, ScriptIndex scriptIndex, size_t length, + TaggedScriptThingIndex** cursor) { + MOZ_ASSERT(gcThingData.length() <= UINT32_MAX); + + auto gcThingsOffset = CompilationGCThingIndex(gcThingData.length()); + + if (length > INDEX_LIMIT) { + ReportAllocationOverflow(cx); + return false; + } + uint32_t gcThingsLength = length; + + if (!gcThingData.growByUninitialized(length)) { + js::ReportOutOfMemory(cx); + return false; + } + + if (gcThingData.length() > UINT32_MAX) { + ReportAllocationOverflow(cx); + return false; + } + + ScriptStencil& script = scriptData[scriptIndex]; + script.gcThingsOffset = gcThingsOffset; + script.gcThingsLength = gcThingsLength; + + *cursor = gcThingData.begin() + gcThingsOffset; + return true; +} + +bool CompilationState::appendGCThings( + JSContext* cx, ScriptIndex scriptIndex, + mozilla::Span<const TaggedScriptThingIndex> things) { + MOZ_ASSERT(gcThingData.length() <= UINT32_MAX); + + auto gcThingsOffset = CompilationGCThingIndex(gcThingData.length()); + + if (things.size() > INDEX_LIMIT) { + ReportAllocationOverflow(cx); + return false; + } + uint32_t gcThingsLength = uint32_t(things.size()); + + if (!gcThingData.append(things.data(), things.size())) { + js::ReportOutOfMemory(cx); + return false; + } + + if (gcThingData.length() > UINT32_MAX) { + ReportAllocationOverflow(cx); + return false; + } + + ScriptStencil& script = scriptData[scriptIndex]; + script.gcThingsOffset = gcThingsOffset; + script.gcThingsLength = gcThingsLength; + return true; +} + +const ParserAtom* BaseCompilationStencil::getParserAtomAt( + JSContext* cx, TaggedParserAtomIndex taggedIndex) const { + if (taggedIndex.isParserAtomIndex()) { + auto index = taggedIndex.toParserAtomIndex(); + MOZ_ASSERT(index < parserAtomData.size()); + return parserAtomData[index]->asAtom(); + } + + return GetWellKnownParserAtomAt(cx, taggedIndex); +} diff --git a/js/src/frontend/Stencil.h b/js/src/frontend/Stencil.h new file mode 100644 index 0000000000..7eb4f5bc0b --- /dev/null +++ b/js/src/frontend/Stencil.h @@ -0,0 +1,886 @@ +/* -*- 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/Attributes.h" // MOZ_MUST_USE +#include "mozilla/Maybe.h" // mozilla::{Maybe, Nothing} +#include "mozilla/Range.h" // mozilla::Range +#include "mozilla/Span.h" // mozilla::Span +#include "mozilla/Variant.h" // mozilla::Variant + +#include <stddef.h> // size_t +#include <stdint.h> // char16_t, uint8_t, uint16_t, uint32_t + +#include "frontend/AbstractScopePtr.h" // AbstractScopePtr, ScopeIndex +#include "frontend/FunctionSyntaxKind.h" // FunctionSyntaxKind +#include "frontend/ObjLiteral.h" // ObjLiteralStencil +#include "frontend/ParserAtom.h" // ParserAtom, TaggedParserAtomIndex +#include "frontend/ScriptIndex.h" // ScriptIndex +#include "frontend/TypedIndex.h" // TypedIndex +#include "js/AllocPolicy.h" // SystemAllocPolicy +#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 "util/Text.h" // DuplicateString +#include "vm/BigIntType.h" // ParseBigIntLiteral +#include "vm/FunctionFlags.h" // FunctionFlags +#include "vm/GeneratorAndAsyncKind.h" // GeneratorKind, FunctionAsyncKind +#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 +#include "vm/StencilEnums.h" // ImmutableScriptFlagsEnum + +namespace js { + +class JSONPrinter; +class RegExpObject; + +namespace frontend { + +struct CompilationStencil; +struct CompilationAtomCache; +struct BaseCompilationStencil; +struct CompilationGCOutput; +class RegExpStencil; +class BigIntStencil; +class StencilXDR; + +using BaseParserScopeData = AbstractBaseScopeData<TaggedParserAtomIndex>; +using ParserBindingName = AbstractBindingName<TaggedParserAtomIndex>; + +template <typename Scope> +using ParserScopeSlotInfo = typename Scope::SlotInfo; +using ParserGlobalScopeSlotInfo = ParserScopeSlotInfo<GlobalScope>; +using ParserEvalScopeSlotInfo = ParserScopeSlotInfo<EvalScope>; +using ParserLexicalScopeSlotInfo = ParserScopeSlotInfo<LexicalScope>; +using ParserFunctionScopeSlotInfo = ParserScopeSlotInfo<FunctionScope>; +using ParserModuleScopeSlotInfo = ParserScopeSlotInfo<ModuleScope>; +using ParserVarScopeSlotInfo = ParserScopeSlotInfo<VarScope>; + +using ParserBindingIter = AbstractBindingIter<TaggedParserAtomIndex>; + +// [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 +// ------------------ +// Parsing a single JavaScript file may generate a tree of `ScriptStencil` that +// we then package up into the `CompilationStencil` type. This contains a series +// of vectors segregated by stencil type for fast processing. Delazifying a +// function will generate its bytecode but some fields remain unchanged from the +// initial lazy parse. We use a base class to capture fields that are meaningful +// for both the initial lazy and delazification parse. +// +// struct BaseCompilationStencil { +// FunctionKey functionKey; +// Span<ScriptStencil> scriptData; +// Span<ScopeStencil> scopeData; +// ... +// } +// +// struct CompilationStencil : BaseCompilationStencil { +// LifoAlloc alloc; +// CompilationInput input; +// Span<ScriptStencilExtra> scriptExtra; +// ... +// } +// +// struct CompilationStencilSet : CompilationStencil { +// Span<BaseCompilationStencil> delazifications; +// ... +// } +// +// When we delazify a function that was lazily parsed, we generate a new Stencil +// at the point too. These delazifications can be cached as well. When loading +// back from a cache we group these together in a `CompilationStencilSet`. Note +// that the base class we inherit from provides storage for the initial lazy +// parse and the `delazifications` field is the collection of delazified +// function data that are available. +// +// +// 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<RegExpStencil>; +using BigIntIndex = TypedIndex<BigIntStencil>; +using ObjLiteralIndex = TypedIndex<ObjLiteralStencil>; + +// Index into {CompilationState,BaseCompilationStencil}.gcThingData. +class CompilationGCThingType {}; +using CompilationGCThingIndex = TypedIndex<CompilationGCThingType>; + +FunctionFlags InitialFunctionFlags(FunctionSyntaxKind kind, + GeneratorKind generatorKind, + FunctionAsyncKind asyncKind, + bool isSelfHosting = false, + bool hasUnclonedName = false); + +// 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_; + + 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, + CompilationAtomCache& atomCache) const; + + // This is used by `Reflect.parse` when we need the RegExpObject but are not + // doing a complete instantiation of the BaseCompilationStencil. + RegExpObject* createRegExpAndEnsureAtom( + JSContext* cx, CompilationAtomCache& atomCache, + BaseCompilationStencil& stencil) const; + +#if defined(DEBUG) || defined(JS_JITSPEW) + void dump(); + void dump(JSONPrinter& json, BaseCompilationStencil* stencil); + void dumpFields(JSONPrinter& json, BaseCompilationStencil* stencil); +#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; + + UniqueTwoByteChars buf_; + size_t length_ = 0; + + public: + BigIntStencil() = default; + + MOZ_MUST_USE bool init(JSContext* cx, const Vector<char16_t, 32>& 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 + length_ = buf.length(); + buf_ = js::DuplicateString(cx, buf.begin(), buf.length()); + return buf_ != nullptr; + } + + BigInt* createBigInt(JSContext* cx) const { + mozilla::Range<const char16_t> source(buf_.get(), length_); + + return js::ParseBigIntLiteral(cx, source); + } + + bool isZero() const { + mozilla::Range<const char16_t> source(buf_.get(), length_); + return js::BigIntLiteralIsZero(source); + } + +#if defined(DEBUG) || defined(JS_JITSPEW) + void dump(); + void dump(JSONPrinter& json); + void dumpCharsNoQuote(GenericPrinter& out); +#endif +}; + +class ScopeStencil { + friend class StencilXDR; + + // 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. + 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<ScopeIndex> enclosing, + uint32_t firstFrameSlot, + mozilla::Maybe<uint32_t> numEnvironmentSlots, + mozilla::Maybe<ScriptIndex> 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. + mozilla::Unused << padding_; + } + + private: + // Create ScopeStencil with `args`, and append ScopeStencil and `data` to + // `compilationState`, and return the index of them as `indexOut`. + template <typename... Args> + static bool appendScopeStencilAndData(JSContext* cx, + CompilationState& compilationState, + BaseParserScopeData* data, + ScopeIndex* indexOut, Args&&... args); + + public: + static bool createForFunctionScope( + JSContext* cx, CompilationStencil& stencil, + CompilationState& compilationState, FunctionScope::ParserData* dataArg, + bool hasParameterExprs, bool needsEnvironment, ScriptIndex functionIndex, + bool isArrow, mozilla::Maybe<ScopeIndex> enclosing, ScopeIndex* index); + + static bool createForLexicalScope(JSContext* cx, CompilationStencil& stencil, + CompilationState& compilationState, + ScopeKind kind, + LexicalScope::ParserData* dataArg, + uint32_t firstFrameSlot, + mozilla::Maybe<ScopeIndex> enclosing, + ScopeIndex* index); + + static bool createForVarScope(JSContext* cx, CompilationStencil& stencil, + CompilationState& compilationState, + ScopeKind kind, VarScope::ParserData* dataArg, + uint32_t firstFrameSlot, bool needsEnvironment, + mozilla::Maybe<ScopeIndex> enclosing, + ScopeIndex* index); + + static bool createForGlobalScope(JSContext* cx, CompilationStencil& stencil, + CompilationState& compilationState, + ScopeKind kind, + GlobalScope::ParserData* dataArg, + ScopeIndex* index); + + static bool createForEvalScope(JSContext* cx, CompilationStencil& stencil, + CompilationState& compilationState, + ScopeKind kind, EvalScope::ParserData* dataArg, + mozilla::Maybe<ScopeIndex> enclosing, + ScopeIndex* index); + + static bool createForModuleScope(JSContext* cx, CompilationStencil& stencil, + CompilationState& compilationState, + ModuleScope::ParserData* dataArg, + mozilla::Maybe<ScopeIndex> enclosing, + ScopeIndex* index); + + static bool createForWithScope(JSContext* cx, CompilationStencil& stencil, + CompilationState& compilationState, + mozilla::Maybe<ScopeIndex> 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; + +#if defined(DEBUG) || defined(JS_JITSPEW) + void dump(); + void dump(JSONPrinter& json, BaseParserScopeData* baseScopeData, + BaseCompilationStencil* stencil); + void dumpFields(JSONPrinter& json, BaseParserScopeData* baseScopeData, + BaseCompilationStencil* stencil); +#endif + + private: + // Transfer ownership into a new UniquePtr. + template <typename SpecificScopeType> + UniquePtr<typename SpecificScopeType::RuntimeData> createSpecificScopeData( + JSContext* cx, CompilationAtomCache& atomCache, + CompilationGCOutput& gcOutput, BaseParserScopeData* baseData) const; + + template <typename SpecificEnvironmentType> + MOZ_MUST_USE bool createSpecificShape(JSContext* cx, ScopeKind kind, + BaseScopeData* scopeData, + MutableHandleShape shape) const; + + template <typename SpecificScopeType, typename SpecificEnvironmentType> + Scope* createSpecificScope(JSContext* cx, CompilationInput& input, + CompilationGCOutput& gcOutput, + BaseParserScopeData* baseData) const; + + template <typename ScopeT> + static constexpr bool matchScopeKind(ScopeKind kind) { + switch (kind) { + case ScopeKind::Function: { + return std::is_same_v<ScopeT, FunctionScope>; + } + case ScopeKind::Lexical: + case ScopeKind::SimpleCatch: + case ScopeKind::Catch: + case ScopeKind::NamedLambda: + case ScopeKind::StrictNamedLambda: + case ScopeKind::FunctionLexical: + case ScopeKind::ClassBody: { + return std::is_same_v<ScopeT, LexicalScope>; + } + case ScopeKind::FunctionBodyVar: { + return std::is_same_v<ScopeT, VarScope>; + } + case ScopeKind::Global: + case ScopeKind::NonSyntactic: { + return std::is_same_v<ScopeT, GlobalScope>; + } + case ScopeKind::Eval: + case ScopeKind::StrictEval: { + return std::is_same_v<ScopeT, EvalScope>; + } + case ScopeKind::Module: { + return std::is_same_v<ScopeT, ModuleScope>; + } + case ScopeKind::With: { + return std::is_same_v<ScopeT, WithScope>; + } + case ScopeKind::WasmFunction: + case ScopeKind::WasmInstance: { + return false; + } + } + return false; + } +}; + +// See JSOp::Lambda for interepretation of this index. +using FunctionDeclaration = GCThingIndex; +using FunctionDeclarationVector = + Vector<FunctionDeclaration, 0, js::SystemAllocPolicy>; + +// 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: + // | ModuleRequest | ImportEntry | ExportAs | ExportFrom | + // |-----------------------------------------------------| + // specifier | required | required | nullptr | required | + // localName | null | required | required | nullptr | + // importName | null | required | nullptr | required | + // exportName | null | null | required | optional | + TaggedParserAtomIndex specifier; + 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; + + static StencilModuleEntry moduleRequest(TaggedParserAtomIndex specifier, + uint32_t lineno, uint32_t column) { + MOZ_ASSERT(!!specifier); + StencilModuleEntry entry(lineno, column); + entry.specifier = specifier; + return entry; + } + + static StencilModuleEntry importEntry(TaggedParserAtomIndex specifier, + TaggedParserAtomIndex localName, + TaggedParserAtomIndex importName, + uint32_t lineno, uint32_t column) { + MOZ_ASSERT(specifier && localName && importName); + StencilModuleEntry entry(lineno, column); + entry.specifier = specifier; + entry.localName = localName; + entry.importName = importName; + 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(TaggedParserAtomIndex specifier, + TaggedParserAtomIndex importName, + TaggedParserAtomIndex exportName, + uint32_t lineno, uint32_t column) { + // NOTE: The `export * from "mod";` syntax generates nullptr exportName. + MOZ_ASSERT(specifier && importName); + StencilModuleEntry entry(lineno, column); + entry.specifier = specifier; + entry.importName = importName; + entry.exportName = exportName; + return entry; + } +}; + +// Metadata generated by parsing module scripts, including import/export tables. +class StencilModuleMetadata { + public: + using EntryVector = Vector<StencilModuleEntry, 0, js::SystemAllocPolicy>; + + 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, CompilationAtomCache& atomCache, + JS::Handle<ModuleObject*> module) const; + +#if defined(DEBUG) || defined(JS_JITSPEW) + void dump(); + void dump(JSONPrinter& json, BaseCompilationStencil* stencil); + void dumpFields(JSONPrinter& json, BaseCompilationStencil* stencil); +#endif +}; + +// 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); } + + uint32_t* rawData() { 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 { + public: + // Fields for BaseScript. + // Used by: + // * Global script + // * Eval + // * Module + // * non-lazy Function (except asm.js module) + // * lazy Function (cannot be asm.js module) + + uint32_t memberInitializers_ = 0; + + // GCThings are stored into + // {CompilationState,BaseCompilationStencil}.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.) + ScopeIndex lazyFunctionEnclosingScopeIndex_; + + // 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 WasFunctionEmittedFlag = 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 BaseCompilationStencil.sharedData. + static constexpr uint16_t HasSharedDataFlag = 1 << 2; + + // Set if this script has member initializer. + // `memberInitializers_` is valid only if this flag is set. + static constexpr uint16_t HasMemberInitializersFlag = 1 << 3; + + // True if this script is lazy function and has enclosing scope. + // `lazyFunctionEnclosingScopeIndex_` is valid only if this flag is set. + static constexpr uint16_t HasLazyFunctionEnclosingScopeIndexFlag = 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<TaggedScriptThingIndex> gcthings( + BaseCompilationStencil& stencil) const; + + bool wasFunctionEmitted() const { return flags_ & WasFunctionEmittedFlag; } + + void setWasFunctionEmitted() { flags_ |= WasFunctionEmittedFlag; } + + bool allowRelazify() const { return flags_ & AllowRelazifyFlag; } + + void setAllowRelazify() { flags_ |= AllowRelazifyFlag; } + + bool hasSharedData() const { return flags_ & HasSharedDataFlag; } + + void setHasSharedData() { flags_ |= HasSharedDataFlag; } + + bool hasMemberInitializers() const { + return flags_ & HasMemberInitializersFlag; + } + + private: + void setHasMemberInitializers() { flags_ |= HasMemberInitializersFlag; } + + public: + void setMemberInitializers(MemberInitializers member) { + memberInitializers_ = member.serialize(); + setHasMemberInitializers(); + } + + MemberInitializers memberInitializers() const { + MOZ_ASSERT(hasMemberInitializers()); + return MemberInitializers(memberInitializers_); + } + + bool hasLazyFunctionEnclosingScopeIndex() const { + return flags_ & HasLazyFunctionEnclosingScopeIndexFlag; + } + + private: + void setHasLazyFunctionEnclosingScopeIndex() { + flags_ |= HasLazyFunctionEnclosingScopeIndexFlag; + } + + public: + void setLazyFunctionEnclosingScopeIndex(ScopeIndex index) { + lazyFunctionEnclosingScopeIndex_ = index; + setHasLazyFunctionEnclosingScopeIndex(); + } + + ScopeIndex lazyFunctionEnclosingScopeIndex() const { + MOZ_ASSERT(hasLazyFunctionEnclosingScopeIndex()); + return lazyFunctionEnclosingScopeIndex_; + } + +#if defined(DEBUG) || defined(JS_JITSPEW) + void dump(); + void dump(JSONPrinter& json, BaseCompilationStencil* stencil); + void dumpFields(JSONPrinter& json, BaseCompilationStencil* stencil); +#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 `JSFunction::nargs_`. + uint16_t nargs = 0; + + // To make this struct packed, add explicit field for padding. + uint16_t padding_ = 0; + + ScriptStencilExtra() = default; + + bool isModule() const { + return immutableFlags.hasFlag(ImmutableScriptFlagsEnum::IsModule); + } + +#if defined(DEBUG) || defined(JS_JITSPEW) + void dump(); + void dump(JSONPrinter& json); + void dumpFields(JSONPrinter& json); +#endif +}; + +#if defined(DEBUG) || defined(JS_JITSPEW) +void DumpTaggedParserAtomIndex(js::JSONPrinter& json, + TaggedParserAtomIndex taggedIndex, + BaseCompilationStencil* stencil); +#endif + +} /* namespace frontend */ +} /* 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..2a42ba0da0 --- /dev/null +++ b/js/src/frontend/StencilXdr.cpp @@ -0,0 +1,661 @@ +/* -*- 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/OperatorNewExtensions.h" // mozilla::KnownNotNull +#include "mozilla/Variant.h" // mozilla::AsVariant + +#include <stddef.h> // size_t +#include <stdint.h> // uint8_t, uint16_t, uint32_t +#include <type_traits> // std::has_unique_object_representations +#include <utility> // std::forward + +#include "frontend/CompilationInfo.h" // BaseCompilationStencil +#include "frontend/ScriptIndex.h" // ScriptIndex +#include "vm/JSScript.h" // js::CheckCompileOptionsMatch +#include "vm/StencilEnums.h" // js::ImmutableScriptFlagsEnum + +using namespace js; +using namespace js::frontend; + +template <typename NameType> +struct CanEncodeNameType { + static constexpr bool value = false; +}; + +template <> +struct CanEncodeNameType<TaggedParserAtomIndex> { + static constexpr bool value = true; +}; + +template <XDRMode mode, typename ScopeT> +/* static */ XDRResult StencilXDR::ScopeSpecificData( + XDRState<mode>* xdr, BaseParserScopeData*& baseScopeData) { + using SlotInfo = typename ScopeT::SlotInfo; + using ScopeDataT = typename ScopeT::ParserData; + + static_assert(CanEncodeNameType<typename ScopeDataT::NameType>::value); + static_assert(CanCopyDataToDisk<ScopeDataT>::value, + "ScopeData cannot be bulk-copied to disk"); + + static_assert(offsetof(ScopeDataT, slotInfo) == 0, + "slotInfo should be the first field"); + static_assert(offsetof(ScopeDataT, trailingNames) == sizeof(SlotInfo), + "trailingNames should be the second field"); + + MOZ_TRY(xdr->align32()); + + const SlotInfo* slotInfo; + if (mode == XDR_ENCODE) { + ScopeDataT* scopeData = static_cast<ScopeDataT*>(baseScopeData); + slotInfo = &scopeData->slotInfo; + } else { + MOZ_TRY(xdr->peekData(&slotInfo)); + } + + uint32_t totalLength = + sizeof(SlotInfo) + + sizeof(AbstractBindingName<TaggedParserAtomIndex>) * slotInfo->length; + MOZ_TRY(xdr->borrowedData(&baseScopeData, totalLength)); + + return Ok(); +} + +template <XDRMode mode, typename T, size_t N, class AP> +static XDRResult XDRVectorUninitialized(XDRState<mode>* xdr, + Vector<T, N, AP>& 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->cx()); + return xdr->fail(JS::TranscodeResult_Throw); + } + } + + return Ok(); +} + +template <XDRMode mode, typename T, size_t N, class AP> +static XDRResult XDRVectorInitialized(XDRState<mode>* xdr, + Vector<T, N, AP>& 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->cx()); + return xdr->fail(JS::TranscodeResult_Throw); + } + } + + return Ok(); +} + +template <XDRMode mode, typename T, size_t N, class AP> +static XDRResult XDRVectorInitialized(XDRState<mode>* xdr, + Vector<T, N, AP>& 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 <XDRMode mode, typename T, size_t N, class AP> +static XDRResult XDRVectorContent(XDRState<mode>* xdr, Vector<T, N, AP>& vec) { + static_assert(CanCopyDataToDisk<T>::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 <XDRMode mode, typename T> +static XDRResult XDRSpanInitialized(XDRState<mode>* xdr, mozilla::Span<T>& 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 = xdr->stencilAlloc().template newArrayUninitialized<T>(size); + if (!p) { + js::ReportOutOfMemory(xdr->cx()); + 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 <XDRMode mode, typename T> +static XDRResult XDRSpanContent(XDRState<mode>* xdr, mozilla::Span<T>& span, + uint32_t size) { + static_assert(CanCopyDataToDisk<T>::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 (mode == XDR_ENCODE) { + data = span.data(); + } + MOZ_TRY(xdr->borrowedData(&data, sizeof(T) * size)); + if (mode == XDR_DECODE) { + span = mozilla::Span(data, size); + } + } + + return Ok(); +} + +template <XDRMode mode, typename T> +static XDRResult XDRSpanContent(XDRState<mode>* xdr, mozilla::Span<T>& 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, span, size); +} + +template <XDRMode mode> +static XDRResult XDRStencilModuleMetadata(XDRState<mode>* xdr, + StencilModuleMetadata& stencil) { + MOZ_TRY(XDRVectorContent(xdr, stencil.requestedModules)); + MOZ_TRY(XDRVectorContent(xdr, stencil.importEntries)); + MOZ_TRY(XDRVectorContent(xdr, stencil.localExportEntries)); + MOZ_TRY(XDRVectorContent(xdr, stencil.indirectExportEntries)); + MOZ_TRY(XDRVectorContent(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 <XDRMode mode> +/* static */ XDRResult StencilXDR::ScopeData( + XDRState<mode>* xdr, ScopeStencil& stencil, + BaseParserScopeData*& baseScopeData) { + // 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. + switch (stencil.kind_) { + // FunctionScope + case ScopeKind::Function: { + // Extra parentheses is for template parameters inside macro. + MOZ_TRY((StencilXDR::ScopeSpecificData<mode, FunctionScope>( + xdr, baseScopeData))); + break; + } + + // VarScope + case ScopeKind::FunctionBodyVar: { + MOZ_TRY( + (StencilXDR::ScopeSpecificData<mode, VarScope>(xdr, baseScopeData))); + break; + } + + // LexicalScope + case ScopeKind::Lexical: + case ScopeKind::SimpleCatch: + case ScopeKind::Catch: + case ScopeKind::NamedLambda: + case ScopeKind::StrictNamedLambda: + case ScopeKind::FunctionLexical: + case ScopeKind::ClassBody: { + MOZ_TRY((StencilXDR::ScopeSpecificData<mode, LexicalScope>( + xdr, baseScopeData))); + break; + } + + // WithScope + case ScopeKind::With: { + // With scopes carry no scope data. + break; + } + + // EvalScope + case ScopeKind::Eval: + case ScopeKind::StrictEval: { + MOZ_TRY( + (StencilXDR::ScopeSpecificData<mode, EvalScope>(xdr, baseScopeData))); + break; + } + + // GlobalScope + case ScopeKind::Global: + case ScopeKind::NonSyntactic: { + MOZ_TRY((StencilXDR::ScopeSpecificData<mode, GlobalScope>( + xdr, baseScopeData))); + break; + } + + // ModuleScope + case ScopeKind::Module: { + MOZ_TRY((StencilXDR::ScopeSpecificData<mode, ModuleScope>( + xdr, baseScopeData))); + break; + } + + // WasmInstanceScope & WasmFunctionScope should not appear in stencils. + case ScopeKind::WasmInstance: + case ScopeKind::WasmFunction: + default: + MOZ_ASSERT_UNREACHABLE("XDR unrecognized ScopeKind."); + } + + return Ok(); +} + +template <XDRMode mode> +/* static */ XDRResult StencilXDR::ObjLiteral(XDRState<mode>* xdr, + ObjLiteralStencil& stencil) { + uint8_t flags = 0; + + if (mode == XDR_ENCODE) { + flags = stencil.flags_.serialize(); + } + MOZ_TRY(xdr->codeUint8(&flags)); + if (mode == XDR_DECODE) { + stencil.flags_.deserialize(flags); + } + + MOZ_TRY(XDRSpanContent(xdr, stencil.code_)); + + return Ok(); +} + +template <XDRMode mode> +/* static */ XDRResult StencilXDR::BigInt(XDRState<mode>* xdr, + BigIntStencil& stencil) { + uint64_t length; + + if (mode == XDR_ENCODE) { + length = stencil.length_; + } + + MOZ_TRY(xdr->codeUint64(&length)); + + XDRTranscodeString<char16_t> chars; + + if (mode == XDR_DECODE) { + stencil.buf_ = xdr->cx()->template make_pod_array<char16_t>(length); + if (!stencil.buf_) { + return xdr->fail(JS::TranscodeResult_Throw); + } + stencil.length_ = length; + } + + return xdr->codeChars(stencil.buf_.get(), stencil.length_); +} + +template <XDRMode mode> +/* static */ +XDRResult StencilXDR::SharedData(XDRState<mode>* xdr, + RefPtr<SharedImmutableScriptData>& sisd) { + if (mode == XDR_ENCODE) { + MOZ_TRY(XDRImmutableScriptData<mode>(xdr, sisd->isd_)); + } else { + JSContext* cx = xdr->cx(); + UniquePtr<SharedImmutableScriptData> data( + SharedImmutableScriptData::create(cx)); + if (!data) { + return xdr->fail(JS::TranscodeResult_Throw); + } + MOZ_TRY(XDRImmutableScriptData<mode>(xdr, data->isd_)); + sisd = data.release(); + } + + return Ok(); +} + +template + /* static */ + XDRResult + StencilXDR::SharedData(XDRState<XDR_ENCODE>* xdr, + RefPtr<SharedImmutableScriptData>& sisd); + +template + /* static */ + XDRResult + StencilXDR::SharedData(XDRState<XDR_DECODE>* xdr, + RefPtr<SharedImmutableScriptData>& sisd); + +namespace js { + +template <XDRMode mode> +XDRResult XDRSharedDataContainer(XDRState<mode>* xdr, + SharedDataContainer& sharedData) { + enum class Kind : uint8_t { + Single, + Vector, + Map, + }; + + uint8_t kind; + if (mode == XDR_ENCODE) { + if (sharedData.isSingle()) { + kind = uint8_t(Kind::Single); + } else if (sharedData.isVector()) { + kind = uint8_t(Kind::Vector); + } else { + MOZ_ASSERT(sharedData.isMap()); + kind = uint8_t(Kind::Map); + } + } + MOZ_TRY(xdr->codeUint8(&kind)); + + switch (Kind(kind)) { + case Kind::Single: { + RefPtr<SharedImmutableScriptData> ref; + if (mode == XDR_ENCODE) { + ref = sharedData.asSingle(); + } + MOZ_TRY(StencilXDR::SharedData<mode>(xdr, ref)); + if (mode == XDR_DECODE) { + sharedData.setSingle(ref.forget()); + } + break; + } + + case Kind::Vector: { + if (mode == XDR_DECODE) { + if (!sharedData.initVector(xdr->cx())) { + 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. + uint8_t exists; + if (mode == XDR_ENCODE) { + exists = !!entry; + } + + MOZ_TRY(xdr->codeUint8(&exists)); + + if (exists) { + MOZ_TRY(StencilXDR::SharedData<mode>(xdr, entry)); + } + } + break; + } + + case Kind::Map: { + if (mode == XDR_DECODE) { + if (!sharedData.initMap(xdr->cx())) { + 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->cx()); + 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(StencilXDR::SharedData<mode>(xdr, data)); + } + } else { + for (uint32_t i = 0; i < count; i++) { + ScriptIndex index; + MOZ_TRY(xdr->codeUint32(&index.index)); + + RefPtr<SharedImmutableScriptData> data; + MOZ_TRY(StencilXDR::SharedData<mode>(xdr, data)); + + if (!map.putNew(index, data)) { + js::ReportOutOfMemory(xdr->cx()); + return xdr->fail(JS::TranscodeResult_Throw); + } + } + } + + break; + } + } + + return Ok(); +} + +template <XDRMode mode> +XDRResult XDRBaseCompilationStencilSpanSize( + XDRState<mode>* xdr, uint32_t* scriptSize, uint32_t* gcThingSize, + uint32_t* scopeSize, 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 { + // The scriptSize, gcThingSize, and scopeSize fit in 1 byte, and others have + // a value of 0. The entire section takes 4 bytes, and expect no padding. + Base8Kind, + + // All of the size values can fit in 1 byte each. The entire section takes 7 + // bytes, and expect 1 byte padding. + All8Kind, + + // Other. This case is less than 1% in practice and indicates the stencil is + // already quite large, so don't try to compress. Expect 3 bytes padding for + // `sizeKind`. + All32Kind, + }; + + uint8_t sizeKind = All32Kind; + if (mode == XDR_ENCODE) { + uint32_t mask_base = (*scriptSize) | (*gcThingSize) | (*scopeSize); + uint32_t mask_ext = (*regExpSize) | (*bigIntSize) | (*objLiteralSize); + + if (mask_base <= 0xff) { + if (mask_ext == 0x00) { + sizeKind = Base8Kind; + } else if (mask_ext <= 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(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 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); + 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)); + + if (sizeKind == All8Kind) { + MOZ_TRY(xdr->codeUint8(®ExpSize8)); + MOZ_TRY(xdr->codeUint8(&bigIntSize8)); + MOZ_TRY(xdr->codeUint8(&objLiteralSize8)); + } else { + MOZ_ASSERT(regExpSize8 == 0); + MOZ_ASSERT(bigIntSize8 == 0); + MOZ_ASSERT(objLiteralSize8 == 0); + } + + if (mode == XDR_DECODE) { + *scriptSize = scriptSize8; + *gcThingSize = gcThingSize8; + *scopeSize = scopeSize8; + *regExpSize = regExpSize8; + *bigIntSize = bigIntSize8; + *objLiteralSize = objLiteralSize8; + } + } + + return Ok(); +} + +template <XDRMode mode> +XDRResult XDRBaseCompilationStencil(XDRState<mode>* xdr, + BaseCompilationStencil& stencil) { + MOZ_TRY(xdr->codeUint32(&stencil.functionKey)); + + uint32_t scriptSize, gcThingSize, scopeSize; + 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()); + + regExpSize = stencil.regExpData.size(); + bigIntSize = stencil.bigIntData.length(); + objLiteralSize = stencil.objLiteralData.length(); + } + MOZ_TRY(XDRBaseCompilationStencilSpanSize(xdr, &scriptSize, &gcThingSize, + &scopeSize, ®ExpSize, + &bigIntSize, &objLiteralSize)); + + // All of the vector-indexed data elements referenced by the + // main script tree must be materialized first. + + MOZ_TRY(XDRSpanContent(xdr, stencil.scopeData, scopeSize)); + MOZ_TRY(XDRSpanInitialized(xdr, stencil.scopeNames, scopeSize)); + MOZ_ASSERT(stencil.scopeData.size() == stencil.scopeNames.size()); + for (uint32_t i = 0; i < scopeSize; i++) { + MOZ_TRY(StencilXDR::ScopeData(xdr, stencil.scopeData[i], + stencil.scopeNames[i])); + } + + MOZ_TRY(XDRSpanContent(xdr, stencil.regExpData, regExpSize)); + + MOZ_TRY(XDRVectorInitialized(xdr, stencil.bigIntData, bigIntSize)); + for (auto& entry : stencil.bigIntData) { + MOZ_TRY(StencilXDR::BigInt(xdr, entry)); + } + + MOZ_TRY(XDRVectorInitialized(xdr, stencil.objLiteralData, objLiteralSize)); + for (auto& entry : stencil.objLiteralData) { + MOZ_TRY(StencilXDR::ObjLiteral(xdr, entry)); + } + + MOZ_TRY(XDRSharedDataContainer(xdr, stencil.sharedData)); + + MOZ_TRY(XDRSpanContent(xdr, stencil.gcThingData, gcThingSize)); + + // Now serialize the vector of ScriptStencils. + + MOZ_TRY(XDRSpanContent(xdr, stencil.scriptData, scriptSize)); + + return Ok(); +} + +template XDRResult XDRBaseCompilationStencil(XDRState<XDR_ENCODE>* xdr, + BaseCompilationStencil& stencil); + +template XDRResult XDRBaseCompilationStencil(XDRState<XDR_DECODE>* xdr, + BaseCompilationStencil& stencil); + +template <XDRMode mode> +XDRResult XDRCompilationStencil(XDRState<mode>* xdr, + CompilationStencil& stencil) { + if (!stencil.asmJS.empty()) { + return xdr->fail(JS::TranscodeResult_Failure_AsmJSNotSupported); + } + + MOZ_TRY(XDRBaseCompilationStencil(xdr, stencil)); + + MOZ_TRY(XDRSpanContent(xdr, stencil.scriptExtra)); + + // We don't support coding non-initial CompilationStencil. + MOZ_ASSERT(stencil.isInitialStencil()); + + if (stencil.scriptExtra[CompilationStencil::TopLevelIndex].isModule()) { + if (mode == XDR_DECODE) { + stencil.moduleMetadata.emplace(); + } + + MOZ_TRY(XDRStencilModuleMetadata(xdr, *stencil.moduleMetadata)); + } + + return Ok(); +} + +template XDRResult XDRCompilationStencil(XDRState<XDR_ENCODE>* xdr, + CompilationStencil& stencil); + +template XDRResult XDRCompilationStencil(XDRState<XDR_DECODE>* xdr, + CompilationStencil& stencil); + +} // namespace js diff --git a/js/src/frontend/StencilXdr.h b/js/src/frontend/StencilXdr.h new file mode 100644 index 0000000000..1fd96a1249 --- /dev/null +++ b/js/src/frontend/StencilXdr.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_StencilXdr_h +#define frontend_StencilXdr_h + +#include "frontend/ObjLiteral.h" // ObjLiteralStencil +#include "frontend/Stencil.h" // *Stencil +#include "vm/Scope.h" // Scope, ScopeKindString +#include "vm/Xdr.h" // XDRMode, XDRResult, XDREncoder + +namespace js { +namespace frontend { + +// Check that we can copy data to disk and restore it in another instance of +// the program in a different address space. +template <typename DataT> +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<DataT>(); +#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 { + public: + template <XDRMode mode> + static XDRResult ScopeData(XDRState<mode>* xdr, ScopeStencil& stencil, + BaseParserScopeData*& baseScopeData); + + template <XDRMode mode, typename ScopeT> + static XDRResult ScopeSpecificData(XDRState<mode>* xdr, + BaseParserScopeData*& baseScopeData); + + template <XDRMode mode> + static XDRResult ObjLiteral(XDRState<mode>* xdr, ObjLiteralStencil& stencil); + + template <XDRMode mode> + static XDRResult BigInt(XDRState<mode>* xdr, BigIntStencil& stencil); + + template <XDRMode mode> + static XDRResult SharedData(js::XDRState<mode>* xdr, + RefPtr<SharedImmutableScriptData>& sisd); +}; + +} /* namespace frontend */ +} /* 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..c7282fb1e5 --- /dev/null +++ b/js/src/frontend/SwitchEmitter.cpp @@ -0,0 +1,410 @@ +/* -*- 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 <algorithm> // 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; + +using mozilla::Maybe; + +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_->cx); + 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(const Maybe<uint32_t>& switchPos) { + MOZ_ASSERT(state_ == State::Start); + switchPos_ = switchPos; + + if (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_->cx); + 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_->cx); + 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<BytecodeOffset> 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; +} diff --git a/js/src/frontend/SwitchEmitter.h b/js/src/frontend/SwitchEmitter.h new file mode 100644 index 0000000000..fb601c28cc --- /dev/null +++ b/js/src/frontend/SwitchEmitter.h @@ -0,0 +1,466 @@ +/* -*- 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, MOZ_MUST_USE +#include "mozilla/Maybe.h" // mozilla::Maybe + +#include <stddef.h> // size_t +#include <stdint.h> // 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 "gc/Rooting.h" // Handle +#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(Some(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(Some(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(Some(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(Some(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(Some(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(Some(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<js::Vector<size_t, 128, SystemAllocPolicy>> 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; } + MOZ_MUST_USE bool isValid() const { return valid_; } + MOZ_MUST_USE bool isInvalid() const { return !valid_; } + + // Add the given number to the table. The number is the value of + // `expr` for `case expr:` syntax. + MOZ_MUST_USE 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<TDZCheckCache> tdzCacheLexical_; + mozilla::Maybe<EmitterScope> emitterScope_; + + // Instantiated while emitting case expression and case/default body. + mozilla::Maybe<TDZCheckCache> tdzCacheCaseAndBody_; + + // Control for switch. + mozilla::Maybe<BreakableControl> controlInfo_; + + mozilla::Maybe<uint32_t> switchPos_; + + // Cond Switch: + // Offset of each JSOp::Case. + // Table Switch: + // Offset of each JSOp::JumpTarget for case. + js::Vector<BytecodeOffset, 32, SystemAllocPolicy> 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 |-+ | + // | +-------------+ | + // | | + // +-------------------------------------+ + // + 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 + // + // Can be Nothing() if not available. + MOZ_MUST_USE bool emitDiscriminant(const mozilla::Maybe<uint32_t>& switchPos); + + // `caseCount` should be the number of cases in the switch statement, + // excluding the default case. + MOZ_MUST_USE 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. + MOZ_MUST_USE bool emitLexical(LexicalScope::ParserData* bindings); + + MOZ_MUST_USE bool emitCond(); + MOZ_MUST_USE bool emitTable(const TableGenerator& tableGen); + + MOZ_MUST_USE bool prepareForCaseValue(); + MOZ_MUST_USE bool emitCaseJump(); + + MOZ_MUST_USE bool emitCaseBody(); + MOZ_MUST_USE bool emitCaseBody(int32_t caseValue, + const TableGenerator& tableGen); + MOZ_MUST_USE bool emitDefaultBody(); + MOZ_MUST_USE bool emitEnd(); + + private: + MOZ_MUST_USE bool emitCaseOrDefaultJump(uint32_t caseIndex, bool isDefault); + MOZ_MUST_USE bool emitImplicitDefault(); +}; + +} /* 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..0c93376700 --- /dev/null +++ b/js/src/frontend/SyntaxParseHandler.h @@ -0,0 +1,733 @@ +/* -*- 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/Attributes.h" +#include "mozilla/Maybe.h" // mozilla::Maybe + +#include <string.h> + +#include "frontend/FunctionSyntaxKind.h" // FunctionSyntaxKind +#include "frontend/NameAnalysisTypes.h" // PrivateNameKind +#include "frontend/ParseNode.h" +#include "frontend/TokenStream.h" +#include "js/GCAnnotations.h" +#include "vm/JSContext.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. + const ParserAtom* 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 + NodePrivateElement, + + // 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 isPropertyAccess(Node node) { + return node == NodeDottedProperty || node == NodeElement || + node == NodePrivateElement; + } + + bool isOptionalPropertyAccess(Node node) { + return node == NodeOptionalDottedProperty || node == NodeOptionalElement; + } + + 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(JSContext* cx, LifoAlloc& alloc, + BaseScript* lazyOuterFunction) + : lastAtom(nullptr) {} + + 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(const ParserName* name, const TokenPos& pos, + JSContext* cx) { + lastAtom = name; + if (name == cx->parserNames().arguments) { + return NodeArgumentsName; + } + if (pos.begin + strlen("async") == pos.end && + name == cx->parserNames().async) { + return NodePotentialAsyncKeyword; + } + if (name == cx->parserNames().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(const ParserAtom* atom, + const TokenPos& pos) { + return NodeName; + } + + NameNodeType newPrivateName(const ParserAtom* 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(const ParserAtom* atom, const TokenPos& pos) { + lastAtom = atom; + lastStringPos = pos; + return NodeUnparenthesizedString; + } + + NameNodeType newTemplateStringLiteral(const ParserAtom* 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; + } + + Node newElision() { 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; + } + MOZ_MUST_USE bool addElision(ListNodeType literal, const TokenPos& pos) { + return true; + } + MOZ_MUST_USE 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; + } + 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, + const TokenPos& pos) { + return NodeGeneric; + } + + LexicalScopeNodeType newLexicalScope(Node body) { + return NodeLexicalDeclaration; + } + + BinaryNodeType newNewTarget(NullaryNodeType newHolder, + NullaryNodeType targetHolder) { + return NodeGeneric; + } + NullaryNodeType newPosHolder(const TokenPos& pos) { return NodeGeneric; } + UnaryNodeType newSuperBase(Node thisName, const TokenPos& pos) { + return NodeSuperBase; + } + + MOZ_MUST_USE 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) {} + MOZ_MUST_USE bool addPropertyDefinition(ListNodeType literal, Node key, + Node expr) { + return true; + } + MOZ_MUST_USE bool addShorthand(ListNodeType literal, NameNodeType name, + NameNodeType expr) { + return true; + } + MOZ_MUST_USE bool addSpreadProperty(ListNodeType literal, uint32_t begin, + Node inner) { + return true; + } + MOZ_MUST_USE bool addObjectMethodDefinition(ListNodeType literal, Node key, + FunctionNodeType funNode, + AccessorType atype) { + return true; + } + MOZ_MUST_USE Node newClassMethodDefinition( + Node key, FunctionNodeType funNode, AccessorType atype, bool isStatic, + mozilla::Maybe<FunctionNodeType> initializerIfPrivate) { + return NodeGeneric; + } + MOZ_MUST_USE Node newClassFieldDefinition(Node name, + FunctionNodeType initializer, + bool isStatic) { + return NodeGeneric; + } + MOZ_MUST_USE 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) {} + MOZ_MUST_USE bool prependInitialYield(ListNodeType stmtList, Node genName) { + return true; + } + NullaryNodeType newEmptyStatement(const TokenPos& pos) { + return NodeEmptyStatement; + } + + UnaryNodeType newExportDeclaration(Node kid, const TokenPos& pos) { + return NodeGeneric; + } + BinaryNodeType newExportFromDeclaration(uint32_t begin, Node exportSpecSet, + Node moduleSpec) { + return NodeGeneric; + } + BinaryNodeType newExportDefaultDeclaration(Node kid, Node maybeBinding, + const TokenPos& pos) { + return NodeGeneric; + } + BinaryNodeType newExportSpec(Node bindingName, 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 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(const ParserName* label, + const TokenPos& pos) { + return NodeGeneric; + } + BreakStatementType newBreakStatement(const ParserName* 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(const ParserName* 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(const ParserName* 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) { + if (isPrivateName(index)) { + return NodePrivateElement; + } + return NodeElement; + } + + PropertyByValueType newOptionalPropertyByValue(Node lhs, Node index, + uint32_t end) { + return NodeOptionalElement; + } + + MOZ_MUST_USE bool setupCatchScope(LexicalScopeNodeType lexicalScope, + Node catchName, Node catchBody) { + return true; + } + + MOZ_MUST_USE bool setLastFunctionFormalParameterDefault( + FunctionNodeType funNode, Node defaultValue) { + return true; + } + + void checkAndSetIsDirectRHSAnonFunction(Node pn) {} + + 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, + ListNodeType 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); + return NodeGeneric; + } + + ListNodeType newList(ParseNodeKind kind, Node kid) { + return newList(kind, TokenPos()); + } + + ListNodeType newDeclarationList(ParseNodeKind kind, const TokenPos& pos) { + if (kind == ParseNodeKind::VarStmt) { + return NodeVarDeclaration; + } + MOZ_ASSERT(kind == ParseNodeKind::LetDecl || + kind == ParseNodeKind::ConstDecl); + return NodeLexicalDeclaration; + } + + bool isDeclarationList(Node node) { + return node == NodeVarDeclaration || node == NodeLexicalDeclaration; + } + + // This method should only be called from parsers using FullParseHandler. + Node singleBindingFromDeclaration(ListNodeType decl) = delete; + + 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; + } + + 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) {} + MOZ_MUST_USE 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 <typename NodeType> + MOZ_MUST_USE 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, JSContext* cx) { + return node == NodeArgumentsName; + } + + bool isEvalName(Node node, JSContext* cx) { return node == NodeEvalName; } + + bool isAsyncKeyword(Node node, JSContext* cx) { + return node == NodePotentialAsyncKeyword; + } + + bool isPrivateName(Node node) { return node == NodePrivateName; } + bool isPrivateField(Node node) { return node == NodePrivateElement; } + + const ParserName* 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 nullptr; + } + return lastAtom->asName(); + } + + const ParserAtom* isStringExprStatement(Node pn, TokenPos* pos) { + if (pn == NodeStringExprStatement) { + *pos = lastStringPos; + return lastAtom; + } + return nullptr; + } + + bool canSkipLazyInnerFunctions() { return false; } + bool canSkipLazyClosedOverBindings() { return false; } + JSAtom* 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..6cb4abfc50 --- /dev/null +++ b/js/src/frontend/TDZCheckCache.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/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<TDZCheckCache>(&bce->innermostTDZCheckCache), + cache_(bce->cx->frontendCollectionPool()) {} + +bool TDZCheckCache::ensureCache(BytecodeEmitter* bce) { + return cache_ || cache_.acquire(bce->cx); +} + +Maybe<MaybeCheckTDZ> TDZCheckCache::needsTDZCheck(BytecodeEmitter* bce, + const ParserAtom* 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->cx); + return Nothing(); + } + + return Some(rv); +} + +bool TDZCheckCache::noteTDZCheck(BytecodeEmitter* bce, const ParserAtom* 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->cx); + return false; + } + } + + return true; +} diff --git a/js/src/frontend/TDZCheckCache.h b/js/src/frontend/TDZCheckCache.h new file mode 100644 index 0000000000..b8c143c80b --- /dev/null +++ b/js/src/frontend/TDZCheckCache.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/. */ + +#ifndef frontend_TDZCheckCache_h +#define frontend_TDZCheckCache_h + +#include "mozilla/Attributes.h" +#include "mozilla/Maybe.h" + +#include "ds/Nestable.h" +#include "frontend/NameCollections.h" +#include "js/TypeDecls.h" +#include "vm/Stack.h" + +namespace js { +namespace frontend { + +struct BytecodeEmitter; + +using CheckTDZMap = RecyclableNameMap<MaybeCheckTDZ>; + +// 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<TDZCheckCache> { + PooledMapPtr<CheckTDZMap> cache_; + + MOZ_MUST_USE bool ensureCache(BytecodeEmitter* bce); + + public: + explicit TDZCheckCache(BytecodeEmitter* bce); + + mozilla::Maybe<MaybeCheckTDZ> needsTDZCheck(BytecodeEmitter* bce, + const ParserAtom* name); + MOZ_MUST_USE bool noteTDZCheck(BytecodeEmitter* bce, const ParserAtom* name, + MaybeCheckTDZ check); +}; + +} /* namespace frontend */ +} /* namespace js */ + +#endif /* frontend_TDZCheckCache_h */ diff --git a/js/src/frontend/Token.h b/js/src/frontend/Token.h new file mode 100644 index 0000000000..5d8db585d8 --- /dev/null +++ b/js/src/frontend/Token.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/. */ + +/* + * 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 <stdint.h> // uint32_t + +#include "frontend/ParserAtom.h" // js::frontend::{ParserAtom,ParserName} +#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: + // WARNING: TokenStreamPosition assumes that the only GC things a Token + // includes are atoms. DON'T ADD NON-ATOM GC THING POINTERS HERE + // UNLESS YOU ADD ADDITIONAL ROOTING TO THAT CLASS. + + /** The type of this token. */ + TokenKind type; + + /** The token's position in the overall script. */ + TokenPos pos; + + union { + private: + friend struct Token; + + /** Non-numeric atom. */ + const ParserName* name; + + /** Potentially-numeric atom. */ + const ParserAtom* 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(const ParserName* name) { + MOZ_ASSERT(type == TokenKind::Name || type == TokenKind::PrivateName); + u.name = name; + } + + void setAtom(const ParserAtom* atom) { + MOZ_ASSERT(type == TokenKind::String || type == TokenKind::TemplateHead || + type == TokenKind::NoSubsTemplate); + u.atom = 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 + + const ParserName* name() const { + MOZ_ASSERT(type == TokenKind::Name || type == TokenKind::PrivateName); + return u.name->asName(); // poor-man's type verification + } + + const ParserAtom* 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..c74c287088 --- /dev/null +++ b/js/src/frontend/TokenKind.h @@ -0,0 +1,325 @@ +/* -*- 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 <stdint.h> + +/* + * List of token kinds and their ranges. + * + * The format for each line is: + * + * MACRO(<TOKEN_KIND_NAME>, <DESCRIPTION>) + * + * or + * + * RANGE(<TOKEN_RANGE_NAME>, <TOKEN_KIND_NAME>) + * + * where ; + * <TOKEN_KIND_NAME> is a legal C identifier of the token, that will be used in + * the JS engine source. + * + * <DESCRIPTION> is a string that describe about the token, and will be used in + * error message. + * + * <TOKEN_RANGE_NAME> 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 <TOKEN_RANGE_NAME>, + * should be same as one of <TOKEN_KIND_NAME> 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, "'['") \ + MACRO(RightBracket, "']'") \ + MACRO(LeftCurly, "'{'") \ + 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") \ + \ + /* 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) \ + 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(Pipeline, "'|>'") \ + RANGE(BinOpFirst, Pipeline) \ + MACRO(Coalesce, "'\?\?'") /* escapes to avoid trigraphs warning */ \ + 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'") \ + RANGE(KeywordBinOpLast, In) \ + \ + /* 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; +} + +inline MOZ_MUST_USE bool TokenKindIsKeyword(TokenKind tt) { + return (TokenKind::KeywordFirst <= tt && tt <= TokenKind::KeywordLast) || + (TokenKind::KeywordBinOpFirst <= tt && + tt <= TokenKind::KeywordBinOpLast) || + (TokenKind::KeywordUnOpFirst <= tt && + tt <= TokenKind::KeywordUnOpLast); +} + +inline MOZ_MUST_USE bool TokenKindIsContextualKeyword(TokenKind tt) { + return TokenKind::ContextualKeywordFirst <= tt && + tt <= TokenKind::ContextualKeywordLast; +} + +inline MOZ_MUST_USE bool TokenKindIsFutureReservedWord(TokenKind tt) { + return TokenKind::FutureReservedKeywordFirst <= tt && + tt <= TokenKind::FutureReservedKeywordLast; +} + +inline MOZ_MUST_USE bool TokenKindIsStrictReservedWord(TokenKind tt) { + return TokenKind::StrictReservedKeywordFirst <= tt && + tt <= TokenKind::StrictReservedKeywordLast; +} + +inline MOZ_MUST_USE bool TokenKindIsReservedWordLiteral(TokenKind tt) { + return TokenKind::ReservedWordLiteralFirst <= tt && + tt <= TokenKind::ReservedWordLiteralLast; +} + +inline MOZ_MUST_USE bool TokenKindIsReservedWord(TokenKind tt) { + return TokenKindIsKeyword(tt) || TokenKindIsFutureReservedWord(tt) || + TokenKindIsReservedWordLiteral(tt); +} + +inline MOZ_MUST_USE bool TokenKindIsPossibleIdentifier(TokenKind tt) { + return tt == TokenKind::Name || TokenKindIsContextualKeyword(tt) || + TokenKindIsStrictReservedWord(tt); +} + +inline MOZ_MUST_USE 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..b6bbd85e78 --- /dev/null +++ b/js/src/frontend/TokenStream.cpp @@ -0,0 +1,3850 @@ +/* -*- 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/IntegerTypeTraits.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 <algorithm> +#include <iterator> +#include <stdarg.h> +#include <stdint.h> +#include <stdio.h> +#include <string.h> +#include <type_traits> +#include <utility> + +#include "jsexn.h" +#include "jsnum.h" + +#include "frontend/BytecodeCompiler.h" +#include "frontend/Parser.h" +#include "frontend/ParserAtom.h" +#include "frontend/ReservedWords.h" +#include "js/CharacterEncoding.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/StringBuffer.h" +#include "util/Text.h" +#include "util/Unicode.h" +#include "vm/FrameIter.h" // js::{,NonBuiltin}FrameIter +#include "vm/HelperThreads.h" +#include "vm/JSAtom.h" +#include "vm/JSContext.h" +#include "vm/Realm.h" + +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 +}; + +// Returns a ReservedWordInfo for the specified characters, or nullptr if the +// string is not a reserved word. +template <typename CharT> +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<unsigned char>(*chars++)) { + goto no_match; + } + } while (--length != 0); + return rw; + +no_match: + return nullptr; +} + +template <> +MOZ_ALWAYS_INLINE const ReservedWordInfo* FindReservedWord<Utf8Unit>( + const Utf8Unit* units, size_t length) { + return FindReservedWord(Utf8AsUnsignedChars(units), length); +} + +template <typename CharT> +static const ReservedWordInfo* FindReservedWord( + const CharT* chars, size_t length, + js::frontend::NameVisibility* visibility) { + if (length > 0 && chars[0] == '#') { + *visibility = js::frontend::NameVisibility::Private; + return nullptr; + } + *visibility = js::frontend::NameVisibility::Public; + return FindReservedWord(chars, length); +} + +static const ReservedWordInfo* FindReservedWord( + JSLinearString* str, js::frontend::NameVisibility* visibility) { + JS::AutoCheckCannotGC nogc; + if (str->hasLatin1Chars()) { + return FindReservedWord(str->latin1Chars(nogc), str->length(), visibility); + } + return FindReservedWord(str->twoByteChars(nogc), str->length(), visibility); +} + +static const ReservedWordInfo* FindReservedWord( + const js::frontend::ParserAtomEntry* atom, + js::frontend::NameVisibility* visibility) { + if (atom->hasLatin1Chars()) { + return FindReservedWord(atom->latin1Chars(), atom->length(), visibility); + } + return FindReservedWord(atom->twoByteChars(), atom->length(), visibility); +} + +static uint32_t GetSingleCodePoint(const char16_t** p, const char16_t* end) { + using namespace js; + + uint32_t codePoint; + 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); + } + } + + codePoint = **p; + (*p)++; + return codePoint; +} + +template <typename CharT> +static constexpr bool IsAsciiBinary(CharT c) { + using UnsignedCharT = std::make_unsigned_t<CharT>; + auto uc = static_cast<UnsignedCharT>(c); + return uc == '0' || uc == '1'; +} + +template <typename CharT> +static constexpr bool IsAsciiOctal(CharT c) { + using UnsignedCharT = std::make_unsigned_t<CharT>; + auto uc = static_cast<UnsignedCharT>(c); + return '0' <= uc && uc <= '7'; +} + +template <typename CharT> +static constexpr uint8_t AsciiOctalToNumber(CharT c) { + using UnsignedCharT = std::make_unsigned_t<CharT>; + auto uc = static_cast<UnsignedCharT>(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 IsIdentifier(const ParserAtom* atom) { + MOZ_ASSERT(atom); + return atom->hasLatin1Chars() + ? IsIdentifier(atom->latin1Chars(), atom->length()) + : IsIdentifier(atom->twoByteChars(), atom->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 IsIdentifierNameOrPrivateName(const ParserAtom* atom) { + if (atom->hasLatin1Chars()) { + return IsIdentifierNameOrPrivateName(atom->latin1Chars(), atom->length()); + } + return IsIdentifierNameOrPrivateName(atom->twoByteChars(), atom->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 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; + uint32_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; + uint32_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(const ParserAtom* atom) { + NameVisibility visibility; + if (const ReservedWordInfo* rw = FindReservedWord(atom, &visibility)) { + return TokenKindIsKeyword(rw->tokentype); + } + + return false; +} +bool IsKeyword(JSLinearString* str) { + NameVisibility visibility; + if (const ReservedWordInfo* rw = FindReservedWord(str, &visibility)) { + return TokenKindIsKeyword(rw->tokentype); + } + + return false; +} + +TokenKind ReservedWordTokenKind(const ParserName* name) { + NameVisibility visibility; + if (const ReservedWordInfo* rw = FindReservedWord(name, &visibility)) { + return rw->tokentype; + } + + return visibility == NameVisibility::Private ? TokenKind::PrivateName + : TokenKind::Name; +} + +const char* ReservedWordToCharZ(const ParserName* name) { + NameVisibility visibility; + if (const ReservedWordInfo* rw = FindReservedWord(name, &visibility)) { + 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; +} + +const ParserName* TokenStreamAnyChars::reservedWordToPropertyName( + TokenKind tt) const { + MOZ_ASSERT(tt != TokenKind::Name); + switch (tt) { +#define EMIT_CASE(word, name, type) \ + case type: \ + return cx->parserNames().name; + FOR_EACH_JAVASCRIPT_RESERVED_WORD(EMIT_CASE) +#undef EMIT_CASE + default: + MOZ_ASSERT_UNREACHABLE("Not a reserved word TokenKind."); + } + return nullptr; +} + +SourceCoords::SourceCoords(JSContext* cx, uint32_t initialLineNumber, + uint32_t initialOffset) + : lineStartOffsets_(cx), 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<decltype(lineStartOffsets_.allocPolicy()), + TempAllocPolicy&>, + "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(JSContext* cx, + const ReadOnlyCompileOptions& options, + StrictModeGetter* smg) + : cx(cx), + options_(options), + strictModeGetter_(smg), + filename_(options.filename()), + longLineColumnInfo_(cx), + srcCoords(cx, 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 <typename Unit> +TokenStreamCharsBase<Unit>::TokenStreamCharsBase(JSContext* cx, + ParserAtomsTable* pasrerAtoms, + const Unit* units, + size_t length, + size_t startOffset) + : TokenStreamCharsShared(cx, pasrerAtoms), + 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<char32_t> 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 <typename Unit, class AnyCharsAccess> +TokenStreamSpecific<Unit, AnyCharsAccess>::TokenStreamSpecific( + JSContext* cx, ParserAtomsTable* pasrerAtoms, + const ReadOnlyCompileOptions& options, const Unit* units, size_t length) + : TokenStreamChars<Unit, AnyCharsAccess>(cx, pasrerAtoms, 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, ...) { + va_list args; + va_start(args, errorNumber); + + reportErrorNoOffsetVA(errorNumber, &args); + + va_end(args); +} + +void TokenStreamAnyChars::reportErrorNoOffsetVA(unsigned errorNumber, + va_list* args) { + ErrorMetadata metadata; + computeErrorMetadataNoOffset(&metadata); + + ReportCompileErrorLatin1(cx, std::move(metadata), nullptr, errorNumber, args); +} + +// Use the fastest available getc. +#if defined(HAVE_GETC_UNLOCKED) +# define fast_getc getc_unlocked +#elif defined(HAVE__GETC_NOLOCK) +# define fast_getc _getc_nolock +#else +# define fast_getc getc +#endif + +MOZ_MUST_USE 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<char16_t>::assertNextCodePoint( + const PeekedCodePoint<char16_t>& 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<Utf8Unit>::assertNextCodePoint( + const PeekedCodePoint<Utf8Unit>& 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<uint8_t>(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 <typename Unit> +uint32_t TokenStreamAnyChars::computePartialColumn( + const LineToken lineToken, const uint32_t offset, + const SourceUnits<Unit>& 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<uint32_t>(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<uint32_t>(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<ChunkInfo>| 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<ChunkInfo>(cx))) { + // In case of OOM, just count columns from the start of the line. + cx->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<uint32_t>(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. + cx->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<Unit>::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 <typename Unit, class AnyCharsAccess> +uint32_t GeneralTokenStreamChars<Unit, AnyCharsAccess>::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 <typename Unit, class AnyCharsAccess> +void GeneralTokenStreamChars<Unit, AnyCharsAccess>::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 <class AnyCharsAccess> +MOZ_COLD void TokenStreamChars<Utf8Unit, AnyCharsAccess>::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<JSErrorNotes>(); + if (!notes) { + ReportOutOfMemory(anyChars.cx); + 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.cx, anyChars.getFilename(), 0, line, + column, GetErrorMessage, nullptr, + JSMSG_BAD_CODE_UNITS, badUnitsStr)) { + break; + } + + ReportCompileErrorLatin1(anyChars.cx, std::move(err), std::move(notes), + errorNumber, &args); + } while (false); + + va_end(args); +} + +template <class AnyCharsAccess> +MOZ_COLD void TokenStreamChars<Utf8Unit, AnyCharsAccess>::badLeadUnit( + Utf8Unit lead) { + uint8_t leadValue = lead.toUint8(); + + char leadByteStr[5]; + byteToTerminatedString(leadValue, leadByteStr); + + internalEncodingError(1, JSMSG_BAD_LEADING_UTF8_UNIT, leadByteStr); +} + +template <class AnyCharsAccess> +MOZ_COLD void TokenStreamChars<Utf8Unit, AnyCharsAccess>::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 <class AnyCharsAccess> +MOZ_COLD void TokenStreamChars<Utf8Unit, AnyCharsAccess>::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 <class AnyCharsAccess> +MOZ_COLD void +TokenStreamChars<Utf8Unit, AnyCharsAccess>::badStructurallyValidCodePoint( + uint32_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 <class AnyCharsAccess> +MOZ_MUST_USE bool +TokenStreamChars<Utf8Unit, AnyCharsAccess>::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<char32_t> maybeCodePoint = DecodeOneUtf8CodePointInline( + lead, &iter, SourceUnitsEnd(), onBadLeadUnit, onNotEnoughUnits, + onBadTrailingUnit, onBadCodePoint, onNotShortestForm); + if (maybeCodePoint.isNothing()) { + return false; + } + + *codePoint = maybeCodePoint.value(); + return true; +} + +template <class AnyCharsAccess> +bool TokenStreamChars<char16_t, AnyCharsAccess>::getNonAsciiCodePoint( + int32_t lead, int32_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 = lead; + + // ECMAScript specifically requires that unpaired UTF-16 surrogates be + // treated as the corresponding code point and not as an error. See + // <https://tc39.github.io/ecma262/#sec-ecmascript-language-types-string-type>. + // 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 + *codePoint = EOF; // sentinel value to hopefully cause errors +#endif + MOZ_MAKE_MEM_UNDEFINED(codePoint, sizeof(*codePoint)); + return false; + } + + *codePoint = '\n'; + } else { + MOZ_ASSERT(!IsLineTerminator(AssertedCast<char32_t>(*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(AssertedCast<char32_t>(*codePoint))); + return true; + } + + // Otherwise we have a multi-unit code point. + *codePoint = unicode::UTF16Decode(lead, this->sourceUnits.getCodeUnit()); + MOZ_ASSERT(!IsLineTerminator(AssertedCast<char32_t>(*codePoint))); + return true; +} + +template <typename Unit, class AnyCharsAccess> +bool TokenStreamSpecific<Unit, AnyCharsAccess>::getCodePoint(int32_t* cp) { + int32_t unit = getCodeUnit(); + if (unit == EOF) { + MOZ_ASSERT(anyCharsAccess().flags.isEOF, + "flags.isEOF should have been set by getCodeUnit()"); + *cp = EOF; + return true; + } + + if (isAsciiCodePoint(unit)) { + return getFullAsciiCodePoint(unit, cp); + } + + return getNonAsciiCodePoint(unit, cp); +} + +template <class AnyCharsAccess> +bool TokenStreamChars<Utf8Unit, AnyCharsAccess>::getNonAsciiCodePoint( + int32_t unit, int32_t* codePoint) { + MOZ_ASSERT(unit != EOF); + MOZ_ASSERT(!isAsciiCodePoint(unit), + "ASCII code unit/point must be handled separately"); + + Utf8Unit lead = Utf8Unit(static_cast<unsigned char>(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<char32_t> 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 + *codePoint = EOF; // sentinel value to hopefully cause errors +#endif + MOZ_MAKE_MEM_UNDEFINED(codePoint, sizeof(*codePoint)); + return false; + } + + *codePoint = '\n'; + } else { + MOZ_ASSERT(!IsLineTerminator(cp)); + *codePoint = AssertedCast<int32_t>(cp); + } + + return true; +} + +template <> +size_t SourceUnits<char16_t>::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<Utf8Unit>::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<char16_t>::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<Utf8Unit>::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<Utf8Unit> 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 <typename Unit, class AnyCharsAccess> +bool TokenStreamSpecific<Unit, AnyCharsAccess>::advance(size_t position) { + const Unit* end = this->sourceUnits.codeUnitPtrAt(position); + while (this->sourceUnits.addressOfNextCodeUnit() < end) { + int32_t c; + if (!getCodePoint(&c)) { + return false; + } + } + + TokenStreamAnyChars& anyChars = anyCharsAccess(); + Token* cur = const_cast<Token*>(&anyChars.currentToken()); + cur->pos.begin = this->sourceUnits.offset(); + cur->pos.end = cur->pos.begin; + MOZ_MAKE_MEM_UNDEFINED(&cur->type, sizeof(cur->type)); + anyChars.lookahead = 0; + return true; +} + +template <typename Unit, class AnyCharsAccess> +void TokenStreamSpecific<Unit, AnyCharsAccess>::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 <typename Unit, class AnyCharsAccess> +bool TokenStreamSpecific<Unit, AnyCharsAccess>::seekTo( + const Position& pos, const TokenStreamAnyChars& other) { + if (!anyCharsAccess().srcCoords.fill(other.srcCoords)) { + return false; + } + + seekTo(pos); + return true; +} + +void TokenStreamAnyChars::computeErrorMetadataNoOffset(ErrorMetadata* err) { + 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) { + err->isMuted = mutedErrors; + + // If this TokenStreamAnyChars doesn't have location information, try to + // get it from the caller. + if (!filename_ && !cx->isHelperThreadContext()) { + NonBuiltinFrameIter iter(cx, FrameIter::FOLLOW_DEBUGGER_EVAL_PREV_LINK, + cx->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 <typename Unit, class AnyCharsAccess> +bool TokenStreamSpecific<Unit, AnyCharsAccess>::hasTokenizationStarted() const { + const TokenStreamAnyChars& anyChars = anyCharsAccess(); + return anyChars.isCurrentTokenType(TokenKind::Eof) && !anyChars.isEOF(); +} + +template <> +inline void SourceUnits<char16_t>::computeWindowOffsetAndLength( + const char16_t* encodedWindow, size_t encodedTokenOffset, + size_t* utf16TokenOffset, size_t encodedWindowLength, + size_t* utf16WindowLength) { + MOZ_ASSERT_UNREACHABLE("shouldn't need to recompute for UTF-16"); +} + +template <> +inline void SourceUnits<Utf8Unit>::computeWindowOffsetAndLength( + const Utf8Unit* encodedWindow, size_t encodedTokenOffset, + size_t* utf16TokenOffset, size_t encodedWindowLength, + size_t* utf16WindowLength) { + 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<char32_t> 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 <typename Unit> +bool TokenStreamCharsBase<Unit>::addLineOfContext(ErrorMetadata* err, + uint32_t offset) { + // 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(cx); + + 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<Unit, char16_t>) { + 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<Unit, Utf8Unit>, "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 <typename Unit, class AnyCharsAccess> +bool TokenStreamSpecific<Unit, AnyCharsAccess>::computeErrorMetadata( + ErrorMetadata* err, const ErrorOffset& errorOffset) { + if (errorOffset.is<NoOffset>()) { + anyCharsAccess().computeErrorMetadataNoOffset(err); + return true; + } + + uint32_t offset; + if (errorOffset.is<uint32_t>()) { + offset = errorOffset.as<uint32_t>(); + } 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 <typename Unit, class AnyCharsAccess> +void TokenStreamSpecific<Unit, AnyCharsAccess>::reportIllegalCharacter( + int32_t cp) { + UniqueChars display = JS_smprintf("U+%04X", cp); + if (!display) { + ReportOutOfMemory(anyCharsAccess().cx); + 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 <typename Unit, class AnyCharsAccess> +uint32_t GeneralTokenStreamChars<Unit, AnyCharsAccess>::matchUnicodeEscape( + uint32_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 <typename Unit, class AnyCharsAccess> +uint32_t +GeneralTokenStreamChars<Unit, AnyCharsAccess>::matchExtendedUnicodeEscape( + uint32_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 <typename Unit, class AnyCharsAccess> +uint32_t +GeneralTokenStreamChars<Unit, AnyCharsAccess>::matchUnicodeEscapeIdStart( + uint32_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 <typename Unit, class AnyCharsAccess> +bool GeneralTokenStreamChars<Unit, AnyCharsAccess>::matchUnicodeEscapeIdent( + uint32_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 <typename Unit, class AnyCharsAccess> +MOZ_MUST_USE bool +TokenStreamSpecific<Unit, AnyCharsAccess>::matchIdentifierStart( + IdentifierEscapes* sawEscape) { + int32_t unit = getCodeUnit(); + if (unicode::IsIdentifierStart(char16_t(unit))) { + ungetCodeUnit(unit); + *sawEscape = IdentifierEscapes::None; + return true; + } + + if (unit == '\\') { + *sawEscape = IdentifierEscapes::SawUnicodeEscape; + + uint32_t codePoint; + uint32_t escapeLength = matchUnicodeEscapeIdStart(&codePoint); + if (escapeLength != 0) { + 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; + } + + *sawEscape = IdentifierEscapes::None; + + // NOTE: |unit| may be EOF here. + ungetCodeUnit(unit); + error(JSMSG_MISSING_PRIVATE_NAME); + return false; +} + +template <typename Unit, class AnyCharsAccess> +bool TokenStreamSpecific<Unit, AnyCharsAccess>::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; +} + +MOZ_MUST_USE bool TokenStreamCharsShared::copyCharBufferTo( + JSContext* cx, UniquePtr<char16_t[], JS::FreePolicy>* destination) { + size_t length = charBuffer.length(); + + *destination = cx->make_pod_array<char16_t>(length + 1); + if (!*destination) { + return false; + } + + std::copy(charBuffer.begin(), charBuffer.end(), destination->get()); + (*destination)[length] = '\0'; + return true; +} + +template <typename Unit, class AnyCharsAccess> +MOZ_MUST_USE bool TokenStreamSpecific<Unit, AnyCharsAccess>::getDirective( + bool isMultiline, bool shouldWarnDeprecated, const char* directive, + uint8_t directiveLength, const char* errorMsgPragma, + UniquePtr<char16_t[], JS::FreePolicy>* 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<Latin1Char>(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<Unit> 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(anyCharsAccess().cx, destination); +} + +template <typename Unit, class AnyCharsAccess> +bool TokenStreamSpecific<Unit, AnyCharsAccess>::getDisplayURL( + bool isMultiline, bool shouldWarnDeprecated) { + // Match comments of the form "//# sourceURL=<url>" or + // "/\* //# sourceURL=<url> *\/" + // + // 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 <typename Unit, class AnyCharsAccess> +bool TokenStreamSpecific<Unit, AnyCharsAccess>::getSourceMappingURL( + bool isMultiline, bool shouldWarnDeprecated) { + // Match comments of the form "//# sourceMappingURL=<url>" or + // "/\* //# sourceMappingURL=<url> *\/" + + static constexpr char sourceMappingURLDirective[] = " sourceMappingURL="; + constexpr uint8_t sourceMappingURLDirectiveLength = + js_strlen(sourceMappingURLDirective); + return getDirective(isMultiline, shouldWarnDeprecated, + sourceMappingURLDirective, + sourceMappingURLDirectiveLength, "sourceMappingURL", + &anyCharsAccess().sourceMapURL_); +} + +template <typename Unit, class AnyCharsAccess> +MOZ_ALWAYS_INLINE Token* +GeneralTokenStreamChars<Unit, AnyCharsAccess>::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 <typename Unit, class AnyCharsAccess> +MOZ_COLD bool GeneralTokenStreamChars<Unit, AnyCharsAccess>::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, uint32_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 <typename Unit, class AnyCharsAccess> +bool TokenStreamSpecific<Unit, AnyCharsAccess>::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; + } + + uint32_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 <typename Unit, class AnyCharsAccess> +MOZ_MUST_USE bool TokenStreamSpecific<Unit, AnyCharsAccess>::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<char16_t>(unit)))) { + // Handle a Unicode escape -- otherwise it's not part of the + // identifier. + uint32_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<Unit> 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); + } + } + + const ParserAtom* atom = nullptr; + 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->asName(), start, modifier, out); + return true; + } + newNameToken(atom->asName(), 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<char16_t>::consumeRestOfSingleLineComment() { + while (MOZ_LIKELY(!atEnd())) { + char16_t unit = peekCodeUnit(); + if (IsLineTerminator(unit)) { + return; + } + + consumeKnownCodeUnit(unit); + } +} + +template <> +void SourceUnits<Utf8Unit>::consumeRestOfSingleLineComment() { + while (MOZ_LIKELY(!atEnd())) { + const Utf8Unit unit = peekCodeUnit(); + if (IsSingleUnitLineTerminator(unit)) { + return; + } + + if (MOZ_LIKELY(IsAscii(unit))) { + consumeKnownCodeUnit(unit); + continue; + } + + PeekedCodePoint<Utf8Unit> 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 <typename Unit, class AnyCharsAccess> +MOZ_MUST_USE MOZ_ALWAYS_INLINE bool +TokenStreamSpecific<Unit, AnyCharsAccess>::matchInteger( + IsIntegerUnit isIntegerUnit, int32_t* nextUnit) { + int32_t unit = getCodeUnit(); + if (!isIntegerUnit(unit)) { + *nextUnit = unit; + return true; + } + return matchIntegerAfterFirstDigit(isIntegerUnit, nextUnit); +} + +template <typename Unit, class AnyCharsAccess> +MOZ_MUST_USE MOZ_ALWAYS_INLINE bool +TokenStreamSpecific<Unit, AnyCharsAccess>::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 == '_') { + error(JSMSG_NUMBER_MULTIPLE_ADJACENT_UNDERSCORES); + } else { + error(JSMSG_NUMBER_END_WITH_UNDERSCORE); + } + return false; + } + } + + *nextUnit = unit; + return true; +} + +template <typename Unit, class AnyCharsAccess> +MOZ_MUST_USE bool TokenStreamSpecific<Unit, AnyCharsAccess>::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(anyCharsAccess().cx, numStart, + this->sourceUnits.addressOfNextCodeUnit(), &dval)) { + 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); + + // "0." and "0e..." numbers parse "." or "e..." here. Neither range + // contains a number, so we can't use |FullStringToDouble|. (Parse + // failures return 0.0, so we'll still get the right result.) + if (!GetDecimalNonInteger(anyCharsAccess().cx, numStart, + this->sourceUnits.addressOfNextCodeUnit(), + &dval)) { + 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<Unit> 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 <typename Unit, class AnyCharsAccess> +MOZ_MUST_USE bool TokenStreamSpecific<Unit, AnyCharsAccess>::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<char32_t>(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 == '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 <typename Unit, class AnyCharsAccess> +MOZ_MUST_USE bool TokenStreamSpecific<Unit, AnyCharsAccess>::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<const Unit> 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 <typename Unit, class AnyCharsAccess> +void GeneralTokenStreamChars<Unit, + AnyCharsAccess>::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 <typename Unit, class AnyCharsAccess> +MOZ_MUST_USE bool TokenStreamSpecific<Unit, AnyCharsAccess>::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<Unit> peeked = this->sourceUnits.peekCodePoint(); + if (peeked.isNone()) { + int32_t bad; + MOZ_ALWAYS_FALSE(getCodePoint(&bad)); + 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<char>(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)) { + // Octal integer literals are not permitted in strict mode code. + if (!strictModeError(JSMSG_DEPRECATED_OCTAL)) { + return badToken(); + } + anyCharsAccess().flags.sawDeprecatedOctal = true; + + 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 == '_') { + error(JSMSG_SEPARATOR_IN_ZERO_PREFIXED_NUMBER); + return badToken(); + } + + if (unit == 'n') { + 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'. + error(JSMSG_SEPARATOR_IN_ZERO_PREFIXED_NUMBER); + return badToken(); + } else { + // '0' not followed by [XxBbOo0-9_]; scan as a decimal number. + numStart = this->sourceUnits.addressOfNextCodeUnit() - 1; + + // NOTE: |unit| may be EOF here. (This is permitted by case #3 + // in TokenStream.h docs for this function.) + return decimalNumber(unit, 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<Unit> 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(anyCharsAccess().cx, numStart, + this->sourceUnits.addressOfNextCodeUnit(), radix, + IntegerSeparatorHandling::SkipUnderscore, &dval)) { + 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<uint8_t>(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 '#': { + if (options().privateClassFields) { + 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); + } + ungetCodeUnit(unit); + error(JSMSG_PRIVATE_FIELDS_NOT_SUPPORTED); + return badToken(); + } + + 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 '\\': { + uint32_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; +#ifdef ENABLE_PIPELINE_OPERATOR + } else if (matchCodeUnit('>')) { + simpleKind = TokenKind::Pipeline; +#endif + } 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))) { + int32_t codePoint; + if (!getFullAsciiCodePoint(unit, &codePoint)) { + return badToken(); + } + } else { + int32_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; + + 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<uint8_t>(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 <typename Unit, class AnyCharsAccess> +bool TokenStreamSpecific<Unit, AnyCharsAccess>::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))) { + int32_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, + AssertedCast<char32_t>(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<uint8_t>(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)) { + 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, then 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)) { + return false; + } + anyChars.flags.sawDeprecatedOctal = true; + } + + 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<uint8_t>(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; + } + } + + const ParserAtom* 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 "<bad TokenKind>"; +} + +#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 "<bad TokenKind>"; +} +#endif + +template class TokenStreamCharsBase<Utf8Unit>; +template class TokenStreamCharsBase<char16_t>; + +template class GeneralTokenStreamChars<char16_t, TokenStreamAnyCharsAccess>; +template class TokenStreamChars<char16_t, TokenStreamAnyCharsAccess>; +template class TokenStreamSpecific<char16_t, TokenStreamAnyCharsAccess>; + +template class GeneralTokenStreamChars< + Utf8Unit, ParserAnyCharsAccess<GeneralParser<FullParseHandler, Utf8Unit>>>; +template class GeneralTokenStreamChars< + Utf8Unit, + ParserAnyCharsAccess<GeneralParser<SyntaxParseHandler, Utf8Unit>>>; +template class GeneralTokenStreamChars< + char16_t, ParserAnyCharsAccess<GeneralParser<FullParseHandler, char16_t>>>; +template class GeneralTokenStreamChars< + char16_t, + ParserAnyCharsAccess<GeneralParser<SyntaxParseHandler, char16_t>>>; + +template class TokenStreamChars< + Utf8Unit, ParserAnyCharsAccess<GeneralParser<FullParseHandler, Utf8Unit>>>; +template class TokenStreamChars< + Utf8Unit, + ParserAnyCharsAccess<GeneralParser<SyntaxParseHandler, Utf8Unit>>>; +template class TokenStreamChars< + char16_t, ParserAnyCharsAccess<GeneralParser<FullParseHandler, char16_t>>>; +template class TokenStreamChars< + char16_t, + ParserAnyCharsAccess<GeneralParser<SyntaxParseHandler, char16_t>>>; + +template class TokenStreamSpecific< + Utf8Unit, ParserAnyCharsAccess<GeneralParser<FullParseHandler, Utf8Unit>>>; +template class TokenStreamSpecific< + Utf8Unit, + ParserAnyCharsAccess<GeneralParser<SyntaxParseHandler, Utf8Unit>>>; +template class TokenStreamSpecific< + char16_t, ParserAnyCharsAccess<GeneralParser<FullParseHandler, char16_t>>>; +template class TokenStreamSpecific< + char16_t, + ParserAnyCharsAccess<GeneralParser<SyntaxParseHandler, char16_t>>>; + +} // namespace frontend + +} // namespace js + +JS_FRIEND_API int js_fgets(char* buf, int size, FILE* file) { + int n, i, c; + bool crflag; + + n = size - 1; + if (n < 0) { + return -1; + } + + crflag = false; + for (i = 0; i < n && (c = fast_getc(file)) != EOF; i++) { + buf[i] = c; + if (c == '\n') { // any \n ends a line + i++; // keep the \n; we know there is room for \0 + break; + } + if (crflag) { // \r not followed by \n ends line at the \r + ungetc(c, file); + break; // and overwrite c in buf with \0 + } + crflag = (c == '\r'); + } + + buf[i] = '\0'; + return i; +} diff --git a/js/src/frontend/TokenStream.h b/js/src/frontend/TokenStream.h new file mode 100644 index 0000000000..f575f15247 --- /dev/null +++ b/js/src/frontend/TokenStream.h @@ -0,0 +1,2976 @@ +/* -*- 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<Unit>, + * TokenStreamChars<Unit>, and TokenStreamSpecific<Unit, AnyCharsAccess>. + * + * == 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 TokenStream<Unit>s 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<Unit> → 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<Unit>. + * + * == SpecializedTokenStreamCharsBase<Unit> → TokenStreamCharsBase<Unit> == + * + * 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<Unit, AnyCharsAccess> → + * SpecializedTokenStreamCharsBase<Unit> == + * + * 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<Unit, AnyCharsAccess>. + * The AnyCharsAccess parameter provides a way for a GeneralTokenStreamChars + * instance to access its corresponding TokenStreamAnyChars, without inheriting + * from it. + * + * GeneralTokenStreamChars<Unit, AnyCharsAccess> is just functionality, no + * actual member data. + * + * Such functionality all lives in TokenStreamChars<Unit, AnyCharsAccess>, a + * declared-but-not-defined template class whose specializations have a common + * public interface (plus whatever private helper functions are desirable). + * + * == TokenStreamChars<Unit, AnyCharsAccess> → + * GeneralTokenStreamChars<Unit, AnyCharsAccess> == + * + * 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<Unit, AnyCharsAccess> specializations, one per Unit, + * are just functionality, no actual member data. + * + * == TokenStreamSpecific<Unit, AnyCharsAccess> → + * TokenStreamChars<Unit, AnyCharsAccess>, 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<ParseHandler, Unit> 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/DebugOnly.h" +#include "mozilla/Maybe.h" +#include "mozilla/MemoryChecking.h" +#include "mozilla/PodOperations.h" +#include "mozilla/Span.h" +#include "mozilla/TextUtils.h" +#include "mozilla/Unused.h" +#include "mozilla/Utf8.h" + +#include <algorithm> +#include <stdarg.h> +#include <stddef.h> +#include <stdint.h> +#include <stdio.h> +#include <type_traits> + +#include "jspubtd.h" + +#include "frontend/ErrorReporter.h" +#include "frontend/ParserAtom.h" +#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/Text.h" +#include "util/Unicode.h" +#include "vm/ErrorReporting.h" +#include "vm/JSAtom.h" +#include "vm/StringType.h" + +struct JS_PUBLIC_API JSContext; +struct KeywordInfo; + +namespace js { + +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<int32_t>::max() / 2; + +extern TokenKind ReservedWordTokenKind(const ParserName* name); + +extern const char* ReservedWordToCharZ(const ParserName* name); + +extern const char* ReservedWordToCharZ(TokenKind tt); + +struct TokenStreamFlags { + // Hit end of file. + bool isEOF : 1; + // Non-whitespace since start of line. + bool isDirtyLine : 1; + // Saw an octal character escape or a 0-prefixed octal literal. + bool sawDeprecatedOctal : 1; + // Hit a syntax error, at start or during a token. + bool hadError : 1; + + TokenStreamFlags() + : isEOF(false), + isDirtyLine(false), + sawDeprecatedOctal(false), + hadError(false) {} +}; + +template <typename Unit> +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 <typename Unit> + 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>, + "TokenStreamShared shouldn't bloat classes that inherit from it"); + +template <typename Unit, class AnyCharsAccess> +class TokenStreamSpecific; + +template <typename Unit> +class MOZ_STACK_CLASS TokenStreamPosition final { + public: + template <class AnyCharsAccess> + inline explicit TokenStreamPosition( + TokenStreamSpecific<Unit, AnyCharsAccess>& tokenStream); + + private: + TokenStreamPosition(const TokenStreamPosition&) = delete; + + // Technically only TokenStreamSpecific<Unit, AnyCharsAccess>::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 <typename Char, class AnyCharsAccess> + 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 <typename Unit> +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<Unit>| 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<uint32_t, 128> 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(JSContext* cx, uint32_t initialLineNumber, + uint32_t initialOffset); + + MOZ_MUST_USE bool add(uint32_t lineNum, uint32_t lineStartOffset); + MOZ_MUST_USE 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<unsigned char>(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>(unitsType_); + } + + void guaranteeSingleUnits() { + MOZ_ASSERT(unitsType() == UnitsType::PossiblyMultiUnit, + "should only be setting to possibly optimize from the " + "pessimistic case"); + unitsType_ = static_cast<unsigned char>(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 +}; + +class TokenStreamAnyChars : public TokenStreamShared { + private: + // Constant-at-construction fields. + + JSContext* const cx; + + /** 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<uint32_t, Vector<ChunkInfo>> 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<ChunkInfo>*| 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<ChunkInfo>* 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(JSContext* cx, const JS::ReadOnlyCompileOptions& options, + StrictModeGetter* smg); + + template <typename Unit, class AnyCharsAccess> + friend class GeneralTokenStreamChars; + template <typename Unit, class AnyCharsAccess> + friend class TokenStreamChars; + template <typename Unit, class AnyCharsAccess> + friend class TokenStreamSpecific; + + template <typename Unit> + 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; + } + + MOZ_MUST_USE bool checkOptions(); + + private: + const ParserName* reservedWordToPropertyName(TokenKind tt) const; + + public: + const ParserName* currentName() const { + if (isCurrentTokenType(TokenKind::Name) || + isCurrentTokenType(TokenKind::PrivateName)) { + return currentToken().name(); + } + + MOZ_ASSERT(TokenKindIsPossibleIdentifierName(currentToken().type)); + return reservedWordToPropertyName(currentToken().type); + } + + bool currentNameHasEscapes() const { + if (isCurrentTokenType(TokenKind::Name) || + isCurrentTokenType(TokenKind::PrivateName)) { + TokenPos pos = currentToken().pos; + const ParserAtom* name = currentToken().name(); + return (pos.end - pos.begin) != name->length(); + } + + MOZ_ASSERT(TokenKindIsPossibleIdentifierName(currentToken().type)); + return false; + } + + bool isCurrentTokenAssignment() const { + return TokenKindIsAssignment(currentToken().type); + } + + // Flag methods. + bool isEOF() const { return flags.isEOF; } + bool sawDeprecatedOctal() const { return flags.sawDeprecatedOctal; } + bool hadError() const { return flags.hadError; } + void clearSawDeprecatedOctal() { flags.sawDeprecatedOctal = false; } + + 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(); } + + JSContext* context() const { return cx; } + + 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); + + 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 | <html> + * 2 | <head> + * 3 | <script>var x = 3; x < 4; + * 4 | const y = 7;</script> + * 5 | </head> + * 6 | <body></body> + * 7 | </html> + * + * 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 <typename Unit> + uint32_t computePartialColumn(const LineToken lineToken, + const uint32_t offset, + const SourceUnits<Unit>& sourceUnits) const; + + /** + * Update line/column information for the start of a new line at + * |lineStartOffset|. + */ + MOZ_MUST_USE 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); + + // 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, ...); + void reportErrorNoOffsetVA(unsigned errorNumber, va_list* args); + + 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 <typename Unit> +class TokenStreamCharsBase; + +template <typename T> +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<char32_t>(unit)); +} + +template <typename Unit> +struct SourceUnitTraits; + +template <> +struct SourceUnitTraits<char16_t> { + public: + static constexpr uint8_t maxUnitsLength = 2; + + static constexpr size_t lengthInUnits(char32_t codePoint) { + return codePoint < unicode::NonBMPMin ? 1 : 2; + } +}; + +template <> +struct SourceUnitTraits<mozilla::Utf8Unit> { + 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<struct { char32_t v; uint8_t len; }>|. + */ +template <typename Unit> +class PeekedCodePoint final { + char32_t codePoint_ = 0; + uint8_t lengthInUnits_ = 0; + + private: + using SourceUnitTraits = frontend::SourceUnitTraits<Unit>; + + 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<char16_t> PeekCodePoint(const char16_t* const ptr, + const char16_t* const end) { + if (MOZ_UNLIKELY(ptr >= end)) { + return PeekedCodePoint<char16_t>::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<char16_t>(c, len); +} + +inline PeekedCodePoint<mozilla::Utf8Unit> PeekCodePoint( + const mozilla::Utf8Unit* const ptr, const mozilla::Utf8Unit* const end) { + if (MOZ_UNLIKELY(ptr >= end)) { + return PeekedCodePoint<mozilla::Utf8Unit>::none(); + } + + const mozilla::Utf8Unit lead = ptr[0]; + if (mozilla::IsAscii(lead)) { + return PeekedCodePoint<mozilla::Utf8Unit>(lead.toUint8(), 1); + } + + const mozilla::Utf8Unit* afterLead = ptr + 1; + mozilla::Maybe<char32_t> codePoint = + mozilla::DecodeOneUtf8CodePoint(lead, &afterLead, end); + if (codePoint.isNothing()) { + return PeekedCodePoint<mozilla::Utf8Unit>::none(); + } + + auto len = + mozilla::AssertedCast<uint8_t>(mozilla::PointerRangeSize(ptr, afterLead)); + MOZ_ASSERT(len <= 4); + + return PeekedCodePoint<mozilla::Utf8Unit>(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 <typename Unit> +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); + } + + 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<Unit> peekCodePoint() const { + return PeekCodePoint(ptr, limit_); + } + + private: +#ifdef DEBUG + void assertNextCodePoint(const PeekedCodePoint<Unit>& 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<Unit>& 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<Unit>; + + 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 the '\n' (CR) that precedes a '\n' (LF), when ungetting a line + * terminator that's a full "\r\n" sequence. If the prior code unit isn't + * '\r', do nothing. + */ + void ungetOptionalCRBeforeLF() { + MOZ_ASSERT(!isPoisoned(), + "shouldn't unget a '\\r' from poisoned SourceUnits"); + MOZ_ASSERT(*ptr == Unit('\n'), + "function should only be called when a '\\n' was just " + "ungotten, and any '\\r' preceding it must also be " + "ungotten"); + if (*(ptr - 1) == Unit('\r')) { + 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' ':' <bad code unit sequence> + * 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<const Utf8Unit*>(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); +}; + +template <> +inline void SourceUnits<char16_t>::ungetLineOrParagraphSeparator() { +#ifdef DEBUG + char16_t prev = previousCodeUnit(); +#endif + MOZ_ASSERT(prev == unicode::LINE_SEPARATOR || + prev == unicode::PARA_SEPARATOR); + + ungetCodeUnit(); +} + +template <> +inline void SourceUnits<mozilla::Utf8Unit>::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<char16_t, 32>; + +/** + * Append the provided code point (in the range [U+0000, U+10FFFF], surrogate + * code points included) to the buffer. + */ +extern MOZ_MUST_USE bool AppendCodePointToCharBuffer(CharBuffer& charBuffer, + uint32_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'. + */ +extern MOZ_MUST_USE 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'. + */ +extern MOZ_MUST_USE bool FillCharBufferFromSourceNormalizingAsciiLineBreaks( + CharBuffer& charBuffer, const mozilla::Utf8Unit* cur, + const mozilla::Utf8Unit* end); + +class TokenStreamCharsShared { + protected: + JSContext* cx; + + /** + * 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(JSContext* cx, ParserAtomsTable* parserAtoms) + : cx(cx), charBuffer(cx), parserAtoms(parserAtoms) {} + + MOZ_MUST_USE bool copyCharBufferTo( + JSContext* cx, UniquePtr<char16_t[], JS::FreePolicy>* 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.) + */ + static constexpr MOZ_ALWAYS_INLINE MOZ_MUST_USE bool isAsciiCodePoint( + int32_t unit) { + return mozilla::IsAscii(static_cast<char32_t>(unit)); + } + + const ParserAtom* drainCharBufferIntoAtom() { + // Add to parser atoms table. + const ParserAtom* atom = this->parserAtoms->internChar16( + cx, charBuffer.begin(), charBuffer.length()); + if (!atom) { + return nullptr; + } + + 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; } +}; + +inline auto ToCharSpan(mozilla::Span<const mozilla::Utf8Unit> codeUnits) { + static_assert(alignof(char) == alignof(mozilla::Utf8Unit), + "must have equal alignment to reinterpret_cast<>"); + static_assert(sizeof(char) == sizeof(mozilla::Utf8Unit), + "must have equal size to reinterpret_cast<>"); + + // This cast is safe for two reasons. + // + // First, per C++11 [basic.lval]p10 it is permitted to access any object's + // memory through |char|. + // + // Second, Utf8Unit *contains* a |char|. Examining that memory as |char| + // is simply, per C++11 [basic.lval]p10, to access the memory according to + // the dynamic type of the object: essentially trivially safe. + return mozilla::Span{reinterpret_cast<const char*>(codeUnits.data()), + codeUnits.size()}; +} + +template <typename Unit> +class TokenStreamCharsBase : public TokenStreamCharsShared { + protected: + using SourceUnits = frontend::SourceUnits<Unit>; + + /** Code units in the source code being tokenized. */ + SourceUnits sourceUnits; + + // End of fields. + + protected: + TokenStreamCharsBase(JSContext* cx, 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) { + return; + } + + sourceUnits.ungetCodeUnit(); + } + + MOZ_ALWAYS_INLINE const ParserAtom* atomizeSourceChars( + mozilla::Span<const Unit> 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. + */ + bool matchLineTerminator(char expect) { + MOZ_ASSERT(expect == '\r' || expect == '\n'); + return this->sourceUnits.internalMatchCodeUnit(Unit(expect)); + } + + template <typename T> + bool matchCodeUnit(T) = delete; + template <typename T> + 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 <typename T> + 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. + */ + MOZ_MUST_USE bool addLineOfContext(ErrorMetadata* err, uint32_t offset); +}; + +template <> +inline char16_t TokenStreamCharsBase<char16_t>::toUnit(int32_t codeUnitValue) { + MOZ_ASSERT(codeUnitValue != EOF, "EOF is not a Unit"); + return mozilla::AssertedCast<char16_t>(codeUnitValue); +} + +template <> +inline mozilla::Utf8Unit TokenStreamCharsBase<mozilla::Utf8Unit>::toUnit( + int32_t value) { + MOZ_ASSERT(value != EOF, "EOF is not a Unit"); + return mozilla::Utf8Unit(mozilla::AssertedCast<unsigned char>(value)); +} + +template <typename Unit> +inline void TokenStreamCharsBase<Unit>::consumeKnownCodeUnit(int32_t unit) { + sourceUnits.consumeKnownCodeUnit(toUnit(unit)); +} + +template <> +MOZ_ALWAYS_INLINE const ParserAtom* +TokenStreamCharsBase<char16_t>::atomizeSourceChars( + mozilla::Span<const char16_t> units) { + return this->parserAtoms->internChar16(cx, units.data(), units.size()); +} + +template <> +/* static */ MOZ_ALWAYS_INLINE const ParserAtom* +TokenStreamCharsBase<mozilla::Utf8Unit>::atomizeSourceChars( + mozilla::Span<const mozilla::Utf8Unit> units) { + return this->parserAtoms->internUtf8(cx, units.data(), units.size()); +} + +template <typename Unit> +class SpecializedTokenStreamCharsBase; + +template <> +class SpecializedTokenStreamCharsBase<char16_t> + : public TokenStreamCharsBase<char16_t> { + using CharsBase = TokenStreamCharsBase<char16_t>; + + 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<mozilla::Utf8Unit> + : public TokenStreamCharsBase<mozilla::Utf8Unit> { + using CharsBase = TokenStreamCharsBase<mozilla::Utf8Unit>; + + 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<const mozilla::Utf8Unit*> + 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 <class SourceUnits> + TokenStart(const SourceUnits& sourceUnits, ptrdiff_t adjust) + : startOffset_(sourceUnits.offset() + adjust) {} + + TokenStart(const TokenStart&) = default; + + uint32_t offset() const { return startOffset_; } +}; + +template <typename Unit, class AnyCharsAccess> +class GeneralTokenStreamChars : public SpecializedTokenStreamCharsBase<Unit> { + using CharsBase = TokenStreamCharsBase<Unit>; + using SpecializedCharsBase = SpecializedTokenStreamCharsBase<Unit>; + + 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(uint32_t* codePoint); + uint32_t matchExtendedUnicodeEscape(uint32_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<Unit, AnyCharsAccess>; + + TokenStreamSpecific* asSpecific() { + static_assert( + std::is_base_of_v<GeneralTokenStreamChars, TokenStreamSpecific>, + "static_cast below presumes an inheritance relationship"); + + return static_cast<TokenStreamSpecific*>(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. + */ + MOZ_MUST_USE bool fillExceptingContext(ErrorMetadata* err, uint32_t offset) { + 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, const ParserAtom* 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(const ParserName* name, TokenStart start, + TokenStreamShared::Modifier modifier, TokenKind* out) { + Token* token = newToken(TokenKind::Name, start, modifier, out); + token->setName(name); + } + + void newPrivateNameToken(const ParserName* 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') 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. + */ + MOZ_MUST_USE bool getFullAsciiCodePoint(int32_t lead, int32_t* codePoint) { + 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')) { + *codePoint = lead; + return true; + } + + *codePoint = '\n'; + bool ok = updateLineInfoForEOL(); + if (!ok) { +#ifdef DEBUG + *codePoint = EOF; // sentinel value to hopefully cause errors +#endif + MOZ_MAKE_MEM_UNDEFINED(codePoint, sizeof(*codePoint)); + } + return ok; + } + + MOZ_MUST_USE MOZ_ALWAYS_INLINE bool updateLineInfoForEOL() { + return anyCharsAccess().internalUpdateLineInfoForEOL( + this->sourceUnits.offset()); + } + + uint32_t matchUnicodeEscapeIdStart(uint32_t* codePoint); + bool matchUnicodeEscapeIdent(uint32_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. + */ + MOZ_MUST_USE bool internalComputeLineOfContext(ErrorMetadata* err, + uint32_t offset) { + // 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(); + + const ParserAtom* 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 nullptr; + } + + return drainCharBufferIntoAtom(); + } +}; + +template <typename Unit, class AnyCharsAccess> +class TokenStreamChars; + +template <class AnyCharsAccess> +class TokenStreamChars<char16_t, AnyCharsAccess> + : public GeneralTokenStreamChars<char16_t, AnyCharsAccess> { + using CharsBase = TokenStreamCharsBase<char16_t>; + using SpecializedCharsBase = SpecializedTokenStreamCharsBase<char16_t>; + using GeneralCharsBase = GeneralTokenStreamChars<char16_t, AnyCharsAccess>; + using Self = TokenStreamChars<char16_t, AnyCharsAccess>; + + 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. + */ + MOZ_MUST_USE 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. + */ + MOZ_MUST_USE bool getNonAsciiCodePoint(int32_t lead, int32_t* codePoint); +}; + +template <class AnyCharsAccess> +class TokenStreamChars<mozilla::Utf8Unit, AnyCharsAccess> + : public GeneralTokenStreamChars<mozilla::Utf8Unit, AnyCharsAccess> { + using CharsBase = TokenStreamCharsBase<mozilla::Utf8Unit>; + using SpecializedCharsBase = + SpecializedTokenStreamCharsBase<mozilla::Utf8Unit>; + using GeneralCharsBase = + GeneralTokenStreamChars<mozilla::Utf8Unit, AnyCharsAccess>; + using Self = TokenStreamChars<mozilla::Utf8Unit, AnyCharsAccess>; + + 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(uint32_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(uint32_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(uint32_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. + */ + MOZ_MUST_USE 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. + */ + MOZ_MUST_USE bool getNonAsciiCodePoint(int32_t lead, int32_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 <typename Unit, class AnyCharsAccess> +class MOZ_STACK_CLASS TokenStreamSpecific + : public TokenStreamChars<Unit, AnyCharsAccess>, + public TokenStreamShared, + public ErrorReporter { + public: + using CharsBase = TokenStreamCharsBase<Unit>; + using SpecializedCharsBase = SpecializedTokenStreamCharsBase<Unit>; + using GeneralCharsBase = GeneralTokenStreamChars<Unit, AnyCharsAccess>; + using SpecializedChars = TokenStreamChars<Unit, AnyCharsAccess>; + + using Position = TokenStreamPosition<Unit>; + + // 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 <typename CharU> + friend class TokenStreamPosition; + + public: + TokenStreamSpecific(JSContext* cx, 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 + * and store the code point in |*cp|. Return false and leave |*cp| + * undefined on failure. + */ + MOZ_MUST_USE bool getCodePoint(int32_t* 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. + + void lineAndColumnAt(size_t offset, uint32_t* line, + uint32_t* column) const final { + computeLineAndColumn(offset, line, column); + } + + void currentLineAndColumn(uint32_t* line, uint32_t* column) const final { + computeLineAndColumn(anyCharsAccess().currentToken().pos.begin, line, + column); + } + + 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); + } + + bool hasTokenizationStarted() const final; + + const char* getFilename() const final { + return anyCharsAccess().getFilename(); + } + + private: + // Implement ErrorReportMixin. + + JSContext* getContext() const override { return anyCharsAccess().cx; } + + MOZ_MUST_USE bool strictMode() const override { + return anyCharsAccess().strictMode(); + } + + public: + // Implement ErrorReportMixin. + + const JS::ReadOnlyCompileOptions& options() const final { + return anyCharsAccess().options(); + } + + MOZ_MUST_USE bool computeErrorMetadata( + ErrorMetadata* err, const ErrorOffset& errorOffset) 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); + return; + } + } + + void reportIllegalCharacter(int32_t cp); + + MOZ_MUST_USE bool putIdentInCharBuffer(const Unit* identStart); + + using IsIntegerUnit = bool (*)(int32_t); + MOZ_MUST_USE MOZ_ALWAYS_INLINE bool matchInteger(IsIntegerUnit isIntegerUnit, + int32_t* nextUnit); + MOZ_MUST_USE 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 '0' or '.', e.g. '1' for "17", '3' for "3.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 "."/"0."-prefixed decimal number or the 'e'/'E' in a + * "0e"/"0E"-prefixed decimal number, e.g. ".17", "0.42", or "0.1e3". + * + * In this case, the next |getCodeUnit()| must return the code unit + * *after* the first decimal digit *after* the '.'. So the next code + * unit would be '7' in ".17", '2' in "0.42", 'e' in "0.4e+8", or '/' in + * "0.5/2" (three separate tokens). + * + * 3. The code unit after the '0' where "0" is the entire number token. + * + * In this case, the next |getCodeUnit()| would return the code unit + * after |unit|, but this function will never perform such call. + * + * 4. (Non-strict mode code only) The first '8' or '9' in 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. '9' in "09.28" or + * '8' in "0386" or '9' in "09+7" (three separate tokens"). + * + * In this case, the next |getCodeUnit()| returns the code unit after + * |unit|: '.', '6', or '+' 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. + */ + MOZ_MUST_USE bool decimalNumber(int32_t unit, TokenStart start, + const Unit* numStart, Modifier modifier, + TokenKind* out); + + /** Tokenize a regular expression literal beginning at |start|. */ + MOZ_MUST_USE bool regexpLiteral(TokenStart start, TokenKind* out); + + /** + * Slurp characters between |start| and sourceUnits.current() into + * charBuffer, to later parse into a bigint. + */ + MOZ_MUST_USE 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|. + MOZ_MUST_USE 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); + } + + MOZ_MUST_USE 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; + } + + MOZ_MUST_USE 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; + } + + MOZ_MUST_USE 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.) + MOZ_ALWAYS_INLINE MOZ_MUST_USE 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|. + MOZ_MUST_USE 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); + } + + MOZ_MUST_USE 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; + } + + MOZ_MUST_USE bool advance(size_t position); + + void seekTo(const Position& pos); + MOZ_MUST_USE 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); + } + + MOZ_MUST_USE 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); + } + + MOZ_MUST_USE 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); + } + + const Unit* rawLimit() const { return this->sourceUnits.limit(); } + + MOZ_MUST_USE bool identifierName(TokenStart start, const Unit* identStart, + IdentifierEscapes escaping, + Modifier modifier, NameVisibility visibility, + TokenKind* out); + + MOZ_MUST_USE bool matchIdentifierStart(IdentifierEscapes* sawEscape); + + MOZ_MUST_USE bool getTokenInternal(TokenKind* const ttp, + const Modifier modifier); + + MOZ_MUST_USE 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. + MOZ_MUST_USE bool getTemplateToken(TokenKind* ttp) { + MOZ_ASSERT(anyCharsAccess().currentToken().type == TokenKind::RightCurly); + return getStringOrTemplateToken('`', SlashIsInvalid, ttp); + } + + MOZ_MUST_USE bool getDirectives(bool isMultiline, bool shouldWarnDeprecated); + MOZ_MUST_USE bool getDirective( + bool isMultiline, bool shouldWarnDeprecated, const char* directive, + uint8_t directiveLength, const char* errorMsgPragma, + UniquePtr<char16_t[], JS::FreePolicy>* destination); + MOZ_MUST_USE bool getDisplayURL(bool isMultiline, bool shouldWarnDeprecated); + MOZ_MUST_USE 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 <typename Unit> +template <class AnyCharsAccess> +inline TokenStreamPosition<Unit>::TokenStreamPosition( + TokenStreamSpecific<Unit, AnyCharsAccess>& 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 <class TokenStreamSpecific> + static inline TokenStreamAnyChars& anyChars(TokenStreamSpecific* tss); + + template <class TokenStreamSpecific> + static inline const TokenStreamAnyChars& anyChars( + const TokenStreamSpecific* tss); +}; + +class MOZ_STACK_CLASS TokenStream + : public TokenStreamAnyChars, + public TokenStreamSpecific<char16_t, TokenStreamAnyCharsAccess> { + using Unit = char16_t; + + public: + TokenStream(JSContext* cx, ParserAtomsTable* parserAtoms, + const JS::ReadOnlyCompileOptions& options, const Unit* units, + size_t length, StrictModeGetter* smg) + : TokenStreamAnyChars(cx, options, smg), + TokenStreamSpecific<Unit, TokenStreamAnyCharsAccess>( + cx, parserAtoms, options, units, length) {} +}; + +class MOZ_STACK_CLASS DummyTokenStream final : public TokenStream { + public: + DummyTokenStream(JSContext* cx, const JS::ReadOnlyCompileOptions& options) + : TokenStream(cx, nullptr, options, nullptr, 0, nullptr) {} +}; + +template <class TokenStreamSpecific> +/* static */ inline TokenStreamAnyChars& TokenStreamAnyCharsAccess::anyChars( + TokenStreamSpecific* tss) { + auto* ts = static_cast<TokenStream*>(tss); + return *static_cast<TokenStreamAnyChars*>(ts); +} + +template <class TokenStreamSpecific> +/* static */ inline const TokenStreamAnyChars& +TokenStreamAnyCharsAccess::anyChars(const TokenStreamSpecific* tss) { + const auto* ts = static_cast<const TokenStream*>(tss); + return *static_cast<const TokenStreamAnyChars*>(ts); +} + +extern const char* TokenKindToDesc(TokenKind tt); + +} // namespace frontend +} // namespace js + +extern JS_FRIEND_API int js_fgets(char* buf, int size, FILE* file); + +#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..1004f4ce25 --- /dev/null +++ b/js/src/frontend/TryEmitter.cpp @@ -0,0 +1,291 @@ +/* -*- 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/SharedContext.h" // StatementKind +#include "vm/JSScript.h" // JSTRY_CATCH, JSTRY_FINALLY +#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::emitTryEnd() { + MOZ_ASSERT(state_ == State::Try); + MOZ_ASSERT(depth_ == bce_->bytecodeSection().stackDepth()); + + // Gosub to finally, if present. + if (hasFinally() && controlInfo_) { + if (!bce_->emitGoSub(&controlInfo_->gosubs)) { + return false; + } + } + + // Emit jump over catch and/or finally. + 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 (controlKind_ == ControlKind::Syntactic) { + // 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; + } + + if (!instrumentEntryPoint()) { + return false; + } + +#ifdef DEBUG + state_ = State::Catch; +#endif + return true; +} + +bool TryEmitter::emitCatchEnd() { + MOZ_ASSERT(state_ == State::Catch); + + if (!controlInfo_) { + return true; + } + + // gosub <finally>, if required. + if (hasFinally()) { + if (!bce_->emitGoSub(&controlInfo_->gosubs)) { + return false; + } + MOZ_ASSERT(bce_->bytecodeSection().stackDepth() == depth_); + + // Jump over the finally block. + if (!bce_->emitJump(JSOp::Goto, &catchAndFinallyJump_)) { + return false; + } + } + + return true; +} + +bool TryEmitter::emitFinally( + const Maybe<uint32_t>& 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_); + + if (!bce_->emitJumpTarget(&finallyStart_)) { + return false; + } + + if (controlInfo_) { + // Fix up the gosubs that might have been emitted before non-local + // jumps to the finally code. + bce_->patchJumpsToTarget(controlInfo_->gosubs, 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 (controlKind_ == ControlKind::Syntactic) { + 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; + } + } + + if (!instrumentEntryPoint()) { + return false; + } + +#ifdef DEBUG + state_ = State::Finally; +#endif + return true; +} + +bool TryEmitter::emitFinallyEnd() { + MOZ_ASSERT(state_ == State::Finally); + + if (controlKind_ == ControlKind::Syntactic) { + if (!bce_->emit1(JSOp::SetRval)) { + return false; + } + } + + if (!bce_->emit1(JSOp::Retsub)) { + 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_); + + // 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::instrumentEntryPoint() { + // Frames for async functions can resume execution at catch or finally blocks + // if an await operation threw an exception. While the frame might already be + // on the stack, the Entry instrumentation kind only indicates that a new + // frame *might* have been pushed. + if (bce_->sc->isFunctionBox() && bce_->sc->asFunctionBox()->isAsync()) { + return bce_->emitInstrumentation(InstrumentationKind::Entry); + } + return true; +} diff --git a/js/src/frontend/TryEmitter.h b/js/src/frontend/TryEmitter.h new file mode 100644 index 0000000000..c380b98741 --- /dev/null +++ b/js/src/frontend/TryEmitter.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/. */ + +#ifndef frontend_TryEmitter_h +#define frontend_TryEmitter_h + +#include "mozilla/Attributes.h" // MOZ_STACK_CLASS, MOZ_MUST_USE +#include "mozilla/Maybe.h" // mozilla::Maybe, mozilla::Nothing + +#include <stdint.h> // 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. + // + // JSOp::Try offsetOf(jumpToEnd) + // + // try_body... + // + // JSOp::Gosub finally + // JSOp::JumpTarget + // jumpToEnd: + // JSOp::Goto end: + // + // catch: + // JSOp::JumpTarget + // * JSOp::Undefined + // * JSOp::SetRval + // + // catch_body... + // + // JSOp::Gosub 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_; + + // Track jumps-over-catches and gosubs-to-finally for later fixup. + // + // When a finally block is active, non-local jumps (including + // jumps-over-catches) result in a Gosub 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<TryFinallyControl> 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); + } + + public: + TryEmitter(BytecodeEmitter* bce, Kind kind, ControlKind controlKind); + + MOZ_MUST_USE bool emitTry(); + MOZ_MUST_USE 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. + MOZ_MUST_USE bool emitFinally( + const mozilla::Maybe<uint32_t>& finallyPos = mozilla::Nothing()); + + MOZ_MUST_USE bool emitEnd(); + + private: + MOZ_MUST_USE bool emitTryEnd(); + MOZ_MUST_USE bool emitCatchEnd(); + MOZ_MUST_USE bool emitFinallyEnd(); + MOZ_MUST_USE bool instrumentEntryPoint(); +}; + +} /* 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..5dd91f9621 --- /dev/null +++ b/js/src/frontend/TypedIndex.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_TypedIndex_h +#define frontend_TypedIndex_h + +#include <cstdint> + +namespace js { +namespace frontend { + +// TypedIndex allows discrimination in variants between different +// index types. Used as a typesafe index for various stencil arrays. +template <typename Tag> +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) { return index < other.index; } + bool operator<=(TypedIndex other) { return index <= other.index; } + bool operator>(TypedIndex other) { return index > other.index; } + bool operator>=(TypedIndex other) { 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..0b7417b540 --- /dev/null +++ b/js/src/frontend/UsedNameTracker.h @@ -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/. */ + +#ifndef frontend_UsedNameTracker_h +#define frontend_UsedNameTracker_h + +#include "mozilla/Attributes.h" + +#include "frontend/Token.h" +#include "js/AllocPolicy.h" +#include "js/HashTable.h" +#include "js/Vector.h" + +#include "vm/StringType.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 { + const ParserAtom* atom; + TokenPos position; + + UnboundPrivateName(const ParserAtom* atom, TokenPos position) + : atom(atom), position(position) {} +}; + +class UsedNameTracker { + public: + struct Use { + uint32_t scriptId; + uint32_t scopeId; + }; + + class UsedNameInfo { + friend class UsedNameTracker; + + Vector<Use, 6> 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<TokenPos> firstUsePos_; + + public: + explicit UsedNameInfo(JSContext* cx, NameVisibility visibility, + mozilla::Maybe<TokenPos> position) + : uses_(cx), 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<TokenPos> pos() { return firstUsePos_; } + }; + + using UsedNameMap = HashMap<const ParserAtom*, UsedNameInfo, + DefaultHasher<const ParserAtom*>>; + + 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(JSContext* cx) + : map_(cx), + 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(const ParserAtom* name) const { + return map_.lookup(name); + } + + MOZ_MUST_USE bool noteUse( + JSContext* cx, const ParserAtom* name, NameVisibility visibility, + uint32_t scriptId, uint32_t scopeId, + mozilla::Maybe<TokenPos> tokenPosition = mozilla::Nothing()); + + // Fill maybeUnboundName with the first (source order) unbound name, or + // Nothing() if there are no unbound names. + MOZ_MUST_USE bool hasUnboundPrivateNames( + JSContext* cx, mozilla::Maybe<UnboundPrivateName>& maybeUnboundName); + + // Return a list of unbound private names, sorted by increasing location in + // the source. + MOZ_MUST_USE bool getUnboundPrivateNames( + Vector<UnboundPrivateName, 8>& 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..98e0ab273a --- /dev/null +++ b/js/src/frontend/ValueUsage.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_ValueUsage_h +#define frontend_ValueUsage_h + +namespace js { +namespace frontend { + +// Used to control whether JSOp::CallIgnoresRv is emitted for function calls. +enum class ValueUsage { + // Assume the value of the current expression may be used. This is always + // correct but prohibits 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..cae600e9a5 --- /dev/null +++ b/js/src/frontend/WhileEmitter.cpp @@ -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/. */ + +#include "frontend/WhileEmitter.h" + +#include "frontend/BytecodeEmitter.h" +#include "frontend/SourceNotes.h" +#include "vm/Opcodes.h" +#include "vm/StencilEnums.h" // TryNoteKind + +using namespace js; +using namespace js::frontend; + +using mozilla::Maybe; + +WhileEmitter::WhileEmitter(BytecodeEmitter* bce) : bce_(bce) {} + +bool WhileEmitter::emitCond(const Maybe<uint32_t>& whilePos, + const Maybe<uint32_t>& condPos, + const Maybe<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 (whilePos && endPos && + bce_->parser->errorReporter().lineAt(*whilePos) == + bce_->parser->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_, condPos)) { + return false; + } + +#ifdef DEBUG + state_ = State::Cond; +#endif + return true; +} + +bool WhileEmitter::emitBody() { + MOZ_ASSERT(state_ == State::Cond); + + if (!bce_->emitJump(JSOp::IfEq, &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..33be7017c5 --- /dev/null +++ b/js/src/frontend/WhileEmitter.h @@ -0,0 +1,93 @@ +/* -*- 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 <stdint.h> + +#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(Some(offset_of_while), +// Some(offset_of_body), +// Some(offset_of_end)); +// emit(cond); +// wh.emitBody(); +// emit(body); +// wh.emitEnd(); +// +class MOZ_STACK_CLASS WhileEmitter { + BytecodeEmitter* bce_; + + mozilla::Maybe<LoopControl> loopInfo_; + + // Cache for the loop body, which is enclosed by the cache in `loopInfo_`, + // which is effectively for the loop condition. + mozilla::Maybe<TDZCheckCache> 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_ + // + // Can be Nothing() if not available. + MOZ_MUST_USE bool emitCond(const mozilla::Maybe<uint32_t>& whilePos, + const mozilla::Maybe<uint32_t>& condPos, + const mozilla::Maybe<uint32_t>& endPos); + MOZ_MUST_USE bool emitBody(); + MOZ_MUST_USE 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..1ae2204dc3 --- /dev/null +++ b/js/src/frontend/align_stack_comment.py @@ -0,0 +1,109 @@ +#!/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 +""" + +from __future__ import print_function +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..9285baf5fb --- /dev/null +++ b/js/src/frontend/moz.build @@ -0,0 +1,83 @@ +# -*- 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 +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", + "DefaultEmitter.cpp", + "DoWhileEmitter.cpp", + "ElemOpEmitter.cpp", + "EmitterScope.cpp", + "ExpressionStatementEmitter.cpp", + "FoldConstants.cpp", + "ForInEmitter.cpp", + "ForOfEmitter.cpp", + "ForOfLoopControl.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", + "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", + ] + + +# 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..8fa413eeff --- /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.8", default-features = false} +# For non-jsparagus developers. +jsparagus = { git = "https://github.com/mozilla-spidermonkey/jsparagus", rev = "d910c7a0f2f836cc70ed14bb92b8d0437b4e4a24" } +# 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 = "d910c7a0f2f836cc70ed14bb92b8d0437b4e4a24" } +# 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..3ae2cf54cd --- /dev/null +++ b/js/src/frontend/smoosh/build.rs @@ -0,0 +1,30 @@ +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() { + match std::env::var("JS_SMOOSH_DISABLE_OPCODE_CHECK") { + Ok(_) => { + return; + } + Err(_) => {} + }; + + 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<T> { + pub data: *mut T, + pub len: usize, + pub capacity: usize, +} + +impl<T> CVec<T> { + fn empty() -> CVec<T> { + Self { + data: std::ptr::null_mut(), + len: 0, + capacity: 0, + } + } + + fn from(mut v: Vec<T>) -> CVec<T> { + let result = Self { + data: v.as_mut_ptr(), + len: v.len(), + capacity: v.capacity(), + }; + mem::forget(v); + result + } + + unsafe fn into(self) -> Vec<T> { + 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<usize, usize>) -> 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<BindingName> 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<SmooshBindingName>, + let_start: usize, + const_start: usize, +} + +#[repr(C)] +pub struct SmooshVarScopeData { + bindings: CVec<SmooshBindingName>, + enclosing: usize, + function_has_extensible_scope: bool, + first_frame_slot: u32, +} + +#[repr(C)] +pub struct SmooshLexicalScopeData { + bindings: CVec<SmooshBindingName>, + const_start: usize, + enclosing: usize, + first_frame_slot: u32, +} + +#[repr(C)] +pub struct SmooshFunctionScopeData { + bindings: CVec<COption<SmooshBindingName>>, + 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<usize, usize>) -> 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<ScopeData>, + scope_index_map: &mut HashMap<usize, usize>, +) -> CVec<SmooshScopeData> { + 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<ScopeNote> 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<RegExpItem> 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<u8>, + pub scope_notes: CVec<SmooshScopeNote>, +} + +#[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<T> { + Some(T), + None, +} + +impl<T> COption<T> { + fn from(v: Option<T>) -> 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<SmooshGCThing>, + pub immutable_script_data: COption<usize>, + pub extent: SmooshSourceExtent, + pub fun_name: COption<usize>, + pub fun_nargs: u16, + pub fun_flags: u16, + pub lazy_function_enclosing_scope_index: COption<usize>, + pub is_standalone_function: bool, + pub was_function_emitted: bool, + pub is_singleton_function: bool, +} + +#[repr(C)] +pub struct SmooshResult { + unimplemented: bool, + error: CVec<u8>, + + scopes: CVec<SmooshScopeData>, + regexps: CVec<SmooshRegExpItem>, + + scripts: CVec<SmooshScriptStencil>, + script_data_list: CVec<SmooshImmutableScriptData>, + + 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<u8>) -> 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<usize, usize>, +) -> 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<u8>, +} + +fn convert_parse_result<'alloc, T>(r: jsparagus::parser::Result<T>) -> 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<EmitResult<'alloc>, 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); + } +} |