summaryrefslogtreecommitdiffstats
path: root/js/src/frontend
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 14:29:10 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 14:29:10 +0000
commit2aa4a82499d4becd2284cdb482213d541b8804dd (patch)
treeb80bf8bf13c3766139fbacc530efd0dd9d54394c /js/src/frontend
parentInitial commit. (diff)
downloadfirefox-2aa4a82499d4becd2284cdb482213d541b8804dd.tar.xz
firefox-2aa4a82499d4becd2284cdb482213d541b8804dd.zip
Adding upstream version 86.0.1.upstream/86.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'js/src/frontend')
-rw-r--r--js/src/frontend/AbstractScopePtr.cpp66
-rw-r--r--js/src/frontend/AbstractScopePtr.h143
-rw-r--r--js/src/frontend/AsyncEmitter.cpp198
-rw-r--r--js/src/frontend/AsyncEmitter.h163
-rw-r--r--js/src/frontend/BCEParserHandle.h30
-rw-r--r--js/src/frontend/BytecodeCompilation.h130
-rw-r--r--js/src/frontend/BytecodeCompiler.cpp1231
-rw-r--r--js/src/frontend/BytecodeCompiler.h224
-rw-r--r--js/src/frontend/BytecodeControlStructures.cpp111
-rw-r--r--js/src/frontend/BytecodeControlStructures.h164
-rw-r--r--js/src/frontend/BytecodeEmitter.cpp11322
-rw-r--r--js/src/frontend/BytecodeEmitter.h920
-rw-r--r--js/src/frontend/BytecodeOffset.h153
-rw-r--r--js/src/frontend/BytecodeSection.cpp197
-rw-r--r--js/src/frontend/BytecodeSection.h418
-rw-r--r--js/src/frontend/CForEmitter.cpp179
-rw-r--r--js/src/frontend/CForEmitter.h176
-rw-r--r--js/src/frontend/CallOrNewEmitter.cpp277
-rw-r--r--js/src/frontend/CallOrNewEmitter.h318
-rw-r--r--js/src/frontend/CompilationInfo.h706
-rw-r--r--js/src/frontend/DefaultEmitter.cpp76
-rw-r--r--js/src/frontend/DefaultEmitter.h65
-rw-r--r--js/src/frontend/DestructuringFlavor.h24
-rw-r--r--js/src/frontend/DoWhileEmitter.cpp76
-rw-r--r--js/src/frontend/DoWhileEmitter.h83
-rw-r--r--js/src/frontend/EitherParser.h167
-rw-r--r--js/src/frontend/ElemOpEmitter.cpp304
-rw-r--r--js/src/frontend/ElemOpEmitter.h279
-rw-r--r--js/src/frontend/EmitterScope.cpp1119
-rw-r--r--js/src/frontend/EmitterScope.h171
-rw-r--r--js/src/frontend/ErrorReporter.h352
-rw-r--r--js/src/frontend/ExpressionStatementEmitter.cpp57
-rw-r--r--js/src/frontend/ExpressionStatementEmitter.h84
-rw-r--r--js/src/frontend/FoldConstants.cpp1571
-rw-r--r--js/src/frontend/FoldConstants.h49
-rw-r--r--js/src/frontend/ForInEmitter.cpp158
-rw-r--r--js/src/frontend/ForInEmitter.h119
-rw-r--r--js/src/frontend/ForOfEmitter.cpp220
-rw-r--r--js/src/frontend/ForOfEmitter.h117
-rw-r--r--js/src/frontend/ForOfLoopControl.cpp223
-rw-r--r--js/src/frontend/ForOfLoopControl.h98
-rw-r--r--js/src/frontend/Frontend2.cpp752
-rw-r--r--js/src/frontend/Frontend2.h67
-rw-r--r--js/src/frontend/FullParseHandler.h1136
-rw-r--r--js/src/frontend/FunctionEmitter.cpp954
-rw-r--r--js/src/frontend/FunctionEmitter.h437
-rw-r--r--js/src/frontend/FunctionSyntaxKind.h37
-rw-r--r--js/src/frontend/GenerateReservedWords.py229
-rw-r--r--js/src/frontend/IfEmitter.cpp285
-rw-r--r--js/src/frontend/IfEmitter.h312
-rw-r--r--js/src/frontend/IteratorKind.h16
-rw-r--r--js/src/frontend/JumpList.cpp48
-rw-r--r--js/src/frontend/JumpList.h81
-rw-r--r--js/src/frontend/LabelEmitter.cpp40
-rw-r--r--js/src/frontend/LabelEmitter.h67
-rw-r--r--js/src/frontend/LexicalScopeEmitter.cpp59
-rw-r--r--js/src/frontend/LexicalScopeEmitter.h96
-rw-r--r--js/src/frontend/ModuleSharedContext.h44
-rw-r--r--js/src/frontend/NameAnalysisTypes.h377
-rw-r--r--js/src/frontend/NameCollections.h378
-rw-r--r--js/src/frontend/NameFunctions.cpp485
-rw-r--r--js/src/frontend/NameFunctions.h26
-rw-r--r--js/src/frontend/NameOpEmitter.cpp403
-rw-r--r--js/src/frontend/NameOpEmitter.h181
-rw-r--r--js/src/frontend/ObjLiteral.cpp246
-rw-r--r--js/src/frontend/ObjLiteral.h562
-rw-r--r--js/src/frontend/ObjectEmitter.cpp913
-rw-r--r--js/src/frontend/ObjectEmitter.h815
-rw-r--r--js/src/frontend/OptionalEmitter.cpp163
-rw-r--r--js/src/frontend/OptionalEmitter.h219
-rw-r--r--js/src/frontend/ParseContext-inl.h180
-rw-r--r--js/src/frontend/ParseContext.cpp720
-rw-r--r--js/src/frontend/ParseContext.h593
-rw-r--r--js/src/frontend/ParseNode.cpp442
-rw-r--r--js/src/frontend/ParseNode.h2336
-rw-r--r--js/src/frontend/ParseNodeVerify.cpp50
-rw-r--r--js/src/frontend/ParseNodeVerify.h42
-rw-r--r--js/src/frontend/ParseNodeVisitor.h134
-rw-r--r--js/src/frontend/Parser.cpp11654
-rw-r--r--js/src/frontend/Parser.h1878
-rw-r--r--js/src/frontend/ParserAtom.cpp864
-rw-r--r--js/src/frontend/ParserAtom.h810
-rw-r--r--js/src/frontend/PropOpEmitter.cpp243
-rw-r--r--js/src/frontend/PropOpEmitter.h254
-rw-r--r--js/src/frontend/ReservedWords.h81
-rw-r--r--js/src/frontend/ScriptIndex.h22
-rw-r--r--js/src/frontend/SharedContext-inl.h28
-rw-r--r--js/src/frontend/SharedContext.cpp492
-rw-r--r--js/src/frontend/SharedContext.h712
-rw-r--r--js/src/frontend/SourceNotes.cpp13
-rw-r--r--js/src/frontend/SourceNotes.h428
-rw-r--r--js/src/frontend/Stencil.cpp2261
-rw-r--r--js/src/frontend/Stencil.h886
-rw-r--r--js/src/frontend/StencilXdr.cpp661
-rw-r--r--js/src/frontend/StencilXdr.h65
-rw-r--r--js/src/frontend/SwitchEmitter.cpp410
-rw-r--r--js/src/frontend/SwitchEmitter.h466
-rw-r--r--js/src/frontend/SyntaxParseHandler.h733
-rw-r--r--js/src/frontend/TDZCheckCache.cpp75
-rw-r--r--js/src/frontend/TDZCheckCache.h58
-rw-r--r--js/src/frontend/Token.h226
-rw-r--r--js/src/frontend/TokenKind.h325
-rw-r--r--js/src/frontend/TokenStream.cpp3850
-rw-r--r--js/src/frontend/TokenStream.h2976
-rw-r--r--js/src/frontend/TryEmitter.cpp291
-rw-r--r--js/src/frontend/TryEmitter.h218
-rw-r--r--js/src/frontend/TypedIndex.h41
-rw-r--r--js/src/frontend/UsedNameTracker.h244
-rw-r--r--js/src/frontend/ValueUsage.h28
-rw-r--r--js/src/frontend/WhileEmitter.cpp90
-rw-r--r--js/src/frontend/WhileEmitter.h93
-rwxr-xr-xjs/src/frontend/align_stack_comment.py109
-rw-r--r--js/src/frontend/moz.build83
-rw-r--r--js/src/frontend/smoosh/Cargo.toml23
-rw-r--r--js/src/frontend/smoosh/build.rs30
-rw-r--r--js/src/frontend/smoosh/cbindgen.toml19
-rw-r--r--js/src/frontend/smoosh/moz.build15
-rw-r--r--js/src/frontend/smoosh/src/lib.rs769
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(&param->as<ListNode>(),
+ DestructuringFlavor::Declaration)) {
+ return false;
+ }
+ if (!emit1(JSOp::Pop)) {
+ return false;
+ }
+ break;
+
+ case ParseNodeKind::Name:
+ if (!emitLexicalInitialization(&param->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, &currentScope);
+ return emitGetNameAtLocation(cx->parserNames().dotGenerator, loc);
+ }
+ NameLocation loc = *locationOfNameBoundInScopeType<FunctionScope>(
+ cx->parserNames().dotGenerator, &currentScope);
+ 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 &notes[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 &notes[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(&ltok, 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(&ltok)) {
+ 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(&regExpSize8));
+ 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, &regExpSize,
+ &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 &lt; 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 "&lt;" 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);
+ }
+}