summaryrefslogtreecommitdiffstats
path: root/js/src/frontend
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
commit36d22d82aa202bb199967e9512281e9a53db42c9 (patch)
tree105e8c98ddea1c1e4784a60a5a6410fa416be2de /js/src/frontend
parentInitial commit. (diff)
downloadfirefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz
firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip
Adding upstream version 115.7.0esr.upstream/115.7.0esrupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'js/src/frontend')
-rw-r--r--js/src/frontend/AbstractScopePtr.cpp61
-rw-r--r--js/src/frontend/AbstractScopePtr.h90
-rw-r--r--js/src/frontend/AsyncEmitter.cpp217
-rw-r--r--js/src/frontend/AsyncEmitter.h174
-rw-r--r--js/src/frontend/BytecodeCompilation.h108
-rw-r--r--js/src/frontend/BytecodeCompiler.cpp1565
-rw-r--r--js/src/frontend/BytecodeCompiler.h226
-rw-r--r--js/src/frontend/BytecodeControlStructures.cpp392
-rw-r--r--js/src/frontend/BytecodeControlStructures.h205
-rw-r--r--js/src/frontend/BytecodeEmitter.cpp12031
-rw-r--r--js/src/frontend/BytecodeEmitter.h1095
-rw-r--r--js/src/frontend/BytecodeOffset.h153
-rw-r--r--js/src/frontend/BytecodeSection.cpp195
-rw-r--r--js/src/frontend/BytecodeSection.h393
-rw-r--r--js/src/frontend/CForEmitter.cpp179
-rw-r--r--js/src/frontend/CForEmitter.h175
-rw-r--r--js/src/frontend/CallOrNewEmitter.cpp365
-rw-r--r--js/src/frontend/CallOrNewEmitter.h352
-rw-r--r--js/src/frontend/CompilationStencil.h1842
-rw-r--r--js/src/frontend/CompileScript.cpp175
-rw-r--r--js/src/frontend/DecoratorEmitter.cpp840
-rw-r--r--js/src/frontend/DecoratorEmitter.h59
-rw-r--r--js/src/frontend/DefaultEmitter.cpp75
-rw-r--r--js/src/frontend/DefaultEmitter.h65
-rw-r--r--js/src/frontend/DestructuringFlavor.h24
-rw-r--r--js/src/frontend/DoWhileEmitter.cpp69
-rw-r--r--js/src/frontend/DoWhileEmitter.h80
-rw-r--r--js/src/frontend/EitherParser.h58
-rw-r--r--js/src/frontend/ElemOpEmitter.cpp257
-rw-r--r--js/src/frontend/ElemOpEmitter.h267
-rw-r--r--js/src/frontend/EmitterScope.cpp1117
-rw-r--r--js/src/frontend/EmitterScope.h210
-rw-r--r--js/src/frontend/ErrorReporter.h342
-rw-r--r--js/src/frontend/ExpressionStatementEmitter.cpp54
-rw-r--r--js/src/frontend/ExpressionStatementEmitter.h81
-rw-r--r--js/src/frontend/FoldConstants.cpp1581
-rw-r--r--js/src/frontend/FoldConstants.h53
-rw-r--r--js/src/frontend/ForInEmitter.cpp154
-rw-r--r--js/src/frontend/ForInEmitter.h117
-rw-r--r--js/src/frontend/ForOfEmitter.cpp223
-rw-r--r--js/src/frontend/ForOfEmitter.h116
-rw-r--r--js/src/frontend/ForOfLoopControl.cpp196
-rw-r--r--js/src/frontend/ForOfLoopControl.h97
-rw-r--r--js/src/frontend/Frontend2.cpp701
-rw-r--r--js/src/frontend/Frontend2.h61
-rw-r--r--js/src/frontend/FrontendContext.cpp237
-rw-r--r--js/src/frontend/FrontendContext.h260
-rw-r--r--js/src/frontend/FullParseHandler.h1195
-rw-r--r--js/src/frontend/FunctionEmitter.cpp1017
-rw-r--r--js/src/frontend/FunctionEmitter.h436
-rw-r--r--js/src/frontend/FunctionSyntaxKind.h41
-rw-r--r--js/src/frontend/GenerateReservedWords.py232
-rw-r--r--js/src/frontend/IfEmitter.cpp286
-rw-r--r--js/src/frontend/IfEmitter.h311
-rw-r--r--js/src/frontend/IteratorKind.h16
-rw-r--r--js/src/frontend/JumpList.cpp46
-rw-r--r--js/src/frontend/JumpList.h84
-rw-r--r--js/src/frontend/LabelEmitter.cpp40
-rw-r--r--js/src/frontend/LabelEmitter.h65
-rw-r--r--js/src/frontend/LexicalScopeEmitter.cpp57
-rw-r--r--js/src/frontend/LexicalScopeEmitter.h94
-rw-r--r--js/src/frontend/ModuleSharedContext.h47
-rw-r--r--js/src/frontend/NameAnalysisTypes.h390
-rw-r--r--js/src/frontend/NameCollections.h455
-rw-r--r--js/src/frontend/NameFunctions.cpp531
-rw-r--r--js/src/frontend/NameFunctions.h27
-rw-r--r--js/src/frontend/NameOpEmitter.cpp481
-rw-r--r--js/src/frontend/NameOpEmitter.h182
-rw-r--r--js/src/frontend/ObjLiteral.cpp537
-rw-r--r--js/src/frontend/ObjLiteral.h772
-rw-r--r--js/src/frontend/ObjectEmitter.cpp897
-rw-r--r--js/src/frontend/ObjectEmitter.h849
-rw-r--r--js/src/frontend/OptionalEmitter.cpp142
-rw-r--r--js/src/frontend/OptionalEmitter.h220
-rw-r--r--js/src/frontend/ParseContext-inl.h177
-rw-r--r--js/src/frontend/ParseContext.cpp723
-rw-r--r--js/src/frontend/ParseContext.h622
-rw-r--r--js/src/frontend/ParseNode.cpp430
-rw-r--r--js/src/frontend/ParseNode.h2556
-rw-r--r--js/src/frontend/ParseNodeVerify.cpp50
-rw-r--r--js/src/frontend/ParseNodeVerify.h48
-rw-r--r--js/src/frontend/ParseNodeVisitor.h136
-rw-r--r--js/src/frontend/Parser.cpp13078
-rw-r--r--js/src/frontend/Parser.h1955
-rw-r--r--js/src/frontend/ParserAtom.cpp1316
-rw-r--r--js/src/frontend/ParserAtom.h905
-rw-r--r--js/src/frontend/PrivateOpEmitter.cpp331
-rw-r--r--js/src/frontend/PrivateOpEmitter.h231
-rw-r--r--js/src/frontend/PropOpEmitter.cpp244
-rw-r--r--js/src/frontend/PropOpEmitter.h254
-rw-r--r--js/src/frontend/ReservedWords.h78
-rw-r--r--js/src/frontend/ScopeBindingCache.h281
-rw-r--r--js/src/frontend/ScopeIndex.h32
-rw-r--r--js/src/frontend/ScriptIndex.h42
-rw-r--r--js/src/frontend/SelfHostedIter.h28
-rw-r--r--js/src/frontend/SharedContext-inl.h23
-rw-r--r--js/src/frontend/SharedContext.cpp409
-rw-r--r--js/src/frontend/SharedContext.h747
-rw-r--r--js/src/frontend/SourceNotes.cpp13
-rw-r--r--js/src/frontend/SourceNotes.h428
-rw-r--r--js/src/frontend/Stencil.cpp5363
-rw-r--r--js/src/frontend/Stencil.h1141
-rw-r--r--js/src/frontend/StencilXdr.cpp1536
-rw-r--r--js/src/frontend/StencilXdr.h217
-rw-r--r--js/src/frontend/SwitchEmitter.cpp414
-rw-r--r--js/src/frontend/SwitchEmitter.h474
-rw-r--r--js/src/frontend/SyntaxParseHandler.h797
-rw-r--r--js/src/frontend/TDZCheckCache.cpp76
-rw-r--r--js/src/frontend/TDZCheckCache.h59
-rw-r--r--js/src/frontend/TaggedParserAtomIndexHasher.h44
-rw-r--r--js/src/frontend/Token.h218
-rw-r--r--js/src/frontend/TokenKind.h333
-rw-r--r--js/src/frontend/TokenStream.cpp3829
-rw-r--r--js/src/frontend/TokenStream.h2962
-rw-r--r--js/src/frontend/TryEmitter.cpp317
-rw-r--r--js/src/frontend/TryEmitter.h226
-rw-r--r--js/src/frontend/TypedIndex.h42
-rw-r--r--js/src/frontend/UsedNameTracker.h259
-rw-r--r--js/src/frontend/ValueUsage.h30
-rw-r--r--js/src/frontend/WhileEmitter.cpp85
-rw-r--r--js/src/frontend/WhileEmitter.h90
-rwxr-xr-xjs/src/frontend/align_stack_comment.py108
-rw-r--r--js/src/frontend/moz.build98
-rw-r--r--js/src/frontend/smoosh/Cargo.toml23
-rw-r--r--js/src/frontend/smoosh/build.rs27
-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
128 files changed, 81545 insertions, 0 deletions
diff --git a/js/src/frontend/AbstractScopePtr.cpp b/js/src/frontend/AbstractScopePtr.cpp
new file mode 100644
index 0000000000..a2dd16a104
--- /dev/null
+++ b/js/src/frontend/AbstractScopePtr.cpp
@@ -0,0 +1,61 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "frontend/AbstractScopePtr.h"
+
+#include "mozilla/Assertions.h"
+
+#include "frontend/CompilationStencil.h" // CompilationState
+#include "frontend/Stencil.h"
+#include "js/Vector.h"
+#include "vm/Scope.h" // for FunctionScope
+
+using namespace js;
+using namespace js::frontend;
+
+ScopeStencil& AbstractScopePtr::scopeData() const {
+ MOZ_ASSERT(isScopeStencil());
+ return compilationState_.scopeData[index_];
+}
+
+ScopeKind AbstractScopePtr::kind() const {
+ if (isScopeStencil()) {
+ return scopeData().kind();
+ }
+ return compilationState_.scopeContext.enclosingScopeKind;
+}
+
+AbstractScopePtr AbstractScopePtr::enclosing() const {
+ MOZ_ASSERT(isScopeStencil());
+ return scopeData().enclosing(compilationState_);
+}
+
+bool AbstractScopePtr::hasEnvironment() const {
+ if (isScopeStencil()) {
+ return scopeData().hasEnvironment();
+ }
+ return compilationState_.scopeContext.enclosingScopeHasEnvironment;
+}
+
+bool AbstractScopePtr::isArrow() const {
+ MOZ_ASSERT(is<FunctionScope>());
+ if (isScopeStencil()) {
+ return scopeData().isArrow();
+ }
+ return compilationState_.scopeContext.enclosingScopeIsArrow;
+}
+
+#ifdef DEBUG
+bool AbstractScopePtr::hasNonSyntacticScopeOnChain() const {
+ if (isScopeStencil()) {
+ if (kind() == ScopeKind::NonSyntactic) {
+ return true;
+ }
+ return enclosing().hasNonSyntacticScopeOnChain();
+ }
+ return compilationState_.scopeContext.hasNonSyntacticScopeOnChain;
+}
+#endif
diff --git a/js/src/frontend/AbstractScopePtr.h b/js/src/frontend/AbstractScopePtr.h
new file mode 100644
index 0000000000..33867632f4
--- /dev/null
+++ b/js/src/frontend/AbstractScopePtr.h
@@ -0,0 +1,90 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef frontend_AbstractScopePtr_h
+#define frontend_AbstractScopePtr_h
+
+#include <type_traits>
+
+#include "frontend/ScopeIndex.h"
+#include "vm/ScopeKind.h" // For ScopeKind
+
+namespace js {
+class Scope;
+class GlobalScope;
+class EvalScope;
+
+namespace frontend {
+struct CompilationState;
+class ScopeStencil;
+} // namespace frontend
+
+// An interface class to support Scope queries in the frontend without requiring
+// a GC Allocated scope to necessarily exist.
+//
+// This abstracts Scope* and a ScopeStencil type used within the frontend before
+// the Scope is allocated.
+//
+// Queries to GC Scope should be pre-calculated and stored into ScopeContext.
+class AbstractScopePtr {
+ private:
+ // ScopeIndex::invalid() if this points CompilationInput.enclosingScope.
+ ScopeIndex index_;
+
+ frontend::CompilationState& compilationState_;
+
+ public:
+ friend class js::Scope;
+
+ AbstractScopePtr(frontend::CompilationState& compilationState,
+ ScopeIndex index)
+ : index_(index), compilationState_(compilationState) {}
+
+ static AbstractScopePtr compilationEnclosingScope(
+ frontend::CompilationState& compilationState) {
+ return AbstractScopePtr(compilationState, ScopeIndex::invalid());
+ }
+
+ private:
+ bool isScopeStencil() const { return index_.isValid(); }
+
+ frontend::ScopeStencil& scopeData() const;
+
+ public:
+ // This allows us to check whether or not this provider wraps
+ // or otherwise would reify to a particular scope type.
+ template <typename T>
+ bool is() const {
+ static_assert(std::is_base_of_v<Scope, T>,
+ "Trying to ask about non-Scope type");
+ return kind() == T::classScopeKind_;
+ }
+
+ ScopeKind kind() const;
+ AbstractScopePtr enclosing() const;
+ bool hasEnvironment() const;
+ // Valid iff is<FunctionScope>
+ bool isArrow() const;
+
+#ifdef DEBUG
+ bool hasNonSyntacticScopeOnChain() const;
+#endif
+};
+
+// Specializations of AbstractScopePtr::is
+template <>
+inline bool AbstractScopePtr::is<GlobalScope>() const {
+ return kind() == ScopeKind::Global || kind() == ScopeKind::NonSyntactic;
+}
+
+template <>
+inline bool AbstractScopePtr::is<EvalScope>() const {
+ return kind() == ScopeKind::Eval || kind() == ScopeKind::StrictEval;
+}
+
+} // namespace js
+
+#endif // frontend_AbstractScopePtr_h
diff --git a/js/src/frontend/AsyncEmitter.cpp b/js/src/frontend/AsyncEmitter.cpp
new file mode 100644
index 0000000000..8596f4ba7f
--- /dev/null
+++ b/js/src/frontend/AsyncEmitter.cpp
@@ -0,0 +1,217 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "frontend/AsyncEmitter.h"
+
+#include "mozilla/Assertions.h" // MOZ_ASSERT
+
+#include "frontend/BytecodeEmitter.h" // BytecodeEmitter
+#include "frontend/NameOpEmitter.h" // NameOpEmitter
+#include "frontend/ParserAtom.h" // TaggedParserAtomIndex
+#include "vm/AsyncFunctionResolveKind.h" // AsyncFunctionResolveKind
+#include "vm/Opcodes.h" // JSOp
+
+using namespace js;
+using namespace js::frontend;
+
+bool AsyncEmitter::prepareForParamsWithExpressionOrDestructuring() {
+ MOZ_ASSERT(state_ == State::Start);
+#ifdef DEBUG
+ state_ = State::Parameters;
+#endif
+
+ rejectTryCatch_.emplace(bce_, TryEmitter::Kind::TryCatch,
+ TryEmitter::ControlKind::NonSyntactic);
+ return rejectTryCatch_->emitTry();
+}
+
+bool AsyncEmitter::prepareForParamsWithoutExpressionOrDestructuring() {
+ MOZ_ASSERT(state_ == State::Start);
+#ifdef DEBUG
+ state_ = State::Parameters;
+#endif
+ return true;
+}
+
+bool AsyncEmitter::emitParamsEpilogue() {
+ MOZ_ASSERT(state_ == State::Parameters);
+
+ if (rejectTryCatch_) {
+ // If we get here, we need to reset the TryEmitter. Parameters can't reuse
+ // the reject try-catch block from the function body, because the body
+ // may have pushed an additional var-environment. This messes up scope
+ // resolution for the |.generator| variable, because we'd need different
+ // hops to reach |.generator| depending on whether the error was thrown
+ // from the parameters or the function body.
+ if (!emitRejectCatch()) {
+ return false;
+ }
+ }
+
+#ifdef DEBUG
+ state_ = State::PostParams;
+#endif
+ return true;
+}
+
+bool AsyncEmitter::prepareForModule() {
+ // Unlike functions, modules do not have params that we need to worry about.
+ // Instead, this code is for setting up the required generator that will be
+ // used for top level await. Before we can start using top-level await in
+ // modules, we need to emit a
+ // |.generator| which we can use to pause and resume execution.
+ MOZ_ASSERT(state_ == State::Start);
+ MOZ_ASSERT(bce_->lookupName(TaggedParserAtomIndex::WellKnown::dotGenerator())
+ .hasKnownSlot());
+
+ NameOpEmitter noe(bce_, TaggedParserAtomIndex::WellKnown::dotGenerator(),
+ NameOpEmitter::Kind::Initialize);
+ if (!noe.prepareForRhs()) {
+ // [stack]
+ return false;
+ }
+ if (!bce_->emit1(JSOp::Generator)) {
+ // [stack] GEN
+ return false;
+ }
+ if (!noe.emitAssignment()) {
+ // [stack] GEN
+ return false;
+ }
+ if (!bce_->emit1(JSOp::Pop)) {
+ // [stack]
+ return false;
+ }
+
+#ifdef DEBUG
+ state_ = State::ModulePrologue;
+#endif
+
+ return true;
+}
+
+bool AsyncEmitter::prepareForBody() {
+ MOZ_ASSERT(state_ == State::PostParams || state_ == State::ModulePrologue);
+
+ rejectTryCatch_.emplace(bce_, TryEmitter::Kind::TryCatch,
+ TryEmitter::ControlKind::NonSyntactic);
+#ifdef DEBUG
+ state_ = State::Body;
+#endif
+ return rejectTryCatch_->emitTry();
+}
+
+bool AsyncEmitter::emitEndFunction() {
+#ifdef DEBUG
+ MOZ_ASSERT(state_ == State::Body);
+#endif
+
+ // The final yield has already been emitted
+ // by FunctionScriptEmitter::emitEndBody().
+
+ if (!emitRejectCatch()) {
+ return false;
+ }
+
+#ifdef DEBUG
+ state_ = State::End;
+#endif
+ return true;
+}
+
+bool AsyncEmitter::emitEndModule() {
+#ifdef DEBUG
+ MOZ_ASSERT(state_ == State::Body);
+#endif
+
+ if (!emitFinalYield()) {
+ return false;
+ }
+
+ if (!emitRejectCatch()) {
+ return false;
+ }
+
+#ifdef DEBUG
+ state_ = State::End;
+#endif
+ return true;
+}
+
+bool AsyncEmitter::emitFinalYield() {
+ if (!bce_->emit1(JSOp::Undefined)) {
+ // [stack] UNDEF
+ return false;
+ }
+
+ if (!bce_->emitGetDotGeneratorInInnermostScope()) {
+ // [stack] UNDEF GEN
+ return false;
+ }
+
+ if (!bce_->emit2(JSOp::AsyncResolve,
+ uint8_t(AsyncFunctionResolveKind::Fulfill))) {
+ // [stack] PROMISE
+ return false;
+ }
+
+ if (!bce_->emit1(JSOp::SetRval)) {
+ // [stack]
+ return false;
+ }
+
+ if (!bce_->emitGetDotGeneratorInInnermostScope()) {
+ // [stack] GEN
+ return false;
+ }
+
+ if (!bce_->emitYieldOp(JSOp::FinalYieldRval)) {
+ // [stack]
+ return false;
+ }
+
+ return true;
+}
+
+bool AsyncEmitter::emitRejectCatch() {
+ if (!rejectTryCatch_->emitCatch()) {
+ // [stack] EXC
+ return false;
+ }
+
+ if (!bce_->emitGetDotGeneratorInInnermostScope()) {
+ // [stack] EXC GEN
+ return false;
+ }
+
+ if (!bce_->emit2(JSOp::AsyncResolve,
+ uint8_t(AsyncFunctionResolveKind::Reject))) {
+ // [stack] PROMISE
+ return false;
+ }
+
+ if (!bce_->emit1(JSOp::SetRval)) {
+ // [stack]
+ return false;
+ }
+
+ if (!bce_->emitGetDotGeneratorInInnermostScope()) {
+ // [stack] GEN
+ return false;
+ }
+
+ if (!bce_->emitYieldOp(JSOp::FinalYieldRval)) {
+ // [stack]
+ return false;
+ }
+
+ if (!rejectTryCatch_->emitEnd()) {
+ return false;
+ }
+
+ rejectTryCatch_.reset();
+ return true;
+}
diff --git a/js/src/frontend/AsyncEmitter.h b/js/src/frontend/AsyncEmitter.h
new file mode 100644
index 0000000000..778f708c4f
--- /dev/null
+++ b/js/src/frontend/AsyncEmitter.h
@@ -0,0 +1,174 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef frontend_AsyncEmitter_h
+#define frontend_AsyncEmitter_h
+
+#include "mozilla/Attributes.h" // MOZ_STACK_CLASS
+
+#include "frontend/TryEmitter.h" // TryEmitter
+
+namespace js {
+namespace frontend {
+
+struct BytecodeEmitter;
+
+// Class for emitting Bytecode associated with the AsyncFunctionGenerator.
+//
+// Usage:
+//
+// For an async function, the params have to be handled separately,
+// because the body may have pushed an additional var environment, changing
+// the number of hops required to reach the |.generator| variable. In order
+// to handle this, we can't reuse the same TryCatch emitter.
+//
+// Simple case - For a function without expression or destructuring
+// parameters:
+// `async function f(<params>) {<body>}`,
+// AsyncEmitter ae(this);
+//
+// ae.prepareForParamsWithoutExpressionOrDestructuring();
+// // Emit Params.
+// ...
+// ae.paramsEpilogue(); // We need to emit the epilogue before the extra
+// VarScope emitExtraBodyVarScope();
+//
+// // Emit new scope
+// ae.prepareForBody();
+//
+// // Emit body of the Function.
+// ...
+//
+// ae.emitEndFunction();
+//
+// Complex case - For a function with expression or destructuring parameters:
+// `async function f(<expression>) {<body>}`,
+// AsyncEmitter ae(this);
+//
+// ae.prepareForParamsWithExpressionOrDestructuring();
+//
+// // Emit Params.
+// ...
+// ae.paramsEpilogue(); // We need to emit the epilogue before the extra
+// // VarScope
+// emitExtraBodyVarScope();
+//
+// // Emit new scope
+// ae.prepareForBody();
+//
+// // Emit body of the Function.
+// ...
+//
+// // The final yield is emitted in FunctionScriptEmitter::emitEndBody().
+// ae.emitEndFunction();
+//
+//
+// Async Module case - For a module with `await` in the top level:
+// AsyncEmitter ae(this);
+// ae.prepareForModule(); // prepareForModule is used to setup the generator
+// // for the async module.
+// switchToMain();
+// ...
+//
+// // Emit new scope
+// ae.prepareForBody();
+//
+// // Emit body of the Script.
+//
+// ae.emitEndModule();
+//
+
+class MOZ_STACK_CLASS AsyncEmitter {
+ private:
+ BytecodeEmitter* bce_;
+
+ // try-catch block for async function parameter and body.
+ mozilla::Maybe<TryEmitter> rejectTryCatch_;
+
+#ifdef DEBUG
+ // The state of this emitter.
+ //
+ // +-------+
+ // | Start |-+
+ // +-------+ |
+ // |
+ // +----------+
+ // |
+ // | [Parameters with Expression or Destructuring]
+ // | prepareForParamsWithExpressionOrDestructuring +------------+
+ // +----------------------------------------------------| Parameters |-->+
+ // | +------------+ |
+ // | |
+ // | [Parameters Without Expression or Destructuring] |
+ // | prepareForParamsWithoutExpressionOrDestructuring +------------+ |
+ // +----------------------------------------------------| Parameters |-->+
+ // | +------------+ |
+ // | [Modules] |
+ // | prepareForModule +----------------+ |
+ // +-------------------->| ModulePrologue |--+ |
+ // +----------------+ | |
+ // | |
+ // | |
+ // +-----------------------------------------+ |
+ // | |
+ // | |
+ // V +------------+ paramsEpilogue |
+ // +<--------------------| PostParams |<---------------------------------+
+ // | +------------+
+ // |
+ // | [Script body]
+ // | prepareForBody +---------+
+ // +-------------------->| Body |--------+
+ // +---------+ | <emit script body>
+ // +----------------------------------------+
+ // |
+ // | [Functions]
+ // | emitEndFunction +-----+
+ // +--------------------->| End |
+ // | +-----+
+ // |
+ // | [Modules]
+ // | emitEndModule +-----+
+ // +--------------------->| End |
+ // +-----+
+
+ enum class State {
+ // The initial state.
+ Start,
+
+ Parameters,
+
+ ModulePrologue,
+
+ PostParams,
+
+ Body,
+
+ End,
+ };
+
+ State state_ = State::Start;
+#endif
+
+ [[nodiscard]] bool emitRejectCatch();
+ [[nodiscard]] bool emitFinalYield();
+
+ public:
+ explicit AsyncEmitter(BytecodeEmitter* bce) : bce_(bce){};
+
+ [[nodiscard]] bool prepareForParamsWithoutExpressionOrDestructuring();
+ [[nodiscard]] bool prepareForParamsWithExpressionOrDestructuring();
+ [[nodiscard]] bool prepareForModule();
+ [[nodiscard]] bool emitParamsEpilogue();
+ [[nodiscard]] bool prepareForBody();
+ [[nodiscard]] bool emitEndFunction();
+ [[nodiscard]] bool emitEndModule();
+};
+
+} /* namespace frontend */
+} /* namespace js */
+
+#endif /* frontend_AsyncEmitter_h */
diff --git a/js/src/frontend/BytecodeCompilation.h b/js/src/frontend/BytecodeCompilation.h
new file mode 100644
index 0000000000..f6bd649725
--- /dev/null
+++ b/js/src/frontend/BytecodeCompilation.h
@@ -0,0 +1,108 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef frontend_BytecodeCompilation_h
+#define frontend_BytecodeCompilation_h
+
+#include "mozilla/AlreadyAddRefed.h"
+#include "mozilla/Utf8.h" // mozilla::Utf8Unit
+
+#include "frontend/ScriptIndex.h" // ScriptIndex
+#include "js/CompileOptions.h" // JS::ReadOnlyCompileOptions, JS::InstantiateOptions
+#include "js/SourceText.h" // JS::SourceText
+#include "js/TypeDecls.h" // JS::Handle (fwd)
+#include "js/UniquePtr.h" // js::UniquePtr
+#include "vm/ScopeKind.h" // js::ScopeKind
+
+namespace js {
+
+class Scope;
+class LifoAlloc;
+class FrontendContext;
+
+namespace frontend {
+
+struct CompilationInput;
+struct CompilationGCOutput;
+struct CompilationStencil;
+struct ExtensibleCompilationStencil;
+class ScopeBindingCache;
+
+extern already_AddRefed<CompilationStencil> CompileGlobalScriptToStencil(
+ JSContext* maybeCx, FrontendContext* fc, js::LifoAlloc& tempLifoAlloc,
+ CompilationInput& input, ScopeBindingCache* scopeCache,
+ JS::SourceText<char16_t>& srcBuf, ScopeKind scopeKind);
+
+extern already_AddRefed<CompilationStencil> CompileGlobalScriptToStencil(
+ JSContext* maybeCx, FrontendContext* fc, js::LifoAlloc& tempLifoAlloc,
+ CompilationInput& input, ScopeBindingCache* scopeCache,
+ JS::SourceText<mozilla::Utf8Unit>& srcBuf, ScopeKind scopeKind);
+
+extern UniquePtr<ExtensibleCompilationStencil>
+CompileGlobalScriptToExtensibleStencil(JSContext* maybeCx, FrontendContext* fc,
+ CompilationInput& input,
+ ScopeBindingCache* scopeCache,
+ JS::SourceText<char16_t>& srcBuf,
+ ScopeKind scopeKind);
+
+extern UniquePtr<ExtensibleCompilationStencil>
+CompileGlobalScriptToExtensibleStencil(
+ JSContext* maybeCx, FrontendContext* fc, CompilationInput& input,
+ ScopeBindingCache* scopeCache, 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.
+[[nodiscard]] extern bool PrepareForInstantiate(
+ JSContext* maybeCx, FrontendContext* fc, CompilationInput& input,
+ const CompilationStencil& stencil, CompilationGCOutput& gcOutput);
+
+[[nodiscard]] extern bool InstantiateStencils(JSContext* cx,
+ CompilationInput& input,
+ const CompilationStencil& stencil,
+ CompilationGCOutput& gcOutput);
+
+extern JSScript* CompileGlobalScript(JSContext* cx, FrontendContext* fc,
+ const JS::ReadOnlyCompileOptions& options,
+ JS::SourceText<char16_t>& srcBuf,
+ ScopeKind scopeKind);
+
+extern JSScript* CompileGlobalScript(JSContext* cx, FrontendContext* fc,
+ 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 bool DelazifyCanonicalScriptedFunction(JSContext* cx,
+ FrontendContext* fc,
+ JS::Handle<JSFunction*> fun);
+
+extern already_AddRefed<CompilationStencil> DelazifyCanonicalScriptedFunction(
+ JSContext* cx, FrontendContext* fc, ScopeBindingCache* scopeCache,
+ CompilationStencil& context, ScriptIndex scriptIndex);
+
+// Certain compile options will disable the syntax parser entirely.
+inline bool CanLazilyParse(const JS::ReadOnlyCompileOptions& options) {
+ return !options.discardSource && !options.sourceIsLazy &&
+ !options.forceFullParse();
+}
+
+void FireOnNewScript(JSContext* cx, const JS::InstantiateOptions& options,
+ JS::Handle<JSScript*> script);
+
+} // namespace frontend
+
+} // namespace js
+
+#endif // frontend_BytecodeCompilation_h
diff --git a/js/src/frontend/BytecodeCompiler.cpp b/js/src/frontend/BytecodeCompiler.cpp
new file mode 100644
index 0000000000..e4c709e7e7
--- /dev/null
+++ b/js/src/frontend/BytecodeCompiler.cpp
@@ -0,0 +1,1565 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "frontend/BytecodeCompiler.h"
+
+#include "mozilla/Attributes.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/Utf8.h" // mozilla::Utf8Unit
+#include "mozilla/Variant.h" // mozilla::Variant
+
+#include "debugger/DebugAPI.h"
+#include "ds/LifoAlloc.h"
+#include "frontend/BytecodeCompilation.h"
+#include "frontend/BytecodeEmitter.h"
+#include "frontend/CompilationStencil.h"
+#include "frontend/EitherParser.h"
+#ifdef JS_ENABLE_SMOOSH
+# include "frontend/Frontend2.h" // Smoosh
+#endif
+#include "frontend/FrontendContext.h" // AutoReportFrontendContext
+#include "frontend/ModuleSharedContext.h"
+#include "js/experimental/JSStencil.h"
+#include "js/Modules.h" // JS::ImportAssertionVector
+#include "js/SourceText.h"
+#include "js/UniquePtr.h"
+#include "vm/FunctionFlags.h" // FunctionFlags
+#include "vm/GeneratorAndAsyncKind.h" // js::GeneratorKind, js::FunctionAsyncKind
+#include "vm/HelperThreads.h" // StartOffThreadDelazification, WaitForAllDelazifyTasks
+#include "vm/JSContext.h"
+#include "vm/JSScript.h" // ScriptSource, UncompressedSourceCache
+#include "vm/ModuleBuilder.h" // js::ModuleBuilder
+#include "vm/Time.h" // AutoIncrementalTimer
+#include "wasm/AsmJS.h"
+
+#include "vm/GeckoProfiler-inl.h"
+#include "vm/JSContext-inl.h"
+
+using namespace js;
+using namespace js::frontend;
+
+using mozilla::Maybe;
+using mozilla::Utf8Unit;
+
+using JS::CompileOptions;
+using JS::ReadOnlyCompileOptions;
+using JS::SourceText;
+
+// RAII class to check the frontend reports an exception when it fails to
+// compile a script.
+class MOZ_RAII AutoAssertReportedException {
+#ifdef DEBUG
+ JSContext* maybeCx_;
+ FrontendContext* fc_;
+ bool check_;
+
+ public:
+ explicit AutoAssertReportedException(JSContext* maybeCx, FrontendContext* fc)
+ : maybeCx_(maybeCx), fc_(fc), check_(true) {}
+ void reset() { check_ = false; }
+ ~AutoAssertReportedException() {
+ if (!check_) {
+ return;
+ }
+
+ // Error while compiling self-hosted code isn't set as an exception.
+ // TODO: Remove this once all errors are added to frontend context.
+ if (maybeCx_ && !maybeCx_->runtime()->hasInitializedSelfHosting()) {
+ return;
+ }
+
+ // TODO: Remove this once JSContext is removed from frontend.
+ if (maybeCx_ && !maybeCx_->isHelperThreadContext()) {
+ MOZ_ASSERT(maybeCx_->isExceptionPending() || fc_->hadErrors());
+ } else {
+ MOZ_ASSERT(fc_->hadErrors());
+ }
+ }
+#else
+ public:
+ explicit AutoAssertReportedException(JSContext*, FrontendContext*) {}
+ void reset() {}
+#endif
+};
+
+static bool EmplaceEmitter(CompilationState& compilationState,
+ Maybe<BytecodeEmitter>& emitter, FrontendContext* fc,
+ const EitherParser& parser, SharedContext* sc);
+
+template <typename Unit>
+class MOZ_STACK_CLASS SourceAwareCompiler {
+ protected:
+ SourceText<Unit>& sourceBuffer_;
+
+ CompilationState compilationState_;
+
+ Maybe<Parser<SyntaxParseHandler, Unit>> syntaxParser;
+ Maybe<Parser<FullParseHandler, Unit>> parser;
+ FrontendContext* fc_ = nullptr;
+
+ using TokenStreamPosition = frontend::TokenStreamPosition<Unit>;
+
+ protected:
+ explicit SourceAwareCompiler(FrontendContext* fc,
+ LifoAllocScope& parserAllocScope,
+ CompilationInput& input,
+ SourceText<Unit>& sourceBuffer)
+ : sourceBuffer_(sourceBuffer),
+ compilationState_(fc, parserAllocScope, input) {
+ MOZ_ASSERT(sourceBuffer_.get() != nullptr);
+ }
+
+ [[nodiscard]] bool init(FrontendContext* fc, ScopeBindingCache* scopeCache,
+ InheritThis inheritThis = InheritThis::No,
+ JSObject* enclosingEnv = nullptr) {
+ if (!compilationState_.init(fc, scopeCache, inheritThis, enclosingEnv)) {
+ return false;
+ }
+
+ return createSourceAndParser(fc);
+ }
+
+ // Call this before calling compile{Global,Eval}Script.
+ [[nodiscard]] bool createSourceAndParser(FrontendContext* fc);
+
+ void assertSourceAndParserCreated() const {
+ MOZ_ASSERT(compilationState_.source != nullptr);
+ MOZ_ASSERT(parser.isSome());
+ }
+
+ void assertSourceParserAndScriptCreated() { assertSourceAndParserCreated(); }
+
+ [[nodiscard]] bool emplaceEmitter(Maybe<BytecodeEmitter>& emitter,
+ SharedContext* sharedContext) {
+ return EmplaceEmitter(compilationState_, emitter, fc_,
+ EitherParser(parser.ptr()), sharedContext);
+ }
+
+ bool canHandleParseFailure(const Directives& newDirectives);
+
+ void handleParseFailure(
+ const Directives& newDirectives, TokenStreamPosition& startPosition,
+ CompilationState::CompilationStatePosition& startStatePosition);
+
+ public:
+ CompilationState& compilationState() { return compilationState_; };
+
+ ExtensibleCompilationStencil& stencil() { return compilationState_; }
+};
+
+template <typename Unit>
+class MOZ_STACK_CLASS 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(FrontendContext* fc, LifoAllocScope& parserAllocScope,
+ CompilationInput& input,
+ SourceText<Unit>& sourceBuffer)
+ : Base(fc, parserAllocScope, input, sourceBuffer) {}
+
+ using Base::init;
+ using Base::stencil;
+
+ [[nodiscard]] bool compile(JSContext* cx, SharedContext* sc);
+};
+
+#ifdef JS_ENABLE_SMOOSH
+[[nodiscard]] static bool TrySmoosh(
+ JSContext* cx, FrontendContext* fc, CompilationInput& input,
+ JS::SourceText<mozilla::Utf8Unit>& srcBuf,
+ UniquePtr<ExtensibleCompilationStencil>& stencilOut) {
+ MOZ_ASSERT(!stencilOut);
+
+ if (!cx->options().trySmoosh()) {
+ return true;
+ }
+
+ JSRuntime* rt = cx->runtime();
+ if (!Smoosh::tryCompileGlobalScriptToExtensibleStencil(cx, fc, input, srcBuf,
+ stencilOut)) {
+ return false;
+ }
+
+ if (cx->options().trackNotImplemented()) {
+ if (stencilOut) {
+ rt->parserWatcherFile.put("1");
+ } else {
+ rt->parserWatcherFile.put("0");
+ }
+ }
+
+ if (!stencilOut) {
+ fprintf(stderr, "Falling back!\n");
+ return true;
+ }
+
+ return stencilOut->source->assignSource(fc, input.options, srcBuf);
+}
+
+[[nodiscard]] static bool TrySmoosh(
+ JSContext* cx, FrontendContext* fc, CompilationInput& input,
+ JS::SourceText<char16_t>& srcBuf,
+ UniquePtr<ExtensibleCompilationStencil>& stencilOut) {
+ MOZ_ASSERT(!stencilOut);
+ return true;
+}
+#endif // JS_ENABLE_SMOOSH
+
+using BytecodeCompilerOutput =
+ mozilla::Variant<UniquePtr<ExtensibleCompilationStencil>,
+ RefPtr<CompilationStencil>, CompilationGCOutput*>;
+
+// Compile global script, and return it as one of:
+// * ExtensibleCompilationStencil (without instantiation)
+// * CompilationStencil (without instantiation, has no external dependency)
+// * CompilationGCOutput (with instantiation).
+template <typename Unit>
+[[nodiscard]] static bool CompileGlobalScriptToStencilAndMaybeInstantiate(
+ JSContext* maybeCx, FrontendContext* fc, js::LifoAlloc& tempLifoAlloc,
+ CompilationInput& input, ScopeBindingCache* scopeCache,
+ JS::SourceText<Unit>& srcBuf, ScopeKind scopeKind,
+ BytecodeCompilerOutput& output) {
+#ifdef JS_ENABLE_SMOOSH
+ if (maybeCx) {
+ UniquePtr<ExtensibleCompilationStencil> extensibleStencil;
+ if (!TrySmoosh(maybeCx, fc, input, srcBuf, extensibleStencil)) {
+ return false;
+ }
+ if (extensibleStencil) {
+ if (input.options.populateDelazificationCache() &&
+ !maybeCx->isHelperThreadContext()) {
+ BorrowingCompilationStencil borrowingStencil(*extensibleStencil);
+ StartOffThreadDelazification(maybeCx, input.options, borrowingStencil);
+
+ // When we are trying to validate whether on-demand delazification
+ // generate the same stencil as concurrent delazification, we want to
+ // parse everything eagerly off-thread ahead of re-parsing everything on
+ // demand, to compare the outcome.
+ if (input.options.waitForDelazificationCache()) {
+ WaitForAllDelazifyTasks(maybeCx->runtime());
+ }
+ }
+ if (output.is<UniquePtr<ExtensibleCompilationStencil>>()) {
+ output.as<UniquePtr<ExtensibleCompilationStencil>>() =
+ std::move(extensibleStencil);
+ } else if (output.is<RefPtr<CompilationStencil>>()) {
+ RefPtr<CompilationStencil> stencil =
+ fc->getAllocator()->new_<frontend::CompilationStencil>(
+ std::move(extensibleStencil));
+ if (!stencil) {
+ return false;
+ }
+
+ output.as<RefPtr<CompilationStencil>>() = std::move(stencil);
+ } else {
+ BorrowingCompilationStencil borrowingStencil(*extensibleStencil);
+ if (!InstantiateStencils(maybeCx, input, borrowingStencil,
+ *(output.as<CompilationGCOutput*>()))) {
+ return false;
+ }
+ }
+ return true;
+ }
+ }
+#endif // JS_ENABLE_SMOOSH
+
+ if (input.options.selfHostingMode) {
+ if (!input.initForSelfHostingGlobal(fc)) {
+ return false;
+ }
+ } else {
+ if (!input.initForGlobal(fc)) {
+ return false;
+ }
+ }
+
+ AutoAssertReportedException assertException(maybeCx, fc);
+
+ LifoAllocScope parserAllocScope(&tempLifoAlloc);
+ ScriptCompiler<Unit> compiler(fc, parserAllocScope, input, srcBuf);
+ if (!compiler.init(fc, scopeCache)) {
+ return false;
+ }
+
+ SourceExtent extent = SourceExtent::makeGlobalExtent(
+ srcBuf.length(), input.options.lineno, input.options.column);
+
+ GlobalSharedContext globalsc(fc, scopeKind, input.options,
+ compiler.compilationState().directives, extent);
+
+ if (!compiler.compile(maybeCx, &globalsc)) {
+ return false;
+ }
+
+ if (input.options.populateDelazificationCache() && maybeCx &&
+ !maybeCx->isHelperThreadContext()) {
+ BorrowingCompilationStencil borrowingStencil(compiler.stencil());
+ StartOffThreadDelazification(maybeCx, input.options, borrowingStencil);
+
+ // When we are trying to validate whether on-demand delazification
+ // generate the same stencil as concurrent delazification, we want to
+ // parse everything eagerly off-thread ahead of re-parsing everything on
+ // demand, to compare the outcome.
+ if (input.options.waitForDelazificationCache()) {
+ WaitForAllDelazifyTasks(maybeCx->runtime());
+ }
+ }
+
+ if (output.is<UniquePtr<ExtensibleCompilationStencil>>()) {
+ auto stencil =
+ fc->getAllocator()->make_unique<ExtensibleCompilationStencil>(
+ std::move(compiler.stencil()));
+ if (!stencil) {
+ return false;
+ }
+ output.as<UniquePtr<ExtensibleCompilationStencil>>() = std::move(stencil);
+ } else if (output.is<RefPtr<CompilationStencil>>()) {
+ Maybe<AutoGeckoProfilerEntry> pseudoFrame;
+ if (maybeCx) {
+ pseudoFrame.emplace(maybeCx, "script emit",
+ JS::ProfilingCategoryPair::JS_Parsing);
+ }
+
+ auto extensibleStencil =
+ fc->getAllocator()->make_unique<frontend::ExtensibleCompilationStencil>(
+ std::move(compiler.stencil()));
+ if (!extensibleStencil) {
+ return false;
+ }
+
+ RefPtr<CompilationStencil> stencil =
+ fc->getAllocator()->new_<CompilationStencil>(
+ std::move(extensibleStencil));
+ if (!stencil) {
+ return false;
+ }
+
+ output.as<RefPtr<CompilationStencil>>() = std::move(stencil);
+ } else {
+ MOZ_ASSERT(maybeCx);
+ BorrowingCompilationStencil borrowingStencil(compiler.stencil());
+ if (!InstantiateStencils(maybeCx, input, borrowingStencil,
+ *(output.as<CompilationGCOutput*>()))) {
+ return false;
+ }
+ }
+
+ assertException.reset();
+ return true;
+}
+
+template <typename Unit>
+static already_AddRefed<CompilationStencil> CompileGlobalScriptToStencilImpl(
+ JSContext* maybeCx, FrontendContext* fc, js::LifoAlloc& tempLifoAlloc,
+ CompilationInput& input, ScopeBindingCache* scopeCache,
+ JS::SourceText<Unit>& srcBuf, ScopeKind scopeKind) {
+ using OutputType = RefPtr<CompilationStencil>;
+ BytecodeCompilerOutput output((OutputType()));
+ if (!CompileGlobalScriptToStencilAndMaybeInstantiate(
+ maybeCx, fc, tempLifoAlloc, input, scopeCache, srcBuf, scopeKind,
+ output)) {
+ return nullptr;
+ }
+ return output.as<OutputType>().forget();
+}
+
+already_AddRefed<CompilationStencil> frontend::CompileGlobalScriptToStencil(
+ JSContext* cx, FrontendContext* fc, js::LifoAlloc& tempLifoAlloc,
+ CompilationInput& input, ScopeBindingCache* scopeCache,
+ JS::SourceText<char16_t>& srcBuf, ScopeKind scopeKind) {
+ return CompileGlobalScriptToStencilImpl(cx, fc, tempLifoAlloc, input,
+ scopeCache, srcBuf, scopeKind);
+}
+
+already_AddRefed<CompilationStencil> frontend::CompileGlobalScriptToStencil(
+ JSContext* cx, FrontendContext* fc, js::LifoAlloc& tempLifoAlloc,
+ CompilationInput& input, ScopeBindingCache* scopeCache,
+ JS::SourceText<Utf8Unit>& srcBuf, ScopeKind scopeKind) {
+ return CompileGlobalScriptToStencilImpl(cx, fc, tempLifoAlloc, input,
+ scopeCache, srcBuf, scopeKind);
+}
+
+template <typename Unit>
+static UniquePtr<ExtensibleCompilationStencil>
+CompileGlobalScriptToExtensibleStencilImpl(JSContext* maybeCx,
+ FrontendContext* fc,
+ CompilationInput& input,
+ ScopeBindingCache* scopeCache,
+ JS::SourceText<Unit>& srcBuf,
+ ScopeKind scopeKind) {
+ using OutputType = UniquePtr<ExtensibleCompilationStencil>;
+ BytecodeCompilerOutput output((OutputType()));
+ if (!CompileGlobalScriptToStencilAndMaybeInstantiate(
+ maybeCx, fc, maybeCx->tempLifoAlloc(), input, scopeCache, srcBuf,
+ scopeKind, output)) {
+ return nullptr;
+ }
+ return std::move(output.as<OutputType>());
+}
+
+UniquePtr<ExtensibleCompilationStencil>
+frontend::CompileGlobalScriptToExtensibleStencil(
+ JSContext* maybeCx, FrontendContext* fc, CompilationInput& input,
+ ScopeBindingCache* scopeCache, JS::SourceText<char16_t>& srcBuf,
+ ScopeKind scopeKind) {
+ return CompileGlobalScriptToExtensibleStencilImpl(
+ maybeCx, fc, input, scopeCache, srcBuf, scopeKind);
+}
+
+UniquePtr<ExtensibleCompilationStencil>
+frontend::CompileGlobalScriptToExtensibleStencil(
+ JSContext* cx, FrontendContext* fc, CompilationInput& input,
+ ScopeBindingCache* scopeCache, JS::SourceText<Utf8Unit>& srcBuf,
+ ScopeKind scopeKind) {
+ return CompileGlobalScriptToExtensibleStencilImpl(cx, fc, input, scopeCache,
+ srcBuf, scopeKind);
+}
+
+bool frontend::InstantiateStencils(JSContext* cx, CompilationInput& input,
+ const CompilationStencil& stencil,
+ CompilationGCOutput& gcOutput) {
+ {
+ AutoGeckoProfilerEntry pseudoFrame(cx, "stencil instantiate",
+ JS::ProfilingCategoryPair::JS_Parsing);
+
+ if (!CompilationStencil::instantiateStencils(cx, input, stencil,
+ gcOutput)) {
+ return false;
+ }
+ }
+
+ // Enqueue an off-thread source compression task after finishing parsing.
+ if (!cx->isHelperThreadContext()) {
+ if (!stencil.source->tryCompressOffThread(cx)) {
+ return false;
+ }
+
+ Rooted<JSScript*> script(cx, gcOutput.script);
+ const JS::InstantiateOptions instantiateOptions(input.options);
+ FireOnNewScript(cx, instantiateOptions, script);
+ }
+
+ return true;
+}
+
+bool frontend::PrepareForInstantiate(JSContext* maybeCx, FrontendContext* fc,
+ CompilationInput& input,
+ const CompilationStencil& stencil,
+ CompilationGCOutput& gcOutput) {
+ Maybe<AutoGeckoProfilerEntry> pseudoFrame;
+ if (maybeCx) {
+ pseudoFrame.emplace(maybeCx, "stencil instantiate",
+ JS::ProfilingCategoryPair::JS_Parsing);
+ }
+
+ return CompilationStencil::prepareForInstantiate(fc, input.atomCache, stencil,
+ gcOutput);
+}
+
+template <typename Unit>
+static JSScript* CompileGlobalScriptImpl(
+ JSContext* cx, FrontendContext* fc,
+ const JS::ReadOnlyCompileOptions& options, JS::SourceText<Unit>& srcBuf,
+ ScopeKind scopeKind) {
+ Rooted<CompilationInput> input(cx, CompilationInput(options));
+ Rooted<CompilationGCOutput> gcOutput(cx);
+ BytecodeCompilerOutput output(gcOutput.address());
+ NoScopeBindingCache scopeCache;
+ if (!CompileGlobalScriptToStencilAndMaybeInstantiate(
+ cx, fc, cx->tempLifoAlloc(), input.get(), &scopeCache, srcBuf,
+ scopeKind, output)) {
+ return nullptr;
+ }
+ return gcOutput.get().script;
+}
+
+JSScript* frontend::CompileGlobalScript(
+ JSContext* cx, FrontendContext* fc,
+ const JS::ReadOnlyCompileOptions& options, JS::SourceText<char16_t>& srcBuf,
+ ScopeKind scopeKind) {
+ return CompileGlobalScriptImpl(cx, fc, options, srcBuf, scopeKind);
+}
+
+JSScript* frontend::CompileGlobalScript(
+ JSContext* cx, FrontendContext* fc,
+ const JS::ReadOnlyCompileOptions& options, JS::SourceText<Utf8Unit>& srcBuf,
+ ScopeKind scopeKind) {
+ return CompileGlobalScriptImpl(cx, fc, 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) {
+ JS::Rooted<JSScript*> script(cx);
+ {
+ AutoReportFrontendContext fc(cx);
+ AutoAssertReportedException assertException(cx, &fc);
+
+ Rooted<CompilationInput> input(cx, CompilationInput(options));
+ if (!input.get().initForEval(&fc, enclosingScope)) {
+ return nullptr;
+ }
+
+ LifoAllocScope parserAllocScope(&cx->tempLifoAlloc());
+
+ ScopeBindingCache* scopeCache = &cx->caches().scopeCache;
+ ScriptCompiler<Unit> compiler(&fc, parserAllocScope, input.get(), srcBuf);
+ if (!compiler.init(&fc, scopeCache, InheritThis::Yes, enclosingEnv)) {
+ return nullptr;
+ }
+
+ uint32_t len = srcBuf.length();
+ SourceExtent extent =
+ SourceExtent::makeGlobalExtent(len, options.lineno, options.column);
+ EvalSharedContext evalsc(&fc, compiler.compilationState(), extent);
+ if (!compiler.compile(cx, &evalsc)) {
+ return nullptr;
+ }
+
+ Rooted<CompilationGCOutput> gcOutput(cx);
+ {
+ BorrowingCompilationStencil borrowingStencil(compiler.stencil());
+ if (!InstantiateStencils(cx, input.get(), borrowingStencil,
+ gcOutput.get())) {
+ return nullptr;
+ }
+ }
+
+ assertException.reset();
+ script = gcOutput.get().script;
+ }
+ return script;
+}
+
+JSScript* frontend::CompileEvalScript(JSContext* cx,
+ const JS::ReadOnlyCompileOptions& options,
+ JS::SourceText<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 ModuleCompiler final : public SourceAwareCompiler<Unit> {
+ using Base = SourceAwareCompiler<Unit>;
+
+ using Base::assertSourceParserAndScriptCreated;
+ using Base::compilationState_;
+ using Base::emplaceEmitter;
+ using Base::parser;
+
+ public:
+ explicit ModuleCompiler(FrontendContext* fc, LifoAllocScope& parserAllocScope,
+ CompilationInput& input,
+ SourceText<Unit>& sourceBuffer)
+ : Base(fc, parserAllocScope, input, sourceBuffer) {}
+
+ using Base::init;
+ using Base::stencil;
+
+ [[nodiscard]] bool compile(JSContext* maybeCx, FrontendContext* fc);
+};
+
+template <typename Unit>
+class MOZ_STACK_CLASS 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(FrontendContext* fc,
+ LifoAllocScope& parserAllocScope,
+ CompilationInput& input,
+ SourceText<Unit>& sourceBuffer)
+ : Base(fc, parserAllocScope, input, sourceBuffer) {}
+
+ using Base::init;
+ using Base::stencil;
+
+ private:
+ FunctionNode* parse(JSContext* cx, FunctionSyntaxKind syntaxKind,
+ GeneratorKind generatorKind, FunctionAsyncKind asyncKind,
+ const Maybe<uint32_t>& parameterListEnd);
+
+ public:
+ [[nodiscard]] bool compile(JSContext* cx, FunctionSyntaxKind syntaxKind,
+ GeneratorKind generatorKind,
+ FunctionAsyncKind asyncKind,
+ const Maybe<uint32_t>& parameterListEnd);
+};
+
+template <typename Unit>
+bool SourceAwareCompiler<Unit>::createSourceAndParser(FrontendContext* fc) {
+ const auto& options = compilationState_.input.options;
+
+ fc_ = fc;
+
+ if (!compilationState_.source->assignSource(fc, options, sourceBuffer_)) {
+ return false;
+ }
+
+ MOZ_ASSERT(compilationState_.canLazilyParse ==
+ CanLazilyParse(compilationState_.input.options));
+ if (compilationState_.canLazilyParse) {
+ syntaxParser.emplace(fc_, options, sourceBuffer_.units(),
+ sourceBuffer_.length(),
+ /* foldConstants = */ false, compilationState_,
+ /* syntaxParser = */ nullptr);
+ if (!syntaxParser->checkOptions()) {
+ return false;
+ }
+ }
+
+ parser.emplace(fc_, options, sourceBuffer_.units(), sourceBuffer_.length(),
+ /* foldConstants = */ true, compilationState_,
+ syntaxParser.ptrOr(nullptr));
+ parser->ss = compilationState_.source.get();
+ return parser->checkOptions();
+}
+
+static bool EmplaceEmitter(CompilationState& compilationState,
+ Maybe<BytecodeEmitter>& emitter, FrontendContext* fc,
+ const EitherParser& parser, SharedContext* sc) {
+ BytecodeEmitter::EmitterMode emitterMode =
+ sc->selfHosted() ? BytecodeEmitter::SelfHosting : BytecodeEmitter::Normal;
+ emitter.emplace(fc, parser, sc, compilationState, emitterMode);
+ return emitter->init();
+}
+
+template <typename Unit>
+bool 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 SourceAwareCompiler<Unit>::handleParseFailure(
+ const Directives& newDirectives, TokenStreamPosition& startPosition,
+ CompilationState::CompilationStatePosition& startStatePosition) {
+ MOZ_ASSERT(canHandleParseFailure(newDirectives));
+
+ // Rewind to starting position to retry.
+ parser->tokenStream.rewind(startPosition);
+ compilationState_.rewind(startStatePosition);
+
+ // Assignment must be monotonic to prevent reparsing iloops
+ MOZ_ASSERT_IF(compilationState_.directives.strict(), newDirectives.strict());
+ MOZ_ASSERT_IF(compilationState_.directives.asmJS(), newDirectives.asmJS());
+ compilationState_.directives = newDirectives;
+}
+
+template <typename Unit>
+bool ScriptCompiler<Unit>::compile(JSContext* maybeCx, SharedContext* sc) {
+ assertSourceParserAndScriptCreated();
+
+ TokenStreamPosition startPosition(parser->tokenStream);
+
+ // Emplace the topLevel stencil
+ MOZ_ASSERT(compilationState_.scriptData.length() ==
+ CompilationStencil::TopLevelIndex);
+ if (!compilationState_.appendScriptStencilAndData(sc->fc_)) {
+ return false;
+ }
+
+ ParseNode* pn;
+ {
+ Maybe<AutoGeckoProfilerEntry> pseudoFrame;
+ if (maybeCx) {
+ pseudoFrame.emplace(maybeCx, "script parsing",
+ JS::ProfilingCategoryPair::JS_Parsing);
+ }
+ if (sc->isEvalContext()) {
+ pn = parser->evalBody(sc->asEvalContext());
+ } else {
+ pn = parser->globalBody(sc->asGlobalContext());
+ }
+ }
+
+ if (!pn) {
+ // Global and eval scripts don't get reparsed after a new directive was
+ // encountered:
+ // - "use strict" doesn't require any special error reporting for scripts.
+ // - "use asm" directives don't have an effect in global/eval contexts.
+ MOZ_ASSERT(!canHandleParseFailure(compilationState_.directives));
+ return false;
+ }
+
+ {
+ // Successfully parsed. Emit the script.
+ Maybe<AutoGeckoProfilerEntry> pseudoFrame;
+ if (maybeCx) {
+ pseudoFrame.emplace(maybeCx, "script emit",
+ JS::ProfilingCategoryPair::JS_Parsing);
+ }
+
+ Maybe<BytecodeEmitter> emitter;
+ if (!emplaceEmitter(emitter, sc)) {
+ return false;
+ }
+
+ if (!emitter->emitScript(pn)) {
+ return false;
+ }
+ }
+
+ MOZ_ASSERT(!this->fc_->hadErrors());
+
+ return true;
+}
+
+template <typename Unit>
+bool ModuleCompiler<Unit>::compile(JSContext* maybeCx, FrontendContext* fc) {
+ // Emplace the topLevel stencil
+ MOZ_ASSERT(compilationState_.scriptData.length() ==
+ CompilationStencil::TopLevelIndex);
+ if (!compilationState_.appendScriptStencilAndData(fc)) {
+ return false;
+ }
+
+ ModuleBuilder builder(fc, parser.ptr());
+
+ const auto& options = compilationState_.input.options;
+
+ uint32_t len = this->sourceBuffer_.length();
+ SourceExtent extent =
+ SourceExtent::makeGlobalExtent(len, options.lineno, options.column);
+ ModuleSharedContext modulesc(fc, options, builder, extent);
+
+ ParseNode* pn = parser->moduleBody(&modulesc);
+ if (!pn) {
+ return false;
+ }
+
+ Maybe<BytecodeEmitter> emitter;
+ if (!emplaceEmitter(emitter, &modulesc)) {
+ return false;
+ }
+
+ if (!emitter->emitScript(pn->as<ModuleNode>().body())) {
+ return false;
+ }
+
+ StencilModuleMetadata& moduleMetadata = *compilationState_.moduleMetadata;
+
+ builder.finishFunctionDecls(moduleMetadata);
+
+ MOZ_ASSERT(!this->fc_->hadErrors());
+
+ return true;
+}
+
+// Parse a standalone JS function, which might appear as the value of an
+// event handler attribute in an HTML <INPUT> tag, or in a Function()
+// constructor.
+template <typename Unit>
+FunctionNode* StandaloneFunctionCompiler<Unit>::parse(
+ JSContext* cx, FunctionSyntaxKind syntaxKind, GeneratorKind generatorKind,
+ FunctionAsyncKind asyncKind, const Maybe<uint32_t>& parameterListEnd) {
+ assertSourceAndParserCreated();
+
+ TokenStreamPosition startPosition(parser->tokenStream);
+ auto startStatePosition = compilationState_.getPosition();
+
+ // Speculatively parse using the default directives implied by the context.
+ // If a directive is encountered (e.g., "use strict") that changes how the
+ // function should have been parsed, we backup and reparse with the new set
+ // of directives.
+
+ FunctionNode* fn;
+ for (;;) {
+ Directives newDirectives = compilationState_.directives;
+ fn = parser->standaloneFunction(parameterListEnd, syntaxKind, generatorKind,
+ asyncKind, compilationState_.directives,
+ &newDirectives);
+ if (fn) {
+ break;
+ }
+
+ // Maybe we encountered a new directive. See if we can try again.
+ if (!canHandleParseFailure(newDirectives)) {
+ return nullptr;
+ }
+
+ handleParseFailure(newDirectives, startPosition, startStatePosition);
+ }
+
+ return fn;
+}
+
+// Compile a standalone JS function.
+template <typename Unit>
+bool StandaloneFunctionCompiler<Unit>::compile(
+ JSContext* cx, FunctionSyntaxKind syntaxKind, GeneratorKind generatorKind,
+ FunctionAsyncKind asyncKind, const Maybe<uint32_t>& parameterListEnd) {
+ FunctionNode* parsedFunction =
+ parse(cx, syntaxKind, generatorKind, asyncKind, parameterListEnd);
+ if (!parsedFunction) {
+ return false;
+ }
+
+ FunctionBox* funbox = parsedFunction->funbox();
+
+ if (funbox->isInterpreted()) {
+ Maybe<BytecodeEmitter> emitter;
+ if (!emplaceEmitter(emitter, funbox)) {
+ return false;
+ }
+
+ if (!emitter->emitFunctionScript(parsedFunction)) {
+ return false;
+ }
+
+ // The parser extent has stripped off the leading `function...` but
+ // we want the SourceExtent used in the final standalone script to
+ // start from the beginning of the buffer, and use the provided
+ // line and column.
+ const auto& options = compilationState_.input.options;
+ compilationState_.scriptExtra[CompilationStencil::TopLevelIndex].extent =
+ SourceExtent{/* sourceStart = */ 0,
+ sourceBuffer_.length(),
+ funbox->extent().toStringStart,
+ funbox->extent().toStringEnd,
+ options.lineno,
+ options.column};
+ } else {
+ // The asm.js module was created by parser. Instantiation below will
+ // allocate the JSFunction that wraps it.
+ MOZ_ASSERT(funbox->isAsmJSModule());
+ MOZ_ASSERT(compilationState_.asmJS->moduleMap.has(funbox->index()));
+ MOZ_ASSERT(compilationState_.scriptData[CompilationStencil::TopLevelIndex]
+ .functionFlags.isAsmJSNative());
+ }
+
+ return true;
+}
+
+// Compile module, and return it as one of:
+// * ExtensibleCompilationStencil (without instantiation)
+// * CompilationStencil (without instantiation, has no external dependency)
+// * CompilationGCOutput (with instantiation).
+template <typename Unit>
+[[nodiscard]] static bool ParseModuleToStencilAndMaybeInstantiate(
+ JSContext* maybeCx, FrontendContext* fc, js::LifoAlloc& tempLifoAlloc,
+ CompilationInput& input, ScopeBindingCache* scopeCache,
+ SourceText<Unit>& srcBuf, BytecodeCompilerOutput& output) {
+ MOZ_ASSERT(srcBuf.get());
+
+ if (!input.initForModule(fc)) {
+ return false;
+ }
+
+ AutoAssertReportedException assertException(maybeCx, fc);
+
+ LifoAllocScope parserAllocScope(&tempLifoAlloc);
+ ModuleCompiler<Unit> compiler(fc, parserAllocScope, input, srcBuf);
+ if (!compiler.init(fc, scopeCache)) {
+ return false;
+ }
+
+ if (!compiler.compile(maybeCx, fc)) {
+ return false;
+ }
+
+ if (output.is<UniquePtr<ExtensibleCompilationStencil>>()) {
+ auto stencil =
+ fc->getAllocator()->make_unique<ExtensibleCompilationStencil>(
+ std::move(compiler.stencil()));
+ if (!stencil) {
+ return false;
+ }
+ output.as<UniquePtr<ExtensibleCompilationStencil>>() = std::move(stencil);
+ } else if (output.is<RefPtr<CompilationStencil>>()) {
+ Maybe<AutoGeckoProfilerEntry> pseudoFrame;
+ if (maybeCx) {
+ pseudoFrame.emplace(maybeCx, "script emit",
+ JS::ProfilingCategoryPair::JS_Parsing);
+ }
+
+ auto extensibleStencil =
+ fc->getAllocator()->make_unique<frontend::ExtensibleCompilationStencil>(
+ std::move(compiler.stencil()));
+ if (!extensibleStencil) {
+ return false;
+ }
+
+ RefPtr<CompilationStencil> stencil =
+ fc->getAllocator()->new_<CompilationStencil>(
+ std::move(extensibleStencil));
+ if (!stencil) {
+ return false;
+ }
+
+ output.as<RefPtr<CompilationStencil>>() = std::move(stencil);
+ } else {
+ MOZ_ASSERT(maybeCx);
+ BorrowingCompilationStencil borrowingStencil(compiler.stencil());
+ if (!InstantiateStencils(maybeCx, input, borrowingStencil,
+ *(output.as<CompilationGCOutput*>()))) {
+ return false;
+ }
+ }
+
+ assertException.reset();
+ return true;
+}
+
+template <typename Unit>
+already_AddRefed<CompilationStencil> ParseModuleToStencilImpl(
+ JSContext* maybeCx, FrontendContext* fc, js::LifoAlloc& tempLifoAlloc,
+ CompilationInput& input, ScopeBindingCache* scopeCache,
+ SourceText<Unit>& srcBuf) {
+ using OutputType = RefPtr<CompilationStencil>;
+ BytecodeCompilerOutput output((OutputType()));
+ if (!ParseModuleToStencilAndMaybeInstantiate(
+ maybeCx, fc, tempLifoAlloc, input, scopeCache, srcBuf, output)) {
+ return nullptr;
+ }
+ return output.as<OutputType>().forget();
+}
+
+already_AddRefed<CompilationStencil> frontend::ParseModuleToStencil(
+ JSContext* maybeCx, FrontendContext* fc, js::LifoAlloc& tempLifoAlloc,
+ CompilationInput& input, ScopeBindingCache* scopeCache,
+ SourceText<char16_t>& srcBuf) {
+ return ParseModuleToStencilImpl(maybeCx, fc, tempLifoAlloc, input, scopeCache,
+ srcBuf);
+}
+
+already_AddRefed<CompilationStencil> frontend::ParseModuleToStencil(
+ JSContext* maybeCx, FrontendContext* fc, js::LifoAlloc& tempLifoAlloc,
+ CompilationInput& input, ScopeBindingCache* scopeCache,
+ SourceText<Utf8Unit>& srcBuf) {
+ return ParseModuleToStencilImpl(maybeCx, fc, tempLifoAlloc, input, scopeCache,
+ srcBuf);
+}
+
+template <typename Unit>
+UniquePtr<ExtensibleCompilationStencil> ParseModuleToExtensibleStencilImpl(
+ JSContext* cx, FrontendContext* fc, js::LifoAlloc& tempLifoAlloc,
+ CompilationInput& input, ScopeBindingCache* scopeCache,
+ SourceText<Unit>& srcBuf) {
+ using OutputType = UniquePtr<ExtensibleCompilationStencil>;
+ BytecodeCompilerOutput output((OutputType()));
+ if (!ParseModuleToStencilAndMaybeInstantiate(cx, fc, tempLifoAlloc, input,
+ scopeCache, srcBuf, output)) {
+ return nullptr;
+ }
+ return std::move(output.as<OutputType>());
+}
+
+UniquePtr<ExtensibleCompilationStencil>
+frontend::ParseModuleToExtensibleStencil(JSContext* cx, FrontendContext* fc,
+ js::LifoAlloc& tempLifoAlloc,
+ CompilationInput& input,
+ ScopeBindingCache* scopeCache,
+ SourceText<char16_t>& srcBuf) {
+ return ParseModuleToExtensibleStencilImpl(cx, fc, tempLifoAlloc, input,
+ scopeCache, srcBuf);
+}
+
+UniquePtr<ExtensibleCompilationStencil>
+frontend::ParseModuleToExtensibleStencil(JSContext* cx, FrontendContext* fc,
+ js::LifoAlloc& tempLifoAlloc,
+ CompilationInput& input,
+ ScopeBindingCache* scopeCache,
+ SourceText<Utf8Unit>& srcBuf) {
+ return ParseModuleToExtensibleStencilImpl(cx, fc, tempLifoAlloc, input,
+ scopeCache, srcBuf);
+}
+
+template <typename Unit>
+static ModuleObject* CompileModuleImpl(
+ JSContext* cx, FrontendContext* fc,
+ const JS::ReadOnlyCompileOptions& optionsInput, SourceText<Unit>& srcBuf) {
+ AutoAssertReportedException assertException(cx, fc);
+
+ CompileOptions options(cx, optionsInput);
+ options.setModule();
+
+ Rooted<CompilationInput> input(cx, CompilationInput(options));
+ Rooted<CompilationGCOutput> gcOutput(cx);
+ BytecodeCompilerOutput output(gcOutput.address());
+
+ NoScopeBindingCache scopeCache;
+ if (!ParseModuleToStencilAndMaybeInstantiate(cx, fc, cx->tempLifoAlloc(),
+ input.get(), &scopeCache, srcBuf,
+ output)) {
+ return nullptr;
+ }
+
+ assertException.reset();
+ return gcOutput.get().module;
+}
+
+ModuleObject* frontend::CompileModule(JSContext* cx, FrontendContext* fc,
+ const JS::ReadOnlyCompileOptions& options,
+ SourceText<char16_t>& srcBuf) {
+ return CompileModuleImpl(cx, fc, options, srcBuf);
+}
+
+ModuleObject* frontend::CompileModule(JSContext* cx, FrontendContext* fc,
+ const JS::ReadOnlyCompileOptions& options,
+ SourceText<Utf8Unit>& srcBuf) {
+ return CompileModuleImpl(cx, fc, options, srcBuf);
+}
+
+static bool InstantiateLazyFunction(JSContext* cx, CompilationInput& input,
+ CompilationStencil& stencil,
+ BytecodeCompilerOutput& output) {
+ // We do check the type, but do not write anything to it as this is not
+ // necessary for lazy function, as the script is patched inside the
+ // JSFunction when instantiating.
+ MOZ_ASSERT(output.is<CompilationGCOutput*>());
+ MOZ_ASSERT(!output.as<CompilationGCOutput*>());
+
+ mozilla::DebugOnly<uint32_t> lazyFlags =
+ static_cast<uint32_t>(input.immutableFlags());
+
+ Rooted<CompilationGCOutput> gcOutput(cx);
+
+ if (input.source->hasEncoder()) {
+ if (!input.source->addDelazificationToIncrementalEncoding(cx, stencil)) {
+ return false;
+ }
+ }
+
+ if (!CompilationStencil::instantiateStencils(cx, input, stencil,
+ gcOutput.get())) {
+ return false;
+ }
+
+ // NOTE: After instantiation succeeds and bytecode is attached, the rest of
+ // this operation should be infallible. Any failure during
+ // delazification should restore the function back to a consistent
+ // lazy state.
+
+ MOZ_ASSERT(lazyFlags == gcOutput.get().script->immutableFlags());
+ MOZ_ASSERT(gcOutput.get().script->outermostScope()->hasOnChain(
+ ScopeKind::NonSyntactic) ==
+ gcOutput.get().script->immutableFlags().hasFlag(
+ JSScript::ImmutableFlags::HasNonSyntacticScope));
+
+ return true;
+}
+
+enum class GetCachedResult {
+ // Similar to return false.
+ Error,
+
+ // We have not found any entry.
+ NotFound,
+
+ // We have found an entry, and set everything according to the desired
+ // BytecodeCompilerOutput out-param.
+ Found
+};
+
+// When we have a cache hit, the addPtr out-param would evaluate to a true-ish
+// value.
+static GetCachedResult GetCachedLazyFunctionStencilMaybeInstantiate(
+ JSContext* cx, FrontendContext* fc, CompilationInput& input,
+ BytecodeCompilerOutput& output) {
+ RefPtr<CompilationStencil> stencil;
+ {
+ StencilCache& cache = cx->runtime()->caches().delazificationCache;
+ auto guard = cache.isSourceCached(input.source);
+ if (!guard) {
+ return GetCachedResult::NotFound;
+ }
+
+ // Before releasing the guard, which is locking the cache, we increment the
+ // reference counter such that we do not reclaim the CompilationStencil
+ // while we are instantiating it.
+ StencilContext key(input.source, input.extent());
+ stencil = cache.lookup(guard, key);
+ if (!stencil) {
+ return GetCachedResult::NotFound;
+ }
+ }
+
+ if (output.is<RefPtr<CompilationStencil>>()) {
+ output.as<RefPtr<CompilationStencil>>() = stencil;
+ return GetCachedResult::Found;
+ }
+
+ if (output.is<UniquePtr<ExtensibleCompilationStencil>>()) {
+ auto extensible =
+ fc->getAllocator()->make_unique<ExtensibleCompilationStencil>(input);
+ if (!extensible) {
+ return GetCachedResult::Error;
+ }
+ if (!extensible->cloneFrom(fc, *stencil)) {
+ return GetCachedResult::Error;
+ }
+
+ output.as<UniquePtr<ExtensibleCompilationStencil>>() =
+ std::move(extensible);
+ return GetCachedResult::Found;
+ }
+
+ if (!InstantiateLazyFunction(cx, input, *stencil, output)) {
+ return GetCachedResult::Error;
+ }
+
+ return GetCachedResult::Found;
+}
+
+template <typename Unit>
+static bool CompileLazyFunctionToStencilMaybeInstantiate(
+ JSContext* cx, FrontendContext* fc, CompilationInput& input,
+ ScopeBindingCache* scopeCache, const Unit* units, size_t length,
+ BytecodeCompilerOutput& output) {
+ MOZ_ASSERT(input.source);
+
+ AutoAssertReportedException assertException(cx, fc);
+ if (input.options.consumeDelazificationCache()) {
+ auto res =
+ GetCachedLazyFunctionStencilMaybeInstantiate(cx, fc, input, output);
+ switch (res) {
+ case GetCachedResult::Error:
+ return false;
+ case GetCachedResult::Found:
+ assertException.reset();
+ return true;
+ case GetCachedResult::NotFound:
+ break;
+ }
+ }
+
+ InheritThis inheritThis =
+ input.functionFlags().isArrow() ? InheritThis::Yes : InheritThis::No;
+
+ LifoAllocScope parserAllocScope(&cx->tempLifoAlloc());
+ CompilationState compilationState(fc, parserAllocScope, input);
+ compilationState.setFunctionKey(input.extent());
+ MOZ_ASSERT(!compilationState.isInitialStencil());
+ if (!compilationState.init(fc, scopeCache, inheritThis)) {
+ return false;
+ }
+
+ Parser<FullParseHandler, Unit> parser(fc, input.options, units, length,
+ /* foldConstants = */ true,
+ compilationState,
+ /* syntaxParser = */ nullptr);
+ if (!parser.checkOptions()) {
+ return false;
+ }
+
+ FunctionNode* pn = parser.standaloneLazyFunction(
+ input, input.extent().toStringStart, input.strict(),
+ input.generatorKind(), input.asyncKind());
+ if (!pn) {
+ return false;
+ }
+
+ BytecodeEmitter bce(fc, &parser, pn->funbox(), compilationState,
+ BytecodeEmitter::LazyFunction);
+ if (!bce.init(pn->pn_pos)) {
+ return false;
+ }
+
+ if (!bce.emitFunctionScript(pn)) {
+ return false;
+ }
+
+ // NOTE: Only allow relazification if there was no lazy PrivateScriptData.
+ // This excludes non-leaf functions and all script class constructors.
+ bool hadLazyScriptData = input.hasPrivateScriptData();
+ bool isRelazifiableAfterDelazify = input.isRelazifiable();
+ if (isRelazifiableAfterDelazify && !hadLazyScriptData) {
+ compilationState.scriptData[CompilationStencil::TopLevelIndex]
+ .setAllowRelazify();
+ }
+
+ if (input.options.checkDelazificationCache()) {
+ using OutputType = RefPtr<CompilationStencil>;
+ BytecodeCompilerOutput cached((OutputType()));
+ auto res =
+ GetCachedLazyFunctionStencilMaybeInstantiate(cx, fc, input, cached);
+ if (res == GetCachedResult::Error) {
+ return false;
+ }
+ // Cached results might be removed by GCs.
+ if (res == GetCachedResult::Found) {
+ auto& concurrentSharedData = cached.as<OutputType>().get()->sharedData;
+ auto concurrentData =
+ concurrentSharedData.isSingle()
+ ? concurrentSharedData.asSingle()->get()->immutableData()
+ : concurrentSharedData.asBorrow()
+ ->asSingle()
+ ->get()
+ ->immutableData();
+ auto ondemandData =
+ compilationState.sharedData.asSingle()->get()->immutableData();
+ MOZ_RELEASE_ASSERT(concurrentData.Length() == ondemandData.Length(),
+ "Non-deterministic stencils");
+ for (size_t i = 0; i < concurrentData.Length(); i++) {
+ MOZ_RELEASE_ASSERT(concurrentData[i] == ondemandData[i],
+ "Non-deterministic stencils");
+ }
+ }
+ }
+
+ if (output.is<UniquePtr<ExtensibleCompilationStencil>>()) {
+ auto stencil =
+ fc->getAllocator()->make_unique<ExtensibleCompilationStencil>(
+ std::move(compilationState));
+ if (!stencil) {
+ return false;
+ }
+ output.as<UniquePtr<ExtensibleCompilationStencil>>() = std::move(stencil);
+ } else if (output.is<RefPtr<CompilationStencil>>()) {
+ Maybe<AutoGeckoProfilerEntry> pseudoFrame;
+ if (cx) {
+ pseudoFrame.emplace(cx, "script emit",
+ JS::ProfilingCategoryPair::JS_Parsing);
+ }
+
+ auto extensibleStencil =
+ fc->getAllocator()->make_unique<frontend::ExtensibleCompilationStencil>(
+ std::move(compilationState));
+ if (!extensibleStencil) {
+ return false;
+ }
+
+ RefPtr<CompilationStencil> stencil =
+ fc->getAllocator()->new_<CompilationStencil>(
+ std::move(extensibleStencil));
+ if (!stencil) {
+ return false;
+ }
+
+ output.as<RefPtr<CompilationStencil>>() = std::move(stencil);
+ } else {
+ BorrowingCompilationStencil borrowingStencil(compilationState);
+ if (!InstantiateLazyFunction(cx, input, borrowingStencil, output)) {
+ return false;
+ }
+ }
+
+ assertException.reset();
+ return true;
+}
+
+template <typename Unit>
+static bool DelazifyCanonicalScriptedFunctionImpl(
+ JSContext* cx, FrontendContext* fc, ScopeBindingCache* scopeCache,
+ HandleFunction fun, Handle<BaseScript*> lazy, ScriptSource* ss) {
+ MOZ_ASSERT(!lazy->hasBytecode(), "Script is already compiled!");
+ MOZ_ASSERT(lazy->function() == fun);
+
+ MOZ_DIAGNOSTIC_ASSERT(!fun->isGhost());
+
+ AutoIncrementalTimer timer(cx->realm()->timers.delazificationTime);
+
+ size_t sourceStart = lazy->sourceStart();
+ size_t sourceLength = lazy->sourceEnd() - lazy->sourceStart();
+
+ MOZ_ASSERT(ss->hasSourceText());
+
+ // Parse and compile the script from source.
+ UncompressedSourceCache::AutoHoldEntry holder;
+
+ MOZ_ASSERT(ss->hasSourceType<Unit>());
+
+ ScriptSource::PinnedUnits<Unit> units(cx, ss, holder, sourceStart,
+ sourceLength);
+ if (!units.get()) {
+ return false;
+ }
+
+ JS::CompileOptions options(cx);
+ options.setMutedErrors(lazy->mutedErrors())
+ .setFileAndLine(lazy->filename(), lazy->lineno())
+ .setColumn(lazy->column())
+ .setScriptSourceOffset(lazy->sourceStart())
+ .setNoScriptRval(false)
+ .setSelfHostingMode(false)
+ .setEagerDelazificationStrategy(lazy->delazificationMode());
+
+ Rooted<CompilationInput> input(cx, CompilationInput(options));
+ input.get().initFromLazy(cx, lazy, ss);
+
+ CompilationGCOutput* unusedGcOutput = nullptr;
+ BytecodeCompilerOutput output(unusedGcOutput);
+ return CompileLazyFunctionToStencilMaybeInstantiate(
+ cx, fc, input.get(), scopeCache, units.get(), sourceLength, output);
+}
+
+bool frontend::DelazifyCanonicalScriptedFunction(JSContext* cx,
+ FrontendContext* fc,
+ HandleFunction fun) {
+ Maybe<AutoGeckoProfilerEntry> pseudoFrame;
+ if (cx) {
+ pseudoFrame.emplace(cx, "script delazify",
+ JS::ProfilingCategoryPair::JS_Parsing);
+ }
+
+ Rooted<BaseScript*> lazy(cx, fun->baseScript());
+ ScriptSource* ss = lazy->scriptSource();
+ ScopeBindingCache* scopeCache = &cx->caches().scopeCache;
+
+ if (ss->hasSourceType<Utf8Unit>()) {
+ // UTF-8 source text.
+ return DelazifyCanonicalScriptedFunctionImpl<Utf8Unit>(cx, fc, scopeCache,
+ fun, lazy, ss);
+ }
+
+ MOZ_ASSERT(ss->hasSourceType<char16_t>());
+
+ // UTF-16 source text.
+ return DelazifyCanonicalScriptedFunctionImpl<char16_t>(cx, fc, scopeCache,
+ fun, lazy, ss);
+}
+
+template <typename Unit>
+static already_AddRefed<CompilationStencil>
+DelazifyCanonicalScriptedFunctionImpl(JSContext* cx, FrontendContext* fc,
+ ScopeBindingCache* scopeCache,
+ CompilationStencil& context,
+ ScriptIndex scriptIndex) {
+ ScriptStencilRef script{context, scriptIndex};
+ const ScriptStencilExtra& extra = script.scriptExtra();
+
+#if defined(EARLY_BETA_OR_EARLIER) || defined(DEBUG)
+ const ScriptStencil& data = script.scriptData();
+ MOZ_ASSERT(!data.hasSharedData(), "Script is already compiled!");
+ MOZ_DIAGNOSTIC_ASSERT(!data.isGhost());
+#endif
+
+ Maybe<AutoIncrementalTimer> timer;
+ if (cx->realm()) {
+ timer.emplace(cx->realm()->timers.delazificationTime);
+ }
+
+ size_t sourceStart = extra.extent.sourceStart;
+ size_t sourceLength = extra.extent.sourceEnd - sourceStart;
+
+ ScriptSource* ss = context.source;
+ MOZ_ASSERT(ss->hasSourceText());
+
+ // Parse and compile the script from source.
+ UncompressedSourceCache::AutoHoldEntry holder;
+
+ MOZ_ASSERT(ss->hasSourceType<Unit>());
+
+ ScriptSource::PinnedUnits<Unit> units(cx, ss, holder, sourceStart,
+ sourceLength);
+ if (!units.get()) {
+ return nullptr;
+ }
+
+ JS::CompileOptions options(cx);
+ options.setMutedErrors(ss->mutedErrors())
+ .setFileAndLine(ss->filename(), extra.extent.lineno)
+ .setColumn(extra.extent.column)
+ .setScriptSourceOffset(sourceStart)
+ .setNoScriptRval(false)
+ .setSelfHostingMode(false);
+
+ Rooted<CompilationInput> input(cx, CompilationInput(options));
+ input.get().initFromStencil(context, scriptIndex, ss);
+
+ using OutputType = RefPtr<CompilationStencil>;
+ BytecodeCompilerOutput output((OutputType()));
+ if (!CompileLazyFunctionToStencilMaybeInstantiate(
+ cx, fc, input.get(), scopeCache, units.get(), sourceLength, output)) {
+ return nullptr;
+ }
+ return output.as<OutputType>().forget();
+}
+
+already_AddRefed<CompilationStencil>
+frontend::DelazifyCanonicalScriptedFunction(JSContext* cx, FrontendContext* fc,
+ ScopeBindingCache* scopeCache,
+ CompilationStencil& context,
+ ScriptIndex scriptIndex) {
+ Maybe<AutoGeckoProfilerEntry> pseudoFrame;
+ if (cx) {
+ pseudoFrame.emplace(cx, "stencil script delazify",
+ JS::ProfilingCategoryPair::JS_Parsing);
+ }
+
+ ScriptSource* ss = context.source;
+ if (ss->hasSourceType<Utf8Unit>()) {
+ // UTF-8 source text.
+ return DelazifyCanonicalScriptedFunctionImpl<Utf8Unit>(
+ cx, fc, scopeCache, context, scriptIndex);
+ }
+
+ // UTF-16 source text.
+ MOZ_ASSERT(ss->hasSourceType<char16_t>());
+ return DelazifyCanonicalScriptedFunctionImpl<char16_t>(cx, fc, scopeCache,
+ context, scriptIndex);
+}
+
+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, Handle<Scope*> enclosingScope = nullptr) {
+ JS::Rooted<JSFunction*> fun(cx);
+ {
+ AutoReportFrontendContext fc(cx);
+ AutoAssertReportedException assertException(cx, &fc);
+
+ Rooted<CompilationInput> input(cx, CompilationInput(options));
+ if (enclosingScope) {
+ if (!input.get().initForStandaloneFunctionInNonSyntacticScope(
+ &fc, enclosingScope)) {
+ return nullptr;
+ }
+ } else {
+ if (!input.get().initForStandaloneFunction(cx, &fc)) {
+ return nullptr;
+ }
+ }
+
+ LifoAllocScope parserAllocScope(&cx->tempLifoAlloc());
+ InheritThis inheritThis = (syntaxKind == FunctionSyntaxKind::Arrow)
+ ? InheritThis::Yes
+ : InheritThis::No;
+ ScopeBindingCache* scopeCache = &cx->caches().scopeCache;
+ StandaloneFunctionCompiler<char16_t> compiler(&fc, parserAllocScope,
+ input.get(), srcBuf);
+ if (!compiler.init(&fc, scopeCache, inheritThis)) {
+ return nullptr;
+ }
+
+ if (!compiler.compile(cx, syntaxKind, generatorKind, asyncKind,
+ parameterListEnd)) {
+ return nullptr;
+ }
+
+ Rooted<CompilationGCOutput> gcOutput(cx);
+ RefPtr<ScriptSource> source;
+ {
+ BorrowingCompilationStencil borrowingStencil(compiler.stencil());
+ if (!CompilationStencil::instantiateStencils(
+ cx, input.get(), borrowingStencil, gcOutput.get())) {
+ return nullptr;
+ }
+ source = borrowingStencil.source;
+ }
+
+ fun = gcOutput.get().getFunctionNoBaseIndex(
+ CompilationStencil::TopLevelIndex);
+ MOZ_ASSERT(fun->hasBytecode() || IsAsmJSModule(fun));
+
+ // Enqueue an off-thread source compression task after finishing parsing.
+ if (!cx->isHelperThreadContext()) {
+ if (!source->tryCompressOffThread(cx)) {
+ return nullptr;
+ }
+ }
+
+ // Note: If AsmJS successfully compiles, the into.script will still be
+ // nullptr. In this case we have compiled to a native function instead of an
+ // interpreted script.
+ if (gcOutput.get().script) {
+ if (parameterListEnd) {
+ source->setParameterListEnd(*parameterListEnd);
+ }
+
+ MOZ_ASSERT(!cx->isHelperThreadContext());
+
+ const JS::InstantiateOptions instantiateOptions(options);
+ Rooted<JSScript*> script(cx, gcOutput.get().script);
+ FireOnNewScript(cx, instantiateOptions, script);
+ }
+
+ assertException.reset();
+ }
+ return fun;
+}
+
+JSFunction* frontend::CompileStandaloneFunction(
+ 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::NotGenerator,
+ FunctionAsyncKind::SyncFunction);
+}
+
+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);
+}
+
+JSFunction* frontend::CompileStandaloneFunctionInNonSyntacticScope(
+ JSContext* cx, const JS::ReadOnlyCompileOptions& options,
+ JS::SourceText<char16_t>& srcBuf, const Maybe<uint32_t>& parameterListEnd,
+ FunctionSyntaxKind syntaxKind, Handle<Scope*> enclosingScope) {
+ MOZ_ASSERT(enclosingScope);
+ return CompileStandaloneFunction(cx, options, srcBuf, parameterListEnd,
+ syntaxKind, GeneratorKind::NotGenerator,
+ FunctionAsyncKind::SyncFunction,
+ enclosingScope);
+}
+
+void frontend::FireOnNewScript(JSContext* cx,
+ const JS::InstantiateOptions& options,
+ JS::Handle<JSScript*> script) {
+ if (!options.hideFromNewScriptInitial()) {
+ DebugAPI::onNewScript(cx, script);
+ }
+}
diff --git a/js/src/frontend/BytecodeCompiler.h b/js/src/frontend/BytecodeCompiler.h
new file mode 100644
index 0000000000..eb67377795
--- /dev/null
+++ b/js/src/frontend/BytecodeCompiler.h
@@ -0,0 +1,226 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef frontend_BytecodeCompiler_h
+#define frontend_BytecodeCompiler_h
+
+#include "mozilla/Maybe.h"
+#include "mozilla/Utf8.h" // mozilla::Utf8Unit
+
+#include "NamespaceImports.h"
+
+#include "ds/LifoAlloc.h"
+#include "frontend/FunctionSyntaxKind.h"
+#include "js/SourceText.h"
+#include "js/UniquePtr.h" // js::UniquePtr
+
+/*
+ * Structure of all of the support classes.
+ *
+ * Parser: described in Parser.h.
+ *
+ * BytecodeCompiler.cpp: BytecodeCompiler.h *and* BytecodeCompilation.h.
+ * This is the "driver", the high-level operations like "compile this source to
+ * bytecode". It calls the parser, bytecode emitter, etc.
+ *
+ * ParseContext.h and SharedContext.h: Both have similar purposes. They're split
+ * because ParseContext contains information used only by the parser, and
+ * SharedContext contains information used by both the parser and
+ * BytecodeEmitter.
+ *
+ * SharedContext.h: class Directives: this contains boolean flags for tracking
+ * if we're in asm.js or "use strict" code. The "use strict" bit is stored in
+ * SharedContext, and additionally, the full Directives class is stored in
+ * ParseContext - if a direcive is encountered while parsing, this is updated,
+ * and checked in GeneralParser::functionDefinition, and if it changed, the
+ * whole function is re-parsed with the new flags.
+ *
+ * SharedContext.h: abstract class SharedContext: This class contains two
+ * different groups of flags:
+ *
+ * Parse context information. This is information conceptually "passed down"
+ * into parsing sub-nodes. This is like "are we parsing strict code?", and so
+ * the parser can make decisions of how to parse based off that.
+ *
+ * Gathered-while-parsing information. This is information conceptually
+ * "returned up" from parsing sub-nodes. This is like "did we see a use strict
+ * directive"?
+ *
+ * Additionally, subclasses (GlobalSharedContext, ModuleSharedContext,
+ * EvalSharedContext, and FunctionBox) contain binding information, scope
+ * information, and other such bits of data.
+ *
+ * ParseContext.h: class UsedNameTracker: Track which bindings are used in which
+ * scopes. This helps determine which bindings are closed-over, which affects
+ * how they're stored; and whether special bindings like `this` and `arguments`
+ * can be optimized away.
+ *
+ * ParseContext.h: class ParseContext: Extremely complex class that serves a lot
+ * of purposes, but it's a single class - essentially no derived classes - so
+ * it's a little easier to comprehend all at once. (SourceParseContext does
+ * derive from ParseContext, but they does nothing except adjust the
+ * constructor's arguments).
+ * Note it uses a thing called Nestable, which implements a stack of objects:
+ * you can push (and pop) instances to a stack (linked list) as you parse
+ * further into the parse tree. You may push to this stack via calling the
+ * constructor with a GeneralParser as an argument (usually `this`), which
+ * pushes itself onto `this->pc` (so it does get assigned/pushed, even though no
+ * assignment ever appears directly in the parser)
+ *
+ * ParseContext contains a pointer to a SharedContext.
+ *
+ * There's a decent chunk of flags/data collection in here too, some "pass-down"
+ * data and some "return-up" data.
+ *
+ * ParseContext also contains a significant number of *sub*-Nestables as fields
+ * of itself (nestables inside nestables). Note you also push/pop to these via
+ * passing `Parser->pc`, which the constructor of the sub-nestable knows which
+ * ParseContext field to push to. The sub-nestables are:
+ *
+ * ParseContext::Statement: stack of statements.
+ * `if (x) { while (true) { try { ..stack of [if, while, try].. } ... } }`
+ *
+ * ParseContext::LabelStatement: interspersed in Statement stack, for labeled
+ * statements, for e.g. `label: while (true) { break label; }`
+ *
+ * ParseContext::ClassStatement: interspersed in Statement stack, for classes
+ * the parser is currently inside of.
+ *
+ * ParseContext::Scope: Set of variables in each scope (stack of sets):
+ * `{ let a; let b; { let c; } }`
+ * (this gets complicated with `var`, etc., check the class for docs)
+ */
+
+class JSLinearString;
+
+namespace JS {
+class JS_PUBLIC_API ReadOnlyCompileOptions;
+}
+
+namespace js {
+
+class ModuleObject;
+class ScriptSourceObject;
+class FrontendContext;
+
+namespace frontend {
+
+struct CompilationInput;
+struct CompilationStencil;
+struct ExtensibleCompilationStencil;
+struct CompilationGCOutput;
+class ErrorReporter;
+class FunctionBox;
+class ParseNode;
+class TaggedParserAtomIndex;
+class ScopeBindingCache;
+
+// Compile a module of the given source using the given options.
+ModuleObject* CompileModule(JSContext* cx, FrontendContext* fc,
+ const JS::ReadOnlyCompileOptions& options,
+ JS::SourceText<char16_t>& srcBuf);
+ModuleObject* CompileModule(JSContext* cx, FrontendContext* fc,
+ 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.
+already_AddRefed<CompilationStencil> ParseModuleToStencil(
+ JSContext* maybeCx, FrontendContext* fc, js::LifoAlloc& tempLifoAlloc,
+ CompilationInput& input, ScopeBindingCache* scopeCache,
+ JS::SourceText<char16_t>& srcBuf);
+already_AddRefed<CompilationStencil> ParseModuleToStencil(
+ JSContext* maybeCx, FrontendContext* fc, js::LifoAlloc& tempLifoAlloc,
+ CompilationInput& input, ScopeBindingCache* scopeCache,
+ JS::SourceText<mozilla::Utf8Unit>& srcBuf);
+
+UniquePtr<ExtensibleCompilationStencil> ParseModuleToExtensibleStencil(
+ JSContext* cx, FrontendContext* fc, js::LifoAlloc& tempLifoAlloc,
+ CompilationInput& input, ScopeBindingCache* scopeCache,
+ JS::SourceText<char16_t>& srcBuf);
+UniquePtr<ExtensibleCompilationStencil> ParseModuleToExtensibleStencil(
+ JSContext* cx, FrontendContext* fc, js::LifoAlloc& tempLifoAlloc,
+ CompilationInput& input, ScopeBindingCache* scopeCache,
+ 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;}")
+//
+[[nodiscard]] JSFunction* CompileStandaloneFunction(
+ JSContext* cx, const JS::ReadOnlyCompileOptions& options,
+ JS::SourceText<char16_t>& srcBuf,
+ const mozilla::Maybe<uint32_t>& parameterListEnd,
+ frontend::FunctionSyntaxKind syntaxKind);
+
+[[nodiscard]] JSFunction* CompileStandaloneGenerator(
+ JSContext* cx, const JS::ReadOnlyCompileOptions& options,
+ JS::SourceText<char16_t>& srcBuf,
+ const mozilla::Maybe<uint32_t>& parameterListEnd,
+ frontend::FunctionSyntaxKind syntaxKind);
+
+[[nodiscard]] JSFunction* CompileStandaloneAsyncFunction(
+ JSContext* cx, const JS::ReadOnlyCompileOptions& options,
+ JS::SourceText<char16_t>& srcBuf,
+ const mozilla::Maybe<uint32_t>& parameterListEnd,
+ frontend::FunctionSyntaxKind syntaxKind);
+
+[[nodiscard]] JSFunction* CompileStandaloneAsyncGenerator(
+ JSContext* cx, const JS::ReadOnlyCompileOptions& options,
+ JS::SourceText<char16_t>& srcBuf,
+ const mozilla::Maybe<uint32_t>& parameterListEnd,
+ frontend::FunctionSyntaxKind syntaxKind);
+
+// Compile a single function in given enclosing non-syntactic scope.
+[[nodiscard]] JSFunction* CompileStandaloneFunctionInNonSyntacticScope(
+ JSContext* cx, const JS::ReadOnlyCompileOptions& options,
+ JS::SourceText<char16_t>& srcBuf,
+ const mozilla::Maybe<uint32_t>& parameterListEnd,
+ frontend::FunctionSyntaxKind syntaxKind, Handle<Scope*> enclosingScope);
+
+/*
+ * True if str consists of an IdentifierStart character, followed by one or
+ * more IdentifierPart characters, i.e. it matches the IdentifierName production
+ * in the language spec.
+ *
+ * This returns true even if str is a keyword like "if".
+ *
+ * Defined in TokenStream.cpp.
+ */
+bool IsIdentifier(JSLinearString* str);
+
+bool IsIdentifierNameOrPrivateName(JSLinearString* str);
+
+/*
+ * As above, but taking chars + length.
+ */
+bool IsIdentifier(const Latin1Char* chars, size_t length);
+bool IsIdentifier(const char16_t* chars, size_t length);
+
+/*
+ * ASCII variant with known length.
+ */
+bool IsIdentifierASCII(char c);
+bool IsIdentifierASCII(char c1, char c2);
+
+bool IsIdentifierNameOrPrivateName(const Latin1Char* chars, size_t length);
+bool IsIdentifierNameOrPrivateName(const char16_t* chars, size_t length);
+
+/* True if str is a keyword. Defined in TokenStream.cpp. */
+bool IsKeyword(TaggedParserAtomIndex atom);
+
+} /* namespace frontend */
+} /* namespace js */
+
+#endif /* frontend_BytecodeCompiler_h */
diff --git a/js/src/frontend/BytecodeControlStructures.cpp b/js/src/frontend/BytecodeControlStructures.cpp
new file mode 100644
index 0000000000..1d8d8f5317
--- /dev/null
+++ b/js/src/frontend/BytecodeControlStructures.cpp
@@ -0,0 +1,392 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "frontend/BytecodeControlStructures.h"
+
+#include "frontend/BytecodeEmitter.h" // BytecodeEmitter
+#include "frontend/EmitterScope.h" // EmitterScope
+#include "frontend/ForOfLoopControl.h" // ForOfLoopControl
+#include "frontend/SwitchEmitter.h" // SwitchEmitter
+#include "vm/Opcodes.h" // JSOp
+
+using namespace js;
+using namespace js::frontend;
+
+using mozilla::Maybe;
+
+NestableControl::NestableControl(BytecodeEmitter* bce, StatementKind kind)
+ : Nestable<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, TaggedParserAtomIndex label,
+ BytecodeOffset startOffset)
+ : BreakableControl(bce, StatementKind::Label),
+ label_(label),
+ startOffset_(startOffset) {}
+
+LoopControl::LoopControl(BytecodeEmitter* bce, StatementKind loopKind)
+ : BreakableControl(bce, loopKind), tdzCache_(bce) {
+ MOZ_ASSERT(is<LoopControl>());
+
+ 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) {
+ MOZ_ASSERT(is<TryFinallyControl>());
+}
+
+bool TryFinallyControl::allocateContinuation(NestableControl* target,
+ NonLocalExitKind kind,
+ uint32_t* idx) {
+ for (uint32_t i = 0; i < continuations_.length(); i++) {
+ if (continuations_[i].target_ == target &&
+ continuations_[i].kind_ == kind) {
+ *idx = i + SpecialContinuations::Count;
+ return true;
+ }
+ }
+ *idx = continuations_.length() + SpecialContinuations::Count;
+ return continuations_.emplaceBack(target, kind);
+}
+
+bool TryFinallyControl::emitContinuations(BytecodeEmitter* bce) {
+ SwitchEmitter::TableGenerator tableGen(bce);
+ for (uint32_t i = 0; i < continuations_.length(); i++) {
+ if (!tableGen.addNumber(i + SpecialContinuations::Count)) {
+ return false;
+ }
+ }
+ tableGen.finish(continuations_.length());
+ MOZ_RELEASE_ASSERT(tableGen.isValid());
+
+ InternalSwitchEmitter se(bce);
+ if (!se.validateCaseCount(continuations_.length())) {
+ return false;
+ }
+ if (!se.emitTable(tableGen)) {
+ return false;
+ }
+
+ // Continuation index 0 is special-cased to be the fallthrough block.
+ // Non-default switch cases are numbered 1-N.
+ uint32_t caseIdx = SpecialContinuations::Count;
+ for (TryFinallyContinuation& continuation : continuations_) {
+ if (!se.emitCaseBody(caseIdx++, tableGen)) {
+ return false;
+ }
+ // Resume the non-local control flow that was intercepted by
+ // this finally.
+ NonLocalExitControl nle(bce, continuation.kind_);
+ if (!nle.emitNonLocalJump(continuation.target_, this)) {
+ return false;
+ }
+ }
+
+ // The only unhandled case is the fallthrough case, which is handled
+ // by the switch default.
+ if (!se.emitDefaultBody()) {
+ return false;
+ }
+ if (!se.emitEnd()) {
+ return false;
+ }
+ return true;
+}
+
+NonLocalExitControl::NonLocalExitControl(BytecodeEmitter* bce,
+ NonLocalExitKind kind)
+ : bce_(bce),
+ savedScopeNoteIndex_(bce->bytecodeSection().scopeNoteList().length()),
+ savedDepth_(bce->bytecodeSection().stackDepth()),
+ openScopeNoteIndex_(bce->innermostEmitterScope()->noteIndex()),
+ kind_(kind) {}
+
+NonLocalExitControl::~NonLocalExitControl() {
+ for (uint32_t n = savedScopeNoteIndex_;
+ n < bce_->bytecodeSection().scopeNoteList().length(); n++) {
+ bce_->bytecodeSection().scopeNoteList().recordEnd(
+ n, bce_->bytecodeSection().offset());
+ }
+ bce_->bytecodeSection().setStackDepth(savedDepth_);
+}
+
+bool NonLocalExitControl::emitReturn(BytecodeOffset setRvalOffset) {
+ MOZ_ASSERT(kind_ == NonLocalExitKind::Return);
+ setRvalOffset_ = setRvalOffset;
+ return emitNonLocalJump(nullptr);
+}
+
+bool NonLocalExitControl::leaveScope(EmitterScope* es) {
+ if (!es->leave(bce_, /* nonLocal = */ true)) {
+ return false;
+ }
+
+ // As we pop each scope due to the non-local jump, emit notes that
+ // record the extent of the enclosing scope. These notes will have
+ // their ends recorded in ~NonLocalExitControl().
+ GCThingIndex enclosingScopeIndex = ScopeNote::NoScopeIndex;
+ if (es->enclosingInFrame()) {
+ enclosingScopeIndex = es->enclosingInFrame()->index();
+ }
+ if (!bce_->bytecodeSection().scopeNoteList().append(
+ enclosingScopeIndex, bce_->bytecodeSection().offset(),
+ openScopeNoteIndex_)) {
+ return false;
+ }
+ openScopeNoteIndex_ = bce_->bytecodeSection().scopeNoteList().length() - 1;
+
+ return true;
+}
+
+/*
+ * Emit additional bytecode(s) for non-local jumps.
+ */
+bool NonLocalExitControl::emitNonLocalJump(NestableControl* target,
+ NestableControl* startingAfter) {
+ NestableControl* startingControl = startingAfter
+ ? startingAfter->enclosing()
+ : bce_->innermostNestableControl;
+ EmitterScope* es = startingAfter ? startingAfter->emitterScope()
+ : bce_->innermostEmitterScope();
+
+ int npops = 0;
+
+ AutoCheckUnstableEmitterScope cues(bce_);
+
+ // We emit IteratorClose bytecode inline. 'continue' statements do
+ // not call IteratorClose for the loop they are continuing.
+ bool emitIteratorCloseAtTarget = kind_ != NonLocalExitKind::Continue;
+
+ auto flushPops = [&npops](BytecodeEmitter* bce) {
+ if (npops && !bce->emitPopN(npops)) {
+ return false;
+ }
+ npops = 0;
+ return true;
+ };
+
+ // If we are closing multiple for-of loops, the resulting FOR_OF_ITERCLOSE
+ // trynotes must be appropriately nested. Each FOR_OF_ITERCLOSE starts when
+ // we close the corresponding for-of iterator, and continues until the
+ // actual jump.
+ Vector<BytecodeOffset, 4> forOfIterCloseScopeStarts(bce_->fc);
+
+ // If we have to execute a finally block, then we will jump there now and
+ // continue the non-local jump from the end of the finally block.
+ bool jumpingToFinally = false;
+
+ // Walk the nestable control stack and patch jumps.
+ for (NestableControl* control = startingControl;
+ control != target && !jumpingToFinally; control = control->enclosing()) {
+ // Walk the scope stack and leave the scopes we entered. Leaving a scope
+ // may emit administrative ops like JSOp::PopLexicalEnv but never anything
+ // that manipulates the stack.
+ for (; es != control->emitterScope(); es = es->enclosingInFrame()) {
+ if (!leaveScope(es)) {
+ return false;
+ }
+ }
+
+ switch (control->kind()) {
+ case StatementKind::Finally: {
+ TryFinallyControl& finallyControl = control->as<TryFinallyControl>();
+ if (finallyControl.emittingSubroutine()) {
+ /*
+ * There's a [resume-index-or-exception, throwing] pair on
+ * the stack that we need to pop. If the script is not a
+ * noScriptRval script, we also need to pop the cached rval.
+ */
+ if (bce_->sc->noScriptRval()) {
+ npops += 2;
+ } else {
+ npops += 3;
+ }
+ } else {
+ jumpingToFinally = true;
+
+ if (!flushPops(bce_)) {
+ return false;
+ }
+ uint32_t idx;
+ if (!finallyControl.allocateContinuation(target, kind_, &idx)) {
+ return false;
+ }
+ if (!bce_->emitJumpToFinally(&finallyControl.finallyJumps_, idx)) {
+ return false;
+ }
+ }
+ break;
+ }
+
+ case StatementKind::ForOfLoop: {
+ if (!flushPops(bce_)) {
+ return false;
+ }
+ BytecodeOffset tryNoteStart;
+ ForOfLoopControl& loopinfo = control->as<ForOfLoopControl>();
+ if (!loopinfo.emitPrepareForNonLocalJumpFromScope(
+ bce_, *es,
+ /* isTarget = */ false, &tryNoteStart)) {
+ // [stack] ...
+ return false;
+ }
+ if (!forOfIterCloseScopeStarts.append(tryNoteStart)) {
+ return false;
+ }
+ break;
+ }
+
+ case StatementKind::ForInLoop:
+ if (!flushPops(bce_)) {
+ return false;
+ }
+
+ // The iterator and the current value are on the stack.
+ if (!bce_->emit1(JSOp::EndIter)) {
+ // [stack] ...
+ return false;
+ }
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ if (!flushPops(bce_)) {
+ return false;
+ }
+
+ if (!jumpingToFinally) {
+ if (target && emitIteratorCloseAtTarget && target->is<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;
+ }
+ }
+ switch (kind_) {
+ case NonLocalExitKind::Continue: {
+ LoopControl* loop = &target->as<LoopControl>();
+ if (!bce_->emitJump(JSOp::Goto, &loop->continues)) {
+ return false;
+ }
+ break;
+ }
+ case NonLocalExitKind::Break: {
+ BreakableControl* breakable = &target->as<BreakableControl>();
+ if (!bce_->emitJump(JSOp::Goto, &breakable->breaks)) {
+ return false;
+ }
+ break;
+ }
+ case NonLocalExitKind::Return:
+ MOZ_ASSERT(!target);
+ if (!bce_->finishReturn(setRvalOffset_)) {
+ return false;
+ }
+ break;
+ }
+ }
+
+ // Close FOR_OF_ITERCLOSE trynotes.
+ BytecodeOffset end = bce_->bytecodeSection().offset();
+ for (BytecodeOffset start : forOfIterCloseScopeStarts) {
+ if (!bce_->addTryNote(TryNoteKind::ForOfIterClose, 0, start, end)) {
+ return false;
+ }
+ }
+
+ return true;
+}
diff --git a/js/src/frontend/BytecodeControlStructures.h b/js/src/frontend/BytecodeControlStructures.h
new file mode 100644
index 0000000000..b246aa04ad
--- /dev/null
+++ b/js/src/frontend/BytecodeControlStructures.h
@@ -0,0 +1,205 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef frontend_BytecodeControlStructures_h
+#define frontend_BytecodeControlStructures_h
+
+#include "mozilla/Assertions.h" // MOZ_ASSERT
+#include "mozilla/Maybe.h" // mozilla::Maybe
+
+#include <stdint.h> // int32_t, uint32_t
+
+#include "ds/Nestable.h" // Nestable
+#include "frontend/BytecodeOffset.h" // BytecodeOffset
+#include "frontend/JumpList.h" // JumpList, JumpTarget
+#include "frontend/ParserAtom.h" // TaggedParserAtomIndex
+#include "frontend/SharedContext.h" // StatementKind, StatementKindIsLoop, StatementKindIsUnlabeledBreakTarget
+#include "frontend/TDZCheckCache.h" // TDZCheckCache
+#include "vm/StencilEnums.h" // TryNoteKind
+
+namespace js {
+namespace frontend {
+
+struct BytecodeEmitter;
+class EmitterScope;
+
+class NestableControl : public Nestable<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);
+
+ [[nodiscard]] bool patchBreaks(BytecodeEmitter* bce);
+};
+template <>
+inline bool NestableControl::is<BreakableControl>() const {
+ return StatementKindIsUnlabeledBreakTarget(kind_) ||
+ kind_ == StatementKind::Label;
+}
+
+class LabelControl : public BreakableControl {
+ TaggedParserAtomIndex label_;
+
+ // The code offset when this was pushed. Used for effectfulness checking.
+ BytecodeOffset startOffset_;
+
+ public:
+ LabelControl(BytecodeEmitter* bce, TaggedParserAtomIndex label,
+ BytecodeOffset startOffset);
+
+ TaggedParserAtomIndex label() const { return label_; }
+
+ BytecodeOffset startOffset() const { return startOffset_; }
+};
+template <>
+inline bool NestableControl::is<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::JumpIfTrue head
+ //
+ // breakTarget:
+
+ // The bytecode offset of JSOp::LoopHead.
+ JumpTarget head_;
+
+ // Stack depth when this loop was pushed on the control stack.
+ int32_t stackDepth_;
+
+ // The loop nesting depth. Used as a hint to Ion.
+ uint32_t loopDepth_;
+
+ public:
+ // Offset of the last continue in the loop.
+ JumpList continues;
+
+ LoopControl(BytecodeEmitter* bce, StatementKind loopKind);
+
+ BytecodeOffset headOffset() const { return head_.offset; }
+
+ [[nodiscard]] bool emitContinueTarget(BytecodeEmitter* bce);
+
+ // `nextPos` is the offset in the source code for the character that
+ // corresponds to the next instruction after JSOp::LoopHead.
+ // Can be Nothing() if not available.
+ [[nodiscard]] bool emitLoopHead(BytecodeEmitter* bce,
+ const mozilla::Maybe<uint32_t>& nextPos);
+
+ [[nodiscard]] bool emitLoopEnd(BytecodeEmitter* bce, JSOp op,
+ TryNoteKind tryNoteKind);
+};
+template <>
+inline bool NestableControl::is<LoopControl>() const {
+ return StatementKindIsLoop(kind_);
+}
+
+enum class NonLocalExitKind { Continue, Break, Return };
+
+class TryFinallyContinuation {
+ public:
+ TryFinallyContinuation(NestableControl* target, NonLocalExitKind kind)
+ : target_(target), kind_(kind) {}
+
+ NestableControl* target_;
+ NonLocalExitKind kind_;
+};
+
+class TryFinallyControl : public NestableControl {
+ bool emittingSubroutine_ = false;
+
+ public:
+ // Offset of the last jump to this `finally`.
+ JumpList finallyJumps_;
+
+ js::Vector<TryFinallyContinuation, 4, SystemAllocPolicy> continuations_;
+
+ TryFinallyControl(BytecodeEmitter* bce, StatementKind kind);
+
+ void setEmittingSubroutine() { emittingSubroutine_ = true; }
+
+ bool emittingSubroutine() const { return emittingSubroutine_; }
+
+ enum SpecialContinuations { Fallthrough, Count };
+ bool allocateContinuation(NestableControl* target, NonLocalExitKind kind,
+ uint32_t* idx);
+ bool emitContinuations(BytecodeEmitter* bce);
+};
+template <>
+inline bool NestableControl::is<TryFinallyControl>() const {
+ return kind_ == StatementKind::Try || kind_ == StatementKind::Finally;
+}
+
+class NonLocalExitControl {
+ BytecodeEmitter* bce_;
+ const uint32_t savedScopeNoteIndex_;
+ const int savedDepth_;
+ uint32_t openScopeNoteIndex_;
+ NonLocalExitKind kind_;
+
+ // The offset of a `JSOp::SetRval` that can be rewritten as a
+ // `JSOp::Return` if we don't generate any code for this
+ // NonLocalExitControl.
+ BytecodeOffset setRvalOffset_ = BytecodeOffset::invalidOffset();
+
+ [[nodiscard]] bool leaveScope(EmitterScope* es);
+
+ public:
+ NonLocalExitControl(const NonLocalExitControl&) = delete;
+ NonLocalExitControl(BytecodeEmitter* bce, NonLocalExitKind kind);
+ ~NonLocalExitControl();
+
+ [[nodiscard]] bool emitNonLocalJump(NestableControl* target,
+ NestableControl* startingAfter = nullptr);
+ [[nodiscard]] bool emitReturn(BytecodeOffset setRvalOffset);
+};
+
+} /* namespace frontend */
+} /* namespace js */
+
+#endif /* frontend_BytecodeControlStructures_h */
diff --git a/js/src/frontend/BytecodeEmitter.cpp b/js/src/frontend/BytecodeEmitter.cpp
new file mode 100644
index 0000000000..24155fc7e6
--- /dev/null
+++ b/js/src/frontend/BytecodeEmitter.cpp
@@ -0,0 +1,12031 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * JS bytecode generation.
+ */
+
+#include "frontend/BytecodeEmitter.h"
+
+#include "mozilla/Casting.h" // mozilla::AssertedCast
+#include "mozilla/DebugOnly.h" // mozilla::DebugOnly
+#include "mozilla/FloatingPoint.h" // mozilla::NumberEqualsInt32, mozilla::NumberIsInt32
+#include "mozilla/HashTable.h" // mozilla::HashSet
+#include "mozilla/Maybe.h" // mozilla::{Maybe,Nothing,Some}
+#include "mozilla/PodOperations.h" // mozilla::PodCopy
+#include "mozilla/Saturate.h"
+#include "mozilla/Variant.h" // mozilla::AsVariant
+
+#include <algorithm>
+#include <iterator>
+#include <string.h>
+
+#include "jstypes.h" // JS_BIT
+
+#include "frontend/AbstractScopePtr.h" // ScopeIndex
+#include "frontend/BytecodeControlStructures.h" // NestableControl, BreakableControl, LabelControl, LoopControl, TryFinallyControl
+#include "frontend/CallOrNewEmitter.h" // CallOrNewEmitter
+#include "frontend/CForEmitter.h" // CForEmitter
+#include "frontend/DecoratorEmitter.h" // DecoratorEmitter
+#include "frontend/DefaultEmitter.h" // DefaultEmitter
+#include "frontend/DoWhileEmitter.h" // DoWhileEmitter
+#include "frontend/ElemOpEmitter.h" // ElemOpEmitter
+#include "frontend/EmitterScope.h" // EmitterScope
+#include "frontend/ExpressionStatementEmitter.h" // ExpressionStatementEmitter
+#include "frontend/ForInEmitter.h" // ForInEmitter
+#include "frontend/ForOfEmitter.h" // ForOfEmitter
+#include "frontend/FunctionEmitter.h" // FunctionEmitter, FunctionScriptEmitter, FunctionParamsEmitter
+#include "frontend/IfEmitter.h" // IfEmitter, InternalIfEmitter, CondEmitter
+#include "frontend/LabelEmitter.h" // LabelEmitter
+#include "frontend/LexicalScopeEmitter.h" // LexicalScopeEmitter
+#include "frontend/ModuleSharedContext.h" // ModuleSharedContext
+#include "frontend/NameAnalysisTypes.h" // PrivateNameKind
+#include "frontend/NameFunctions.h" // NameFunctions
+#include "frontend/NameOpEmitter.h" // NameOpEmitter
+#include "frontend/ObjectEmitter.h" // PropertyEmitter, ObjectEmitter, ClassEmitter
+#include "frontend/OptionalEmitter.h" // OptionalEmitter
+#include "frontend/ParseNode.h" // ParseNodeKind, ParseNode and subclasses
+#include "frontend/Parser.h" // Parser
+#include "frontend/ParserAtom.h" // ParserAtomsTable, ParserAtom
+#include "frontend/PrivateOpEmitter.h" // PrivateOpEmitter
+#include "frontend/PropOpEmitter.h" // PropOpEmitter
+#include "frontend/SourceNotes.h" // SrcNote, SrcNoteType, SrcNoteWriter
+#include "frontend/SwitchEmitter.h" // SwitchEmitter
+#include "frontend/TaggedParserAtomIndexHasher.h" // TaggedParserAtomIndexHasher
+#include "frontend/TDZCheckCache.h" // TDZCheckCache
+#include "frontend/TryEmitter.h" // TryEmitter
+#include "frontend/WhileEmitter.h" // WhileEmitter
+#include "js/friend/ErrorMessages.h" // JSMSG_*
+#include "js/friend/StackLimits.h" // AutoCheckRecursionLimit
+#include "util/StringBuffer.h" // StringBuffer
+#include "vm/BytecodeUtil.h" // JOF_*, IsArgOp, IsLocalOp, SET_UINT24, SET_ICINDEX, BytecodeFallsThrough, BytecodeIsJumpTarget
+#include "vm/CompletionKind.h" // CompletionKind
+#include "vm/FunctionPrefixKind.h" // FunctionPrefixKind
+#include "vm/GeneratorObject.h" // AbstractGeneratorObject
+#include "vm/Opcodes.h" // JSOp, JSOpLength_*
+#include "vm/PropMap.h" // SharedPropMap::MaxPropsForNonDictionary
+#include "vm/Scope.h" // GetScopeDataTrailingNames
+#include "vm/SharedStencil.h" // ScopeNote
+#include "vm/ThrowMsgKind.h" // ThrowMsgKind
+#include "vm/WellKnownAtom.h" // js_*_str
+
+using namespace js;
+using namespace js::frontend;
+
+using mozilla::AssertedCast;
+using mozilla::AsVariant;
+using mozilla::DebugOnly;
+using mozilla::Maybe;
+using mozilla::Nothing;
+using mozilla::NumberEqualsInt32;
+using mozilla::NumberIsInt32;
+using mozilla::PodCopy;
+using mozilla::Some;
+
+static bool ParseNodeRequiresSpecialLineNumberNotes(ParseNode* pn) {
+ // The few node types listed below are exceptions to the usual
+ // location-source-note-emitting code in BytecodeEmitter::emitTree().
+ // Single-line `while` loops and C-style `for` loops require careful
+ // handling to avoid strange stepping behavior.
+ // Functions usually shouldn't have location information (bug 1431202).
+
+ ParseNodeKind kind = pn->getKind();
+ return kind == ParseNodeKind::WhileStmt || kind == ParseNodeKind::ForStmt ||
+ kind == ParseNodeKind::Function;
+}
+
+static bool NeedsFieldInitializer(ParseNode* member, bool inStaticContext) {
+ // For the purposes of bytecode emission, StaticClassBlocks are treated as if
+ // they were static initializers.
+ return (member->is<StaticClassBlock>() && inStaticContext) ||
+ (member->is<ClassField>() &&
+ member->as<ClassField>().isStatic() == inStaticContext);
+}
+
+static bool NeedsAccessorInitializer(ParseNode* member, bool isStatic) {
+ if (isStatic) {
+ return false;
+ }
+ return member->is<ClassMethod>() &&
+ member->as<ClassMethod>().name().isKind(ParseNodeKind::PrivateName) &&
+ !member->as<ClassMethod>().isStatic() &&
+ member->as<ClassMethod>().accessorType() != AccessorType::None;
+}
+
+static bool ShouldSuppressBreakpointsAndSourceNotes(
+ SharedContext* sc, BytecodeEmitter::EmitterMode emitterMode) {
+ // Suppress for all self-hosting code.
+ if (emitterMode == BytecodeEmitter::EmitterMode::SelfHosting) {
+ return true;
+ }
+
+ // Suppress for synthesized class constructors.
+ if (sc->isFunctionBox()) {
+ FunctionBox* funbox = sc->asFunctionBox();
+ return funbox->isSyntheticFunction() && funbox->isClassConstructor();
+ }
+
+ return false;
+}
+
+BytecodeEmitter::BytecodeEmitter(BytecodeEmitter* parent, FrontendContext* fc,
+ SharedContext* sc,
+ const ErrorReporter& errorReporter,
+ CompilationState& compilationState,
+ EmitterMode emitterMode)
+ : sc(sc),
+ fc(fc),
+ parent(parent),
+ bytecodeSection_(fc, sc->extent().lineno, sc->extent().column),
+ perScriptData_(fc, compilationState),
+ errorReporter_(errorReporter),
+ compilationState(compilationState),
+ suppressBreakpointsAndSourceNotes(
+ ShouldSuppressBreakpointsAndSourceNotes(sc, emitterMode)),
+ emitterMode(emitterMode) {
+ MOZ_ASSERT_IF(parent, fc == parent->fc);
+}
+
+BytecodeEmitter::BytecodeEmitter(BytecodeEmitter* parent, SharedContext* sc)
+ : BytecodeEmitter(parent, parent->fc, sc, parent->errorReporter_,
+ parent->compilationState, parent->emitterMode) {}
+
+BytecodeEmitter::BytecodeEmitter(FrontendContext* fc,
+ const EitherParser& parser, SharedContext* sc,
+ CompilationState& compilationState,
+ EmitterMode emitterMode)
+ : BytecodeEmitter(nullptr, fc, sc, parser.errorReporter(), compilationState,
+ emitterMode) {
+ ep_.emplace(parser);
+}
+
+void BytecodeEmitter::initFromBodyPosition(TokenPos bodyPosition) {
+ setScriptStartOffsetIfUnset(bodyPosition.begin);
+ setFunctionBodyEndPos(bodyPosition.end);
+}
+
+bool BytecodeEmitter::init() {
+ if (!parent) {
+ if (!compilationState.prepareSharedDataStorage(fc)) {
+ return false;
+ }
+ }
+ return perScriptData_.init(fc);
+}
+
+bool BytecodeEmitter::init(TokenPos bodyPosition) {
+ initFromBodyPosition(bodyPosition);
+ return init();
+}
+
+template <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(TaggedParserAtomIndex name) {
+ return innermostEmitterScope()->lookup(this, name);
+}
+
+void BytecodeEmitter::lookupPrivate(TaggedParserAtomIndex name,
+ NameLocation& loc,
+ Maybe<NameLocation>& brandLoc) {
+ innermostEmitterScope()->lookupPrivate(this, name, loc, brandLoc);
+}
+
+Maybe<NameLocation> BytecodeEmitter::locationOfNameBoundInScope(
+ TaggedParserAtomIndex name, EmitterScope* target) {
+ return innermostEmitterScope()->locationBoundInScope(name, target);
+}
+
+template <typename T>
+Maybe<NameLocation> BytecodeEmitter::locationOfNameBoundInScopeType(
+ TaggedParserAtomIndex 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 (!newSrcNote(SrcNoteType::StepSep)) {
+ return false;
+ }
+
+ if (!newSrcNote(SrcNoteType::Breakpoint)) {
+ return false;
+ }
+
+ // We track the location of the most recent separator for use in
+ // markSimpleBreakpoint. Note that this means that the position must already
+ // be set before markStepBreakpoint is called.
+ bytecodeSection().updateSeparatorPosition();
+
+ return true;
+}
+
+bool BytecodeEmitter::markSimpleBreakpoint() {
+ if (skipBreakpointSrcNotes()) {
+ return true;
+ }
+
+ // If a breakable call ends up being the same location as the most recent
+ // expression start, we need to skip marking it breakable in order to avoid
+ // having two breakpoints with the same line/column position.
+ // Note: This assumes that the position for the call has already been set.
+ if (!bytecodeSection().isDuplicateLocation()) {
+ if (!newSrcNote(SrcNoteType::Breakpoint)) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool BytecodeEmitter::emitCheck(JSOp op, ptrdiff_t delta,
+ BytecodeOffset* offset) {
+ size_t oldLength = bytecodeSection().code().length();
+ *offset = BytecodeOffset(oldLength);
+
+ size_t newLength = oldLength + size_t(delta);
+ if (MOZ_UNLIKELY(newLength > MaxBytecodeLength)) {
+ ReportAllocationOverflow(fc);
+ return false;
+ }
+
+ if (!bytecodeSection().code().growByUninitialized(delta)) {
+ return false;
+ }
+
+ if (BytecodeOpHasIC(op)) {
+ // Even if every bytecode op is a JOF_IC op and the function has ARGC_LIMIT
+ // arguments, numICEntries cannot overflow.
+ static_assert(MaxBytecodeLength + 1 /* this */ + ARGC_LIMIT <= UINT32_MAX,
+ "numICEntries must not overflow");
+ bytecodeSection().incrementNumICEntries();
+ }
+
+ return true;
+}
+
+#ifdef DEBUG
+bool BytecodeEmitter::checkStrictOrSloppy(JSOp op) {
+ if (IsCheckStrictOp(op) && !sc->strict()) {
+ return false;
+ }
+ if (IsCheckSloppyOp(op) && sc->strict()) {
+ return false;
+ }
+ return true;
+}
+#endif
+
+bool BytecodeEmitter::emit1(JSOp op) {
+ MOZ_ASSERT(checkStrictOrSloppy(op));
+
+ BytecodeOffset offset;
+ if (!emitCheck(op, 1, &offset)) {
+ return false;
+ }
+
+ jsbytecode* code = bytecodeSection().code(offset);
+ code[0] = jsbytecode(op);
+ bytecodeSection().updateDepth(op, offset);
+ return true;
+}
+
+bool BytecodeEmitter::emit2(JSOp op, uint8_t op1) {
+ MOZ_ASSERT(checkStrictOrSloppy(op));
+
+ BytecodeOffset offset;
+ if (!emitCheck(op, 2, &offset)) {
+ return false;
+ }
+
+ jsbytecode* code = bytecodeSection().code(offset);
+ code[0] = jsbytecode(op);
+ code[1] = jsbytecode(op1);
+ bytecodeSection().updateDepth(op, offset);
+ return true;
+}
+
+bool BytecodeEmitter::emit3(JSOp op, jsbytecode op1, jsbytecode op2) {
+ MOZ_ASSERT(checkStrictOrSloppy(op));
+
+ /* These should filter through emitVarOp. */
+ MOZ_ASSERT(!IsArgOp(op));
+ MOZ_ASSERT(!IsLocalOp(op));
+
+ BytecodeOffset offset;
+ if (!emitCheck(op, 3, &offset)) {
+ return false;
+ }
+
+ jsbytecode* code = bytecodeSection().code(offset);
+ code[0] = jsbytecode(op);
+ code[1] = op1;
+ code[2] = op2;
+ bytecodeSection().updateDepth(op, offset);
+ return true;
+}
+
+bool BytecodeEmitter::emitN(JSOp op, size_t extra, BytecodeOffset* offset) {
+ MOZ_ASSERT(checkStrictOrSloppy(op));
+ ptrdiff_t length = 1 + ptrdiff_t(extra);
+
+ BytecodeOffset off;
+ if (!emitCheck(op, length, &off)) {
+ return false;
+ }
+
+ jsbytecode* code = bytecodeSection().code(off);
+ code[0] = jsbytecode(op);
+ /* The remaining |extra| bytes are set by the caller */
+
+ /*
+ * Don't updateDepth if op's use-count comes from the immediate
+ * operand yet to be stored in the extra bytes after op.
+ */
+ if (CodeSpec(op).nuses >= 0) {
+ bytecodeSection().updateDepth(op, off);
+ }
+
+ if (offset) {
+ *offset = off;
+ }
+ return true;
+}
+
+bool BytecodeEmitter::emitJumpTargetOp(JSOp op, BytecodeOffset* off) {
+ MOZ_ASSERT(BytecodeIsJumpTarget(op));
+
+ // Record the current IC-entry index at start of this op.
+ uint32_t numEntries = bytecodeSection().numICEntries();
+
+ size_t n = GetOpLength(op) - 1;
+ MOZ_ASSERT(GetOpLength(op) >= 1 + ICINDEX_LEN);
+
+ if (!emitN(op, n, off)) {
+ return false;
+ }
+
+ SET_ICINDEX(bytecodeSection().code(*off), numEntries);
+ return true;
+}
+
+bool BytecodeEmitter::emitJumpTarget(JumpTarget* target) {
+ BytecodeOffset off = bytecodeSection().offset();
+
+ // Alias consecutive jump targets.
+ if (bytecodeSection().lastTargetOffset().valid() &&
+ off == bytecodeSection().lastTargetOffset() +
+ BytecodeOffsetDiff(JSOpLength_JumpTarget)) {
+ target->offset = bytecodeSection().lastTargetOffset();
+ return true;
+ }
+
+ target->offset = off;
+ bytecodeSection().setLastTargetOffset(off);
+
+ BytecodeOffset opOff;
+ return emitJumpTargetOp(JSOp::JumpTarget, &opOff);
+}
+
+bool BytecodeEmitter::emitJumpNoFallthrough(JSOp op, JumpList* jump) {
+ BytecodeOffset offset;
+ if (!emitCheck(op, 5, &offset)) {
+ return false;
+ }
+
+ jsbytecode* code = bytecodeSection().code(offset);
+ code[0] = jsbytecode(op);
+ MOZ_ASSERT(!jump->offset.valid() ||
+ (0 <= jump->offset.value() && jump->offset < offset));
+ jump->push(bytecodeSection().code(BytecodeOffset(0)), offset);
+ bytecodeSection().updateDepth(op, offset);
+ return true;
+}
+
+bool BytecodeEmitter::emitJump(JSOp op, JumpList* jump) {
+ if (!emitJumpNoFallthrough(op, jump)) {
+ return false;
+ }
+ if (BytecodeFallsThrough(op)) {
+ JumpTarget fallthrough;
+ if (!emitJumpTarget(&fallthrough)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+void BytecodeEmitter::patchJumpsToTarget(JumpList jump, JumpTarget target) {
+ MOZ_ASSERT(
+ !jump.offset.valid() ||
+ (0 <= jump.offset.value() && jump.offset <= bytecodeSection().offset()));
+ MOZ_ASSERT(0 <= target.offset.value() &&
+ target.offset <= bytecodeSection().offset());
+ MOZ_ASSERT_IF(
+ jump.offset.valid() &&
+ target.offset + BytecodeOffsetDiff(4) <= bytecodeSection().offset(),
+ BytecodeIsJumpTarget(JSOp(*bytecodeSection().code(target.offset))));
+ jump.patchAll(bytecodeSection().code(BytecodeOffset(0)), target);
+}
+
+bool BytecodeEmitter::emitJumpTargetAndPatch(JumpList jump) {
+ if (!jump.offset.valid()) {
+ return true;
+ }
+ JumpTarget target;
+ if (!emitJumpTarget(&target)) {
+ return false;
+ }
+ patchJumpsToTarget(jump, target);
+ return true;
+}
+
+bool BytecodeEmitter::emitCall(JSOp op, uint16_t argc,
+ const Maybe<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;
+ }
+
+ const ErrorReporter& er = errorReporter();
+ bool onThisLine;
+ if (!er.isOnThisLine(offset, bytecodeSection().currentLine(), &onThisLine)) {
+ er.errorNoOffset(JSMSG_OUT_OF_MEMORY);
+ return false;
+ }
+
+ if (!onThisLine) {
+ unsigned line = er.lineAt(offset);
+ unsigned delta = line - bytecodeSection().currentLine();
+
+ // If we use a `SetLine` note below, we want it to be relative to the
+ // scripts initial line number for better chance of sharing.
+ unsigned initialLine = sc->extent().lineno;
+ MOZ_ASSERT(line >= initialLine);
+
+ /*
+ * Encode any change in the current source line number by using
+ * either several SrcNoteType::NewLine notes or just one
+ * SrcNoteType::SetLine note, whichever consumes less space.
+ *
+ * NB: We handle backward line number deltas (possible with for
+ * loops where the update part is emitted after the body, but its
+ * line number is <= any line number in the body) here by letting
+ * unsigned delta_ wrap to a very large number, which triggers a
+ * SrcNoteType::SetLine.
+ */
+ bytecodeSection().setCurrentLine(line, offset);
+ if (delta >= SrcNote::SetLine::lengthFor(line, initialLine)) {
+ if (!newSrcNote2(SrcNoteType::SetLine,
+ SrcNote::SetLine::toOperand(line, initialLine))) {
+ return false;
+ }
+ } else {
+ do {
+ if (!newSrcNote(SrcNoteType::NewLine)) {
+ return false;
+ }
+ } while (--delta != 0);
+ }
+
+ bytecodeSection().updateSeparatorPositionIfPresent();
+ }
+ return true;
+}
+
+/* Updates the line number and column number information in the source notes. */
+bool BytecodeEmitter::updateSourceCoordNotes(uint32_t offset) {
+ if (!updateLineNumberNotes(offset)) {
+ return false;
+ }
+
+ if (skipLocationSrcNotes()) {
+ return true;
+ }
+
+ uint32_t columnIndex = errorReporter().columnAt(offset);
+ MOZ_ASSERT(columnIndex <= ColumnLimit);
+
+ // Assert colspan is always representable.
+ static_assert((0 - ptrdiff_t(ColumnLimit)) >= SrcNote::ColSpan::MinColSpan);
+ static_assert((ptrdiff_t(ColumnLimit) - 0) <= SrcNote::ColSpan::MaxColSpan);
+
+ ptrdiff_t colspan =
+ ptrdiff_t(columnIndex) - ptrdiff_t(bytecodeSection().lastColumn());
+
+ if (colspan != 0) {
+ if (!newSrcNote2(SrcNoteType::ColSpan,
+ SrcNote::ColSpan::toOperand(colspan))) {
+ return false;
+ }
+ bytecodeSection().setLastColumn(columnIndex, offset);
+ bytecodeSection().updateSeparatorPositionIfPresent();
+ }
+ return true;
+}
+
+uint32_t BytecodeEmitter::getOffsetForLoop(ParseNode* nextpn) {
+ // Try to give the JSOp::LoopHead the same line number as the next
+ // instruction. nextpn is often a block, in which case the next instruction
+ // typically comes from the first statement inside.
+ if (nextpn->is<LexicalScopeNode>()) {
+ nextpn = nextpn->as<LexicalScopeNode>().scopeBody();
+ }
+ if (nextpn->isKind(ParseNodeKind::StatementList)) {
+ if (ParseNode* firstStatement = nextpn->as<ListNode>().head()) {
+ nextpn = firstStatement;
+ }
+ }
+
+ return nextpn->pn_pos.begin;
+}
+
+bool BytecodeEmitter::emitUint16Operand(JSOp op, uint32_t operand) {
+ MOZ_ASSERT(operand <= UINT16_MAX);
+ if (!emit3(op, UINT16_LO(operand), UINT16_HI(operand))) {
+ return false;
+ }
+ return true;
+}
+
+bool BytecodeEmitter::emitUint32Operand(JSOp op, uint32_t operand) {
+ BytecodeOffset off;
+ if (!emitN(op, 4, &off)) {
+ return false;
+ }
+ SET_UINT32(bytecodeSection().code(off), operand);
+ return true;
+}
+
+bool BytecodeEmitter::emitGoto(NestableControl* target, GotoKind kind) {
+ NonLocalExitControl nle(this, kind == GotoKind::Continue
+ ? NonLocalExitKind::Continue
+ : NonLocalExitKind::Break);
+ return nle.emitNonLocalJump(target);
+}
+
+AbstractScopePtr BytecodeEmitter::innermostScope() const {
+ return innermostEmitterScope()->scope(this);
+}
+
+ScopeIndex BytecodeEmitter::innermostScopeIndex() const {
+ return *innermostEmitterScope()->scopeIndex(this);
+}
+
+bool BytecodeEmitter::emitGCIndexOp(JSOp op, GCThingIndex index) {
+ MOZ_ASSERT(checkStrictOrSloppy(op));
+
+ constexpr size_t OpLength = 1 + GCTHING_INDEX_LEN;
+ MOZ_ASSERT(GetOpLength(op) == OpLength);
+
+ BytecodeOffset offset;
+ if (!emitCheck(op, OpLength, &offset)) {
+ return false;
+ }
+
+ jsbytecode* code = bytecodeSection().code(offset);
+ code[0] = jsbytecode(op);
+ SET_GCTHING_INDEX(code, index);
+ bytecodeSection().updateDepth(op, offset);
+ return true;
+}
+
+bool BytecodeEmitter::emitAtomOp(JSOp op, TaggedParserAtomIndex atom) {
+ MOZ_ASSERT(atom);
+
+ // .generator lookups should be emitted as JSOp::GetAliasedVar instead of
+ // JSOp::GetName etc, to bypass |with| objects on the scope chain.
+ // It's safe to emit .this lookups though because |with| objects skip
+ // those.
+ MOZ_ASSERT_IF(op == JSOp::GetName || op == JSOp::GetGName,
+ atom != TaggedParserAtomIndex::WellKnown::dotGenerator());
+
+ GCThingIndex index;
+ if (!makeAtomIndex(atom, ParserAtom::Atomize::Yes, &index)) {
+ return false;
+ }
+
+ return emitAtomOp(op, index);
+}
+
+bool BytecodeEmitter::emitAtomOp(JSOp op, GCThingIndex atomIndex) {
+ MOZ_ASSERT(JOF_OPTYPE(op) == JOF_ATOM);
+#ifdef DEBUG
+ auto atom = perScriptData().gcThingList().getAtom(atomIndex);
+ MOZ_ASSERT(compilationState.parserAtoms.isInstantiatedAsJSAtom(atom));
+#endif
+ return emitGCIndexOp(op, atomIndex);
+}
+
+bool BytecodeEmitter::emitStringOp(JSOp op, TaggedParserAtomIndex atom) {
+ MOZ_ASSERT(atom);
+ GCThingIndex index;
+ if (!makeAtomIndex(atom, ParserAtom::Atomize::No, &index)) {
+ return false;
+ }
+
+ return emitStringOp(op, index);
+}
+
+bool BytecodeEmitter::emitStringOp(JSOp op, GCThingIndex atomIndex) {
+ MOZ_ASSERT(JOF_OPTYPE(op) == JOF_STRING);
+ return emitGCIndexOp(op, atomIndex);
+}
+
+bool BytecodeEmitter::emitInternedScopeOp(GCThingIndex index, JSOp op) {
+ MOZ_ASSERT(JOF_OPTYPE(op) == JOF_SCOPE);
+ MOZ_ASSERT(index < perScriptData().gcThingList().length());
+ return emitGCIndexOp(op, index);
+}
+
+bool BytecodeEmitter::emitInternedObjectOp(GCThingIndex index, JSOp op) {
+ MOZ_ASSERT(JOF_OPTYPE(op) == JOF_OBJECT);
+ MOZ_ASSERT(index < perScriptData().gcThingList().length());
+ return emitGCIndexOp(op, index);
+}
+
+bool BytecodeEmitter::emitRegExp(GCThingIndex index) {
+ return emitGCIndexOp(JSOp::RegExp, index);
+}
+
+bool BytecodeEmitter::emitLocalOp(JSOp op, uint32_t slot) {
+ MOZ_ASSERT(JOF_OPTYPE(op) != JOF_ENVCOORD);
+ MOZ_ASSERT(IsLocalOp(op));
+
+ BytecodeOffset off;
+ if (!emitN(op, LOCALNO_LEN, &off)) {
+ return false;
+ }
+
+ SET_LOCALNO(bytecodeSection().code(off), slot);
+ return true;
+}
+
+bool BytecodeEmitter::emitArgOp(JSOp op, uint16_t slot) {
+ MOZ_ASSERT(IsArgOp(op));
+ BytecodeOffset off;
+ if (!emitN(op, ARGNO_LEN, &off)) {
+ return false;
+ }
+
+ SET_ARGNO(bytecodeSection().code(off), slot);
+ return true;
+}
+
+bool BytecodeEmitter::emitEnvCoordOp(JSOp op, EnvironmentCoordinate ec) {
+ MOZ_ASSERT(JOF_OPTYPE(op) == JOF_ENVCOORD ||
+ JOF_OPTYPE(op) == JOF_DEBUGCOORD);
+
+ constexpr size_t N = ENVCOORD_HOPS_LEN + ENVCOORD_SLOT_LEN;
+ MOZ_ASSERT(GetOpLength(op) == 1 + N);
+
+ BytecodeOffset off;
+ if (!emitN(op, N, &off)) {
+ return false;
+ }
+
+ jsbytecode* pc = bytecodeSection().code(off);
+ SET_ENVCOORD_HOPS(pc, ec.hops());
+ pc += ENVCOORD_HOPS_LEN;
+ SET_ENVCOORD_SLOT(pc, ec.slot());
+ pc += ENVCOORD_SLOT_LEN;
+ return true;
+}
+
+JSOp BytecodeEmitter::strictifySetNameOp(JSOp op) {
+ switch (op) {
+ case JSOp::SetName:
+ if (sc->strict()) {
+ op = JSOp::StrictSetName;
+ }
+ break;
+ case JSOp::SetGName:
+ if (sc->strict()) {
+ op = JSOp::StrictSetGName;
+ }
+ break;
+ default:;
+ }
+ return op;
+}
+
+bool BytecodeEmitter::checkSideEffects(ParseNode* pn, bool* answer) {
+ AutoCheckRecursionLimit recursion(fc);
+ if (!recursion.check(fc)) {
+ return false;
+ }
+
+restart:
+
+ switch (pn->getKind()) {
+ // Trivial cases with no side effects.
+ case ParseNodeKind::EmptyStmt:
+ case ParseNodeKind::TrueExpr:
+ case ParseNodeKind::FalseExpr:
+ case ParseNodeKind::NullExpr:
+ case ParseNodeKind::RawUndefinedExpr:
+ case ParseNodeKind::Elision:
+ case ParseNodeKind::Generator:
+ MOZ_ASSERT(pn->is<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;
+
+ // |new.target| doesn't have any side-effects.
+ case ParseNodeKind::NewTargetExpr: {
+ MOZ_ASSERT(pn->is<NewTargetNode>());
+ *answer = false;
+ return true;
+ }
+
+ // Trivial binary nodes with more token pos holders.
+ 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;
+
+#ifdef ENABLE_RECORD_TUPLE
+ case ParseNodeKind::RecordExpr:
+ case ParseNodeKind::TupleExpr:
+ MOZ_CRASH("Record and Tuple are not supported yet");
+#endif
+
+#ifdef ENABLE_DECORATORS
+ case ParseNodeKind::DecoratorList:
+ MOZ_CRASH("Decorators are not supported yet");
+#endif
+
+ // Most other binary operations (parsed as lists in SpiderMonkey) may
+ // perform conversions triggering side effects. Math operations perform
+ // ToNumber and may fail invoking invalid user-defined toString/valueOf:
+ // |5 < { toString: null }|. |instanceof| throws if provided a
+ // non-object constructor: |null instanceof null|. |in| throws if given
+ // a non-object RHS: |5 in null|.
+ case ParseNodeKind::BitOrExpr:
+ case ParseNodeKind::BitXorExpr:
+ case ParseNodeKind::BitAndExpr:
+ case ParseNodeKind::EqExpr:
+ case ParseNodeKind::NeExpr:
+ case ParseNodeKind::LtExpr:
+ case ParseNodeKind::LeExpr:
+ case ParseNodeKind::GtExpr:
+ case ParseNodeKind::GeExpr:
+ case ParseNodeKind::InstanceOfExpr:
+ case ParseNodeKind::InExpr:
+ case ParseNodeKind::PrivateInExpr:
+ case ParseNodeKind::LshExpr:
+ case ParseNodeKind::RshExpr:
+ case ParseNodeKind::UrshExpr:
+ case ParseNodeKind::AddExpr:
+ case ParseNodeKind::SubExpr:
+ case ParseNodeKind::MulExpr:
+ case ParseNodeKind::DivExpr:
+ case ParseNodeKind::ModExpr:
+ case ParseNodeKind::PowExpr:
+ MOZ_ASSERT(pn->as<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::ElemExpr:
+ case ParseNodeKind::OptionalElemExpr:
+ MOZ_ASSERT(pn->is<BinaryNode>());
+ *answer = true;
+ return true;
+
+ // Throws if the operand is not of the right class. Can also call a private
+ // getter.
+ case ParseNodeKind::PrivateMemberExpr:
+ case ParseNodeKind::OptionalPrivateMemberExpr:
+ *answer = true;
+ return true;
+
+ // These affect visible names in this code, or in other code.
+ case ParseNodeKind::ImportDecl:
+ case ParseNodeKind::ExportFromStmt:
+ case ParseNodeKind::ExportDefaultStmt:
+ MOZ_ASSERT(pn->is<BinaryNode>());
+ *answer = true;
+ return true;
+
+ // Likewise.
+ case ParseNodeKind::ExportStmt:
+ MOZ_ASSERT(pn->is<UnaryNode>());
+ *answer = true;
+ return true;
+
+ case ParseNodeKind::CallImportExpr:
+ case ParseNodeKind::CallImportSpec:
+ 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;
+
+ // 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::DefaultConstructor: // by ParseNodeKind::ClassDecl
+ case ParseNodeKind::ClassBodyScope: // by ParseNodeKind::ClassDecl
+ case ParseNodeKind::ClassMethod: // by ParseNodeKind::ClassDecl
+ case ParseNodeKind::ClassField: // by ParseNodeKind::ClassDecl
+ case ParseNodeKind::ClassNames: // by ParseNodeKind::ClassDecl
+ case ParseNodeKind::StaticClassBlock: // by ParseNodeKind::ClassDecl
+ case ParseNodeKind::ClassMemberList: // by ParseNodeKind::ClassDecl
+ case ParseNodeKind::ImportSpecList: // by ParseNodeKind::Import
+ case ParseNodeKind::ImportSpec: // by ParseNodeKind::Import
+ case ParseNodeKind::ImportNamespaceSpec: // by ParseNodeKind::Import
+ case ParseNodeKind::ImportAssertion: // by ParseNodeKind::Import
+ case ParseNodeKind::ImportAssertionList: // by ParseNodeKind::Import
+ case ParseNodeKind::ImportModuleRequest: // by ParseNodeKind::Import
+ case ParseNodeKind::ExportBatchSpecStmt: // by ParseNodeKind::Export
+ case ParseNodeKind::ExportSpecList: // by ParseNodeKind::Export
+ case ParseNodeKind::ExportSpec: // by ParseNodeKind::Export
+ case ParseNodeKind::ExportNamespaceSpec: // by ParseNodeKind::Export
+ case ParseNodeKind::CallSiteObj: // by ParseNodeKind::TaggedTemplate
+ case ParseNodeKind::PosHolder: // by ParseNodeKind::NewTarget
+ case ParseNodeKind::SuperBase: // by ParseNodeKind::Elem and others
+ case ParseNodeKind::PropertyNameExpr: // by ParseNodeKind::Dot
+ MOZ_CRASH("handled by parent nodes");
+
+ case ParseNodeKind::LastUnused:
+ case ParseNodeKind::Limit:
+ MOZ_CRASH("invalid node kind");
+ }
+
+ MOZ_CRASH(
+ "invalid, unenumerated ParseNodeKind value encountered in "
+ "BytecodeEmitter::checkSideEffects");
+}
+
+bool BytecodeEmitter::isInLoop() {
+ return findInnermostNestableControl<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");
+
+ MOZ_ASSERT(numHops < ENVCOORD_HOPS_LIMIT - 1);
+
+ return emit2(JSOp::EnvCallee, numHops);
+}
+
+bool BytecodeEmitter::emitSuperBase() {
+ if (!emitThisEnvironmentCallee()) {
+ return false;
+ }
+
+ return emit1(JSOp::SuperBase);
+}
+
+void BytecodeEmitter::reportError(ParseNode* pn, unsigned errorNumber, ...) {
+ uint32_t offset = pn ? pn->pn_pos.begin : *scriptStartOffset;
+
+ va_list args;
+ va_start(args, errorNumber);
+
+ errorReporter().errorWithNotesAtVA(nullptr, AsVariant(offset), errorNumber,
+ &args);
+
+ va_end(args);
+}
+
+void BytecodeEmitter::reportError(uint32_t offset, unsigned errorNumber, ...) {
+ va_list args;
+ va_start(args, errorNumber);
+
+ errorReporter().errorWithNotesAtVA(nullptr, AsVariant(offset), errorNumber,
+ &args);
+
+ va_end(args);
+}
+
+bool BytecodeEmitter::addObjLiteralData(ObjLiteralWriter& writer,
+ GCThingIndex* outIndex) {
+ if (!writer.checkForDuplicatedNames(fc)) {
+ return false;
+ }
+
+ size_t len = writer.getCode().size();
+ auto* code = compilationState.alloc.newArrayUninitialized<uint8_t>(len);
+ if (!code) {
+ js::ReportOutOfMemory(fc);
+ return false;
+ }
+ memcpy(code, writer.getCode().data(), len);
+
+ ObjLiteralIndex objIndex(compilationState.objLiteralData.length());
+ if (uint32_t(objIndex) >= TaggedScriptThingIndex::IndexLimit) {
+ ReportAllocationOverflow(fc);
+ return false;
+ }
+ if (!compilationState.objLiteralData.emplaceBack(code, len, writer.getKind(),
+ writer.getFlags(),
+ writer.getPropertyCount())) {
+ js::ReportOutOfMemory(fc);
+ return false;
+ }
+
+ return perScriptData().gcThingList().append(objIndex, outIndex);
+}
+
+bool BytecodeEmitter::emitPrepareIteratorResult() {
+ constexpr JSOp op = JSOp::NewObject;
+
+ ObjLiteralWriter writer;
+ writer.beginShape(op);
+
+ writer.setPropNameNoDuplicateCheck(parserAtoms(),
+ TaggedParserAtomIndex::WellKnown::value());
+ if (!writer.propWithUndefinedValue(fc)) {
+ return false;
+ }
+ writer.setPropNameNoDuplicateCheck(parserAtoms(),
+ TaggedParserAtomIndex::WellKnown::done());
+ if (!writer.propWithUndefinedValue(fc)) {
+ return false;
+ }
+
+ GCThingIndex shape;
+ if (!addObjLiteralData(writer, &shape)) {
+ return false;
+ }
+
+ return emitGCIndexOp(op, shape);
+}
+
+bool BytecodeEmitter::emitFinishIteratorResult(bool done) {
+ if (!emitAtomOp(JSOp::InitProp, TaggedParserAtomIndex::WellKnown::value())) {
+ return false;
+ }
+ if (!emit1(done ? JSOp::True : JSOp::False)) {
+ return false;
+ }
+ if (!emitAtomOp(JSOp::InitProp, TaggedParserAtomIndex::WellKnown::done())) {
+ return false;
+ }
+ return true;
+}
+
+bool BytecodeEmitter::emitGetNameAtLocation(TaggedParserAtomIndex name,
+ const NameLocation& loc) {
+ NameOpEmitter noe(this, name, loc, NameOpEmitter::Kind::Get);
+ if (!noe.emitGet()) {
+ return false;
+ }
+
+ return true;
+}
+
+bool BytecodeEmitter::emitGetName(NameNode* name) {
+ MOZ_ASSERT(name->isKind(ParseNodeKind::Name));
+
+ return emitGetName(name->name());
+}
+
+bool BytecodeEmitter::emitGetPrivateName(NameNode* name) {
+ MOZ_ASSERT(name->isKind(ParseNodeKind::PrivateName));
+ return emitGetPrivateName(name->name());
+}
+
+bool BytecodeEmitter::emitGetPrivateName(TaggedParserAtomIndex nameAtom) {
+ // The parser ensures the private name is present on the environment chain,
+ // but its location can be Dynamic or Global when emitting debugger
+ // eval-in-frame code.
+ NameLocation location = lookupName(nameAtom);
+ MOZ_ASSERT(location.kind() == NameLocation::Kind::FrameSlot ||
+ location.kind() == NameLocation::Kind::EnvironmentCoordinate ||
+ location.kind() == NameLocation::Kind::Dynamic ||
+ location.kind() == NameLocation::Kind::Global);
+
+ return emitGetNameAtLocation(nameAtom, location);
+}
+
+bool BytecodeEmitter::emitTDZCheckIfNeeded(TaggedParserAtomIndex name,
+ const NameLocation& loc,
+ ValueIsOnStack isOnStack) {
+ // Dynamic accesses have TDZ checks built into their VM code and should
+ // never emit explicit TDZ checks.
+ MOZ_ASSERT(loc.hasKnownSlot());
+ MOZ_ASSERT(loc.isLexical() || loc.isPrivateMethod() || loc.isSynthetic());
+
+ // Private names are implemented as lexical bindings, but it's just an
+ // implementation detail. Per spec there's no TDZ check when using them.
+ if (parserAtoms().isPrivateName(name)) {
+ return true;
+ }
+
+ Maybe<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())) {
+ 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, ValueUsage valueUsage) {
+ 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(), valueUsage)) {
+ // [stack] RESULT
+ return false;
+ }
+
+ return true;
+}
+
+bool BytecodeEmitter::emitNameIncDec(UnaryNode* incDec, ValueUsage valueUsage) {
+ MOZ_ASSERT(incDec->kid()->isKind(ParseNodeKind::Name));
+
+ ParseNodeKind kind = incDec->getKind();
+ NameNode* name = &incDec->kid()->as<NameNode>();
+ NameOpEmitter noe(this, name->atom(),
+ kind == ParseNodeKind::PostIncrementExpr
+ ? NameOpEmitter::Kind::PostIncrement
+ : kind == ParseNodeKind::PreIncrementExpr
+ ? NameOpEmitter::Kind::PreIncrement
+ : kind == ParseNodeKind::PostDecrementExpr
+ ? NameOpEmitter::Kind::PostDecrement
+ : NameOpEmitter::Kind::PreDecrement);
+ if (!noe.emitIncDec(valueUsage)) {
+ return false;
+ }
+
+ return true;
+}
+
+bool BytecodeEmitter::emitObjAndKey(ParseNode* exprOrSuper, ParseNode* key,
+ ElemOpEmitter& eoe) {
+ if (exprOrSuper->isKind(ParseNodeKind::SuperBase)) {
+ if (!eoe.prepareForObj()) {
+ // [stack]
+ return false;
+ }
+ UnaryNode* base = &exprOrSuper->as<UnaryNode>();
+ if (!emitGetThisForSuperBase(base)) {
+ // [stack] THIS
+ return false;
+ }
+ if (!eoe.prepareForKey()) {
+ // [stack] THIS
+ return false;
+ }
+ if (!emitTree(key)) {
+ // [stack] THIS KEY
+ return false;
+ }
+
+ return true;
+ }
+
+ if (!eoe.prepareForObj()) {
+ // [stack]
+ return false;
+ }
+ if (!emitTree(exprOrSuper)) {
+ // [stack] OBJ
+ return false;
+ }
+ if (!eoe.prepareForKey()) {
+ // [stack] OBJ? OBJ
+ return false;
+ }
+ if (!emitTree(key)) {
+ // [stack] OBJ? OBJ KEY
+ return false;
+ }
+
+ return true;
+}
+
+bool BytecodeEmitter::emitElemOpBase(JSOp op) {
+ if (!emit1(op)) {
+ return false;
+ }
+
+ return true;
+}
+
+bool BytecodeEmitter::emitElemObjAndKey(PropertyByValue* elem, bool isSuper,
+ ElemOpEmitter& eoe) {
+ MOZ_ASSERT(isSuper == elem->expression().isKind(ParseNodeKind::SuperBase));
+ return emitObjAndKey(&elem->expression(), &elem->key(), eoe);
+}
+
+static ElemOpEmitter::Kind ConvertIncDecKind(ParseNodeKind kind) {
+ switch (kind) {
+ case ParseNodeKind::PostIncrementExpr:
+ return ElemOpEmitter::Kind::PostIncrement;
+ case ParseNodeKind::PreIncrementExpr:
+ return ElemOpEmitter::Kind::PreIncrement;
+ case ParseNodeKind::PostDecrementExpr:
+ return ElemOpEmitter::Kind::PostDecrement;
+ case ParseNodeKind::PreDecrementExpr:
+ return ElemOpEmitter::Kind::PreDecrement;
+ default:
+ MOZ_CRASH("unexpected inc/dec node kind");
+ }
+}
+
+static PrivateOpEmitter::Kind PrivateConvertIncDecKind(ParseNodeKind kind) {
+ switch (kind) {
+ case ParseNodeKind::PostIncrementExpr:
+ return PrivateOpEmitter::Kind::PostIncrement;
+ case ParseNodeKind::PreIncrementExpr:
+ return PrivateOpEmitter::Kind::PreIncrement;
+ case ParseNodeKind::PostDecrementExpr:
+ return PrivateOpEmitter::Kind::PostDecrement;
+ case ParseNodeKind::PreDecrementExpr:
+ return PrivateOpEmitter::Kind::PreDecrement;
+ default:
+ MOZ_CRASH("unexpected inc/dec node kind");
+ }
+}
+
+bool BytecodeEmitter::emitElemIncDec(UnaryNode* incDec, ValueUsage valueUsage) {
+ PropertyByValue* elemExpr = &incDec->kid()->as<PropertyByValue>();
+ bool isSuper = elemExpr->isSuper();
+ MOZ_ASSERT(!elemExpr->key().isKind(ParseNodeKind::PrivateName));
+ ParseNodeKind kind = incDec->getKind();
+ ElemOpEmitter eoe(
+ this, ConvertIncDecKind(kind),
+ isSuper ? ElemOpEmitter::ObjKind::Super : ElemOpEmitter::ObjKind::Other);
+ if (!emitElemObjAndKey(elemExpr, isSuper, eoe)) {
+ // [stack] # if Super
+ // [stack] THIS KEY
+ // [stack] # otherwise
+ // [stack] OBJ KEY
+ return false;
+ }
+ if (!eoe.emitIncDec(valueUsage)) {
+ // [stack] RESULT
+ return false;
+ }
+
+ return true;
+}
+
+bool BytecodeEmitter::emitCallIncDec(UnaryNode* incDec) {
+ MOZ_ASSERT(incDec->isKind(ParseNodeKind::PreIncrementExpr) ||
+ incDec->isKind(ParseNodeKind::PostIncrementExpr) ||
+ incDec->isKind(ParseNodeKind::PreDecrementExpr) ||
+ incDec->isKind(ParseNodeKind::PostDecrementExpr));
+
+ ParseNode* call = incDec->kid();
+ MOZ_ASSERT(call->isKind(ParseNodeKind::CallExpr));
+ if (!emitTree(call)) {
+ // [stack] CALLRESULT
+ return false;
+ }
+ if (!emit1(JSOp::ToNumeric)) {
+ // [stack] N
+ return false;
+ }
+
+ // The increment/decrement has no side effects, so proceed to throw for
+ // invalid assignment target.
+ return emit2(JSOp::ThrowMsg, uint8_t(ThrowMsgKind::AssignToCall));
+}
+
+bool BytecodeEmitter::emitPrivateIncDec(UnaryNode* incDec,
+ ValueUsage valueUsage) {
+ PrivateMemberAccess* privateExpr = &incDec->kid()->as<PrivateMemberAccess>();
+ ParseNodeKind kind = incDec->getKind();
+ PrivateOpEmitter xoe(this, PrivateConvertIncDecKind(kind),
+ privateExpr->privateName().name());
+ if (!emitTree(&privateExpr->expression())) {
+ // [stack] OBJ
+ return false;
+ }
+ if (!xoe.emitReference()) {
+ // [stack] OBJ NAME
+ return false;
+ }
+ if (!xoe.emitIncDec(valueUsage)) {
+ // [stack] RESULT
+ return false;
+ }
+
+ return true;
+}
+
+bool BytecodeEmitter::emitDouble(double d) {
+ BytecodeOffset offset;
+ if (!emitCheck(JSOp::Double, 9, &offset)) {
+ return false;
+ }
+
+ jsbytecode* code = bytecodeSection().code(offset);
+ code[0] = jsbytecode(JSOp::Double);
+ SET_INLINE_VALUE(code, DoubleValue(d));
+ bytecodeSection().updateDepth(JSOp::Double, offset);
+ return true;
+}
+
+bool BytecodeEmitter::emitNumberOp(double dval) {
+ int32_t ival;
+ if (NumberIsInt32(dval, &ival)) {
+ if (ival == 0) {
+ return emit1(JSOp::Zero);
+ }
+ if (ival == 1) {
+ return emit1(JSOp::One);
+ }
+ if ((int)(int8_t)ival == ival) {
+ return emit2(JSOp::Int8, uint8_t(int8_t(ival)));
+ }
+
+ uint32_t u = uint32_t(ival);
+ if (u < Bit(16)) {
+ if (!emitUint16Operand(JSOp::Uint16, u)) {
+ return false;
+ }
+ } else if (u < Bit(24)) {
+ BytecodeOffset off;
+ if (!emitN(JSOp::Uint24, 3, &off)) {
+ return false;
+ }
+ SET_UINT24(bytecodeSection().code(off), u);
+ } else {
+ BytecodeOffset off;
+ if (!emitN(JSOp::Int32, 4, &off)) {
+ return false;
+ }
+ SET_INT32(bytecodeSection().code(off), ival);
+ }
+ return true;
+ }
+
+ return emitDouble(dval);
+}
+
+/*
+ * Using MOZ_NEVER_INLINE in here is a workaround for llvm.org/pr14047.
+ * LLVM is deciding to inline this function which uses a lot of stack space
+ * into emitTree which is recursive and uses relatively little stack space.
+ */
+MOZ_NEVER_INLINE bool BytecodeEmitter::emitSwitch(SwitchStatement* switchStmt) {
+ LexicalScopeNode& lexical = switchStmt->lexicalForCaseList();
+ MOZ_ASSERT(lexical.isKind(ParseNodeKind::LexicalScope));
+ ListNode* cases = &lexical.scopeBody()->as<ListNode>();
+ MOZ_ASSERT(cases->isKind(ParseNodeKind::StatementList));
+
+ SwitchEmitter se(this);
+ if (!se.emitDiscriminant(switchStmt->discriminant().pn_pos.begin)) {
+ return false;
+ }
+
+ if (!markStepBreakpoint()) {
+ return false;
+ }
+ if (!emitTree(&switchStmt->discriminant())) {
+ return false;
+ }
+
+ // Enter the scope before pushing the switch BreakableControl since all
+ // breaks are under this scope.
+
+ if (!lexical.isEmptyScope()) {
+ if (!se.emitLexical(lexical.scopeBindings())) {
+ return false;
+ }
+
+ // A switch statement may contain hoisted functions inside its
+ // cases. The PNX_FUNCDEFS flag is propagated from the STATEMENTLIST
+ // bodies of the cases to the case list.
+ if (cases->hasTopLevelFunctionDeclarations()) {
+ for (ParseNode* item : cases->contents()) {
+ CaseClause* caseClause = &item->as<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) {
+ if (op == JSOp::FinalYieldRval) {
+ return emit1(JSOp::FinalYieldRval);
+ }
+
+ MOZ_ASSERT(op == JSOp::InitialYield || op == JSOp::Yield ||
+ op == JSOp::Await);
+
+ BytecodeOffset off;
+ if (!emitN(op, 3, &off)) {
+ return false;
+ }
+
+ if (op == JSOp::InitialYield || op == JSOp::Yield) {
+ bytecodeSection().addNumYields();
+ }
+
+ uint32_t resumeIndex;
+ if (!allocateResumeIndex(bytecodeSection().offset(), &resumeIndex)) {
+ return false;
+ }
+
+ SET_RESUMEINDEX(bytecodeSection().code(off), resumeIndex);
+
+ BytecodeOffset unusedOffset;
+ return emitJumpTargetOp(JSOp::AfterYield, &unusedOffset);
+}
+
+bool BytecodeEmitter::emitPushResumeKind(GeneratorResumeKind kind) {
+ return emit2(JSOp::ResumeKind, uint8_t(kind));
+}
+
+bool BytecodeEmitter::emitSetThis(BinaryNode* setThisNode) {
+ // ParseNodeKind::SetThis is used to update |this| after a super() call
+ // in a derived class constructor.
+
+ MOZ_ASSERT(setThisNode->isKind(ParseNodeKind::SetThis));
+ MOZ_ASSERT(setThisNode->left()->isKind(ParseNodeKind::Name));
+
+ auto name = setThisNode->left()->as<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(true)) {
+ return false;
+ }
+
+ return true;
+}
+
+bool BytecodeEmitter::defineHoistedTopLevelFunctions(ParseNode* body) {
+ MOZ_ASSERT(inPrologue());
+ MOZ_ASSERT(sc->isGlobalContext() || (sc->isEvalContext() && !sc->strict()));
+ MOZ_ASSERT(body->is<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.
+ // NOTE: The self-hosting top-level script should not populate the builtins
+ // directly on the GlobalObject (and instead uses JSOp::GetIntrinsic for
+ // lookups).
+ if (emitterMode == BytecodeEmitter::EmitterMode::Normal) {
+ if (!emitGCIndexOp(JSOp::GlobalOrEvalDeclInstantiation, lastFun)) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool BytecodeEmitter::emitScript(ParseNode* body) {
+ setScriptStartOffsetIfUnset(body->pn_pos.begin);
+
+ MOZ_ASSERT(inPrologue());
+
+ TDZCheckCache tdzCache(this);
+ EmitterScope emitterScope(this);
+ Maybe<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;
+ }
+
+ switchToMain();
+
+ ParseNode* scopeBody = scope->scopeBody();
+ if (!emitLexicalScopeBody(scopeBody)) {
+ return false;
+ }
+
+ if (!updateSourceCoordNotes(scopeBody->pn_pos.end)) {
+ return false;
+ }
+
+ if (!lexicalEmitterScope.leave(this)) {
+ return false;
+ }
+ } else {
+ if (!emitDeclarationInstantiation(body)) {
+ return false;
+ }
+ if (topLevelAwait) {
+ if (!topLevelAwait->prepareForModule()) {
+ return false;
+ }
+ }
+
+ switchToMain();
+
+ if (topLevelAwait) {
+ if (!topLevelAwait->prepareForBody()) {
+ return false;
+ }
+ }
+
+ if (!emitTree(body)) {
+ // [stack]
+ return false;
+ }
+
+ if (!updateSourceCoordNotes(body->pn_pos.end)) {
+ return false;
+ }
+ }
+
+ if (topLevelAwait) {
+ if (!topLevelAwait->emitEndModule()) {
+ return false;
+ }
+ }
+
+ if (!markSimpleBreakpoint()) {
+ return false;
+ }
+
+ if (!emitReturnRval()) {
+ return false;
+ }
+
+ if (!emitterScope.leave(this)) {
+ return false;
+ }
+
+ if (!NameFunctions(fc, parserAtoms(), body)) {
+ return false;
+ }
+
+ // Create a Stencil and convert it into a JSScript.
+ return intoScriptStencil(CompilationStencil::TopLevelIndex);
+}
+
+js::UniquePtr<ImmutableScriptData>
+BytecodeEmitter::createImmutableScriptData() {
+ uint32_t nslots;
+ if (!getNslots(&nslots)) {
+ return nullptr;
+ }
+
+ bool isFunction = sc->isFunctionBox();
+ uint16_t funLength = isFunction ? sc->asFunctionBox()->length() : 0;
+
+ mozilla::SaturateUint8 propertyCountEstimate = propertyAdditionEstimate;
+
+ // Add fields to the property count estimate.
+ if (isFunction && sc->asFunctionBox()->useMemberInitializers()) {
+ propertyCountEstimate +=
+ sc->asFunctionBox()->memberInitializers().numMemberInitializers;
+ }
+
+ return ImmutableScriptData::new_(
+ fc, mainOffset(), maxFixedSlots, nslots, bodyScopeIndex,
+ bytecodeSection().numICEntries(), isFunction, funLength,
+ propertyCountEstimate.value(), bytecodeSection().code(),
+ bytecodeSection().notes(), bytecodeSection().resumeOffsetList().span(),
+ bytecodeSection().scopeNoteList().span(),
+ bytecodeSection().tryNoteList().span());
+}
+
+bool BytecodeEmitter::getNslots(uint32_t* nslots) {
+ uint64_t nslots64 =
+ maxFixedSlots + static_cast<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());
+ ParamsBodyNode* paramsBody = funNode->body();
+ FunctionBox* funbox = sc->asFunctionBox();
+
+ setScriptStartOffsetIfUnset(paramsBody->pn_pos.begin);
+
+ // [stack]
+
+ FunctionScriptEmitter fse(this, funbox, Some(paramsBody->pn_pos.begin),
+ Some(paramsBody->pn_pos.end));
+ if (!fse.prepareForParameters()) {
+ // [stack]
+ return false;
+ }
+
+ if (!emitFunctionFormalParameters(paramsBody)) {
+ // [stack]
+ return false;
+ }
+
+ if (!fse.prepareForBody()) {
+ // [stack]
+ return false;
+ }
+
+ if (!emitTree(paramsBody->body())) {
+ // [stack]
+ return false;
+ }
+
+ if (!fse.emitEndBody()) {
+ // [stack]
+ return false;
+ }
+
+ if (funbox->index() == CompilationStencil::TopLevelIndex) {
+ if (!NameFunctions(fc, parserAtoms(), funNode)) {
+ return false;
+ }
+ }
+
+ return fse.intoStencil();
+}
+
+bool BytecodeEmitter::emitDestructuringLHSRef(ParseNode* target,
+ size_t* emitted) {
+#ifdef DEBUG
+ int depth = bytecodeSection().stackDepth();
+#endif
+
+ switch (target->getKind()) {
+ case ParseNodeKind::Name:
+ case ParseNodeKind::ArrayExpr:
+ case ParseNodeKind::ObjectExpr:
+ // No need to recurse into ParseNodeKind::Array and ParseNodeKind::Object
+ // subpatterns here, since emitSetOrInitializeDestructuring does the
+ // recursion when setting or initializing the value. Getting reference
+ // doesn't recurse.
+ *emitted = 0;
+ break;
+
+ case ParseNodeKind::DotExpr: {
+ PropertyAccess* prop = &target->as<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;
+ }
+ } else {
+ if (!emitTree(&prop->expression())) {
+ // [stack] OBJ
+ return false;
+ }
+ }
+ if (!poe.prepareForRhs()) {
+ // [stack] # if Super
+ // [stack] THIS SUPERBASE
+ // [stack] # otherwise
+ // [stack] OBJ
+ return false;
+ }
+
+ // SUPERBASE was pushed onto THIS in poe.prepareForRhs above.
+ *emitted = 1 + isSuper;
+ break;
+ }
+
+ case ParseNodeKind::ElemExpr: {
+ PropertyByValue* elem = &target->as<PropertyByValue>();
+ bool isSuper = elem->isSuper();
+ MOZ_ASSERT(!elem->key().isKind(ParseNodeKind::PrivateName));
+ ElemOpEmitter eoe(this, ElemOpEmitter::Kind::SimpleAssignment,
+ isSuper ? ElemOpEmitter::ObjKind::Super
+ : ElemOpEmitter::ObjKind::Other);
+ if (!emitElemObjAndKey(elem, isSuper, eoe)) {
+ // [stack] # if Super
+ // [stack] THIS KEY
+ // [stack] # otherwise
+ // [stack] OBJ KEY
+ return false;
+ }
+ if (!eoe.prepareForRhs()) {
+ // [stack] # if Super
+ // [stack] THIS KEY SUPERBASE
+ // [stack] # otherwise
+ // [stack] OBJ KEY
+ return false;
+ }
+
+ // SUPERBASE was pushed onto KEY in eoe.prepareForRhs above.
+ *emitted = 2 + isSuper;
+ break;
+ }
+
+ case ParseNodeKind::PrivateMemberExpr: {
+ PrivateMemberAccess* privateExpr = &target->as<PrivateMemberAccess>();
+ PrivateOpEmitter xoe(this, PrivateOpEmitter::Kind::SimpleAssignment,
+ privateExpr->privateName().name());
+ if (!emitTree(&privateExpr->expression())) {
+ // [stack] OBJ
+ return false;
+ }
+ if (!xoe.emitReference()) {
+ // [stack] OBJ NAME
+ return false;
+ }
+ *emitted = xoe.numReferenceSlots();
+ break;
+ }
+
+ case ParseNodeKind::CallExpr:
+ MOZ_ASSERT_UNREACHABLE(
+ "Parser::reportIfNotValidSimpleAssignmentTarget "
+ "rejects function calls as assignment "
+ "targets in destructuring assignments");
+ break;
+
+ default:
+ MOZ_CRASH("emitDestructuringLHSRef: bad lhs kind");
+ }
+
+ MOZ_ASSERT(bytecodeSection().stackDepth() == depth + int(*emitted));
+
+ return true;
+}
+
+bool BytecodeEmitter::emitSetOrInitializeDestructuring(
+ ParseNode* target, DestructuringFlavor flav) {
+ // Now emit the lvalue opcode sequence. If the lvalue is a nested
+ // destructuring initialiser-form, call ourselves to handle it, then pop
+ // the matched value. Otherwise emit an lvalue bytecode sequence followed
+ // by an assignment op.
+
+ switch (target->getKind()) {
+ case ParseNodeKind::ArrayExpr:
+ case ParseNodeKind::ObjectExpr:
+ if (!emitDestructuringOps(&target->as<ListNode>(), flav)) {
+ return false;
+ }
+ // emitDestructuringOps leaves the assigned (to-be-destructured) value on
+ // top of the stack.
+ break;
+
+ case ParseNodeKind::Name: {
+ auto 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();
+ MOZ_ASSERT(!elem->key().isKind(ParseNodeKind::PrivateName));
+ ElemOpEmitter eoe(this, ElemOpEmitter::Kind::SimpleAssignment,
+ isSuper ? ElemOpEmitter::ObjKind::Super
+ : ElemOpEmitter::ObjKind::Other);
+ if (!eoe.skipObjAndKeyAndRhs()) {
+ return false;
+ }
+ if (!eoe.emitAssignment()) {
+ // [stack] VAL
+ return false;
+ }
+ break;
+ }
+
+ case ParseNodeKind::PrivateMemberExpr: {
+ // The reference is already pushed by emitDestructuringLHSRef.
+ // [stack] OBJ NAME VAL
+ PrivateMemberAccess* privateExpr = &target->as<PrivateMemberAccess>();
+ PrivateOpEmitter xoe(this, PrivateOpEmitter::Kind::SimpleAssignment,
+ privateExpr->privateName().name());
+ if (!xoe.skipReference()) {
+ return false;
+ }
+ if (!xoe.emitAssignment()) {
+ // [stack] VAL
+ return false;
+ }
+ break;
+ }
+
+ case ParseNodeKind::CallExpr:
+ MOZ_ASSERT_UNREACHABLE(
+ "Parser::reportIfNotValidSimpleAssignmentTarget "
+ "rejects function calls as assignment "
+ "targets in destructuring assignments");
+ break;
+
+ default:
+ MOZ_CRASH("emitSetOrInitializeDestructuring: bad lhs kind");
+ }
+
+ // Pop the assigned value.
+ if (!emit1(JSOp::Pop)) {
+ // [stack] # empty
+ return false;
+ }
+
+ return true;
+}
+
+JSOp BytecodeEmitter::getIterCallOp(JSOp callOp,
+ SelfHostedIter selfHostedIter) {
+ if (emitterMode == BytecodeEmitter::SelfHosting) {
+ MOZ_ASSERT(selfHostedIter == SelfHostedIter::Allow);
+
+ switch (callOp) {
+ case JSOp::Call:
+ return JSOp::CallContent;
+ case JSOp::CallIter:
+ return JSOp::CallContentIter;
+ default:
+ MOZ_CRASH("Unknown iterator call op");
+ }
+ }
+
+ return callOp;
+}
+
+bool BytecodeEmitter::emitIteratorNext(
+ const Maybe<uint32_t>& callSourceCoordOffset,
+ IteratorKind iterKind /* = IteratorKind::Sync */,
+ SelfHostedIter selfHostedIter /* = SelfHostedIter::Deny */) {
+ MOZ_ASSERT(selfHostedIter == SelfHostedIter::Allow ||
+ emitterMode != BytecodeEmitter::SelfHosting,
+ ".next() iteration is prohibited in self-hosted code because it"
+ "can run user-modifiable iteration code");
+
+ // [stack] ... NEXT ITER
+ MOZ_ASSERT(bytecodeSection().stackDepth() >= 2);
+
+ if (!emitCall(getIterCallOp(JSOp::Call, selfHostedIter), 0,
+ callSourceCoordOffset)) {
+ // [stack] ... RESULT
+ return false;
+ }
+
+ if (iterKind == IteratorKind::Async) {
+ if (!emitAwaitInInnermostScope()) {
+ // [stack] ... RESULT
+ return false;
+ }
+ }
+
+ if (!emitCheckIsObj(CheckIsObjectKind::IteratorNext)) {
+ // [stack] ... RESULT
+ return false;
+ }
+ return true;
+}
+
+bool BytecodeEmitter::emitIteratorCloseInScope(
+ EmitterScope& currentScope,
+ IteratorKind iterKind /* = IteratorKind::Sync */,
+ CompletionKind completionKind /* = CompletionKind::Normal */,
+ SelfHostedIter selfHostedIter /* = SelfHostedIter::Deny */) {
+ MOZ_ASSERT(selfHostedIter == SelfHostedIter::Allow ||
+ emitterMode != BytecodeEmitter::SelfHosting,
+ ".close() on iterators is prohibited in self-hosted code because "
+ "it can run user-modifiable iteration code");
+
+ if (iterKind == IteratorKind::Sync) {
+ return emit2(JSOp::CloseIter, uint8_t(completionKind));
+ }
+
+ // Generate inline logic corresponding to IteratorClose (ES2021 7.4.6) and
+ // AsyncIteratorClose (ES2021 7.4.7). Steps numbers apply to both operations.
+ //
+ // Callers need to ensure that the iterator object is at the top of the
+ // stack.
+
+ // For non-Throw completions, we emit the equivalent of:
+ //
+ // var returnMethod = GetMethod(iterator, "return");
+ // if (returnMethod !== undefined) {
+ // var innerResult = [Await] Call(returnMethod, iterator);
+ // CheckIsObj(innerResult);
+ // }
+ //
+ // Whereas for Throw completions, we emit:
+ //
+ // try {
+ // var returnMethod = GetMethod(iterator, "return");
+ // if (returnMethod !== undefined) {
+ // [Await] Call(returnMethod, iterator);
+ // }
+ // } catch {}
+
+ Maybe<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, TaggedParserAtomIndex::WellKnown::return_())) {
+ // [stack] ... ITER RET
+ return false;
+ }
+
+ // Step 5.
+ //
+ // Do nothing if "return" is undefined or null.
+ InternalIfEmitter ifReturnMethodIsDefined(this);
+ if (!emit1(JSOp::IsNullOrUndefined)) {
+ // [stack] ... ITER RET NULL-OR-UNDEF
+ return false;
+ }
+
+ if (!ifReturnMethodIsDefined.emitThenElse(
+ IfEmitter::ConditionKind::Negative)) {
+ // [stack] ... ITER RET
+ return false;
+ }
+
+ // Steps 5.c, 7.
+ //
+ // Call the "return" method.
+ if (!emit1(JSOp::Swap)) {
+ // [stack] ... RET ITER
+ return false;
+ }
+
+ if (!emitCall(getIterCallOp(JSOp::Call, selfHostedIter), 0)) {
+ // [stack] ... RESULT
+ return false;
+ }
+
+ // 7.4.7 AsyncIteratorClose, step 5.d.
+ if (iterKind == IteratorKind::Async) {
+ if (completionKind != CompletionKind::Throw) {
+ // Await clobbers rval, so save the current rval.
+ if (!emit1(JSOp::GetRval)) {
+ // [stack] ... RESULT RVAL
+ return false;
+ }
+ if (!emit1(JSOp::Swap)) {
+ // [stack] ... RVAL RESULT
+ return false;
+ }
+ }
+
+ if (!emitAwaitInScope(currentScope)) {
+ // [stack] ... RVAL? RESULT
+ return false;
+ }
+
+ if (completionKind != CompletionKind::Throw) {
+ if (!emit1(JSOp::Swap)) {
+ // [stack] ... RESULT RVAL
+ return false;
+ }
+ if (!emit1(JSOp::SetRval)) {
+ // [stack] ... RESULT
+ return false;
+ }
+ }
+ }
+
+ // Step 6 (Handled in caller).
+
+ // Step 8.
+ if (completionKind != CompletionKind::Throw) {
+ // Check that the "return" result is an object.
+ if (!emitCheckIsObj(CheckIsObjectKind::IteratorReturn)) {
+ // [stack] ... RESULT
+ return false;
+ }
+ }
+
+ if (!ifReturnMethodIsDefined.emitElse()) {
+ // [stack] ... ITER RET
+ return false;
+ }
+
+ if (!emit1(JSOp::Pop)) {
+ // [stack] ... ITER
+ return false;
+ }
+
+ if (!ifReturnMethodIsDefined.emitEnd()) {
+ return false;
+ }
+
+ if (completionKind == CompletionKind::Throw) {
+ if (!tryCatch->emitCatch()) {
+ // [stack] ... ITER EXC
+ return false;
+ }
+
+ // Just ignore the exception thrown by call and await.
+ if (!emit1(JSOp::Pop)) {
+ // [stack] ... ITER
+ return false;
+ }
+
+ if (!tryCatch->emitEnd()) {
+ // [stack] ... ITER
+ return false;
+ }
+ }
+
+ // Step 9 (Handled in caller).
+
+ return emit1(JSOp::Pop);
+ // [stack] ...
+}
+
+template <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, TaggedParserAtomIndex 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,
+ TaggedParserAtomIndex name) {
+ // The inferred name may already be set if this function is an interpreted
+ // lazy function and we OOM'ed after we set the inferred name the first
+ // time.
+ if (funbox->hasInferredName()) {
+ MOZ_ASSERT(!funbox->emitBytecode);
+ MOZ_ASSERT(funbox->displayAtom() == name);
+
+ return true;
+ }
+
+ funbox->setInferredName(name);
+ return true;
+}
+
+bool BytecodeEmitter::emitInitializer(ParseNode* initializer,
+ ParseNode* pattern) {
+ if (initializer->isDirectRHSAnonFunction()) {
+ MOZ_ASSERT(!pattern->isInParens());
+ auto name = pattern->as<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;
+
+ ParseNode* subpattern;
+ if (member->isKind(ParseNodeKind::Spread)) {
+ subpattern = member->as<UnaryNode>().kid();
+
+ MOZ_ASSERT(!subpattern->isKind(ParseNodeKind::AssignExpr));
+ } else {
+ subpattern = member;
+ }
+
+ ParseNode* lhsPattern = subpattern;
+ ParseNode* pndefault = nullptr;
+ if (subpattern->isKind(ParseNodeKind::AssignExpr)) {
+ lhsPattern = subpattern->as<AssignmentNode>().left();
+ pndefault = subpattern->as<AssignmentNode>().right();
+ }
+
+ // Number of stack slots emitted for the LHS reference.
+ size_t emitted = 0;
+
+ // Spec requires LHS reference to be evaluated first.
+ bool isElision = lhsPattern->isKind(ParseNodeKind::Elision);
+ if (!isElision) {
+ auto emitLHSRef = [lhsPattern, &emitted](BytecodeEmitter* bce) {
+ return bce->emitDestructuringLHSRef(lhsPattern, &emitted);
+ // [stack] ... OBJ NEXT ITER DONE LREF*
+ };
+ if (!wrapWithDestructuringTryNote(tryNoteDepth, emitLHSRef)) {
+ return false;
+ }
+ }
+
+ // Pick the DONE value to the top of the stack.
+ if (emitted) {
+ if (!emitPickN(emitted)) {
+ // [stack] ... OBJ NEXT ITER LREF* DONE
+ return false;
+ }
+ }
+
+ if (isFirst) {
+ // If this element is the first, DONE is always FALSE, so pop it.
+ //
+ // Non-first elements should emit if-else depending on the
+ // member pattern, below.
+ if (!emit1(JSOp::Pop)) {
+ // [stack] ... OBJ NEXT ITER LREF*
+ return false;
+ }
+ }
+
+ if (member->isKind(ParseNodeKind::Spread)) {
+ InternalIfEmitter ifThenElse(this);
+ if (!isFirst) {
+ // If spread is not the first element of the pattern,
+ // iterator can already be completed.
+ // [stack] ... OBJ NEXT ITER LREF* DONE
+
+ if (!ifThenElse.emitThenElse()) {
+ // [stack] ... OBJ NEXT ITER LREF*
+ return false;
+ }
+
+ if (!emitUint32Operand(JSOp::NewArray, 0)) {
+ // [stack] ... OBJ NEXT ITER LREF* ARRAY
+ return false;
+ }
+ if (!ifThenElse.emitElse()) {
+ // [stack] ... OBJ NEXT ITER LREF*
+ return false;
+ }
+ }
+
+ // If iterator is not completed, create a new array with the rest
+ // of the iterator.
+ if (!emitDupAt(emitted + 1, 2)) {
+ // [stack] ... OBJ NEXT ITER LREF* NEXT ITER
+ return false;
+ }
+ if (!emitUint32Operand(JSOp::NewArray, 0)) {
+ // [stack] ... OBJ NEXT ITER LREF* NEXT ITER ARRAY
+ return false;
+ }
+ if (!emitNumberOp(0)) {
+ // [stack] ... OBJ NEXT ITER LREF* NEXT ITER ARRAY INDEX
+ return false;
+ }
+ if (!emitSpread()) {
+ // [stack] ... OBJ NEXT ITER LREF* ARRAY INDEX
+ return false;
+ }
+ if (!emit1(JSOp::Pop)) {
+ // [stack] ... OBJ NEXT ITER LREF* ARRAY
+ return false;
+ }
+
+ if (!isFirst) {
+ if (!ifThenElse.emitEnd()) {
+ return false;
+ }
+ MOZ_ASSERT(ifThenElse.pushed() == 1);
+ }
+
+ // At this point the iterator is done. Unpick a TRUE value for DONE above
+ // ITER.
+ if (!emit1(JSOp::True)) {
+ // [stack] ... OBJ NEXT ITER LREF* ARRAY TRUE
+ return false;
+ }
+ if (!emitUnpickN(emitted + 1)) {
+ // [stack] ... OBJ NEXT ITER TRUE LREF* ARRAY
+ return false;
+ }
+
+ auto emitAssignment = [lhsPattern, flav](BytecodeEmitter* bce) {
+ return bce->emitSetOrInitializeDestructuring(lhsPattern, flav);
+ // [stack] ... OBJ NEXT ITER TRUE
+ };
+ if (!wrapWithDestructuringTryNote(tryNoteDepth, emitAssignment)) {
+ return false;
+ }
+
+ MOZ_ASSERT(!hasNext);
+ break;
+ }
+
+ InternalIfEmitter ifAlreadyDone(this);
+ if (!isFirst) {
+ // [stack] ... OBJ NEXT ITER LREF* DONE
+
+ if (!ifAlreadyDone.emitThenElse()) {
+ // [stack] ... OBJ NEXT ITER LREF*
+ return false;
+ }
+
+ if (!emit1(JSOp::Undefined)) {
+ // [stack] ... OBJ NEXT ITER LREF* UNDEF
+ return false;
+ }
+ if (!emit1(JSOp::NopDestructuring)) {
+ // [stack] ... OBJ NEXT ITER LREF* UNDEF
+ return false;
+ }
+
+ // The iterator is done. Unpick a TRUE value for DONE above ITER.
+ if (!emit1(JSOp::True)) {
+ // [stack] ... OBJ NEXT ITER LREF* UNDEF TRUE
+ return false;
+ }
+ if (!emitUnpickN(emitted + 1)) {
+ // [stack] ... OBJ NEXT ITER TRUE LREF* UNDEF
+ return false;
+ }
+
+ if (!ifAlreadyDone.emitElse()) {
+ // [stack] ... OBJ NEXT ITER LREF*
+ return false;
+ }
+ }
+
+ if (!emitDupAt(emitted + 1, 2)) {
+ // [stack] ... OBJ NEXT ITER LREF* NEXT
+ return false;
+ }
+ if (!emitIteratorNext(Some(pattern->pn_pos.begin))) {
+ // [stack] ... OBJ NEXT ITER LREF* RESULT
+ return false;
+ }
+ if (!emit1(JSOp::Dup)) {
+ // [stack] ... OBJ NEXT ITER LREF* RESULT RESULT
+ return false;
+ }
+ if (!emitAtomOp(JSOp::GetProp, TaggedParserAtomIndex::WellKnown::done())) {
+ // [stack] ... OBJ NEXT ITER LREF* RESULT DONE
+ return false;
+ }
+
+ if (!emit1(JSOp::Dup)) {
+ // [stack] ... OBJ NEXT ITER LREF* RESULT DONE DONE
+ return false;
+ }
+ if (!emitUnpickN(emitted + 2)) {
+ // [stack] ... OBJ NEXT ITER DONE LREF* RESULT DONE
+ return false;
+ }
+
+ InternalIfEmitter ifDone(this);
+ if (!ifDone.emitThenElse()) {
+ // [stack] ... OBJ NEXT ITER DONE LREF* RESULT
+ return false;
+ }
+
+ if (!emit1(JSOp::Pop)) {
+ // [stack] ... OBJ NEXT ITER DONE LREF*
+ return false;
+ }
+ if (!emit1(JSOp::Undefined)) {
+ // [stack] ... OBJ NEXT ITER DONE LREF* UNDEF
+ return false;
+ }
+ if (!emit1(JSOp::NopDestructuring)) {
+ // [stack] ... OBJ NEXT ITER DONE LREF* UNDEF
+ return false;
+ }
+
+ if (!ifDone.emitElse()) {
+ // [stack] ... OBJ NEXT ITER DONE LREF* RESULT
+ return false;
+ }
+
+ if (!emitAtomOp(JSOp::GetProp, TaggedParserAtomIndex::WellKnown::value())) {
+ // [stack] ... OBJ NEXT ITER DONE LREF* VALUE
+ return false;
+ }
+
+ if (!ifDone.emitEnd()) {
+ return false;
+ }
+ MOZ_ASSERT(ifDone.pushed() == 0);
+
+ if (!isFirst) {
+ if (!ifAlreadyDone.emitEnd()) {
+ return false;
+ }
+ MOZ_ASSERT(ifAlreadyDone.pushed() == 2);
+ }
+
+ if (pndefault) {
+ auto emitDefault = [pndefault, lhsPattern](BytecodeEmitter* bce) {
+ return bce->emitDefault(pndefault, lhsPattern);
+ // [stack] ... OBJ NEXT ITER DONE LREF* VALUE
+ };
+
+ if (!wrapWithDestructuringTryNote(tryNoteDepth, emitDefault)) {
+ return false;
+ }
+ }
+
+ if (!isElision) {
+ auto emitAssignment = [lhsPattern, flav](BytecodeEmitter* bce) {
+ return bce->emitSetOrInitializeDestructuring(lhsPattern, flav);
+ // [stack] ... OBJ NEXT ITER DONE
+ };
+
+ if (!wrapWithDestructuringTryNote(tryNoteDepth, emitAssignment)) {
+ return false;
+ }
+ } else {
+ if (!emit1(JSOp::Pop)) {
+ // [stack] ... OBJ NEXT ITER DONE
+ return false;
+ }
+ }
+ }
+
+ // The last DONE value is on top of the stack. If not DONE, call
+ // IteratorClose.
+ // [stack] ... OBJ NEXT ITER DONE
+
+ InternalIfEmitter ifDone(this);
+ if (!ifDone.emitThenElse()) {
+ // [stack] ... OBJ NEXT ITER
+ return false;
+ }
+ if (!emitPopN(2)) {
+ // [stack] ... OBJ
+ return false;
+ }
+ if (!ifDone.emitElse()) {
+ // [stack] ... OBJ NEXT ITER
+ return false;
+ }
+ if (!emit1(JSOp::Swap)) {
+ // [stack] ... OBJ ITER NEXT
+ return false;
+ }
+ if (!emit1(JSOp::Pop)) {
+ // [stack] ... OBJ ITER
+ return false;
+ }
+ if (!emitIteratorCloseInInnermostScope()) {
+ // [stack] ... OBJ
+ return false;
+ }
+ if (!ifDone.emitEnd()) {
+ return false;
+ }
+
+ return true;
+}
+
+bool BytecodeEmitter::emitComputedPropertyName(UnaryNode* computedPropName) {
+ MOZ_ASSERT(computedPropName->isKind(ParseNodeKind::ComputedName));
+ return emitTree(computedPropName->kid()) && emit1(JSOp::ToPropertyKey);
+}
+
+bool BytecodeEmitter::emitDestructuringOpsObject(ListNode* pattern,
+ DestructuringFlavor flav) {
+ MOZ_ASSERT(pattern->isKind(ParseNodeKind::ObjectExpr));
+
+ // [stack] ... RHS
+ MOZ_ASSERT(bytecodeSection().stackDepth() > 0);
+
+ if (!emit1(JSOp::CheckObjCoercible)) {
+ // [stack] ... RHS
+ return false;
+ }
+
+ bool needsRestPropertyExcludedSet =
+ pattern->count() > 1 && pattern->last()->isKind(ParseNodeKind::Spread);
+ if (needsRestPropertyExcludedSet) {
+ if (!emitDestructuringObjRestExclusionSet(pattern)) {
+ // [stack] ... RHS SET
+ return false;
+ }
+
+ if (!emit1(JSOp::Swap)) {
+ // [stack] ... SET RHS
+ return false;
+ }
+ }
+
+ for (ParseNode* member : pattern->contents()) {
+ ParseNode* subpattern;
+ if (member->isKind(ParseNodeKind::MutateProto) ||
+ member->isKind(ParseNodeKind::Spread)) {
+ subpattern = member->as<UnaryNode>().kid();
+
+ MOZ_ASSERT_IF(member->isKind(ParseNodeKind::Spread),
+ !subpattern->isKind(ParseNodeKind::AssignExpr));
+ } else {
+ MOZ_ASSERT(member->isKind(ParseNodeKind::PropertyDefinition) ||
+ member->isKind(ParseNodeKind::Shorthand));
+ subpattern = member->as<BinaryNode>().right();
+ }
+
+ ParseNode* lhs = subpattern;
+ ParseNode* pndefault = nullptr;
+ if (subpattern->isKind(ParseNodeKind::AssignExpr)) {
+ lhs = subpattern->as<AssignmentNode>().left();
+ pndefault = subpattern->as<AssignmentNode>().right();
+ }
+
+ // Number of stack slots emitted for the LHS reference.
+ size_t emitted = 0;
+
+ // Spec requires LHS reference to be evaluated first.
+ if (!emitDestructuringLHSRef(lhs, &emitted)) {
+ // [stack] ... SET? RHS LREF*
+ return false;
+ }
+
+ // Duplicate the value being destructured to use as a reference base.
+ if (!emitDupAt(emitted)) {
+ // [stack] ... SET? RHS LREF* RHS
+ return false;
+ }
+
+ if (member->isKind(ParseNodeKind::Spread)) {
+ if (!updateSourceCoordNotes(member->pn_pos.begin)) {
+ return false;
+ }
+
+ if (!emit1(JSOp::NewInit)) {
+ // [stack] ... SET? RHS LREF* RHS TARGET
+ return false;
+ }
+ if (!emit1(JSOp::Dup)) {
+ // [stack] ... SET? RHS LREF* RHS TARGET TARGET
+ return false;
+ }
+ if (!emit2(JSOp::Pick, 2)) {
+ // [stack] ... SET? RHS LREF* TARGET TARGET RHS
+ return false;
+ }
+
+ if (needsRestPropertyExcludedSet) {
+ if (!emit2(JSOp::Pick, emitted + 4)) {
+ // [stack] ... RHS LREF* TARGET TARGET RHS SET
+ return false;
+ }
+ }
+
+ CopyOption option = needsRestPropertyExcludedSet ? CopyOption::Filtered
+ : CopyOption::Unfiltered;
+ if (!emitCopyDataProperties(option)) {
+ // [stack] ... RHS LREF* TARGET
+ return false;
+ }
+
+ // Destructure TARGET per this member's lhs.
+ if (!emitSetOrInitializeDestructuring(lhs, flav)) {
+ // [stack] ... RHS
+ return false;
+ }
+
+ MOZ_ASSERT(member == pattern->last(), "Rest property is always last");
+ break;
+ }
+
+ // Now push the property value currently being matched, which is the value
+ // of the current property name "label" on the left of a colon in the object
+ // initialiser.
+ if (member->isKind(ParseNodeKind::MutateProto)) {
+ if (!emitAtomOp(JSOp::GetProp,
+ TaggedParserAtomIndex::WellKnown::proto())) {
+ // [stack] ... SET? RHS LREF* PROP
+ return false;
+ }
+ } else {
+ MOZ_ASSERT(member->isKind(ParseNodeKind::PropertyDefinition) ||
+ member->isKind(ParseNodeKind::Shorthand));
+
+ ParseNode* key = member->as<BinaryNode>().left();
+ if (key->isKind(ParseNodeKind::ObjectPropertyName) ||
+ key->isKind(ParseNodeKind::StringExpr)) {
+ if (!emitAtomOp(JSOp::GetProp, key->as<NameNode>().atom())) {
+ // [stack] ... SET? RHS LREF* PROP
+ return false;
+ }
+ } else {
+ if (key->isKind(ParseNodeKind::NumberExpr)) {
+ if (!emitNumberOp(key->as<NumericLiteral>().value())) {
+ // [stack]... SET? RHS LREF* RHS KEY
+ return false;
+ }
+ } else {
+ // Otherwise this is a computed property name. BigInt keys are parsed
+ // as (synthetic) computed property names, too.
+ MOZ_ASSERT(key->isKind(ParseNodeKind::ComputedName));
+
+ if (!emitComputedPropertyName(&key->as<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 (!emitElemOpBase(JSOp::GetElem)) {
+ // [stack] ... SET? RHS LREF* PROP
+ return false;
+ }
+ }
+ }
+
+ if (pndefault) {
+ if (!emitDefault(pndefault, lhs)) {
+ // [stack] ... SET? RHS LREF* VALUE
+ return false;
+ }
+ }
+
+ // Destructure PROP per this member's lhs.
+ if (!emitSetOrInitializeDestructuring(lhs, flav)) {
+ // [stack] ... SET? RHS
+ return false;
+ }
+ }
+
+ return true;
+}
+
+static bool IsDestructuringRestExclusionSetObjLiteralCompatible(
+ ListNode* pattern) {
+ uint32_t propCount = 0;
+ for (ParseNode* member : pattern->contents()) {
+ if (member->isKind(ParseNodeKind::Spread)) {
+ MOZ_ASSERT(!member->pn_next, "unexpected trailing element after spread");
+ break;
+ }
+
+ propCount++;
+
+ if (member->isKind(ParseNodeKind::MutateProto)) {
+ continue;
+ }
+
+ ParseNode* key = member->as<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 > SharedPropMap::MaxPropsForNonDictionary) {
+ // JSOp::NewObject cannot accept dictionary-mode objects.
+ return false;
+ }
+
+ return true;
+}
+
+bool BytecodeEmitter::emitDestructuringObjRestExclusionSet(ListNode* pattern) {
+ MOZ_ASSERT(pattern->isKind(ParseNodeKind::ObjectExpr));
+ MOZ_ASSERT(pattern->last()->isKind(ParseNodeKind::Spread));
+
+ // See if we can use ObjLiteral to construct the exclusion set object.
+ if (IsDestructuringRestExclusionSetObjLiteralCompatible(pattern)) {
+ if (!emitDestructuringRestExclusionSetObjLiteral(pattern)) {
+ // [stack] OBJ
+ return false;
+ }
+ } else {
+ // Take the slow but sure way and start off with a blank object.
+ if (!emit1(JSOp::NewInit)) {
+ // [stack] OBJ
+ return false;
+ }
+ }
+
+ for (ParseNode* member : pattern->contents()) {
+ if (member->isKind(ParseNodeKind::Spread)) {
+ MOZ_ASSERT(!member->pn_next, "unexpected trailing element after spread");
+ break;
+ }
+
+ TaggedParserAtomIndex pnatom;
+ if (member->isKind(ParseNodeKind::MutateProto)) {
+ pnatom = TaggedParserAtomIndex::WellKnown::proto();
+ } else {
+ ParseNode* key = member->as<BinaryNode>().left();
+ if (key->isKind(ParseNodeKind::ObjectPropertyName) ||
+ key->isKind(ParseNodeKind::StringExpr)) {
+ pnatom = key->as<NameNode>().atom();
+ } else if (key->isKind(ParseNodeKind::NumberExpr)) {
+ if (!emitNumberOp(key->as<NumericLiteral>().value())) {
+ return false;
+ }
+ } else {
+ // Otherwise this is a computed property name which needs to be added
+ // dynamically. BigInt keys are parsed as (synthetic) computed property
+ // names, too.
+ MOZ_ASSERT(key->isKind(ParseNodeKind::ComputedName));
+ continue;
+ }
+ }
+
+ // Initialize elements with |undefined|.
+ if (!emit1(JSOp::Undefined)) {
+ return false;
+ }
+
+ if (!pnatom) {
+ if (!emit1(JSOp::InitElem)) {
+ return false;
+ }
+ } else {
+ if (!emitAtomOp(JSOp::InitProp, pnatom)) {
+ return false;
+ }
+ }
+ }
+
+ return true;
+}
+
+bool BytecodeEmitter::emitDestructuringOps(ListNode* pattern,
+ DestructuringFlavor flav) {
+ if (pattern->isKind(ParseNodeKind::ArrayExpr)) {
+ return emitDestructuringOpsArray(pattern, flav);
+ }
+ return emitDestructuringOpsObject(pattern, flav);
+}
+
+bool BytecodeEmitter::emitTemplateString(ListNode* templateString) {
+ bool pushedString = false;
+
+ for (ParseNode* item : templateString->contents()) {
+ bool isString = (item->getKind() == ParseNodeKind::StringExpr ||
+ item->getKind() == ParseNodeKind::TemplateStringExpr);
+
+ // Skip empty strings. These are very common: a template string like
+ // `${a}${b}` has three empty strings and without this optimization
+ // we'd emit four JSOp::Add operations instead of just one.
+ if (isString && item->as<NameNode>().atom() ==
+ TaggedParserAtomIndex::WellKnown::empty()) {
+ continue;
+ }
+
+ if (!isString) {
+ // We update source notes before emitting the expression
+ if (!updateSourceCoordNotes(item->pn_pos.begin)) {
+ return false;
+ }
+ }
+
+ if (!emitTree(item)) {
+ return false;
+ }
+
+ if (!isString) {
+ // We need to convert the expression to a string
+ if (!emit1(JSOp::ToString)) {
+ return false;
+ }
+ }
+
+ if (pushedString) {
+ // We've pushed two strings onto the stack. Add them together, leaving
+ // just one.
+ if (!emit1(JSOp::Add)) {
+ return false;
+ }
+ } else {
+ pushedString = true;
+ }
+ }
+
+ if (!pushedString) {
+ // All strings were empty, this can happen for something like `${""}`.
+ // Just push an empty string.
+ if (!emitStringOp(JSOp::String,
+ TaggedParserAtomIndex::WellKnown::empty())) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool BytecodeEmitter::emitDeclarationList(ListNode* declList) {
+ for (ParseNode* decl : declList->contents()) {
+ ParseNode* pattern;
+ ParseNode* initializer;
+ if (decl->isKind(ParseNodeKind::Name)) {
+ pattern = decl;
+ initializer = nullptr;
+ } else {
+ AssignmentNode* assignNode = &decl->as<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;
+ }
+
+ auto nameAtom = decl->name();
+ NameOpEmitter noe(this, nameAtom, NameOpEmitter::Kind::Initialize);
+ if (!noe.prepareForRhs()) {
+ // [stack] ENV?
+ return false;
+ }
+ if (!initializer) {
+ // Lexical declarations are initialized to undefined without an
+ // initializer.
+ MOZ_ASSERT(declList->isKind(ParseNodeKind::LetDecl),
+ "var declarations without initializers handled above, "
+ "and const declarations must have initializers");
+ if (!emit1(JSOp::Undefined)) {
+ // [stack] ENV? UNDEF
+ return false;
+ }
+ } else {
+ MOZ_ASSERT(initializer);
+
+ if (!updateSourceCoordNotes(initializer->pn_pos.begin)) {
+ return false;
+ }
+ if (!markStepBreakpoint()) {
+ return false;
+ }
+ if (!emitInitializer(initializer, decl)) {
+ // [stack] ENV? V
+ return false;
+ }
+ }
+ if (!noe.emitAssignment()) {
+ // [stack] V
+ return false;
+ }
+ if (!emit1(JSOp::Pop)) {
+ // [stack]
+ return false;
+ }
+
+ return true;
+}
+
+bool BytecodeEmitter::emitAssignmentRhs(
+ ParseNode* rhs, TaggedParserAtomIndex anonFunctionName) {
+ if (rhs->isDirectRHSAnonFunction()) {
+ if (anonFunctionName) {
+ return emitAnonymousFunctionWithName(rhs, anonFunctionName);
+ }
+ return emitAnonymousFunctionWithComputedName(rhs, FunctionPrefixKind::None);
+ }
+ return emitTree(rhs);
+}
+
+// The RHS value to assign is already on the stack, i.e., the next enumeration
+// value in a for-in or for-of loop. Offset is the location in the stack of the
+// already-emitted rhs. If we emitted a BIND[G]NAME, then the scope is on the
+// top of the stack and we need to dig one deeper to get the right RHS value.
+bool BytecodeEmitter::emitAssignmentRhs(uint8_t offset) {
+ if (offset != 1) {
+ return emitPickN(offset - 1);
+ }
+
+ return true;
+}
+
+static inline JSOp CompoundAssignmentParseNodeKindToJSOp(ParseNodeKind pnk) {
+ switch (pnk) {
+ case ParseNodeKind::InitExpr:
+ return JSOp::Nop;
+ case ParseNodeKind::AssignExpr:
+ return JSOp::Nop;
+ case ParseNodeKind::AddAssignExpr:
+ return JSOp::Add;
+ case ParseNodeKind::SubAssignExpr:
+ return JSOp::Sub;
+ case ParseNodeKind::BitOrAssignExpr:
+ return JSOp::BitOr;
+ case ParseNodeKind::BitXorAssignExpr:
+ return JSOp::BitXor;
+ case ParseNodeKind::BitAndAssignExpr:
+ return JSOp::BitAnd;
+ case ParseNodeKind::LshAssignExpr:
+ return JSOp::Lsh;
+ case ParseNodeKind::RshAssignExpr:
+ return JSOp::Rsh;
+ case ParseNodeKind::UrshAssignExpr:
+ return JSOp::Ursh;
+ case ParseNodeKind::MulAssignExpr:
+ return JSOp::Mul;
+ case ParseNodeKind::DivAssignExpr:
+ return JSOp::Div;
+ case ParseNodeKind::ModAssignExpr:
+ return JSOp::Mod;
+ case ParseNodeKind::PowAssignExpr:
+ return JSOp::Pow;
+ case ParseNodeKind::CoalesceAssignExpr:
+ case ParseNodeKind::OrAssignExpr:
+ case ParseNodeKind::AndAssignExpr:
+ // Short-circuit assignment operators are handled elsewhere.
+ [[fallthrough]];
+ default:
+ MOZ_CRASH("unexpected compound assignment op");
+ }
+}
+
+bool BytecodeEmitter::emitAssignmentOrInit(ParseNodeKind kind, ParseNode* lhs,
+ ParseNode* rhs) {
+ JSOp compoundOp = CompoundAssignmentParseNodeKindToJSOp(kind);
+ bool isCompound = compoundOp != JSOp::Nop;
+ bool isInit = kind == ParseNodeKind::InitExpr;
+
+ // We estimate the number of properties this could create
+ // if used as constructor merely by counting this.foo = assignment
+ // or init expressions;
+ //
+ // This currently doesn't handle this[x] = foo;
+ if (isInit || kind == ParseNodeKind::AssignExpr) {
+ if (lhs->isKind(ParseNodeKind::DotExpr)) {
+ if (lhs->as<PropertyAccess>().expression().isKind(
+ ParseNodeKind::ThisExpr)) {
+ propertyAdditionEstimate++;
+ }
+ }
+ }
+
+ MOZ_ASSERT_IF(isInit, lhs->isKind(ParseNodeKind::DotExpr) ||
+ lhs->isKind(ParseNodeKind::ElemExpr) ||
+ lhs->isKind(ParseNodeKind::PrivateMemberExpr));
+
+ // |name| is used within NameOpEmitter, so its lifetime must surpass |noe|.
+ TaggedParserAtomIndex name;
+
+ Maybe<NameOpEmitter> noe;
+ Maybe<PropOpEmitter> poe;
+ Maybe<ElemOpEmitter> eoe;
+ Maybe<PrivateOpEmitter> xoe;
+
+ // Deal with non-name assignments.
+ uint8_t offset = 1;
+
+ // Purpose of anonFunctionName:
+ //
+ // In normal name assignments (`f = function(){}`), an anonymous function gets
+ // an inferred name based on the left-hand side name node.
+ //
+ // In normal property assignments (`obj.x = function(){}`), the anonymous
+ // function does not have a computed name, and rhs->isDirectRHSAnonFunction()
+ // will be false (and anonFunctionName will not be used). However, in field
+ // initializers (`class C { x = function(){} }`), field initialization is
+ // implemented via a property or elem assignment (where we are now), and
+ // rhs->isDirectRHSAnonFunction() is set - so we'll assign the name of the
+ // function.
+ TaggedParserAtomIndex anonFunctionName;
+
+ switch (lhs->getKind()) {
+ case ParseNodeKind::Name: {
+ name = lhs->as<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();
+ MOZ_ASSERT(!elem->key().isKind(ParseNodeKind::PrivateName));
+ eoe.emplace(this,
+ isCompound ? ElemOpEmitter::Kind::CompoundAssignment
+ : isInit ? ElemOpEmitter::Kind::PropInit
+ : ElemOpEmitter::Kind::SimpleAssignment,
+ isSuper ? ElemOpEmitter::ObjKind::Super
+ : ElemOpEmitter::ObjKind::Other);
+ if (!emitElemObjAndKey(elem, isSuper, *eoe)) {
+ // [stack] # if Super
+ // [stack] THIS KEY
+ // [stack] # otherwise
+ // [stack] OBJ KEY
+ return false;
+ }
+ if (isSuper) {
+ // SUPERBASE is pushed onto KEY in eoe->emitGet below.
+ offset += 3;
+ } else {
+ offset += 2;
+ }
+ break;
+ }
+ case ParseNodeKind::PrivateMemberExpr: {
+ PrivateMemberAccess* privateExpr = &lhs->as<PrivateMemberAccess>();
+ xoe.emplace(this,
+ isCompound ? PrivateOpEmitter::Kind::CompoundAssignment
+ : isInit ? PrivateOpEmitter::Kind::PropInit
+ : PrivateOpEmitter::Kind::SimpleAssignment,
+ privateExpr->privateName().name());
+ if (!emitTree(&privateExpr->expression())) {
+ // [stack] OBJ
+ return false;
+ }
+ if (!xoe->emitReference()) {
+ // [stack] OBJ KEY
+ return false;
+ }
+ offset += xoe->numReferenceSlots();
+ break;
+ }
+ case ParseNodeKind::ArrayExpr:
+ case ParseNodeKind::ObjectExpr:
+ break;
+ case ParseNodeKind::CallExpr:
+ if (!emitTree(lhs)) {
+ return false;
+ }
+
+ // Assignment to function calls is forbidden, but we have to make the
+ // call first. Now we can throw.
+ if (!emit2(JSOp::ThrowMsg, uint8_t(ThrowMsgKind::AssignToCall))) {
+ return false;
+ }
+
+ // Rebalance the stack to placate stack-depth assertions.
+ if (!emit1(JSOp::Pop)) {
+ return false;
+ }
+ break;
+ default:
+ MOZ_ASSERT(0);
+ }
+
+ if (isCompound) {
+ MOZ_ASSERT(rhs);
+ switch (lhs->getKind()) {
+ case ParseNodeKind::DotExpr: {
+ PropertyAccess* prop = &lhs->as<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::PrivateMemberExpr: {
+ if (!xoe->emitGet()) {
+ // [stack] OBJ KEY VALUE
+ return false;
+ }
+ break;
+ }
+ case ParseNodeKind::CallExpr:
+ // We just emitted a JSOp::ThrowMsg and popped the call's return
+ // value. Push a random value to make sure the stack depth is
+ // correct.
+ if (!emit1(JSOp::Null)) {
+ // [stack] NULL
+ return false;
+ }
+ break;
+ default:;
+ }
+ }
+
+ switch (lhs->getKind()) {
+ case ParseNodeKind::Name:
+ if (!noe->prepareForRhs()) {
+ // [stack] ENV? VAL?
+ return false;
+ }
+ offset += noe->emittedBindOp();
+ break;
+ case ParseNodeKind::DotExpr:
+ if (!poe->prepareForRhs()) {
+ // [stack] # if Simple Assignment with Super
+ // [stack] THIS SUPERBASE
+ // [stack] # if Simple Assignment with other
+ // [stack] OBJ
+ // [stack] # if Compound Assignment with Super
+ // [stack] THIS SUPERBASE PROP
+ // [stack] # if Compound Assignment with other
+ // [stack] OBJ PROP
+ return false;
+ }
+ break;
+ case ParseNodeKind::ElemExpr:
+ if (!eoe->prepareForRhs()) {
+ // [stack] # if Simple Assignment with Super
+ // [stack] THIS KEY SUPERBASE
+ // [stack] # if Simple Assignment with other
+ // [stack] OBJ KEY
+ // [stack] # if Compound Assignment with Super
+ // [stack] THIS KEY SUPERBASE ELEM
+ // [stack] # if Compound Assignment with other
+ // [stack] OBJ KEY ELEM
+ return false;
+ }
+ break;
+ case ParseNodeKind::PrivateMemberExpr:
+ // no stack adjustment needed
+ break;
+ default:
+ break;
+ }
+
+ if (rhs) {
+ if (!emitAssignmentRhs(rhs, anonFunctionName)) {
+ // [stack] ... VAL? RHS
+ return false;
+ }
+ } else {
+ // Assumption: Things with pre-emitted RHS values never need to be named.
+ if (!emitAssignmentRhs(offset)) {
+ // [stack] ... VAL? RHS
+ return false;
+ }
+ }
+
+ /* If += etc., emit the binary operator with a source note. */
+ if (isCompound) {
+ if (!newSrcNote(SrcNoteType::AssignOp)) {
+ return false;
+ }
+ if (!emit1(compoundOp)) {
+ // [stack] ... VAL
+ return false;
+ }
+ }
+
+ /* Finally, emit the specialized assignment bytecode. */
+ switch (lhs->getKind()) {
+ case ParseNodeKind::Name: {
+ if (!noe->emitAssignment()) {
+ // [stack] VAL
+ return false;
+ }
+ break;
+ }
+ case ParseNodeKind::DotExpr: {
+ PropertyAccess* prop = &lhs->as<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::PrivateMemberExpr:
+ if (!xoe->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|.
+ TaggedParserAtomIndex name;
+
+ // Select the appropriate emitter based on the left-hand side.
+ Maybe<NameOpEmitter> noe;
+ Maybe<PropOpEmitter> poe;
+ Maybe<ElemOpEmitter> eoe;
+ Maybe<PrivateOpEmitter> xoe;
+
+ int32_t depth = bytecodeSection().stackDepth();
+
+ // Number of values pushed onto the stack in addition to the lhs value.
+ int32_t numPushed;
+
+ // Evaluate the left-hand side expression and compute any stack values needed
+ // for the assignment.
+ switch (lhs->getKind()) {
+ case ParseNodeKind::Name: {
+ name = lhs->as<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();
+ MOZ_ASSERT(!elem->key().isKind(ParseNodeKind::PrivateName));
+ eoe.emplace(this, ElemOpEmitter::Kind::CompoundAssignment,
+ isSuper ? ElemOpEmitter::ObjKind::Super
+ : ElemOpEmitter::ObjKind::Other);
+
+ if (!emitElemObjAndKey(elem, isSuper, *eoe)) {
+ // [stack] # if Super
+ // [stack] THIS KEY
+ // [stack] # otherwise
+ // [stack] OBJ KEY
+ return false;
+ }
+
+ if (!eoe->emitGet()) {
+ // [stack] # if Super
+ // [stack] THIS KEY SUPERBASE LHS
+ // [stack] # otherwise
+ // [stack] OBJ KEY LHS
+ return false;
+ }
+
+ if (!eoe->prepareForRhs()) {
+ // [stack] # if Super
+ // [stack] THIS KEY SUPERBASE LHS
+ // [stack] # otherwise
+ // [stack] OBJ KEY LHS
+ return false;
+ }
+
+ numPushed = 2 + isSuper;
+ break;
+ }
+
+ case ParseNodeKind::PrivateMemberExpr: {
+ PrivateMemberAccess* privateExpr = &lhs->as<PrivateMemberAccess>();
+ xoe.emplace(this, PrivateOpEmitter::Kind::CompoundAssignment,
+ privateExpr->privateName().name());
+ if (!emitTree(&privateExpr->expression())) {
+ // [stack] OBJ
+ return false;
+ }
+ if (!xoe->emitReference()) {
+ // [stack] OBJ NAME
+ return false;
+ }
+ if (!xoe->emitGet()) {
+ // [stack] OBJ NAME LHS
+ return false;
+ }
+ numPushed = xoe->numReferenceSlots();
+ break;
+ }
+
+ default:
+ MOZ_CRASH();
+ }
+
+ MOZ_ASSERT(bytecodeSection().stackDepth() == depth + numPushed + 1);
+
+ // Test for the short-circuit condition.
+ JumpList jump;
+ if (!emitJump(op, &jump)) {
+ // [stack] ... LHS
+ return false;
+ }
+
+ // The short-circuit condition wasn't fulfilled, pop the left-hand side value
+ // which was kept on the stack.
+ if (!emit1(JSOp::Pop)) {
+ // [stack] ...
+ return false;
+ }
+
+ if (!emitAssignmentRhs(rhs, name)) {
+ // [stack] ... RHS
+ return false;
+ }
+
+ // Perform the actual assignment.
+ switch (lhs->getKind()) {
+ case ParseNodeKind::Name: {
+ if (!noe->emitAssignment()) {
+ // [stack] RHS
+ return false;
+ }
+ break;
+ }
+
+ case ParseNodeKind::DotExpr: {
+ PropertyAccess* prop = &lhs->as<PropertyAccess>();
+
+ if (!poe->emitAssignment(prop->key().atom())) {
+ // [stack] RHS
+ return false;
+ }
+ break;
+ }
+
+ case ParseNodeKind::ElemExpr: {
+ if (!eoe->emitAssignment()) {
+ // [stack] RHS
+ return false;
+ }
+ break;
+ }
+
+ case ParseNodeKind::PrivateMemberExpr:
+ if (!xoe->emitAssignment()) {
+ // [stack] RHS
+ return false;
+ }
+ break;
+
+ default:
+ MOZ_CRASH();
+ }
+
+ MOZ_ASSERT(bytecodeSection().stackDepth() == depth + 1);
+
+ // Join with the short-circuit jump and pop anything left on the stack.
+ if (numPushed > 0) {
+ JumpList jumpAroundPop;
+ if (!emitJump(JSOp::Goto, &jumpAroundPop)) {
+ // [stack] RHS
+ return false;
+ }
+
+ if (!emitJumpTargetAndPatch(jump)) {
+ // [stack] ... LHS
+ return false;
+ }
+
+ // Reconstruct the stack depth after the jump.
+ bytecodeSection().setStackDepth(depth + 1 + numPushed);
+
+ // Move the left-hand side value to the bottom and pop the rest.
+ if (!emitUnpickN(numPushed)) {
+ // [stack] LHS ...
+ return false;
+ }
+ if (!emitPopN(numPushed)) {
+ // [stack] LHS
+ return false;
+ }
+
+ if (!emitJumpTargetAndPatch(jumpAroundPop)) {
+ // [stack] LHS | RHS
+ return false;
+ }
+ } else {
+ if (!emitJumpTargetAndPatch(jump)) {
+ // [stack] LHS | RHS
+ return false;
+ }
+ }
+
+ MOZ_ASSERT(bytecodeSection().stackDepth() == depth + 1);
+
+ return true;
+}
+
+bool BytecodeEmitter::emitCallSiteObjectArray(ObjLiteralWriter& writer,
+ ListNode* cookedOrRaw,
+ ParseNode* head, uint32_t count) {
+ DebugOnly<size_t> idx = 0;
+ for (ParseNode* pn : cookedOrRaw->contentsFrom(head)) {
+ MOZ_ASSERT(pn->isKind(ParseNodeKind::TemplateStringExpr) ||
+ pn->isKind(ParseNodeKind::RawUndefinedExpr));
+
+ if (!emitObjLiteralValue(writer, pn)) {
+ return false;
+ }
+ idx++;
+ }
+ MOZ_ASSERT(idx == count);
+
+ return true;
+}
+
+bool BytecodeEmitter::emitCallSiteObject(CallSiteNode* callSiteObj) {
+ constexpr JSOp op = JSOp::CallSiteObj;
+
+ // The first element of a call-site node is the raw-values list. Skip over it.
+ ListNode* raw = callSiteObj->rawNodes();
+ MOZ_ASSERT(raw->isKind(ParseNodeKind::ArrayExpr));
+ ParseNode* head = callSiteObj->head()->pn_next;
+
+ uint32_t count = callSiteObj->count() - 1;
+ MOZ_ASSERT(count == raw->count());
+
+ ObjLiteralWriter writer;
+ writer.beginCallSiteObj(op);
+ writer.beginDenseArrayElements();
+
+ // Write elements of the two arrays: the 'cooked' values followed by the
+ // 'raw' values.
+ MOZ_RELEASE_ASSERT(count < UINT32_MAX / 2,
+ "Number of elements for both arrays must fit in uint32_t");
+ if (!emitCallSiteObjectArray(writer, callSiteObj, head, count)) {
+ return false;
+ }
+ if (!emitCallSiteObjectArray(writer, raw, raw->head(), count)) {
+ return false;
+ }
+
+ GCThingIndex cookedIndex;
+ if (!addObjLiteralData(writer, &cookedIndex)) {
+ return false;
+ }
+
+ MOZ_ASSERT(sc->hasCallSiteObj());
+
+ return emitInternedObjectOp(cookedIndex, op);
+}
+
+bool BytecodeEmitter::emitCatch(BinaryNode* catchClause) {
+ // We must be nested under a try-finally statement.
+ MOZ_ASSERT(innermostNestableControl->is<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:
+ // goto <finally>
+ // [jump target for returning from 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;
+}
+
+[[nodiscard]] bool BytecodeEmitter::emitJumpToFinally(JumpList* jump,
+ uint32_t idx) {
+ // Push the continuation index.
+ if (!emitNumberOp(idx)) {
+ return false;
+ }
+
+ // Push |throwing|.
+ if (!emit1(JSOp::False)) {
+ return false;
+ }
+
+ // Jump to the finally block.
+ if (!emitJumpNoFallthrough(JSOp::Goto, jump)) {
+ return false;
+ }
+
+ return true;
+}
+
+bool BytecodeEmitter::emitIf(TernaryNode* ifNode) {
+ IfEmitter ifThenElse(this);
+
+ if (!ifThenElse.emitIf(Some(ifNode->kid1()->pn_pos.begin))) {
+ return false;
+ }
+
+if_again:
+ ParseNode* testNode = ifNode->kid1();
+ auto conditionKind = IfEmitter::ConditionKind::Positive;
+ if (testNode->isKind(ParseNodeKind::NotExpr)) {
+ testNode = testNode->as<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,
+ TaggedParserAtomIndex::WellKnown::CopyDataProperties())) {
+ // [stack] TARGET SOURCE SET COPYDATAPROPERTIES
+ return false;
+ }
+ } else {
+ MOZ_ASSERT(depth > 1);
+ // [stack] TARGET SOURCE
+ argc = 2;
+
+ if (!emitAtomOp(
+ JSOp::GetIntrinsic,
+ TaggedParserAtomIndex::WellKnown::CopyDataPropertiesUnfiltered())) {
+ // [stack] TARGET SOURCE COPYDATAPROPERTIES
+ return false;
+ }
+ }
+
+ if (!emit1(JSOp::Undefined)) {
+ // [stack] TARGET SOURCE SET? COPYDATAPROPERTIES
+ // UNDEFINED
+ return false;
+ }
+ if (!emit2(JSOp::Pick, argc + 1)) {
+ // [stack] SOURCE SET? COPYDATAPROPERTIES UNDEFINED
+ // TARGET
+ return false;
+ }
+ if (!emit2(JSOp::Pick, argc + 1)) {
+ // [stack] SET? COPYDATAPROPERTIES UNDEFINED TARGET
+ // SOURCE
+ return false;
+ }
+ if (option == CopyOption::Filtered) {
+ if (!emit2(JSOp::Pick, argc + 1)) {
+ // [stack] COPYDATAPROPERTIES UNDEFINED TARGET SOURCE SET
+ return false;
+ }
+ }
+ // Callee is always self-hosted instrinsic, and cannot be content function.
+ if (!emitCall(JSOp::CallIgnoresRv, argc)) {
+ // [stack] IGNORED
+ return false;
+ }
+
+ if (!emit1(JSOp::Pop)) {
+ // [stack]
+ return false;
+ }
+
+ MOZ_ASSERT(depth - int(argc) == bytecodeSection().stackDepth());
+ return true;
+}
+
+bool BytecodeEmitter::emitBigIntOp(BigIntLiteral* bigint) {
+ GCThingIndex index;
+ if (!perScriptData().gcThingList().append(bigint, &index)) {
+ return false;
+ }
+ return emitGCIndexOp(JSOp::BigInt, index);
+}
+
+bool BytecodeEmitter::emitIterator(
+ SelfHostedIter selfHostedIter /* = SelfHostedIter::Deny */,
+ bool isIteratorMethodOnStack /* = false */) {
+ MOZ_ASSERT(selfHostedIter == SelfHostedIter::Allow ||
+ emitterMode != BytecodeEmitter::SelfHosting,
+ "[Symbol.iterator]() call is prohibited in self-hosted code "
+ "because it can run user-modifiable iteration code");
+
+ if (!isIteratorMethodOnStack) {
+ // [stack] OBJ
+
+ // Convert iterable to iterator.
+ if (!emit1(JSOp::Dup)) {
+ // [stack] OBJ OBJ
+ return false;
+ }
+ if (!emit2(JSOp::Symbol, uint8_t(JS::SymbolCode::iterator))) {
+ // [stack] OBJ OBJ @@ITERATOR
+ return false;
+ }
+ if (!emitElemOpBase(JSOp::GetElem)) {
+ // [stack] OBJ ITERFN
+ return false;
+ }
+ }
+
+ if (!emit1(JSOp::Swap)) {
+ // [stack] ITERFN OBJ
+ return false;
+ }
+ if (!emitCall(getIterCallOp(JSOp::CallIter, selfHostedIter), 0)) {
+ // [stack] ITER
+ return false;
+ }
+ if (!emitCheckIsObj(CheckIsObjectKind::GetIterator)) {
+ // [stack] ITER
+ return false;
+ }
+ if (!emit1(JSOp::Dup)) {
+ // [stack] ITER ITER
+ return false;
+ }
+ if (!emitAtomOp(JSOp::GetProp, TaggedParserAtomIndex::WellKnown::next())) {
+ // [stack] ITER NEXT
+ return false;
+ }
+ if (!emit1(JSOp::Swap)) {
+ // [stack] NEXT ITER
+ return false;
+ }
+ return true;
+}
+
+bool BytecodeEmitter::emitAsyncIterator(
+ SelfHostedIter selfHostedIter /* = SelfHostedIter::Deny */,
+ bool isIteratorMethodOnStack /* = false */) {
+ MOZ_ASSERT(selfHostedIter == SelfHostedIter::Allow ||
+ emitterMode != BytecodeEmitter::SelfHosting,
+ "[Symbol.asyncIterator]() call is prohibited in self-hosted code "
+ "because it can run user-modifiable iteration code");
+
+ if (!isIteratorMethodOnStack) {
+ // [stack] OBJ
+
+ // Convert iterable to iterator.
+ if (!emit1(JSOp::Dup)) {
+ // [stack] OBJ OBJ
+ return false;
+ }
+ if (!emit2(JSOp::Symbol, uint8_t(JS::SymbolCode::asyncIterator))) {
+ // [stack] OBJ OBJ @@ASYNCITERATOR
+ return false;
+ }
+ if (!emitElemOpBase(JSOp::GetElem)) {
+ // [stack] OBJ ASYNC_ITERFN
+ return false;
+ }
+ } else {
+ // [stack] OBJ ASYNC_ITERFN SYNC_ITERFN
+
+ if (!emitElemOpBase(JSOp::Swap)) {
+ // [stack] OBJ SYNC_ITERFN ASYNC_ITERFN
+ return false;
+ }
+ }
+
+ InternalIfEmitter ifAsyncIterIsUndefined(this);
+ if (!emit1(JSOp::IsNullOrUndefined)) {
+ // [stack] OBJ SYNC_ITERFN? ASYNC_ITERFN NULL-OR-UNDEF
+ return false;
+ }
+ if (!ifAsyncIterIsUndefined.emitThenElse()) {
+ // [stack] OBJ SYNC_ITERFN? ASYNC_ITERFN
+ return false;
+ }
+
+ if (!emit1(JSOp::Pop)) {
+ // [stack] OBJ SYNC_ITERFN?
+ return false;
+ }
+
+ if (!isIteratorMethodOnStack) {
+ if (!emit1(JSOp::Dup)) {
+ // [stack] OBJ OBJ
+ return false;
+ }
+ if (!emit2(JSOp::Symbol, uint8_t(JS::SymbolCode::iterator))) {
+ // [stack] OBJ OBJ @@ITERATOR
+ return false;
+ }
+ if (!emitElemOpBase(JSOp::GetElem)) {
+ // [stack] OBJ SYNC_ITERFN
+ return false;
+ }
+ } else {
+ // [stack] OBJ SYNC_ITERFN
+ }
+
+ if (!emit1(JSOp::Swap)) {
+ // [stack] SYNC_ITERFN OBJ
+ return false;
+ }
+ if (!emitCall(getIterCallOp(JSOp::CallIter, selfHostedIter), 0)) {
+ // [stack] ITER
+ return false;
+ }
+ if (!emitCheckIsObj(CheckIsObjectKind::GetIterator)) {
+ // [stack] ITER
+ return false;
+ }
+
+ if (!emit1(JSOp::Dup)) {
+ // [stack] ITER ITER
+ return false;
+ }
+ if (!emitAtomOp(JSOp::GetProp, TaggedParserAtomIndex::WellKnown::next())) {
+ // [stack] ITER SYNCNEXT
+ return false;
+ }
+
+ if (!emit1(JSOp::ToAsyncIter)) {
+ // [stack] ITER
+ return false;
+ }
+
+ if (!ifAsyncIterIsUndefined.emitElse()) {
+ // [stack] OBJ SYNC_ITERFN? ASYNC_ITERFN
+ return false;
+ }
+
+ if (isIteratorMethodOnStack) {
+ if (!emit1(JSOp::Swap)) {
+ // [stack] OBJ ASYNC_ITERFN SYNC_ITERFN
+ return false;
+ }
+ if (!emit1(JSOp::Pop)) {
+ // [stack] OBJ ASYNC_ITERFN
+ return false;
+ }
+ }
+
+ if (!emit1(JSOp::Swap)) {
+ // [stack] ASYNC_ITERFN OBJ
+ return false;
+ }
+ if (!emitCall(getIterCallOp(JSOp::CallIter, selfHostedIter), 0)) {
+ // [stack] ITER
+ return false;
+ }
+ if (!emitCheckIsObj(CheckIsObjectKind::GetAsyncIterator)) {
+ // [stack] ITER
+ return false;
+ }
+
+ if (!ifAsyncIterIsUndefined.emitEnd()) {
+ // [stack] ITER
+ return false;
+ }
+
+ if (!emit1(JSOp::Dup)) {
+ // [stack] ITER ITER
+ return false;
+ }
+ if (!emitAtomOp(JSOp::GetProp, TaggedParserAtomIndex::WellKnown::next())) {
+ // [stack] ITER NEXT
+ return false;
+ }
+ if (!emit1(JSOp::Swap)) {
+ // [stack] NEXT ITER
+ return false;
+ }
+
+ return true;
+}
+
+bool BytecodeEmitter::emitSpread(SelfHostedIter selfHostedIter) {
+ // [stack] NEXT ITER ARR I
+ return emitSpread(selfHostedIter, 2, JSOp::InitElemInc);
+ // [stack] ARR FINAL_INDEX
+}
+
+bool BytecodeEmitter::emitSpread(SelfHostedIter selfHostedIter,
+ int spreadeeStackItems, JSOp storeElementOp) {
+ LoopControl loopInfo(this, StatementKind::Spread);
+ // In the [stack] annotations, (spreadee) can be "ARR I" (when spreading
+ // into an array or into call parameters, or "TUPLE" (when spreading into a
+ // tuple)
+
+ if (!loopInfo.emitLoopHead(this, Nothing())) {
+ // [stack] NEXT ITER (spreadee)
+ return false;
+ }
+
+ {
+#ifdef DEBUG
+ auto loopDepth = bytecodeSection().stackDepth();
+#endif
+
+ // Spread operations can't contain |continue|, so don't bother setting loop
+ // and enclosing "update" offsets, as we do with for-loops.
+
+ if (!emitDupAt(spreadeeStackItems + 1, 2)) {
+ // [stack] NEXT ITER (spreadee) NEXT ITER
+ return false;
+ }
+ if (!emitIteratorNext(Nothing(), IteratorKind::Sync, selfHostedIter)) {
+ // [stack] NEXT ITER (spreadee) RESULT
+ return false;
+ }
+ if (!emit1(JSOp::Dup)) {
+ // [stack] NEXT ITER (spreadee) RESULT RESULT
+ return false;
+ }
+ if (!emitAtomOp(JSOp::GetProp, TaggedParserAtomIndex::WellKnown::done())) {
+ // [stack] NEXT ITER (spreadee) RESULT DONE
+ return false;
+ }
+ if (!emitJump(JSOp::JumpIfTrue, &loopInfo.breaks)) {
+ // [stack] NEXT ITER (spreadee) RESULT
+ return false;
+ }
+
+ // Emit code to assign result.value to the iteration variable.
+ if (!emitAtomOp(JSOp::GetProp, TaggedParserAtomIndex::WellKnown::value())) {
+ // [stack] NEXT ITER (spreadee) VALUE
+ return false;
+ }
+ if (!emit1(storeElementOp)) {
+ // [stack] NEXT ITER (spreadee)
+ return false;
+ }
+
+ if (!loopInfo.emitLoopEnd(this, JSOp::Goto, TryNoteKind::ForOf)) {
+ // [stack] NEXT ITER (spreadee)
+ return false;
+ }
+
+ MOZ_ASSERT(bytecodeSection().stackDepth() == loopDepth);
+ }
+
+ // When we leave the loop body and jump to this point, the result value is
+ // still on the stack. Account for that by updating the stack depth
+ // manually.
+ bytecodeSection().setStackDepth(bytecodeSection().stackDepth() + 1);
+
+ // No continues should occur in spreads.
+ MOZ_ASSERT(!loopInfo.continues.offset.valid());
+
+ if (!emit2(JSOp::Pick, spreadeeStackItems + 2)) {
+ // [stack] ITER (spreadee) RESULT NEXT
+ return false;
+ }
+ if (!emit2(JSOp::Pick, spreadeeStackItems + 2)) {
+ // [stack] (spreadee) RESULT NEXT ITER
+ return false;
+ }
+
+ return emitPopN(3);
+ // [stack] (spreadee)
+}
+
+bool BytecodeEmitter::emitInitializeForInOrOfTarget(TernaryNode* forHead) {
+ MOZ_ASSERT(forHead->isKind(ParseNodeKind::ForIn) ||
+ forHead->isKind(ParseNodeKind::ForOf));
+
+ MOZ_ASSERT(bytecodeSection().stackDepth() >= 1,
+ "must have a per-iteration value for initializing");
+
+ ParseNode* target = forHead->kid1();
+ MOZ_ASSERT(!forHead->kid2());
+
+ // If the for-in/of loop didn't have a variable declaration, per-loop
+ // initialization is just assigning the iteration value to a target
+ // expression.
+ if (!target->is<DeclarationListNode>()) {
+ return emitAssignmentOrInit(ParseNodeKind::AssignExpr, target, nullptr);
+ // [stack] ... ITERVAL
+ }
+
+ // Otherwise, per-loop initialization is (possibly) declaration
+ // initialization. If the declaration is a lexical declaration, it must be
+ // initialized. If the declaration is a variable declaration, an
+ // assignment to that name (which does *not* necessarily assign to the
+ // variable!) must be generated.
+
+ auto* declarationList = &target->as<DeclarationListNode>();
+ if (!updateSourceCoordNotes(declarationList->pn_pos.begin)) {
+ return false;
+ }
+
+ target = declarationList->singleBinding();
+
+ NameNode* nameNode = nullptr;
+ if (target->isKind(ParseNodeKind::Name)) {
+ nameNode = &target->as<NameNode>();
+ } else if (target->isKind(ParseNodeKind::AssignExpr)) {
+ BinaryNode* assignNode = &target->as<BinaryNode>();
+ if (assignNode->left()->is<NameNode>()) {
+ nameNode = &assignNode->left()->as<NameNode>();
+ }
+ }
+
+ if (nameNode) {
+ auto nameAtom = nameNode->name();
+ NameOpEmitter noe(this, nameAtom, NameOpEmitter::Kind::Initialize);
+ if (!noe.prepareForRhs()) {
+ return false;
+ }
+ if (noe.emittedBindOp()) {
+ // Per-iteration initialization in for-in/of loops computes the
+ // iteration value *before* initializing. Thus the initializing
+ // value may be buried under a bind-specific value on the stack.
+ // Swap it to the top of the stack.
+ MOZ_ASSERT(bytecodeSection().stackDepth() >= 2);
+ if (!emit1(JSOp::Swap)) {
+ return false;
+ }
+ } else {
+ // In cases of emitting a frame slot or environment slot,
+ // nothing needs be done.
+ MOZ_ASSERT(bytecodeSection().stackDepth() >= 1);
+ }
+ if (!noe.emitAssignment()) {
+ return false;
+ }
+
+ // The caller handles removing the iteration value from the stack.
+ return true;
+ }
+
+ MOZ_ASSERT(
+ !target->isKind(ParseNodeKind::AssignExpr),
+ "for-in/of loop destructuring declarations can't have initializers");
+
+ MOZ_ASSERT(target->isKind(ParseNodeKind::ArrayExpr) ||
+ target->isKind(ParseNodeKind::ObjectExpr));
+ return emitDestructuringOps(&target->as<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,
+ getSelfHostedIterFor(forHeadExpr), iterKind);
+
+ if (!forOf.emitIterated()) {
+ // [stack]
+ return false;
+ }
+
+ if (!updateSourceCoordNotes(forHeadExpr->pn_pos.begin)) {
+ return false;
+ }
+ if (!markStepBreakpoint()) {
+ return false;
+ }
+ if (!emitTree(forHeadExpr)) {
+ // [stack] ITERABLE
+ return false;
+ }
+
+ if (headLexicalEmitterScope) {
+ DebugOnly<ParseNode*> forOfTarget = forOfHead->kid1();
+ MOZ_ASSERT(forOfTarget->isKind(ParseNodeKind::LetDecl) ||
+ forOfTarget->isKind(ParseNodeKind::ConstDecl));
+ }
+
+ bool isIteratorMethodOnStack = false;
+ if (emitterMode == BytecodeEmitter::SelfHosting &&
+ forHeadExpr->isKind(ParseNodeKind::CallExpr) &&
+ forHeadExpr->as<BinaryNode>().left()->isName(
+ TaggedParserAtomIndex::WellKnown::allowContentIterWith())) {
+ // This is the following case:
+ //
+ // for (const nextValue of allowContentIterWith(items, usingIterator)) {
+ //
+ // `items` is emitted by `emitTree(forHeadExpr)` above, and the result
+ // is on the stack as ITERABLE.
+ // `usingIterator` is the value of `items[Symbol.iterator]`, that's already
+ // retrieved.
+ ListNode* argsList = &forHeadExpr->as<BinaryNode>().right()->as<ListNode>();
+ MOZ_ASSERT_IF(iterKind == IteratorKind::Sync, argsList->count() == 2);
+ MOZ_ASSERT_IF(iterKind == IteratorKind::Async, argsList->count() == 3);
+
+ if (!emitTree(argsList->head()->pn_next)) {
+ // [stack] ITERABLE ITERFN
+ return false;
+ }
+
+ // Async iterator has two possible iterators: An async iterator and a sync
+ // iterator.
+ if (iterKind == IteratorKind::Async) {
+ if (!emitTree(argsList->head()->pn_next->pn_next)) {
+ // [stack] ITERABLE ASYNC_ITERFN SYNC_ITERFN
+ return false;
+ }
+ }
+
+ isIteratorMethodOnStack = true;
+ }
+
+ if (!forOf.emitInitialize(forOfHead->pn_pos.begin, isIteratorMethodOnStack)) {
+ // [stack] NEXT ITER VALUE
+ return false;
+ }
+
+ if (!emitInitializeForInOrOfTarget(forOfHead)) {
+ // [stack] NEXT ITER VALUE
+ return false;
+ }
+
+ if (!forOf.emitBody()) {
+ // [stack] NEXT ITER UNDEF
+ return false;
+ }
+
+ // Perform the loop body.
+ ParseNode* forBody = forOfLoop->body();
+ if (!emitTree(forBody)) {
+ // [stack] NEXT ITER UNDEF
+ return false;
+ }
+
+ if (!forOf.emitEnd(forHeadExpr->pn_pos.begin)) {
+ // [stack]
+ return false;
+ }
+
+ return true;
+}
+
+bool BytecodeEmitter::emitForIn(ForNode* forInLoop,
+ const EmitterScope* headLexicalEmitterScope) {
+ TernaryNode* forInHead = forInLoop->head();
+ MOZ_ASSERT(forInHead->isKind(ParseNodeKind::ForIn));
+
+ ForInEmitter forIn(this, headLexicalEmitterScope);
+
+ // Annex B: Evaluate the var-initializer expression if present.
+ // |for (var i = initializer in expr) { ... }|
+ ParseNode* forInTarget = forInHead->kid1();
+ if (forInTarget->is<DeclarationListNode>()) {
+ auto* declarationList = &forInTarget->as<DeclarationListNode>();
+
+ ParseNode* decl = declarationList->singleBinding();
+ if (decl->isKind(ParseNodeKind::AssignExpr)) {
+ 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;
+ }
+
+ auto nameAtom = nameNode->name();
+ NameOpEmitter noe(this, nameAtom, NameOpEmitter::Kind::Initialize);
+ if (!noe.prepareForRhs()) {
+ return false;
+ }
+ if (!emitInitializer(initializer, nameNode)) {
+ return false;
+ }
+ if (!noe.emitAssignment()) {
+ return false;
+ }
+
+ // Pop the initializer.
+ if (!emit1(JSOp::Pop)) {
+ return false;
+ }
+ }
+ }
+ }
+
+ if (!forIn.emitIterated()) {
+ // [stack]
+ return false;
+ }
+
+ // Evaluate the expression being iterated.
+ ParseNode* expr = forInHead->kid3();
+
+ if (!updateSourceCoordNotes(expr->pn_pos.begin)) {
+ return false;
+ }
+ if (!markStepBreakpoint()) {
+ return false;
+ }
+ if (!emitTree(expr)) {
+ // [stack] EXPR
+ return false;
+ }
+
+ MOZ_ASSERT(forInLoop->iflags() == 0);
+
+ MOZ_ASSERT_IF(headLexicalEmitterScope,
+ forInTarget->isKind(ParseNodeKind::LetDecl) ||
+ forInTarget->isKind(ParseNodeKind::ConstDecl));
+
+ if (!forIn.emitInitialize()) {
+ // [stack] ITER ITERVAL
+ return false;
+ }
+
+ if (!emitInitializeForInOrOfTarget(forInHead)) {
+ // [stack] ITER ITERVAL
+ return false;
+ }
+
+ if (!forIn.emitBody()) {
+ // [stack] ITER ITERVAL
+ return false;
+ }
+
+ // Perform the loop body.
+ ParseNode* forBody = forInLoop->body();
+ if (!emitTree(forBody)) {
+ // [stack] ITER ITERVAL
+ return false;
+ }
+
+ if (!forIn.emitEnd(forInHead->pn_pos.begin)) {
+ // [stack]
+ return false;
+ }
+
+ return true;
+}
+
+/* C-style `for (init; cond; update) ...` loop. */
+bool BytecodeEmitter::emitCStyleFor(
+ ForNode* forNode, const EmitterScope* headLexicalEmitterScope) {
+ TernaryNode* forHead = forNode->head();
+ ParseNode* forBody = forNode->body();
+ ParseNode* init = forHead->kid1();
+ ParseNode* cond = forHead->kid2();
+ ParseNode* update = forHead->kid3();
+ bool isLet = init && init->isKind(ParseNodeKind::LetDecl);
+
+ CForEmitter cfor(this, isLet ? headLexicalEmitterScope : nullptr);
+
+ if (!cfor.emitInit(init ? Some(init->pn_pos.begin) : Nothing())) {
+ // [stack]
+ return false;
+ }
+
+ // If the head of this for-loop declared any lexical variables, the parser
+ // wrapped this ParseNodeKind::For node in a ParseNodeKind::LexicalScope
+ // representing the implicit scope of those variables. By the time we get
+ // here, we have already entered that scope. So far, so good.
+ if (init) {
+ // Emit the `init` clause, whether it's an expression or a variable
+ // declaration. (The loop variables were hoisted into an enclosing
+ // scope, but we still need to emit code for the initializers.)
+ if (init->is<DeclarationListNode>()) {
+ MOZ_ASSERT(!init->as<DeclarationListNode>().empty());
+
+ if (!emitTree(init)) {
+ // [stack]
+ return false;
+ }
+ } else {
+ if (!updateSourceCoordNotes(init->pn_pos.begin)) {
+ return false;
+ }
+ if (!markStepBreakpoint()) {
+ return false;
+ }
+
+ // 'init' is an expression, not a declaration. emitTree left its
+ // value on the stack.
+ if (!emitTree(init, ValueUsage::IgnoreValue)) {
+ // [stack] VAL
+ return false;
+ }
+ if (!emit1(JSOp::Pop)) {
+ // [stack]
+ return false;
+ }
+ }
+ }
+
+ if (!cfor.emitCond(cond ? Some(cond->pn_pos.begin) : Nothing())) {
+ // [stack]
+ return false;
+ }
+
+ if (cond) {
+ if (!updateSourceCoordNotes(cond->pn_pos.begin)) {
+ return false;
+ }
+ if (!markStepBreakpoint()) {
+ return false;
+ }
+ if (!emitTree(cond)) {
+ // [stack] VAL
+ return false;
+ }
+ }
+
+ if (!cfor.emitBody(cond ? CForEmitter::Cond::Present
+ : CForEmitter::Cond::Missing)) {
+ // [stack]
+ return false;
+ }
+
+ if (!emitTree(forBody)) {
+ // [stack]
+ return false;
+ }
+
+ if (!cfor.emitUpdate(
+ update ? CForEmitter::Update::Present : CForEmitter::Update::Missing,
+ update ? Some(update->pn_pos.begin) : Nothing())) {
+ // [stack]
+ return false;
+ }
+
+ // Check for update code to do before the condition (if any).
+ if (update) {
+ if (!updateSourceCoordNotes(update->pn_pos.begin)) {
+ return false;
+ }
+ if (!markStepBreakpoint()) {
+ return false;
+ }
+ if (!emitTree(update, ValueUsage::IgnoreValue)) {
+ // [stack] VAL
+ return false;
+ }
+ }
+
+ if (!cfor.emitEnd(forNode->pn_pos.begin)) {
+ // [stack]
+ return false;
+ }
+
+ return true;
+}
+
+bool BytecodeEmitter::emitFor(ForNode* forNode,
+ const EmitterScope* headLexicalEmitterScope) {
+ if (forNode->head()->isKind(ParseNodeKind::ForHead)) {
+ return emitCStyleFor(forNode, headLexicalEmitterScope);
+ }
+
+ if (!updateLineNumberNotes(forNode->pn_pos.begin)) {
+ return false;
+ }
+
+ if (forNode->head()->isKind(ParseNodeKind::ForIn)) {
+ return emitForIn(forNode, headLexicalEmitterScope);
+ }
+
+ MOZ_ASSERT(forNode->head()->isKind(ParseNodeKind::ForOf));
+ return emitForOf(forNode, headLexicalEmitterScope);
+}
+
+MOZ_NEVER_INLINE bool BytecodeEmitter::emitFunction(
+ FunctionNode* funNode, bool needsProto /* = false */) {
+ FunctionBox* funbox = funNode->funbox();
+
+ // [stack]
+
+ FunctionEmitter fe(this, funbox, funNode->syntaxKind(),
+ funNode->functionIsHoisted()
+ ? FunctionEmitter::IsHoisted::Yes
+ : FunctionEmitter::IsHoisted::No);
+
+ // |wasEmittedByEnclosingScript| flag is set to true once the function has
+ // been emitted. Function definitions that need hoisting to the top of the
+ // function will be seen by emitFunction in two places.
+ if (funbox->wasEmittedByEnclosingScript()) {
+ if (!fe.emitAgain()) {
+ // [stack]
+ return false;
+ }
+ MOZ_ASSERT(funNode->functionIsHoisted());
+ } else if (funbox->isInterpreted()) {
+ if (!funbox->emitBytecode) {
+ return fe.emitLazy();
+ // [stack] FUN?
+ }
+
+ if (!fe.prepareForNonLazy()) {
+ // [stack]
+ return false;
+ }
+
+ BytecodeEmitter bce2(this, funbox);
+ if (!bce2.init(funNode->pn_pos)) {
+ return false;
+ }
+
+ /* We measured the max scope depth when we parsed the function. */
+ if (!bce2.emitFunctionScript(funNode)) {
+ return false;
+ }
+
+ if (!fe.emitNonLazyEnd()) {
+ // [stack] FUN?
+ return false;
+ }
+ } else {
+ if (!fe.emitAsmJSModule()) {
+ // [stack]
+ return false;
+ }
+ }
+
+ // Track the last emitted top-level self-hosted function, so that intrinsics
+ // can adjust attributes at parse time.
+ //
+ // NOTE: We also disallow lambda functions in the top-level body. This is done
+ // to simplify handling of the self-hosted stencil. Within normal function
+ // declarations there are no such restrictions.
+ if (emitterMode == EmitterMode::SelfHosting) {
+ if (sc->isTopLevelContext()) {
+ MOZ_ASSERT(!funbox->isLambda());
+ MOZ_ASSERT(funbox->explicitName());
+ prevSelfHostedTopLevelFunction = funbox;
+ }
+ }
+
+ return true;
+}
+
+bool BytecodeEmitter::emitDo(BinaryNode* doNode) {
+ ParseNode* bodyNode = doNode->left();
+
+ DoWhileEmitter doWhile(this);
+ if (!doWhile.emitBody(doNode->pn_pos.begin, getOffsetForLoop(bodyNode))) {
+ return false;
+ }
+
+ if (!emitTree(bodyNode)) {
+ return false;
+ }
+
+ if (!doWhile.emitCond()) {
+ return false;
+ }
+
+ ParseNode* condNode = doNode->right();
+ if (!updateSourceCoordNotes(condNode->pn_pos.begin)) {
+ return false;
+ }
+ if (!markStepBreakpoint()) {
+ return false;
+ }
+ if (!emitTree(condNode)) {
+ return false;
+ }
+
+ if (!doWhile.emitEnd()) {
+ return false;
+ }
+
+ return true;
+}
+
+bool BytecodeEmitter::emitWhile(BinaryNode* whileNode) {
+ ParseNode* bodyNode = whileNode->right();
+
+ WhileEmitter wh(this);
+
+ ParseNode* condNode = whileNode->left();
+ if (!wh.emitCond(whileNode->pn_pos.begin, getOffsetForLoop(condNode),
+ whileNode->pn_pos.end)) {
+ return false;
+ }
+
+ if (!updateSourceCoordNotes(condNode->pn_pos.begin)) {
+ return false;
+ }
+ if (!markStepBreakpoint()) {
+ return false;
+ }
+ if (!emitTree(condNode)) {
+ return false;
+ }
+
+ if (!wh.emitBody()) {
+ return false;
+ }
+ if (!emitTree(bodyNode)) {
+ return false;
+ }
+
+ if (!wh.emitEnd()) {
+ return false;
+ }
+
+ return true;
+}
+
+bool BytecodeEmitter::emitBreak(TaggedParserAtomIndex label) {
+ BreakableControl* target;
+ if (label) {
+ // Any statement with the matching label may be the break target.
+ auto hasSameLabel = [label](LabelControl* labelControl) {
+ return labelControl->label() == label;
+ };
+ target = findInnermostNestableControl<LabelControl>(hasSameLabel);
+ } else {
+ auto isNotLabel = [](BreakableControl* control) {
+ return !control->is<LabelControl>();
+ };
+ target = findInnermostNestableControl<BreakableControl>(isNotLabel);
+ }
+
+ return emitGoto(target, GotoKind::Break);
+}
+
+bool BytecodeEmitter::emitContinue(TaggedParserAtomIndex label) {
+ LoopControl* target = nullptr;
+ if (label) {
+ // Find the loop statement enclosed by the matching label.
+ NestableControl* control = innermostNestableControl;
+ while (!control->is<LabelControl>() ||
+ control->as<LabelControl>().label() != label) {
+ if (control->is<LoopControl>()) {
+ target = &control->as<LoopControl>();
+ }
+ control = control->enclosing();
+ }
+ } else {
+ target = findInnermostNestableControl<LoopControl>();
+ }
+ return emitGoto(target, GotoKind::Continue);
+}
+
+bool BytecodeEmitter::emitGetFunctionThis(NameNode* thisName) {
+ MOZ_ASSERT(sc->hasFunctionThisBinding());
+ MOZ_ASSERT(thisName->isName(TaggedParserAtomIndex::WellKnown::dotThis()));
+
+ if (!updateLineNumberNotes(thisName->pn_pos.begin)) {
+ return false;
+ }
+
+ if (!emitGetName(TaggedParserAtomIndex::WellKnown::dotThis())) {
+ // [stack] THIS
+ return false;
+ }
+ if (sc->needsThisTDZChecks()) {
+ if (!emit1(JSOp::CheckThis)) {
+ // [stack] THIS
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool BytecodeEmitter::emitGetThisForSuperBase(UnaryNode* superBase) {
+ MOZ_ASSERT(superBase->isKind(ParseNodeKind::SuperBase));
+ NameNode* nameNode = &superBase->kid()->as<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);
+
+ MOZ_ASSERT(outermostScope().hasNonSyntacticScopeOnChain() ==
+ sc->hasNonSyntacticScope());
+ if (sc->hasNonSyntacticScope()) {
+ return emit1(JSOp::NonSyntacticGlobalThis);
+ // [stack] THIS
+ }
+
+ return emit1(JSOp::GlobalThis);
+ // [stack] THIS
+}
+
+bool BytecodeEmitter::emitCheckDerivedClassConstructorReturn() {
+ MOZ_ASSERT(
+ lookupName(TaggedParserAtomIndex::WellKnown::dotThis()).hasKnownSlot());
+ if (!emitGetName(TaggedParserAtomIndex::WellKnown::dotThis())) {
+ return false;
+ }
+ if (!emit1(JSOp::CheckReturn)) {
+ return false;
+ }
+ if (!emit1(JSOp::SetRval)) {
+ return false;
+ }
+ return true;
+}
+
+bool BytecodeEmitter::emitNewTarget() {
+ MOZ_ASSERT(sc->allowNewTarget());
+
+ if (!emitGetName(TaggedParserAtomIndex::WellKnown::dotNewTarget())) {
+ // [stack] NEW.TARGET
+ return false;
+ }
+ return true;
+}
+
+bool BytecodeEmitter::emitNewTarget(NewTargetNode* pn) {
+ MOZ_ASSERT(pn->newTargetName()->isName(
+ TaggedParserAtomIndex::WellKnown::dotNewTarget()));
+
+ return emitNewTarget();
+}
+
+bool BytecodeEmitter::emitNewTarget(CallNode* pn) {
+ MOZ_ASSERT(pn->callOp() == JSOp::SuperCall ||
+ pn->callOp() == JSOp::SpreadSuperCall);
+
+ // The parser is responsible for marking the "new.target" binding as being
+ // implicitly used in super() calls.
+ return emitNewTarget();
+}
+
+bool BytecodeEmitter::emitReturn(UnaryNode* returnNode) {
+ if (!updateSourceCoordNotes(returnNode->pn_pos.begin)) {
+ return false;
+ }
+
+ if (!markStepBreakpoint()) {
+ return false;
+ }
+
+ /* Push a return value */
+ if (ParseNode* expr = returnNode->kid()) {
+ if (!emitTree(expr)) {
+ return false;
+ }
+
+ if (sc->asSuspendableContext()->isAsync() &&
+ sc->asSuspendableContext()->isGenerator()) {
+ if (!emitAwaitInInnermostScope()) {
+ return false;
+ }
+ }
+ } else {
+ /* No explicit return value provided */
+ if (!emit1(JSOp::Undefined)) {
+ return false;
+ }
+ }
+
+ // We know functionBodyEndPos is set because "return" is only
+ // valid in a function, and so we've passed through
+ // emitFunctionScript.
+ if (!updateSourceCoordNotes(*functionBodyEndPos)) {
+ return false;
+ }
+
+ /*
+ * The return value is currently on the stack. We would like to
+ * generate JSOp::Return, but if we have work to do before returning,
+ * we will instead generate JSOp::SetRval / JSOp::RetRval.
+ *
+ * We don't know whether we will need fixup code until after calling
+ * prepareForNonLocalJumpToOutermost, so we start by generating
+ * JSOp::SetRval, then mutate it to JSOp::Return in finishReturn if it
+ * wasn't needed.
+ */
+ BytecodeOffset setRvalOffset = bytecodeSection().offset();
+ if (!emit1(JSOp::SetRval)) {
+ return false;
+ }
+
+ NonLocalExitControl nle(this, NonLocalExitKind::Return);
+ return nle.emitReturn(setRvalOffset);
+}
+
+bool BytecodeEmitter::finishReturn(BytecodeOffset setRvalOffset) {
+ // The return value is currently in rval. Depending on the current function,
+ // we may have to do additional work before returning:
+ // - Derived class constructors must check if the return value is an object.
+ // - Generators and async functions must do a final yield.
+ // - Non-async generators must return the value as an iterator result:
+ // { value: <rval>, done: true }
+ // - Non-generator async functions must resolve the function's result promise
+ // with the value.
+ //
+ // If we have not generated any code since the SetRval that stored the return
+ // value, we can also optimize the bytecode by rewriting that SetRval as a
+ // JSOp::Return. See |emitReturn| above.
+
+ bool isDerivedClassConstructor =
+ sc->isFunctionBox() && sc->asFunctionBox()->isDerivedClassConstructor();
+ bool needsFinalYield =
+ sc->isFunctionBox() && sc->asFunctionBox()->needsFinalYield();
+ bool isSimpleReturn =
+ setRvalOffset.valid() &&
+ setRvalOffset + BytecodeOffsetDiff(JSOpLength_SetRval) ==
+ bytecodeSection().offset();
+
+ if (isDerivedClassConstructor) {
+ MOZ_ASSERT(!needsFinalYield);
+ if (!emitJump(JSOp::Goto, &endOfDerivedClassConstructorBody)) {
+ return false;
+ }
+ return true;
+ }
+
+ if (needsFinalYield) {
+ if (!emitJump(JSOp::Goto, &finalYields)) {
+ return false;
+ }
+ return true;
+ }
+
+ if (isSimpleReturn) {
+ MOZ_ASSERT(JSOp(bytecodeSection().code()[setRvalOffset.value()]) ==
+ JSOp::SetRval);
+ bytecodeSection().code()[setRvalOffset.value()] = jsbytecode(JSOp::Return);
+ return true;
+ }
+
+ // Nothing special needs to be done.
+ return emitReturnRval();
+}
+
+bool BytecodeEmitter::emitGetDotGeneratorInScope(EmitterScope& currentScope) {
+ if (!sc->isFunction() && sc->isModuleContext() &&
+ sc->asModuleContext()->isAsync()) {
+ NameLocation loc = *locationOfNameBoundInScopeType<ModuleScope>(
+ TaggedParserAtomIndex::WellKnown::dotGenerator(), &currentScope);
+ return emitGetNameAtLocation(
+ TaggedParserAtomIndex::WellKnown::dotGenerator(), loc);
+ }
+ NameLocation loc = *locationOfNameBoundInScopeType<FunctionScope>(
+ TaggedParserAtomIndex::WellKnown::dotGenerator(), &currentScope);
+ return emitGetNameAtLocation(TaggedParserAtomIndex::WellKnown::dotGenerator(),
+ loc);
+}
+
+bool BytecodeEmitter::emitInitialYield(UnaryNode* yieldNode) {
+ if (!emitTree(yieldNode->kid())) {
+ return false;
+ }
+
+ if (!emitYieldOp(JSOp::InitialYield)) {
+ // [stack] RVAL GENERATOR RESUMEKIND
+ return false;
+ }
+ if (!emit1(JSOp::CheckResumeKind)) {
+ // [stack] RVAL
+ return false;
+ }
+ if (!emit1(JSOp::Pop)) {
+ // [stack]
+ return false;
+ }
+
+ return true;
+}
+
+bool BytecodeEmitter::emitYield(UnaryNode* yieldNode) {
+ MOZ_ASSERT(sc->isFunctionBox());
+ MOZ_ASSERT(sc->asFunctionBox()->isGenerator());
+ MOZ_ASSERT(yieldNode->isKind(ParseNodeKind::YieldExpr));
+
+ bool needsIteratorResult = sc->asFunctionBox()->needsIteratorResult();
+ if (needsIteratorResult) {
+ if (!emitPrepareIteratorResult()) {
+ // [stack] ITEROBJ
+ return false;
+ }
+ }
+ if (ParseNode* expr = yieldNode->kid()) {
+ if (!emitTree(expr)) {
+ // [stack] ITEROBJ? VAL
+ return false;
+ }
+ } else {
+ if (!emit1(JSOp::Undefined)) {
+ // [stack] ITEROBJ? UNDEFINED
+ return false;
+ }
+ }
+
+ if (sc->asSuspendableContext()->isAsync()) {
+ MOZ_ASSERT(!needsIteratorResult);
+ if (!emitAwaitInInnermostScope()) {
+ // [stack] RESULT
+ return false;
+ }
+ }
+
+ if (needsIteratorResult) {
+ if (!emitFinishIteratorResult(false)) {
+ // [stack] ITEROBJ
+ return false;
+ }
+ }
+
+ if (!emitGetDotGeneratorInInnermostScope()) {
+ // [stack] # if needsIteratorResult
+ // [stack] ITEROBJ .GENERATOR
+ // [stack] # else
+ // [stack] RESULT .GENERATOR
+ return false;
+ }
+
+ if (!emitYieldOp(JSOp::Yield)) {
+ // [stack] YIELDRESULT GENERATOR RESUMEKIND
+ return false;
+ }
+
+ if (!emit1(JSOp::CheckResumeKind)) {
+ // [stack] YIELDRESULT
+ return false;
+ }
+
+ return true;
+}
+
+bool BytecodeEmitter::emitAwaitInInnermostScope(UnaryNode* awaitNode) {
+ MOZ_ASSERT(sc->isSuspendableContext());
+ MOZ_ASSERT(awaitNode->isKind(ParseNodeKind::AwaitExpr));
+
+ if (!emitTree(awaitNode->kid())) {
+ return false;
+ }
+ return emitAwaitInInnermostScope();
+}
+
+bool BytecodeEmitter::emitAwaitInScope(EmitterScope& currentScope) {
+ if (!emit1(JSOp::CanSkipAwait)) {
+ // [stack] VALUE CANSKIP
+ return false;
+ }
+
+ if (!emit1(JSOp::MaybeExtractAwaitValue)) {
+ // [stack] VALUE_OR_RESOLVED CANSKIP
+ return false;
+ }
+
+ InternalIfEmitter ifCanSkip(this);
+ if (!ifCanSkip.emitThen(IfEmitter::ConditionKind::Negative)) {
+ // [stack] VALUE_OR_RESOLVED
+ return false;
+ }
+
+ if (sc->asSuspendableContext()->needsPromiseResult()) {
+ if (!emitGetDotGeneratorInScope(currentScope)) {
+ // [stack] VALUE GENERATOR
+ return false;
+ }
+ if (!emit1(JSOp::AsyncAwait)) {
+ // [stack] PROMISE
+ return false;
+ }
+ }
+
+ if (!emitGetDotGeneratorInScope(currentScope)) {
+ // [stack] VALUE|PROMISE GENERATOR
+ return false;
+ }
+ if (!emitYieldOp(JSOp::Await)) {
+ // [stack] RESOLVED GENERATOR RESUMEKIND
+ return false;
+ }
+ if (!emit1(JSOp::CheckResumeKind)) {
+ // [stack] RESOLVED
+ return false;
+ }
+
+ if (!ifCanSkip.emitEnd()) {
+ return false;
+ }
+
+ MOZ_ASSERT(ifCanSkip.popped() == 0);
+
+ return true;
+}
+
+// ES2019 draft rev 49b781ec80117b60f73327ef3054703a3111e40c
+// 14.4.14 Runtime Semantics: Evaluation
+// YieldExpression : yield* AssignmentExpression
+bool BytecodeEmitter::emitYieldStar(ParseNode* iter) {
+ MOZ_ASSERT(emitterMode != BytecodeEmitter::SelfHosting,
+ "yield* is prohibited in self-hosted code because it can run "
+ "user-modifiable iteration code");
+
+ MOZ_ASSERT(sc->isSuspendableContext());
+ MOZ_ASSERT(sc->asSuspendableContext()->isGenerator());
+
+ // Step 1.
+ IteratorKind iterKind = sc->asSuspendableContext()->isAsync()
+ ? IteratorKind::Async
+ : IteratorKind::Sync;
+ bool needsIteratorResult = sc->asSuspendableContext()->needsIteratorResult();
+
+ // Steps 2-5.
+ if (!emitTree(iter)) {
+ // [stack] ITERABLE
+ return false;
+ }
+ if (iterKind == IteratorKind::Async) {
+ if (!emitAsyncIterator()) {
+ // [stack] NEXT ITER
+ return false;
+ }
+ } else {
+ if (!emitIterator()) {
+ // [stack] NEXT ITER
+ return false;
+ }
+ }
+
+ // Step 6.
+ // Start with NormalCompletion(undefined).
+ if (!emit1(JSOp::Undefined)) {
+ // [stack] NEXT ITER RECEIVED
+ return false;
+ }
+ if (!emitPushResumeKind(GeneratorResumeKind::Next)) {
+ // [stack] NEXT ITER RECEIVED RESUMEKIND
+ return false;
+ }
+
+ const int32_t startDepth = bytecodeSection().stackDepth();
+ MOZ_ASSERT(startDepth >= 4);
+
+ // Step 7 is a loop.
+ LoopControl loopInfo(this, StatementKind::YieldStar);
+ if (!loopInfo.emitLoopHead(this, Nothing())) {
+ // [stack] NEXT ITER RECEIVED RESUMEKIND
+ return false;
+ }
+
+ // Step 7.a. Check for Normal completion.
+ if (!emit1(JSOp::Dup)) {
+ // [stack] NEXT ITER RECEIVED RESUMEKIND RESUMEKIND
+ return false;
+ }
+ if (!emitPushResumeKind(GeneratorResumeKind::Next)) {
+ // [stack] NEXT ITER RECEIVED RESUMEKIND RESUMEKIND NORMAL
+ return false;
+ }
+ if (!emit1(JSOp::StrictEq)) {
+ // [stack] NEXT ITER RECEIVED RESUMEKIND IS_NORMAL
+ return false;
+ }
+
+ InternalIfEmitter ifKind(this);
+ if (!ifKind.emitThenElse()) {
+ // [stack] NEXT ITER RECEIVED RESUMEKIND
+ return false;
+ }
+ {
+ if (!emit1(JSOp::Pop)) {
+ // [stack] NEXT ITER RECEIVED
+ return false;
+ }
+
+ // Step 7.a.i.
+ // result = iter.next(received)
+ if (!emit2(JSOp::Unpick, 2)) {
+ // [stack] RECEIVED NEXT ITER
+ return false;
+ }
+ if (!emit1(JSOp::Dup2)) {
+ // [stack] RECEIVED NEXT ITER NEXT ITER
+ return false;
+ }
+ if (!emit2(JSOp::Pick, 4)) {
+ // [stack] NEXT ITER NEXT ITER RECEIVED
+ return false;
+ }
+ if (!emitCall(JSOp::Call, 1, iter)) {
+ // [stack] NEXT ITER RESULT
+ return false;
+ }
+
+ // Step 7.a.ii.
+ if (iterKind == IteratorKind::Async) {
+ if (!emitAwaitInInnermostScope()) {
+ // [stack] NEXT ITER RESULT
+ return false;
+ }
+ }
+
+ // Step 7.a.iii.
+ if (!emitCheckIsObj(CheckIsObjectKind::IteratorNext)) {
+ // [stack] NEXT ITER RESULT
+ return false;
+ }
+
+ // Bytecode for steps 7.a.iv-vii is emitted after the ifKind if-else because
+ // it's shared with other branches.
+ }
+
+ // Step 7.b. Check for Throw completion.
+ if (!ifKind.emitElseIf(Nothing())) {
+ // [stack] NEXT ITER RECEIVED RESUMEKIND
+ return false;
+ }
+ if (!emit1(JSOp::Dup)) {
+ // [stack] NEXT ITER RECEIVED RESUMEKIND RESUMEKIND
+ return false;
+ }
+ if (!emitPushResumeKind(GeneratorResumeKind::Throw)) {
+ // [stack] NEXT ITER RECEIVED RESUMEKIND RESUMEKIND THROW
+ return false;
+ }
+ if (!emit1(JSOp::StrictEq)) {
+ // [stack] NEXT ITER RECEIVED RESUMEKIND IS_THROW
+ return false;
+ }
+ if (!ifKind.emitThenElse()) {
+ // [stack] NEXT ITER RECEIVED RESUMEKIND
+ return false;
+ }
+ {
+ if (!emit1(JSOp::Pop)) {
+ // [stack] NEXT ITER RECEIVED
+ return false;
+ }
+ // Step 7.b.i.
+ if (!emitDupAt(1)) {
+ // [stack] NEXT ITER RECEIVED ITER
+ return false;
+ }
+ if (!emit1(JSOp::Dup)) {
+ // [stack] NEXT ITER RECEIVED ITER ITER
+ return false;
+ }
+ if (!emitAtomOp(JSOp::GetProp,
+ TaggedParserAtomIndex::WellKnown::throw_())) {
+ // [stack] NEXT ITER RECEIVED ITER THROW
+ return false;
+ }
+
+ // Step 7.b.ii.
+ InternalIfEmitter ifThrowMethodIsNotDefined(this);
+ if (!emit1(JSOp::IsNullOrUndefined)) {
+ // [stack] NEXT ITER RECEIVED ITER THROW NULL-OR-UNDEF
+ return false;
+ }
+
+ if (!ifThrowMethodIsNotDefined.emitThenElse(
+ IfEmitter::ConditionKind::Negative)) {
+ // [stack] NEXT ITER RECEIVED ITER THROW
+ return false;
+ }
+
+ // Step 7.b.ii.1.
+ // RESULT = ITER.throw(EXCEPTION)
+ if (!emit1(JSOp::Swap)) {
+ // [stack] NEXT ITER RECEIVED THROW ITER
+ return false;
+ }
+ if (!emit2(JSOp::Pick, 2)) {
+ // [stack] NEXT ITER THROW ITER RECEIVED
+ return false;
+ }
+ if (!emitCall(JSOp::Call, 1, iter)) {
+ // [stack] NEXT ITER RESULT
+ return false;
+ }
+
+ // Step 7.b.ii.2.
+ if (iterKind == IteratorKind::Async) {
+ if (!emitAwaitInInnermostScope()) {
+ // [stack] NEXT ITER RESULT
+ return false;
+ }
+ }
+
+ // Step 7.b.ii.4.
+ if (!emitCheckIsObj(CheckIsObjectKind::IteratorThrow)) {
+ // [stack] NEXT ITER RESULT
+ return false;
+ }
+
+ // Bytecode for steps 7.b.ii.5-8 is emitted after the ifKind if-else because
+ // it's shared with other branches.
+
+ // Step 7.b.iii.
+ if (!ifThrowMethodIsNotDefined.emitElse()) {
+ // [stack] NEXT ITER RECEIVED ITER THROW
+ return false;
+ }
+ if (!emit1(JSOp::Pop)) {
+ // [stack] NEXT ITER RECEIVED ITER
+ return false;
+ }
+
+ // Steps 7.b.iii.1-4.
+ //
+ // If the iterator does not have a "throw" method, it calls IteratorClose
+ // and then throws a TypeError.
+ if (!emitIteratorCloseInInnermostScope(iterKind, CompletionKind::Normal,
+ getSelfHostedIterFor(iter))) {
+ // [stack] NEXT ITER RECEIVED ITER
+ return false;
+ }
+ // Steps 7.b.iii.5-6.
+ if (!emit2(JSOp::ThrowMsg, uint8_t(ThrowMsgKind::IteratorNoThrow))) {
+ // [stack] NEXT ITER RECEIVED ITER
+ // [stack] # throw
+ return false;
+ }
+
+ if (!ifThrowMethodIsNotDefined.emitEnd()) {
+ return false;
+ }
+ }
+
+ // Step 7.c. It must be a Return completion.
+ if (!ifKind.emitElse()) {
+ // [stack] NEXT ITER RECEIVED RESUMEKIND
+ return false;
+ }
+ {
+ if (!emit1(JSOp::Pop)) {
+ // [stack] NEXT ITER RECEIVED
+ return false;
+ }
+
+ // Step 7.c.i.
+ //
+ // Call iterator.return() for receiving a "forced return" completion from
+ // the generator.
+
+ // Step 7.c.ii.
+ //
+ // Get the "return" method.
+ if (!emitDupAt(1)) {
+ // [stack] NEXT ITER RECEIVED ITER
+ return false;
+ }
+ if (!emit1(JSOp::Dup)) {
+ // [stack] NEXT ITER RECEIVED ITER ITER
+ return false;
+ }
+ if (!emitAtomOp(JSOp::GetProp,
+ TaggedParserAtomIndex::WellKnown::return_())) {
+ // [stack] NEXT ITER RECEIVED ITER RET
+ return false;
+ }
+
+ // Step 7.c.iii.
+ //
+ // Do nothing if "return" is undefined or null.
+ InternalIfEmitter ifReturnMethodIsDefined(this);
+ if (!emit1(JSOp::IsNullOrUndefined)) {
+ // [stack] NEXT ITER RECEIVED ITER RET NULL-OR-UNDEF
+ return false;
+ }
+
+ // Step 7.c.iv.
+ //
+ // Call "return" with the argument passed to Generator.prototype.return.
+ if (!ifReturnMethodIsDefined.emitThenElse(
+ IfEmitter::ConditionKind::Negative)) {
+ // [stack] NEXT ITER RECEIVED ITER RET
+ return false;
+ }
+ if (!emit1(JSOp::Swap)) {
+ // [stack] NEXT ITER RECEIVED RET ITER
+ return false;
+ }
+ if (!emit2(JSOp::Pick, 2)) {
+ // [stack] NEXT ITER RET ITER RECEIVED
+ return false;
+ }
+ if (needsIteratorResult) {
+ if (!emitAtomOp(JSOp::GetProp,
+ TaggedParserAtomIndex::WellKnown::value())) {
+ // [stack] NEXT ITER RET ITER VAL
+ return false;
+ }
+ }
+ if (!emitCall(JSOp::Call, 1)) {
+ // [stack] NEXT ITER RESULT
+ return false;
+ }
+
+ // Step 7.c.v.
+ if (iterKind == IteratorKind::Async) {
+ if (!emitAwaitInInnermostScope()) {
+ // [stack] NEXT ITER RESULT
+ return false;
+ }
+ }
+
+ // Step 7.c.vi.
+ if (!emitCheckIsObj(CheckIsObjectKind::IteratorReturn)) {
+ // [stack] NEXT ITER RESULT
+ return false;
+ }
+
+ // Check if the returned object from iterator.return() is done. If not,
+ // continue yielding.
+
+ // Steps 7.c.vii-viii.
+ InternalIfEmitter ifReturnDone(this);
+ if (!emit1(JSOp::Dup)) {
+ // [stack] NEXT ITER RESULT RESULT
+ return false;
+ }
+ if (!emitAtomOp(JSOp::GetProp, TaggedParserAtomIndex::WellKnown::done())) {
+ // [stack] NEXT ITER RESULT DONE
+ return false;
+ }
+ if (!ifReturnDone.emitThenElse()) {
+ // [stack] NEXT ITER RESULT
+ return false;
+ }
+
+ // Step 7.c.viii.1.
+ if (!emitAtomOp(JSOp::GetProp, TaggedParserAtomIndex::WellKnown::value())) {
+ // [stack] NEXT ITER VALUE
+ return false;
+ }
+ if (needsIteratorResult) {
+ if (!emitPrepareIteratorResult()) {
+ // [stack] NEXT ITER VALUE RESULT
+ return false;
+ }
+ if (!emit1(JSOp::Swap)) {
+ // [stack] NEXT ITER RESULT VALUE
+ return false;
+ }
+ if (!emitFinishIteratorResult(true)) {
+ // [stack] NEXT ITER RESULT
+ return false;
+ }
+ }
+
+ if (!ifReturnDone.emitElse()) {
+ // [stack] NEXT ITER RESULT
+ return false;
+ }
+
+ // Jump to continue label for steps 7.c.ix-x.
+ if (!emitJump(JSOp::Goto, &loopInfo.continues)) {
+ // [stack] NEXT ITER RESULT
+ return false;
+ }
+
+ if (!ifReturnDone.emitEnd()) {
+ // [stack] NEXT ITER RESULT
+ return false;
+ }
+
+ // Step 7.c.iii.
+ if (!ifReturnMethodIsDefined.emitElse()) {
+ // [stack] NEXT ITER RECEIVED ITER RET
+ return false;
+ }
+ if (!emitPopN(2)) {
+ // [stack] NEXT ITER RECEIVED
+ return false;
+ }
+ if (iterKind == IteratorKind::Async) {
+ // Step 7.c.iii.1.
+ if (!emitAwaitInInnermostScope()) {
+ // [stack] NEXT ITER RECEIVED
+ return false;
+ }
+ }
+ if (!ifReturnMethodIsDefined.emitEnd()) {
+ // [stack] NEXT ITER RECEIVED
+ return false;
+ }
+
+ // Perform a "forced generator return".
+ //
+ // Step 7.c.iii.2.
+ // Step 7.c.viii.2.
+ if (!emitGetDotGeneratorInInnermostScope()) {
+ // [stack] NEXT ITER RESULT GENOBJ
+ return false;
+ }
+ if (!emitPushResumeKind(GeneratorResumeKind::Return)) {
+ // [stack] NEXT ITER RESULT GENOBJ RESUMEKIND
+ return false;
+ }
+ if (!emit1(JSOp::CheckResumeKind)) {
+ // [stack] NEXT ITER RESULT GENOBJ RESUMEKIND
+ return false;
+ }
+ }
+
+ if (!ifKind.emitEnd()) {
+ // [stack] NEXT ITER RESULT
+ return false;
+ }
+
+ // Shared tail for Normal/Throw completions.
+ //
+ // Steps 7.a.iv-v.
+ // Steps 7.b.ii.5-6.
+ //
+ // [stack] NEXT ITER RESULT
+
+ // if (result.done) break;
+ if (!emit1(JSOp::Dup)) {
+ // [stack] NEXT ITER RESULT RESULT
+ return false;
+ }
+ if (!emitAtomOp(JSOp::GetProp, TaggedParserAtomIndex::WellKnown::done())) {
+ // [stack] NEXT ITER RESULT DONE
+ return false;
+ }
+ if (!emitJump(JSOp::JumpIfTrue, &loopInfo.breaks)) {
+ // [stack] NEXT ITER RESULT
+ return false;
+ }
+
+ // Steps 7.a.vi-vii.
+ // Steps 7.b.ii.7-8.
+ // Steps 7.c.ix-x.
+ if (!loopInfo.emitContinueTarget(this)) {
+ // [stack] NEXT ITER RESULT
+ return false;
+ }
+ if (iterKind == IteratorKind::Async) {
+ if (!emitAtomOp(JSOp::GetProp, TaggedParserAtomIndex::WellKnown::value())) {
+ // [stack] NEXT ITER RESULT
+ return false;
+ }
+ }
+ if (!emitGetDotGeneratorInInnermostScope()) {
+ // [stack] NEXT ITER RESULT GENOBJ
+ return false;
+ }
+ if (!emitYieldOp(JSOp::Yield)) {
+ // [stack] NEXT ITER RVAL GENOBJ RESUMEKIND
+ return false;
+ }
+ if (!emit1(JSOp::Swap)) {
+ // [stack] NEXT ITER RVAL RESUMEKIND GENOBJ
+ return false;
+ }
+ if (!emit1(JSOp::Pop)) {
+ // [stack] NEXT ITER RVAL RESUMEKIND
+ return false;
+ }
+ if (!loopInfo.emitLoopEnd(this, JSOp::Goto, TryNoteKind::Loop)) {
+ // [stack] NEXT ITER RVAL RESUMEKIND
+ return false;
+ }
+
+ // Jumps to this point have 3 (instead of 4) values on the stack.
+ MOZ_ASSERT(bytecodeSection().stackDepth() == startDepth);
+ bytecodeSection().setStackDepth(startDepth - 1);
+
+ // [stack] NEXT ITER RESULT
+
+ // Step 7.a.v.1.
+ // Step 7.b.ii.6.a.
+ //
+ // result.value
+ if (!emit2(JSOp::Unpick, 2)) {
+ // [stack] RESULT NEXT ITER
+ return false;
+ }
+ if (!emitPopN(2)) {
+ // [stack] RESULT
+ return false;
+ }
+ if (!emitAtomOp(JSOp::GetProp, TaggedParserAtomIndex::WellKnown::value())) {
+ // [stack] VALUE
+ return false;
+ }
+
+ MOZ_ASSERT(bytecodeSection().stackDepth() == startDepth - 3);
+
+ return true;
+}
+
+bool BytecodeEmitter::emitStatementList(ListNode* stmtList) {
+ for (ParseNode* stmt : stmtList->contents()) {
+ if (!emitTree(stmt)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+bool BytecodeEmitter::emitExpressionStatement(UnaryNode* exprStmt) {
+ MOZ_ASSERT(exprStmt->isKind(ParseNodeKind::ExpressionStmt));
+
+ /*
+ * Top-level or called-from-a-native JS_Execute/EvaluateScript,
+ * debugger, and eval frames may need the value of the ultimate
+ * expression statement as the script's result, despite the fact
+ * that it appears useless to the compiler.
+ *
+ * API users may also set the JSOPTION_NO_SCRIPT_RVAL option when
+ * calling JS_Compile* to suppress JSOp::SetRval.
+ */
+ bool wantval = false;
+ bool useful = false;
+ if (sc->isTopLevelContext()) {
+ useful = wantval = !sc->noScriptRval();
+ }
+
+ /* Don't eliminate expressions with side effects. */
+ ParseNode* expr = exprStmt->kid();
+ if (!useful) {
+ if (!checkSideEffects(expr, &useful)) {
+ return false;
+ }
+
+ /*
+ * Don't eliminate apparently useless expressions if they are labeled
+ * expression statements. The startOffset() test catches the case
+ * where we are nesting in emitTree for a labeled compound statement.
+ */
+ if (innermostNestableControl &&
+ innermostNestableControl->is<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(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);
+ 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);
+
+ if (!eoe.prepareForObj()) {
+ // [stack]
+ return false;
+ }
+
+ if (!emitOptionalTree(&elemExpr->expression(), oe)) {
+ // [stack] OBJ
+ return false;
+ }
+
+ if (elemExpr->isKind(ParseNodeKind::OptionalElemExpr)) {
+ if (!oe.emitJumpShortCircuit()) {
+ // [stack] # if Jump
+ // [stack] UNDEFINED-OR-NULL
+ // [stack] # otherwise
+ // [stack] OBJ
+ return false;
+ }
+ }
+
+ if (!eoe.prepareForKey()) {
+ // [stack] OBJ
+ return false;
+ }
+
+ if (!emitTree(&elemExpr->key())) {
+ // [stack] OBJ KEY
+ return false;
+ }
+
+ if (!eoe.emitDelete()) {
+ // [stack] SUCCEEDED
+ return false;
+ }
+
+ return true;
+}
+
+bool BytecodeEmitter::emitDebugCheckSelfHosted() {
+ // [stack] CALLEE
+
+#ifdef DEBUG
+ if (!emit1(JSOp::DebugCheckSelfHosted)) {
+ // [stack] CALLEE
+ return false;
+ }
+#endif
+
+ return true;
+}
+
+bool BytecodeEmitter::emitSelfHostedCallFunction(CallNode* callNode, JSOp op) {
+ // Special-casing of callFunction to emit bytecode that directly
+ // invokes the callee with the correct |this| object and arguments.
+ // callFunction(fun, thisArg, arg0, arg1) thus becomes:
+ // - emit lookup for fun
+ // - emit lookup for thisArg
+ // - emit lookups for arg0, arg1
+ //
+ // argc is set to the amount of actually emitted args and the
+ // emitting of args below is disabled by setting emitArgs to false.
+ NameNode* calleeNode = &callNode->left()->as<NameNode>();
+ ListNode* argsList = &callNode->right()->as<ListNode>();
+
+ MOZ_ASSERT(argsList->count() >= 2);
+
+ MOZ_ASSERT(callNode->callOp() == JSOp::Call);
+
+ bool constructing =
+ calleeNode->name() ==
+ TaggedParserAtomIndex::WellKnown::constructContentFunction();
+ ParseNode* funNode = argsList->head();
+
+ if (!emitTree(funNode)) {
+ // [stack] CALLEE
+ return false;
+ }
+
+#ifdef DEBUG
+ MOZ_ASSERT(op == JSOp::Call || op == JSOp::CallContent ||
+ op == JSOp::NewContent);
+ if (op == JSOp::Call) {
+ if (!emitDebugCheckSelfHosted()) {
+ // [stack] CALLEE
+ return false;
+ }
+ }
+#endif
+
+ ParseNode* thisOrNewTarget = funNode->pn_next;
+ if (constructing) {
+ // Save off the new.target value, but here emit a proper |this| for a
+ // constructing call.
+ if (!emit1(JSOp::IsConstructing)) {
+ // [stack] CALLEE IS_CONSTRUCTING
+ return false;
+ }
+ } else {
+ // It's |this|, emit it.
+ if (!emitTree(thisOrNewTarget)) {
+ // [stack] CALLEE THIS
+ return false;
+ }
+ }
+
+ for (ParseNode* argpn : argsList->contentsFrom(thisOrNewTarget->pn_next)) {
+ if (!emitTree(argpn)) {
+ // [stack] CALLEE ... ARGS...
+ return false;
+ }
+ }
+
+ if (constructing) {
+ if (!emitTree(thisOrNewTarget)) {
+ // [stack] CALLEE IS_CONSTRUCTING ARGS... NEW.TARGET
+ return false;
+ }
+ }
+
+ uint32_t argc = argsList->count() - 2;
+ if (!emitCall(op, argc)) {
+ // [stack] RVAL
+ return false;
+ }
+
+ return true;
+}
+
+bool BytecodeEmitter::emitSelfHostedResumeGenerator(CallNode* callNode) {
+ ListNode* argsList = &callNode->right()->as<ListNode>();
+
+ // Syntax: resumeGenerator(gen, value, 'next'|'throw'|'return')
+ MOZ_ASSERT(argsList->count() == 3);
+
+ ParseNode* genNode = argsList->head();
+ if (!emitTree(genNode)) {
+ // [stack] GENERATOR
+ return false;
+ }
+
+ ParseNode* valNode = genNode->pn_next;
+ if (!emitTree(valNode)) {
+ // [stack] GENERATOR VALUE
+ return false;
+ }
+
+ ParseNode* kindNode = valNode->pn_next;
+ MOZ_ASSERT(kindNode->isKind(ParseNodeKind::StringExpr));
+ GeneratorResumeKind kind =
+ ParserAtomToResumeKind(kindNode->as<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(CallNode* callNode) {
+ ListNode* argsList = &callNode->right()->as<ListNode>();
+
+ MOZ_ASSERT(argsList->count() == 1);
+
+ // We're just here as a sentinel. Pass the value through directly.
+ return emitTree(argsList->head());
+}
+
+bool BytecodeEmitter::emitSelfHostedAllowContentIterWith(CallNode* callNode) {
+ ListNode* argsList = &callNode->right()->as<ListNode>();
+
+ MOZ_ASSERT(argsList->count() == 2 || argsList->count() == 3);
+
+ // We're just here as a sentinel. Pass the value through directly.
+ return emitTree(argsList->head());
+}
+
+bool BytecodeEmitter::emitSelfHostedDefineDataProperty(CallNode* callNode) {
+ ListNode* argsList = &callNode->right()->as<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(CallNode* callNode) {
+ ListNode* argsList = &callNode->right()->as<ListNode>();
+
+ MOZ_ASSERT(argsList->count() == 2);
+
+ ParseNode* idNode = argsList->head();
+ if (!emitTree(idNode)) {
+ return false;
+ }
+
+ ParseNode* objNode = idNode->pn_next;
+ if (!emitTree(objNode)) {
+ return false;
+ }
+
+ return emit1(JSOp::HasOwn);
+}
+
+bool BytecodeEmitter::emitSelfHostedGetPropertySuper(CallNode* callNode) {
+ ListNode* argsList = &callNode->right()->as<ListNode>();
+
+ MOZ_ASSERT(argsList->count() == 3);
+
+ ParseNode* objNode = argsList->head();
+ ParseNode* idNode = objNode->pn_next;
+ ParseNode* receiverNode = idNode->pn_next;
+
+ if (!emitTree(receiverNode)) {
+ return false;
+ }
+
+ if (!emitTree(idNode)) {
+ return false;
+ }
+
+ if (!emitTree(objNode)) {
+ return false;
+ }
+
+ return emitElemOpBase(JSOp::GetElemSuper);
+}
+
+bool BytecodeEmitter::emitSelfHostedToNumeric(CallNode* callNode) {
+ ListNode* argsList = &callNode->right()->as<ListNode>();
+
+ MOZ_ASSERT(argsList->count() == 1);
+
+ ParseNode* argNode = argsList->head();
+
+ if (!emitTree(argNode)) {
+ return false;
+ }
+
+ return emit1(JSOp::ToNumeric);
+}
+
+bool BytecodeEmitter::emitSelfHostedToString(CallNode* callNode) {
+ ListNode* argsList = &callNode->right()->as<ListNode>();
+
+ MOZ_ASSERT(argsList->count() == 1);
+
+ ParseNode* argNode = argsList->head();
+
+ if (!emitTree(argNode)) {
+ return false;
+ }
+
+ return emit1(JSOp::ToString);
+}
+
+bool BytecodeEmitter::emitSelfHostedIsNullOrUndefined(CallNode* callNode) {
+ ListNode* argsList = &callNode->right()->as<ListNode>();
+
+ MOZ_ASSERT(argsList->count() == 1);
+
+ ParseNode* argNode = argsList->head();
+
+ if (!emitTree(argNode)) {
+ // [stack] ARG
+ return false;
+ }
+ if (!emit1(JSOp::IsNullOrUndefined)) {
+ // [stack] ARG IS_NULL_OR_UNDEF
+ return false;
+ }
+ if (!emit1(JSOp::Swap)) {
+ // [stack] IS_NULL_OR_UNDEF ARG
+ return false;
+ }
+ if (!emit1(JSOp::Pop)) {
+ // [stack] IS_NULL_OR_UNDEF
+ return false;
+ }
+ return true;
+}
+
+bool BytecodeEmitter::emitSelfHostedGetBuiltinConstructorOrPrototype(
+ CallNode* callNode, bool isConstructor) {
+ ListNode* argsList = &callNode->right()->as<ListNode>();
+
+ MOZ_ASSERT(argsList->count() == 1);
+
+ ParseNode* argNode = argsList->head();
+
+ if (!argNode->isKind(ParseNodeKind::StringExpr)) {
+ reportError(callNode, JSMSG_UNEXPECTED_TYPE, "built-in name",
+ "not a string constant");
+ return false;
+ }
+
+ auto name = argNode->as<NameNode>().atom();
+
+ BuiltinObjectKind kind;
+ if (isConstructor) {
+ kind = BuiltinConstructorForName(name);
+ } else {
+ kind = BuiltinPrototypeForName(name);
+ }
+
+ if (kind == BuiltinObjectKind::None) {
+ reportError(callNode, JSMSG_UNEXPECTED_TYPE, "built-in name",
+ "not a valid built-in");
+ return false;
+ }
+
+ return emitBuiltinObject(kind);
+}
+
+bool BytecodeEmitter::emitSelfHostedGetBuiltinConstructor(CallNode* callNode) {
+ return emitSelfHostedGetBuiltinConstructorOrPrototype(
+ callNode, /* isConstructor = */ true);
+}
+
+bool BytecodeEmitter::emitSelfHostedGetBuiltinPrototype(CallNode* callNode) {
+ return emitSelfHostedGetBuiltinConstructorOrPrototype(
+ callNode, /* isConstructor = */ false);
+}
+
+JS::SymbolCode ParserAtomToSymbolCode(TaggedParserAtomIndex atom) {
+ // NOTE: This is a linear search, but the set of entries is quite small and
+ // this is only used for initial self-hosted parse.
+#define MATCH_WELL_KNOWN_SYMBOL(NAME) \
+ if (atom == TaggedParserAtomIndex::WellKnown::NAME()) { \
+ return JS::SymbolCode::NAME; \
+ }
+ JS_FOR_EACH_WELL_KNOWN_SYMBOL(MATCH_WELL_KNOWN_SYMBOL)
+#undef MATCH_WELL_KNOWN_SYMBOL
+
+ return JS::SymbolCode::Limit;
+}
+
+bool BytecodeEmitter::emitSelfHostedGetBuiltinSymbol(CallNode* callNode) {
+ ListNode* argsList = &callNode->right()->as<ListNode>();
+
+ MOZ_ASSERT(argsList->count() == 1);
+
+ ParseNode* argNode = argsList->head();
+
+ if (!argNode->isKind(ParseNodeKind::StringExpr)) {
+ reportError(callNode, JSMSG_UNEXPECTED_TYPE, "built-in name",
+ "not a string constant");
+ return false;
+ }
+
+ auto name = argNode->as<NameNode>().atom();
+
+ JS::SymbolCode code = ParserAtomToSymbolCode(name);
+ if (code == JS::SymbolCode::Limit) {
+ reportError(callNode, JSMSG_UNEXPECTED_TYPE, "built-in name",
+ "not a valid built-in");
+ return false;
+ }
+
+ return emit2(JSOp::Symbol, uint8_t(code));
+}
+
+bool BytecodeEmitter::emitSelfHostedArgumentsLength(CallNode* callNode) {
+ MOZ_ASSERT(!sc->asFunctionBox()->needsArgsObj());
+ sc->asFunctionBox()->setUsesArgumentsIntrinsics();
+
+ MOZ_ASSERT(callNode->right()->as<ListNode>().count() == 0);
+
+ return emit1(JSOp::ArgumentsLength);
+}
+
+bool BytecodeEmitter::emitSelfHostedGetArgument(CallNode* callNode) {
+ MOZ_ASSERT(!sc->asFunctionBox()->needsArgsObj());
+ sc->asFunctionBox()->setUsesArgumentsIntrinsics();
+
+ ListNode* argsList = &callNode->right()->as<ListNode>();
+ MOZ_ASSERT(argsList->count() == 1);
+
+ ParseNode* argNode = argsList->head();
+ if (!emitTree(argNode)) {
+ return false;
+ }
+
+ return emit1(JSOp::GetActualArg);
+}
+
+#ifdef DEBUG
+void BytecodeEmitter::assertSelfHostedExpectedTopLevel(ParseNode* node) {
+ // The function argument is expected to be a simple binding/function name.
+ // Eg. `function foo() { }; SpecialIntrinsic(foo)`
+ MOZ_ASSERT(node->isKind(ParseNodeKind::Name),
+ "argument must be a function name");
+ TaggedParserAtomIndex targetName = node->as<NameNode>().name();
+
+ // The special intrinsics must follow the target functions definition. A
+ // simple assert is fine here since any hoisted function will cause a non-null
+ // value to be set here.
+ MOZ_ASSERT(prevSelfHostedTopLevelFunction);
+
+ // The target function must match the most recently defined top-level
+ // self-hosted function.
+ MOZ_ASSERT(prevSelfHostedTopLevelFunction->explicitName() == targetName,
+ "selfhost decorator must immediately follow target function");
+}
+#endif
+
+bool BytecodeEmitter::emitSelfHostedSetIsInlinableLargeFunction(
+ CallNode* callNode) {
+#ifdef DEBUG
+ ListNode* argsList = &callNode->right()->as<ListNode>();
+
+ MOZ_ASSERT(argsList->count() == 1);
+
+ assertSelfHostedExpectedTopLevel(argsList->head());
+#endif
+
+ MOZ_ASSERT(prevSelfHostedTopLevelFunction->isInitialCompilation);
+ prevSelfHostedTopLevelFunction->setIsInlinableLargeFunction();
+
+ // This is still a call node, so we must generate a stack value.
+ return emit1(JSOp::Undefined);
+}
+
+bool BytecodeEmitter::emitSelfHostedSetCanonicalName(CallNode* callNode) {
+ ListNode* argsList = &callNode->right()->as<ListNode>();
+
+ MOZ_ASSERT(argsList->count() == 2);
+
+#ifdef DEBUG
+ assertSelfHostedExpectedTopLevel(argsList->head());
+#endif
+
+ ParseNode* nameNode = argsList->last();
+ MOZ_ASSERT(nameNode->isKind(ParseNodeKind::StringExpr));
+ TaggedParserAtomIndex specName = nameNode->as<NameNode>().atom();
+ // Canonical name must be atomized.
+ compilationState.parserAtoms.markUsedByStencil(specName,
+ ParserAtom::Atomize::Yes);
+
+ // Store the canonical name for instantiation.
+ prevSelfHostedTopLevelFunction->functionStencil().setSelfHostedCanonicalName(
+ specName);
+
+ return emit1(JSOp::Undefined);
+}
+
+#ifdef DEBUG
+void BytecodeEmitter::assertSelfHostedUnsafeGetReservedSlot(
+ ListNode* argsList) {
+ MOZ_ASSERT(argsList->count() == 2);
+
+ ParseNode* objNode = argsList->head();
+ ParseNode* slotNode = objNode->pn_next;
+
+ // Ensure that the slot argument is fixed, this is required by the JITs.
+ MOZ_ASSERT(slotNode->isKind(ParseNodeKind::NumberExpr),
+ "slot argument must be a constant");
+}
+
+void BytecodeEmitter::assertSelfHostedUnsafeSetReservedSlot(
+ ListNode* argsList) {
+ MOZ_ASSERT(argsList->count() == 3);
+
+ ParseNode* objNode = argsList->head();
+ ParseNode* slotNode = objNode->pn_next;
+
+ // Ensure that the slot argument is fixed, this is required by the JITs.
+ MOZ_ASSERT(slotNode->isKind(ParseNodeKind::NumberExpr),
+ "slot argument must be a constant");
+}
+#endif
+
+/* A version of emitCalleeAndThis for the optional cases:
+ * * a?.()
+ * * a?.b()
+ * * a?.["b"]()
+ * * (a?.b)()
+ * * a?.#b()
+ *
+ * See emitCallOrNew and emitOptionalCall for more context.
+ */
+bool BytecodeEmitter::emitOptionalCalleeAndThis(ParseNode* callee,
+ CallNode* call,
+ CallOrNewEmitter& cone,
+ OptionalEmitter& oe) {
+ AutoCheckRecursionLimit recursion(fc);
+ if (!recursion.check(fc)) {
+ return false;
+ }
+
+ switch (ParseNodeKind kind = callee->getKind()) {
+ case ParseNodeKind::Name: {
+ auto name = callee->as<NameNode>().name();
+ if (!cone.emitNameCallee(name)) {
+ // [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;
+ MOZ_ASSERT(!elem->key().isKind(ParseNodeKind::PrivateName));
+ ElemOpEmitter& eoe = cone.prepareForElemCallee(isSuper);
+ if (!emitOptionalElemExpression(elem, eoe, isSuper, oe)) {
+ // [stack] CALLEE THIS
+ return false;
+ }
+ break;
+ }
+ case ParseNodeKind::ElemExpr: {
+ PropertyByValue* elem = &callee->as<PropertyByValue>();
+ bool isSuper = elem->isSuper();
+ MOZ_ASSERT(!elem->key().isKind(ParseNodeKind::PrivateName));
+ ElemOpEmitter& eoe = cone.prepareForElemCallee(isSuper);
+ if (!emitOptionalElemExpression(elem, eoe, isSuper, oe)) {
+ // [stack] CALLEE THIS
+ return false;
+ }
+ break;
+ }
+
+ case ParseNodeKind::PrivateMemberExpr:
+ case ParseNodeKind::OptionalPrivateMemberExpr: {
+ PrivateMemberAccessBase* privateExpr =
+ &callee->as<PrivateMemberAccessBase>();
+ PrivateOpEmitter& xoe =
+ cone.prepareForPrivateCallee(privateExpr->privateName().name());
+ if (!emitOptionalPrivateExpression(privateExpr, xoe, oe)) {
+ // [stack] CALLEE THIS
+ return false;
+ }
+ break;
+ }
+
+ case ParseNodeKind::Function:
+ if (!cone.prepareForFunctionCallee()) {
+ return false;
+ }
+ if (!emitOptionalTree(callee, oe)) {
+ // [stack] CALLEE
+ return false;
+ }
+ break;
+
+ case ParseNodeKind::OptionalChain: {
+ return emitCalleeAndThisForOptionalChain(&callee->as<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: {
+ auto name = callee->as<NameNode>().name();
+ if (!cone.emitNameCallee(name)) {
+ // [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();
+ MOZ_ASSERT(!elem->key().isKind(ParseNodeKind::PrivateName));
+ ElemOpEmitter& eoe = cone.prepareForElemCallee(isSuper);
+ if (!emitElemObjAndKey(elem, isSuper, eoe)) {
+ // [stack] # if Super
+ // [stack] THIS? THIS KEY
+ // [stack] # otherwise
+ // [stack] OBJ? OBJ KEY
+ return false;
+ }
+ if (!eoe.emitGet()) {
+ // [stack] CALLEE THIS?
+ return false;
+ }
+
+ break;
+ }
+ case ParseNodeKind::PrivateMemberExpr: {
+ MOZ_ASSERT(emitterMode != BytecodeEmitter::SelfHosting);
+ PrivateMemberAccessBase* privateExpr =
+ &callee->as<PrivateMemberAccessBase>();
+ PrivateOpEmitter& xoe =
+ cone.prepareForPrivateCallee(privateExpr->privateName().name());
+ if (!emitTree(&privateExpr->expression())) {
+ // [stack] OBJ
+ return false;
+ }
+ if (!xoe.emitReference()) {
+ // [stack] OBJ NAME
+ return false;
+ }
+ if (!xoe.emitGetForCallOrNew()) {
+ // [stack] CALLEE THIS
+ return false;
+ }
+
+ break;
+ }
+ case ParseNodeKind::Function:
+ if (!cone.prepareForFunctionCallee()) {
+ return false;
+ }
+ if (!emitTree(callee)) {
+ // [stack] CALLEE
+ return false;
+ }
+ break;
+ case ParseNodeKind::SuperBase:
+ MOZ_ASSERT(call->isKind(ParseNodeKind::SuperCallExpr));
+ MOZ_ASSERT(callee->isKind(ParseNodeKind::SuperBase));
+ if (!cone.emitSuperCallee()) {
+ // [stack] CALLEE IsConstructing
+ return false;
+ }
+ break;
+ case ParseNodeKind::OptionalChain: {
+ return emitCalleeAndThisForOptionalChain(&callee->as<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;
+}
+
+ParseNode* BytecodeEmitter::getCoordNode(ParseNode* callNode,
+ ParseNode* calleeNode, JSOp op,
+ ListNode* argsList) {
+ ParseNode* coordNode = callNode;
+ if (op == JSOp::Call || op == JSOp::SpreadCall) {
+ // Default to using the location of the `(` itself.
+ // obj[expr]() // expression
+ // ^ // column coord
+ coordNode = argsList;
+
+ switch (calleeNode->getKind()) {
+ case ParseNodeKind::DotExpr:
+ // Use the position of a property access identifier.
+ //
+ // obj().aprop() // expression
+ // ^ // column coord
+ //
+ // Note: Because of the constant folding logic in FoldElement,
+ // this case also applies for constant string properties.
+ //
+ // obj()['aprop']() // expression
+ // ^ // column coord
+ coordNode = &calleeNode->as<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()) {
+ auto* spreadNode = &argsList->head()->as<UnaryNode>();
+ if (!emitTree(spreadNode->kid())) {
+ // [stack] CALLEE THIS ARG0
+ return false;
+ }
+
+ if (!cone.emitSpreadArgumentsTest()) {
+ // [stack] CALLEE THIS ARG0
+ return false;
+ }
+
+ if (cone.wantSpreadIteration()) {
+ if (!emitSpreadIntoArray(spreadNode)) {
+ // [stack] CALLEE THIS ARR
+ return false;
+ }
+ }
+
+ if (!cone.emitSpreadArgumentsTestEnd()) {
+ // [stack] CALLEE THIS ARR
+ return false;
+ }
+ } else {
+ if (!cone.prepareForSpreadArguments()) {
+ // [stack] CALLEE THIS
+ return false;
+ }
+ if (!emitArray(argsList)) {
+ // [stack] CALLEE THIS ARR
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool BytecodeEmitter::emitOptionalCall(CallNode* callNode, OptionalEmitter& oe,
+ ValueUsage valueUsage) {
+ /*
+ * A modified version of emitCallOrNew that handles optional calls.
+ *
+ * These include the following:
+ * a?.()
+ * a.b?.()
+ * a.["b"]?.()
+ * (a?.b)?.()
+ *
+ * See CallOrNewEmitter for more context.
+ */
+ ParseNode* calleeNode = callNode->left();
+ ListNode* argsList = &callNode->right()->as<ListNode>();
+ bool isSpread = IsSpreadOp(callNode->callOp());
+ JSOp op = callNode->callOp();
+ uint32_t argc = argsList->count();
+ bool isOptimizableSpread = isSpread && argc == 1;
+
+ CallOrNewEmitter cone(this, op,
+ isOptimizableSpread
+ ? CallOrNewEmitter::ArgumentsKind::SingleSpread
+ : CallOrNewEmitter::ArgumentsKind::Other,
+ valueUsage);
+
+ ParseNode* coordNode = getCoordNode(callNode, calleeNode, op, argsList);
+
+ if (!emitOptionalCalleeAndThis(calleeNode, callNode, cone, oe)) {
+ // [stack] CALLEE THIS
+ return false;
+ }
+
+ if (callNode->isKind(ParseNodeKind::OptionalCallExpr)) {
+ if (!oe.emitJumpShortCircuitForCall()) {
+ // [stack] CALLEE THIS
+ return false;
+ }
+ }
+
+ if (!emitArguments(argsList, /* isCall = */ true, isSpread, cone)) {
+ // [stack] CALLEE THIS ARGS...
+ return false;
+ }
+
+ if (!cone.emitEnd(argc, coordNode->pn_pos.begin)) {
+ // [stack] RVAL
+ return false;
+ }
+
+ return true;
+}
+
+bool BytecodeEmitter::emitCallOrNew(CallNode* callNode, ValueUsage valueUsage) {
+ /*
+ * Emit callable invocation or operator new (constructor call) code.
+ * First, emit code for the left operand to evaluate the callable or
+ * constructable object expression.
+ *
+ * Then (or in a call case that has no explicit reference-base
+ * object) we emit JSOp::Undefined to produce the undefined |this|
+ * value required for calls (which non-strict mode functions
+ * will box into the global object).
+ */
+ bool isCall = callNode->isKind(ParseNodeKind::CallExpr) ||
+ callNode->isKind(ParseNodeKind::TaggedTemplateExpr);
+ ParseNode* calleeNode = callNode->left();
+ ListNode* argsList = &callNode->right()->as<ListNode>();
+ JSOp op = callNode->callOp();
+
+ if (calleeNode->isKind(ParseNodeKind::Name) &&
+ emitterMode == BytecodeEmitter::SelfHosting && op == JSOp::Call) {
+ // Calls to "forceInterpreter", "callFunction",
+ // "callContentFunction", or "resumeGenerator" in self-hosted
+ // code generate inline bytecode.
+ //
+ // NOTE: The list of special instruction names has to be kept in sync with
+ // "js/src/builtin/.eslintrc.js".
+ auto calleeName = calleeNode->as<NameNode>().name();
+ if (calleeName == TaggedParserAtomIndex::WellKnown::callFunction()) {
+ return emitSelfHostedCallFunction(callNode, JSOp::Call);
+ }
+ if (calleeName == TaggedParserAtomIndex::WellKnown::callContentFunction()) {
+ return emitSelfHostedCallFunction(callNode, JSOp::CallContent);
+ }
+ if (calleeName ==
+ TaggedParserAtomIndex::WellKnown::constructContentFunction()) {
+ return emitSelfHostedCallFunction(callNode, JSOp::NewContent);
+ }
+ if (calleeName == TaggedParserAtomIndex::WellKnown::resumeGenerator()) {
+ return emitSelfHostedResumeGenerator(callNode);
+ }
+ if (calleeName == TaggedParserAtomIndex::WellKnown::forceInterpreter()) {
+ return emitSelfHostedForceInterpreter();
+ }
+ if (calleeName == TaggedParserAtomIndex::WellKnown::allowContentIter()) {
+ return emitSelfHostedAllowContentIter(callNode);
+ }
+ if (calleeName ==
+ TaggedParserAtomIndex::WellKnown::allowContentIterWith()) {
+ return emitSelfHostedAllowContentIterWith(callNode);
+ }
+ if (calleeName ==
+ TaggedParserAtomIndex::WellKnown::defineDataPropertyIntrinsic() &&
+ argsList->count() == 3) {
+ return emitSelfHostedDefineDataProperty(callNode);
+ }
+ if (calleeName == TaggedParserAtomIndex::WellKnown::hasOwn()) {
+ return emitSelfHostedHasOwn(callNode);
+ }
+ if (calleeName == TaggedParserAtomIndex::WellKnown::getPropertySuper()) {
+ return emitSelfHostedGetPropertySuper(callNode);
+ }
+ if (calleeName == TaggedParserAtomIndex::WellKnown::ToNumeric()) {
+ return emitSelfHostedToNumeric(callNode);
+ }
+ if (calleeName == TaggedParserAtomIndex::WellKnown::ToString()) {
+ return emitSelfHostedToString(callNode);
+ }
+ if (calleeName ==
+ TaggedParserAtomIndex::WellKnown::GetBuiltinConstructor()) {
+ return emitSelfHostedGetBuiltinConstructor(callNode);
+ }
+ if (calleeName == TaggedParserAtomIndex::WellKnown::GetBuiltinPrototype()) {
+ return emitSelfHostedGetBuiltinPrototype(callNode);
+ }
+ if (calleeName == TaggedParserAtomIndex::WellKnown::GetBuiltinSymbol()) {
+ return emitSelfHostedGetBuiltinSymbol(callNode);
+ }
+ if (calleeName == TaggedParserAtomIndex::WellKnown::ArgumentsLength()) {
+ return emitSelfHostedArgumentsLength(callNode);
+ }
+ if (calleeName == TaggedParserAtomIndex::WellKnown::GetArgument()) {
+ return emitSelfHostedGetArgument(callNode);
+ }
+ if (calleeName ==
+ TaggedParserAtomIndex::WellKnown::SetIsInlinableLargeFunction()) {
+ return emitSelfHostedSetIsInlinableLargeFunction(callNode);
+ }
+ if (calleeName == TaggedParserAtomIndex::WellKnown::SetCanonicalName()) {
+ return emitSelfHostedSetCanonicalName(callNode);
+ }
+ if (calleeName == TaggedParserAtomIndex::WellKnown::IsNullOrUndefined()) {
+ return emitSelfHostedIsNullOrUndefined(callNode);
+ }
+#ifdef DEBUG
+ if (calleeName ==
+ TaggedParserAtomIndex::WellKnown::UnsafeGetReservedSlot() ||
+ calleeName == TaggedParserAtomIndex::WellKnown::
+ UnsafeGetObjectFromReservedSlot() ||
+ calleeName == TaggedParserAtomIndex::WellKnown::
+ UnsafeGetInt32FromReservedSlot() ||
+ calleeName == TaggedParserAtomIndex::WellKnown::
+ UnsafeGetStringFromReservedSlot()) {
+ // Make sure that this call is correct, but don't emit any special code.
+ assertSelfHostedUnsafeGetReservedSlot(argsList);
+ }
+ if (calleeName ==
+ TaggedParserAtomIndex::WellKnown::UnsafeSetReservedSlot()) {
+ // Make sure that this call is correct, but don't emit any special code.
+ assertSelfHostedUnsafeSetReservedSlot(argsList);
+ }
+#endif
+ // Fall through
+ }
+
+ uint32_t argc = argsList->count();
+ bool isSpread = IsSpreadOp(op);
+ bool isOptimizableSpread = isSpread && argc == 1;
+ bool isDefaultDerivedClassConstructor =
+ sc->isFunctionBox() && sc->asFunctionBox()->isDerivedClassConstructor() &&
+ sc->asFunctionBox()->isSyntheticFunction();
+ MOZ_ASSERT_IF(isDefaultDerivedClassConstructor, isOptimizableSpread);
+ CallOrNewEmitter cone(
+ this, op,
+ isOptimizableSpread
+ ? isDefaultDerivedClassConstructor
+ ? CallOrNewEmitter::ArgumentsKind::PassthroughRest
+ : CallOrNewEmitter::ArgumentsKind::SingleSpread
+ : CallOrNewEmitter::ArgumentsKind::Other,
+ valueUsage);
+
+ if (!emitCalleeAndThis(calleeNode, callNode, cone)) {
+ // [stack] CALLEE THIS
+ return false;
+ }
+ if (!emitArguments(argsList, isCall, isSpread, cone)) {
+ // [stack] CALLEE THIS ARGS...
+ return false;
+ }
+
+ // Push new.target for construct calls.
+ if (IsConstructOp(op)) {
+ if (op == JSOp::SuperCall || op == JSOp::SpreadSuperCall) {
+ if (!emitNewTarget(callNode)) {
+ // [stack] CALLEE THIS ARGS.. NEW.TARGET
+ return false;
+ }
+ } else {
+ // Repush the callee as new.target
+ uint32_t effectiveArgc = isSpread ? 1 : argc;
+ if (!emitDupAt(effectiveArgc + 1)) {
+ // [stack] CALLEE THIS ARGS.. CALLEE
+ return false;
+ }
+ }
+ }
+
+ ParseNode* coordNode = getCoordNode(callNode, calleeNode, op, argsList);
+
+ if (!cone.emitEnd(argc, coordNode->pn_pos.begin)) {
+ // [stack] RVAL
+ return false;
+ }
+
+ return true;
+}
+
+// This list must be kept in the same order in several places:
+// - The binary operators in ParseNode.h ,
+// - the binary operators in TokenKind.h
+// - the precedence list in Parser.cpp
+static const JSOp ParseNodeKindToJSOp[] = {
+ // Some binary ops require special code generation (PrivateIn);
+ // these should not use BinaryOpParseNodeKindToJSOp. This table fills those
+ // slots with Nops to make the rest of the table lookup work.
+ JSOp::Coalesce, JSOp::Or, JSOp::And, JSOp::BitOr, JSOp::BitXor,
+ JSOp::BitAnd, JSOp::StrictEq, JSOp::Eq, JSOp::StrictNe, JSOp::Ne,
+ JSOp::Lt, JSOp::Le, JSOp::Gt, JSOp::Ge, JSOp::Instanceof,
+ JSOp::In, JSOp::Nop, JSOp::Lsh, JSOp::Rsh, JSOp::Ursh,
+ JSOp::Add, JSOp::Sub, JSOp::Mul, JSOp::Div, JSOp::Mod,
+ JSOp::Pow};
+
+static inline JSOp BinaryOpParseNodeKindToJSOp(ParseNodeKind pnk) {
+ MOZ_ASSERT(pnk >= ParseNodeKind::BinOpFirst);
+ MOZ_ASSERT(pnk <= ParseNodeKind::BinOpLast);
+ int parseNodeFirst = size_t(ParseNodeKind::BinOpFirst);
+#ifdef DEBUG
+ int jsopArraySize = std::size(ParseNodeKindToJSOp);
+ int parseNodeKindListSize =
+ size_t(ParseNodeKind::BinOpLast) - parseNodeFirst + 1;
+ MOZ_ASSERT(jsopArraySize == parseNodeKindListSize);
+ // Ensure we don't use this to find an op for a parse node
+ // requiring special emission rules.
+ MOZ_ASSERT(ParseNodeKindToJSOp[size_t(pnk) - parseNodeFirst] != JSOp::Nop);
+#endif
+ return ParseNodeKindToJSOp[size_t(pnk) - parseNodeFirst];
+}
+
+bool BytecodeEmitter::emitRightAssociative(ListNode* node) {
+ // ** is the only right-associative operator.
+ MOZ_ASSERT(node->isKind(ParseNodeKind::PowExpr));
+
+ // Right-associative operator chain.
+ for (ParseNode* subexpr : node->contents()) {
+ if (!emitTree(subexpr)) {
+ return false;
+ }
+ }
+ for (uint32_t i = 0; i < node->count() - 1; i++) {
+ if (!emit1(JSOp::Pow)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+bool BytecodeEmitter::emitLeftAssociative(ListNode* node) {
+ // Left-associative operator chain.
+ if (!emitTree(node->head())) {
+ return false;
+ }
+ JSOp op = BinaryOpParseNodeKindToJSOp(node->getKind());
+ ParseNode* nextExpr = node->head()->pn_next;
+ do {
+ if (!emitTree(nextExpr)) {
+ return false;
+ }
+ if (!emit1(op)) {
+ return false;
+ }
+ } while ((nextExpr = nextExpr->pn_next));
+ return true;
+}
+
+bool BytecodeEmitter::emitPrivateInExpr(ListNode* node) {
+ MOZ_ASSERT(node->head()->isKind(ParseNodeKind::PrivateName));
+
+ NameNode& privateNameNode = node->head()->as<NameNode>();
+ TaggedParserAtomIndex privateName = privateNameNode.name();
+
+ PrivateOpEmitter xoe(this, PrivateOpEmitter::Kind::ErgonomicBrandCheck,
+ privateName);
+
+ ParseNode* valueNode = node->head()->pn_next;
+ MOZ_ASSERT(valueNode->pn_next == nullptr);
+
+ if (!emitTree(valueNode)) {
+ // [stack] OBJ
+ return false;
+ }
+
+ if (!xoe.emitReference()) {
+ // [stack] OBJ BRAND if private method
+ // [stack] OBJ NAME if private field or accessor.
+ return false;
+ }
+
+ if (!xoe.emitBrandCheck()) {
+ // [stack] OBJ BRAND BOOL if private method
+ // [stack] OBJ NAME BOOL if private field or accessor.
+ return false;
+ }
+
+ if (!emitUnpickN(2)) {
+ // [stack] BOOL OBJ BRAND if private method
+ // [stack] BOOL OBJ NAME if private field or accessor.
+ return false;
+ }
+
+ if (!emitPopN(2)) {
+ // [stack] BOOL
+ return false;
+ }
+
+ return true;
+}
+
+/*
+ * Special `emitTree` for Optional Chaining case.
+ * Examples of this are `emitOptionalChain`, `emitDeleteOptionalChain` and
+ * `emitCalleeAndThisForOptionalChain`.
+ */
+bool BytecodeEmitter::emitOptionalTree(
+ ParseNode* pn, OptionalEmitter& oe,
+ ValueUsage valueUsage /* = ValueUsage::WantValue */) {
+ AutoCheckRecursionLimit recursion(fc);
+ if (!recursion.check(fc)) {
+ return false;
+ }
+ ParseNodeKind kind = pn->getKind();
+ switch (kind) {
+ case ParseNodeKind::OptionalDotExpr: {
+ OptionalPropertyAccess* prop = &pn->as<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;
+ MOZ_ASSERT(!elem->key().isKind(ParseNodeKind::PrivateName));
+ ElemOpEmitter eoe(this, ElemOpEmitter::Kind::Get,
+ ElemOpEmitter::ObjKind::Other);
+
+ if (!emitOptionalElemExpression(elem, eoe, isSuper, oe)) {
+ return false;
+ }
+ break;
+ }
+ case ParseNodeKind::ElemExpr: {
+ PropertyByValue* elem = &pn->as<PropertyByValue>();
+ bool isSuper = elem->isSuper();
+ MOZ_ASSERT(!elem->key().isKind(ParseNodeKind::PrivateName));
+ ElemOpEmitter eoe(this, ElemOpEmitter::Kind::Get,
+ isSuper ? ElemOpEmitter::ObjKind::Super
+ : ElemOpEmitter::ObjKind::Other);
+
+ if (!emitOptionalElemExpression(elem, eoe, isSuper, oe)) {
+ return false;
+ }
+ break;
+ }
+ case ParseNodeKind::PrivateMemberExpr:
+ case ParseNodeKind::OptionalPrivateMemberExpr: {
+ PrivateMemberAccessBase* privateExpr = &pn->as<PrivateMemberAccessBase>();
+ PrivateOpEmitter xoe(this, PrivateOpEmitter::Kind::Get,
+ privateExpr->privateName().name());
+ if (!emitOptionalPrivateExpression(privateExpr, xoe, oe)) {
+ return false;
+ }
+ break;
+ }
+ case ParseNodeKind::CallExpr:
+ case ParseNodeKind::OptionalCallExpr:
+ if (!emitOptionalCall(&pn->as<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::emitOptionalPrivateExpression(
+ PrivateMemberAccessBase* privateExpr, PrivateOpEmitter& xoe,
+ OptionalEmitter& oe) {
+ if (!emitOptionalTree(&privateExpr->expression(), oe)) {
+ // [stack] OBJ
+ return false;
+ }
+
+ if (privateExpr->isKind(ParseNodeKind::OptionalPrivateMemberExpr)) {
+ if (!oe.emitJumpShortCircuit()) {
+ // [stack] # if Jump
+ // [stack] UNDEFINED-OR-NULL
+ // [stack] # otherwise
+ // [stack] OBJ
+ return false;
+ }
+ }
+
+ if (!xoe.emitReference()) {
+ // [stack] OBJ NAME
+ return false;
+ }
+ if (!xoe.emitGet()) {
+ // [stack] CALLEE THIS # if call
+ // [stack] VALUE # otherwise
+ return false;
+ }
+
+ return true;
+}
+
+bool BytecodeEmitter::emitShortCircuit(ListNode* node, ValueUsage valueUsage) {
+ MOZ_ASSERT(node->isKind(ParseNodeKind::OrExpr) ||
+ node->isKind(ParseNodeKind::CoalesceExpr) ||
+ node->isKind(ParseNodeKind::AndExpr));
+
+ /*
+ * JSOp::Or converts the operand on the stack to boolean, leaves the original
+ * value on the stack and jumps if true; otherwise it falls into the next
+ * bytecode, which pops the left operand and then evaluates the right operand.
+ * The jump goes around the right operand evaluation.
+ *
+ * JSOp::And converts the operand on the stack to boolean and jumps if false;
+ * otherwise it falls into the right operand's bytecode.
+ */
+
+ TDZCheckCache tdzCache(this);
+
+ JSOp op;
+ switch (node->getKind()) {
+ case ParseNodeKind::OrExpr:
+ op = JSOp::Or;
+ break;
+ case ParseNodeKind::CoalesceExpr:
+ op = JSOp::Coalesce;
+ break;
+ case ParseNodeKind::AndExpr:
+ op = JSOp::And;
+ break;
+ default:
+ MOZ_CRASH("Unexpected ParseNodeKind");
+ }
+
+ JumpList jump;
+
+ // Left-associative operator chain: avoid too much recursion.
+ //
+ // Emit all nodes but the last.
+ for (ParseNode* expr : node->contentsTo(node->last())) {
+ if (!emitTree(expr)) {
+ return false;
+ }
+ if (!emitJump(op, &jump)) {
+ return false;
+ }
+ if (!emit1(JSOp::Pop)) {
+ return false;
+ }
+ }
+
+ // Emit the last node
+ if (!emitTree(node->last(), valueUsage)) {
+ return false;
+ }
+
+ if (!emitJumpTargetAndPatch(jump)) {
+ return false;
+ }
+ return true;
+}
+
+bool BytecodeEmitter::emitSequenceExpr(ListNode* node, ValueUsage valueUsage) {
+ for (ParseNode* child : node->contentsTo(node->last())) {
+ if (!updateSourceCoordNotes(child->pn_pos.begin)) {
+ return false;
+ }
+ if (!emitTree(child, ValueUsage::IgnoreValue)) {
+ return false;
+ }
+ if (!emit1(JSOp::Pop)) {
+ return false;
+ }
+ }
+
+ ParseNode* child = node->last();
+ if (!updateSourceCoordNotes(child->pn_pos.begin)) {
+ return false;
+ }
+ if (!emitTree(child, valueUsage)) {
+ return false;
+ }
+ return true;
+}
+
+// Using MOZ_NEVER_INLINE in here is a workaround for llvm.org/pr14047. See
+// the comment on emitSwitch.
+MOZ_NEVER_INLINE bool BytecodeEmitter::emitIncOrDec(UnaryNode* incDec,
+ ValueUsage valueUsage) {
+ switch (incDec->kid()->getKind()) {
+ case ParseNodeKind::DotExpr:
+ return emitPropIncDec(incDec, valueUsage);
+ case ParseNodeKind::ElemExpr:
+ return emitElemIncDec(incDec, valueUsage);
+ case ParseNodeKind::PrivateMemberExpr:
+ return emitPrivateIncDec(incDec, valueUsage);
+ case ParseNodeKind::CallExpr:
+ return emitCallIncDec(incDec);
+ default:
+ return emitNameIncDec(incDec, valueUsage);
+ }
+}
+
+// Using MOZ_NEVER_INLINE in here is a workaround for llvm.org/pr14047. See
+// the comment on emitSwitch.
+MOZ_NEVER_INLINE bool BytecodeEmitter::emitLabeledStatement(
+ const LabeledStatement* labeledStmt) {
+ auto name = labeledStmt->label();
+ LabelEmitter label(this);
+
+ label.emitLabel(name);
+
+ if (!emitTree(labeledStmt->statement())) {
+ return false;
+ }
+ if (!label.emitEnd()) {
+ return false;
+ }
+
+ return true;
+}
+
+bool BytecodeEmitter::emitConditionalExpression(
+ ConditionalExpression& conditional, ValueUsage valueUsage) {
+ CondEmitter cond(this);
+ if (!cond.emitCond()) {
+ return false;
+ }
+
+ ParseNode* conditionNode = &conditional.condition();
+ auto conditionKind = IfEmitter::ConditionKind::Positive;
+ if (conditionNode->isKind(ParseNodeKind::NotExpr)) {
+ conditionNode = conditionNode->as<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;
+ uint32_t 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 > SharedPropMap::MaxPropsForNonDictionary) {
+ // JSOp::NewObject cannot accept dictionary-mode objects.
+ keysOK = false;
+ }
+
+ *withValues = keysOK && valuesOK;
+ *withoutValues = keysOK;
+}
+
+bool BytecodeEmitter::isArrayObjLiteralCompatible(ListNode* array) {
+ for (ParseNode* elem : array->contents()) {
+ if (elem->isKind(ParseNodeKind::Spread)) {
+ return false;
+ }
+ if (!isRHSObjLiteralCompatible(elem)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+bool BytecodeEmitter::emitPropertyList(ListNode* obj, PropertyEmitter& pe,
+ PropListType type) {
+ // [stack] CTOR? OBJ
+
+ size_t curFieldKeyIndex = 0;
+ size_t curStaticFieldKeyIndex = 0;
+ for (ParseNode* propdef : obj->contents()) {
+ if (propdef->is<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) {
+ auto fieldKeys =
+ field->isStatic()
+ ? TaggedParserAtomIndex::WellKnown::dotStaticFieldKeys()
+ : TaggedParserAtomIndex::WellKnown::dotFieldKeys();
+ if (!emitGetName(fieldKeys)) {
+ // [stack] CTOR OBJ ARRAY
+ return false;
+ }
+
+ ParseNode* nameExpr = field->name().as<UnaryNode>().kid();
+
+ if (!emitTree(nameExpr, ValueUsage::WantValue)) {
+ // [stack] CTOR OBJ ARRAY KEY
+ return false;
+ }
+
+ if (!emit1(JSOp::ToPropertyKey)) {
+ // [stack] CTOR OBJ ARRAY KEY
+ return false;
+ }
+
+ size_t fieldKeysIndex;
+ if (field->isStatic()) {
+ fieldKeysIndex = curStaticFieldKeyIndex++;
+ } else {
+ fieldKeysIndex = curFieldKeyIndex++;
+ }
+
+ if (!emitUint32Operand(JSOp::InitElemArray, fieldKeysIndex)) {
+ // [stack] CTOR OBJ ARRAY
+ return false;
+ }
+
+ if (!emit1(JSOp::Pop)) {
+ // [stack] CTOR OBJ
+ return false;
+ }
+ }
+ continue;
+ }
+
+ if (propdef->isKind(ParseNodeKind::StaticClassBlock)) {
+ // Static class blocks are emitted as part of
+ // emitCreateMemberInitializers.
+ continue;
+ }
+
+ if (propdef->is<LexicalScopeNode>()) {
+ // Constructors are sometimes wrapped in LexicalScopeNodes. As we
+ // already handled emitting the constructor, skip it.
+ MOZ_ASSERT(
+ propdef->as<LexicalScopeNode>().scopeBody()->is<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(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(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();
+ AccessorType accessorType;
+ if (prop->is<ClassMethod>()) {
+ ClassMethod& method = prop->as<ClassMethod>();
+ accessorType = method.accessorType();
+
+ if (!method.isStatic() && key->isKind(ParseNodeKind::PrivateName) &&
+ accessorType != AccessorType::None) {
+ // Private non-static accessors are stamped onto instances from
+ // initializers; see emitCreateMemberInitializers.
+ continue;
+ }
+ } else if (prop->is<PropertyDefinition>()) {
+ accessorType = prop->as<PropertyDefinition>().accessorType();
+ } else {
+ accessorType = AccessorType::None;
+ }
+
+ auto emitValue = [this, &key, &prop, accessorType, &pe]() {
+ // [stack] CTOR? OBJ CTOR? KEY?
+
+ ParseNode* propVal = prop->right();
+ if (propVal->isDirectRHSAnonFunction()) {
+ // The following branches except for the last `else` clause emit the
+ // cases handled in NameResolver::resolveFun (see NameFunctions.cpp)
+ if (key->isKind(ParseNodeKind::ObjectPropertyName) ||
+ key->isKind(ParseNodeKind::StringExpr)) {
+ auto keyAtom = key->as<NameNode>().atom();
+ if (!emitAnonymousFunctionWithName(propVal, keyAtom)) {
+ // [stack] CTOR? OBJ CTOR? VAL
+ return false;
+ }
+ } else if (key->isKind(ParseNodeKind::NumberExpr)) {
+ MOZ_ASSERT(accessorType == AccessorType::None);
+
+ auto keyAtom = key->as<NumericLiteral>().toAtom(fc, parserAtoms());
+ if (!keyAtom) {
+ return false;
+ }
+ if (!emitAnonymousFunctionWithName(propVal, keyAtom)) {
+ // [stack] CTOR? OBJ CTOR? KEY VAL
+ return false;
+ }
+ } else if (key->isKind(ParseNodeKind::ComputedName) &&
+ (key->as<UnaryNode>().kid()->isKind(
+ ParseNodeKind::NumberExpr) ||
+ key->as<UnaryNode>().kid()->isKind(
+ ParseNodeKind::StringExpr)) &&
+ accessorType == AccessorType::None) {
+ ParseNode* keyKid = key->as<UnaryNode>().kid();
+ if (keyKid->isKind(ParseNodeKind::NumberExpr)) {
+ auto keyAtom =
+ keyKid->as<NumericLiteral>().toAtom(fc, parserAtoms());
+ if (!keyAtom) {
+ return false;
+ }
+ if (!emitAnonymousFunctionWithName(propVal, keyAtom)) {
+ // [stack] CTOR? OBJ CTOR? KEY VAL
+ return false;
+ }
+ } else {
+ MOZ_ASSERT(keyKid->isKind(ParseNodeKind::StringExpr));
+ auto keyAtom = keyKid->as<NameNode>().atom();
+ if (!emitAnonymousFunctionWithName(propVal, keyAtom)) {
+ // [stack] CTOR? OBJ CTOR? KEY VAL
+ return false;
+ }
+ }
+ } else {
+ // Either a proper computed property name or a synthetic computed
+ // property name for BigInt keys.
+ MOZ_ASSERT(key->isKind(ParseNodeKind::ComputedName));
+
+ FunctionPrefixKind prefix =
+ accessorType == AccessorType::None ? FunctionPrefixKind::None
+ : accessorType == AccessorType::Getter ? FunctionPrefixKind::Get
+ : FunctionPrefixKind::Set;
+
+ if (!emitAnonymousFunctionWithComputedName(propVal, prefix)) {
+ // [stack] CTOR? OBJ CTOR? KEY VAL
+ return false;
+ }
+ }
+ } else {
+ if (!emitTree(propVal)) {
+ // [stack] CTOR? OBJ CTOR? KEY? VAL
+ return false;
+ }
+ }
+
+ if (propVal->is<FunctionNode>() &&
+ propVal->as<FunctionNode>().funbox()->needsHomeObject()) {
+ if (!pe.emitInitHomeObject()) {
+ // [stack] CTOR? OBJ CTOR? KEY? FUN
+ return false;
+ }
+ }
+
+#ifdef ENABLE_DECORATORS
+ if (prop->is<ClassMethod>()) {
+ ClassMethod& method = prop->as<ClassMethod>();
+ if (method.decorators() && !method.decorators()->empty()) {
+ DecoratorEmitter::Kind kind;
+ switch (method.accessorType()) {
+ case AccessorType::Getter:
+ kind = DecoratorEmitter::Getter;
+ break;
+ case AccessorType::Setter:
+ kind = DecoratorEmitter::Setter;
+ break;
+ case AccessorType::None:
+ kind = DecoratorEmitter::Method;
+ break;
+ }
+
+ // The decorators are applied to the current value on the stack,
+ // possibly replacing it.
+ DecoratorEmitter de(this);
+ if (!de.emitApplyDecoratorsToElementDefinition(
+ kind, key, method.decorators(), method.isStatic())) {
+ // [stack] CTOR? OBJ CTOR? KEY? VAL
+ return false;
+ }
+ }
+ }
+#endif
+
+ return true;
+ };
+
+ PropertyEmitter::Kind kind =
+ (type == ClassBody && propdef->as<ClassMethod>().isStatic())
+ ? PropertyEmitter::Kind::Static
+ : PropertyEmitter::Kind::Prototype;
+ if (key->isKind(ParseNodeKind::ObjectPropertyName) ||
+ key->isKind(ParseNodeKind::StringExpr)) {
+ // [stack] CTOR? OBJ
+
+ auto keyAtom = key->as<NameNode>().atom();
+
+ // emitClass took care of constructor already.
+ if (type == ClassBody &&
+ keyAtom == TaggedParserAtomIndex::WellKnown::constructor() &&
+ !propdef->as<ClassMethod>().isStatic()) {
+ continue;
+ }
+
+ if (!pe.prepareForPropValue(propdef->pn_pos.begin, kind)) {
+ // [stack] CTOR? OBJ CTOR?
+ return false;
+ }
+
+ if (!emitValue()) {
+ // [stack] CTOR? OBJ CTOR? VAL
+ return false;
+ }
+
+ if (!pe.emitInit(accessorType, keyAtom)) {
+ // [stack] CTOR? OBJ
+ return false;
+ }
+
+ continue;
+ }
+
+ if (key->isKind(ParseNodeKind::NumberExpr)) {
+ // [stack] CTOR? OBJ
+ if (!pe.prepareForIndexPropKey(propdef->pn_pos.begin, kind)) {
+ // [stack] CTOR? OBJ CTOR?
+ return false;
+ }
+ if (!emitNumberOp(key->as<NumericLiteral>().value())) {
+ // [stack] CTOR? OBJ CTOR? KEY
+ return false;
+ }
+ if (!pe.prepareForIndexPropValue()) {
+ // [stack] CTOR? OBJ CTOR? KEY
+ return false;
+ }
+ if (!emitValue()) {
+ // [stack] CTOR? OBJ CTOR? KEY VAL
+ return false;
+ }
+
+ if (!pe.emitInitIndexOrComputed(accessorType)) {
+ // [stack] CTOR? OBJ
+ return false;
+ }
+
+ continue;
+ }
+
+ if (key->isKind(ParseNodeKind::ComputedName)) {
+ // Either a proper computed property name or a synthetic computed property
+ // name for BigInt keys.
+
+ // [stack] CTOR? OBJ
+
+ if (!pe.prepareForComputedPropKey(propdef->pn_pos.begin, kind)) {
+ // [stack] CTOR? OBJ CTOR?
+ return false;
+ }
+ if (!emitTree(key->as<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)) {
+ // [stack] CTOR? OBJ
+ return false;
+ }
+
+ continue;
+ }
+
+ MOZ_ASSERT(key->isKind(ParseNodeKind::PrivateName));
+ MOZ_ASSERT(type == ClassBody);
+
+ auto* privateName = &key->as<NameNode>();
+
+ if (kind == PropertyEmitter::Kind::Prototype) {
+ MOZ_ASSERT(accessorType == AccessorType::None);
+ if (!pe.prepareForPrivateMethod()) {
+ // [stack] CTOR OBJ
+ return false;
+ }
+ NameOpEmitter noe(this, privateName->atom(),
+ NameOpEmitter::Kind::SimpleAssignment);
+
+ // Ensure the NameOp emitter doesn't push an environment onto the stack,
+ // because that would change the stack location of the home object.
+ MOZ_ASSERT(noe.loc().kind() == NameLocation::Kind::FrameSlot ||
+ noe.loc().kind() == NameLocation::Kind::EnvironmentCoordinate);
+
+ if (!noe.prepareForRhs()) {
+ // [stack] CTOR OBJ
+ return false;
+ }
+ if (!emitValue()) {
+ // [stack] CTOR OBJ METHOD
+ return false;
+ }
+ if (!noe.emitAssignment()) {
+ // [stack] CTOR OBJ METHOD
+ return false;
+ }
+ if (!emit1(JSOp::Pop)) {
+ // [stack] CTOR OBJ
+ return false;
+ }
+ if (!pe.skipInit()) {
+ // [stack] CTOR OBJ
+ return false;
+ }
+ continue;
+ }
+
+ MOZ_ASSERT(kind == PropertyEmitter::Kind::Static);
+
+ // [stack] CTOR OBJ
+
+ if (!pe.prepareForPrivateStaticMethod(propdef->pn_pos.begin)) {
+ // [stack] CTOR OBJ CTOR
+ return false;
+ }
+ if (!emitGetPrivateName(privateName)) {
+ // [stack] CTOR OBJ CTOR KEY
+ return false;
+ }
+ if (!emitValue()) {
+ // [stack] CTOR OBJ CTOR KEY VAL
+ return false;
+ }
+
+ if (!pe.emitPrivateStaticMethod(accessorType)) {
+ // [stack] CTOR OBJ
+ return false;
+ }
+
+ if (privateName->privateNameKind() == PrivateNameKind::Setter) {
+ if (!emitDupAt(1)) {
+ // [stack] CTOR OBJ CTOR
+ return false;
+ }
+ if (!emitGetPrivateName(privateName)) {
+ // [stack] CTOR OBJ CTOR NAME
+ return false;
+ }
+ if (!emitAtomOp(JSOp::GetIntrinsic,
+ TaggedParserAtomIndex::WellKnown::NoPrivateGetter())) {
+ // [stack] CTOR OBJ CTOR NAME FUN
+ return false;
+ }
+ if (!emit1(JSOp::InitHiddenElemGetter)) {
+ // [stack] CTOR OBJ CTOR
+ return false;
+ }
+ if (!emit1(JSOp::Pop)) {
+ // [stack] CTOR OBJ
+ return false;
+ }
+ }
+ }
+
+ return true;
+}
+
+bool BytecodeEmitter::emitPropertyListObjLiteral(ListNode* obj, JSOp op,
+ bool useObjLiteralValues) {
+ ObjLiteralWriter writer;
+
+#ifdef DEBUG
+ // In self-hosted JS, we check duplication only on debug build.
+ mozilla::Maybe<mozilla::HashSet<frontend::TaggedParserAtomIndex,
+ frontend::TaggedParserAtomIndexHasher>>
+ selfHostedPropNames;
+ if (emitterMode == BytecodeEmitter::SelfHosting) {
+ selfHostedPropNames.emplace();
+ }
+#endif
+
+ if (op == JSOp::Object) {
+ writer.beginObject(op);
+ } else {
+ MOZ_ASSERT(op == JSOp::NewObject);
+ writer.beginShape(op);
+ }
+
+ for (ParseNode* propdef : obj->contents()) {
+ BinaryNode* prop = &propdef->as<BinaryNode>();
+ ParseNode* key = prop->left();
+
+ if (key->is<NameNode>()) {
+ if (emitterMode == BytecodeEmitter::SelfHosting) {
+ auto propName = key->as<NameNode>().atom();
+#ifdef DEBUG
+ // Self-hosted JS shouldn't contain duplicate properties.
+ auto p = selfHostedPropNames->lookupForAdd(propName);
+ MOZ_ASSERT(!p);
+ if (!selfHostedPropNames->add(p, propName)) {
+ js::ReportOutOfMemory(fc);
+ return false;
+ }
+#endif
+ writer.setPropNameNoDuplicateCheck(parserAtoms(), propName);
+ } else {
+ if (!writer.setPropName(parserAtoms(), key->as<NameNode>().atom())) {
+ return false;
+ }
+ }
+ } 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.
+
+ // Ignore indexed properties if we're not storing property values, and
+ // rely on InitElem ops to define those. These properties will be either
+ // dense elements (not possible to represent in the literal's shape) or
+ // sparse elements (enumerated separately, so this doesn't affect property
+ // iteration order).
+ if (!useObjLiteralValues) {
+ continue;
+ }
+
+ writer.setPropIndex(i);
+ }
+
+ if (useObjLiteralValues) {
+ MOZ_ASSERT(op == JSOp::Object);
+ ParseNode* value = prop->right();
+ if (!emitObjLiteralValue(writer, value)) {
+ return false;
+ }
+ } else {
+ if (!writer.propWithUndefinedValue(fc)) {
+ return false;
+ }
+ }
+ }
+
+ GCThingIndex index;
+ if (!addObjLiteralData(writer, &index)) {
+ return false;
+ }
+
+ // JSOp::Object may only be used by (top-level) run-once scripts.
+ MOZ_ASSERT_IF(op == JSOp::Object,
+ sc->isTopLevelContext() && sc->treatAsRunOnce());
+
+ if (!emitGCIndexOp(op, index)) {
+ // [stack] OBJ
+ return false;
+ }
+
+ return true;
+}
+
+bool BytecodeEmitter::emitDestructuringRestExclusionSetObjLiteral(
+ ListNode* pattern) {
+ // Note: if we want to squeeze out a little more performance, we could switch
+ // to the `JSOp::Object` opcode, because the exclusion set object is never
+ // exposed to the user, so it's safe to bake the object into the bytecode.
+ constexpr JSOp op = JSOp::NewObject;
+
+ ObjLiteralWriter writer;
+ writer.beginShape(op);
+
+ for (ParseNode* member : pattern->contents()) {
+ if (member->isKind(ParseNodeKind::Spread)) {
+ MOZ_ASSERT(!member->pn_next, "unexpected trailing element after spread");
+ break;
+ }
+
+ TaggedParserAtomIndex atom;
+ if (member->isKind(ParseNodeKind::MutateProto)) {
+ atom = TaggedParserAtomIndex::WellKnown::proto();
+ } else {
+ ParseNode* key = member->as<BinaryNode>().left();
+ atom = key->as<NameNode>().atom();
+ }
+
+ if (!writer.setPropName(parserAtoms(), atom)) {
+ return false;
+ }
+
+ if (!writer.propWithUndefinedValue(fc)) {
+ return false;
+ }
+ }
+
+ GCThingIndex index;
+ if (!addObjLiteralData(writer, &index)) {
+ return false;
+ }
+
+ if (!emitGCIndexOp(op, index)) {
+ // [stack] OBJ
+ return false;
+ }
+
+ return true;
+}
+
+bool BytecodeEmitter::emitObjLiteralArray(ListNode* array) {
+ MOZ_ASSERT(checkSingletonContext());
+
+ constexpr JSOp op = JSOp::Object;
+
+ ObjLiteralWriter writer;
+ writer.beginArray(op);
+
+ writer.beginDenseArrayElements();
+ for (ParseNode* elem : array->contents()) {
+ if (!emitObjLiteralValue(writer, elem)) {
+ return false;
+ }
+ }
+
+ GCThingIndex index;
+ if (!addObjLiteralData(writer, &index)) {
+ return false;
+ }
+
+ if (!emitGCIndexOp(op, index)) {
+ // [stack] OBJ
+ return false;
+ }
+
+ return true;
+}
+
+bool BytecodeEmitter::isRHSObjLiteralCompatible(ParseNode* value) {
+ return value->isKind(ParseNodeKind::NumberExpr) ||
+ value->isKind(ParseNodeKind::TrueExpr) ||
+ value->isKind(ParseNodeKind::FalseExpr) ||
+ value->isKind(ParseNodeKind::NullExpr) ||
+ value->isKind(ParseNodeKind::RawUndefinedExpr) ||
+ value->isKind(ParseNodeKind::StringExpr) ||
+ value->isKind(ParseNodeKind::TemplateStringExpr);
+}
+
+bool BytecodeEmitter::emitObjLiteralValue(ObjLiteralWriter& writer,
+ ParseNode* value) {
+ MOZ_ASSERT(isRHSObjLiteralCompatible(value));
+ if (value->isKind(ParseNodeKind::NumberExpr)) {
+ double numValue = value->as<NumericLiteral>().value();
+ int32_t i = 0;
+ js::Value v;
+ if (NumberIsInt32(numValue, &i)) {
+ v.setInt32(i);
+ } else {
+ v.setDouble(numValue);
+ }
+ if (!writer.propWithConstNumericValue(fc, v)) {
+ return false;
+ }
+ } else if (value->isKind(ParseNodeKind::TrueExpr)) {
+ if (!writer.propWithTrueValue(fc)) {
+ return false;
+ }
+ } else if (value->isKind(ParseNodeKind::FalseExpr)) {
+ if (!writer.propWithFalseValue(fc)) {
+ return false;
+ }
+ } else if (value->isKind(ParseNodeKind::NullExpr)) {
+ if (!writer.propWithNullValue(fc)) {
+ return false;
+ }
+ } else if (value->isKind(ParseNodeKind::RawUndefinedExpr)) {
+ if (!writer.propWithUndefinedValue(fc)) {
+ return false;
+ }
+ } else if (value->isKind(ParseNodeKind::StringExpr) ||
+ value->isKind(ParseNodeKind::TemplateStringExpr)) {
+ if (!writer.propWithAtomValue(fc, parserAtoms(),
+ value->as<NameNode>().atom())) {
+ return false;
+ }
+ } else {
+ MOZ_CRASH("Unexpected parse node");
+ }
+ return true;
+}
+
+static bool NeedsPrivateBrand(ParseNode* member) {
+ return member->is<ClassMethod>() &&
+ member->as<ClassMethod>().name().isKind(ParseNodeKind::PrivateName) &&
+ !member->as<ClassMethod>().isStatic();
+}
+
+mozilla::Maybe<MemberInitializers> BytecodeEmitter::setupMemberInitializers(
+ ListNode* classMembers, FieldPlacement placement) {
+ bool isStatic = placement == FieldPlacement::Static;
+
+ size_t numFields = 0;
+ size_t numPrivateInitializers = 0;
+ bool hasPrivateBrand = false;
+ for (ParseNode* member : classMembers->contents()) {
+ if (NeedsFieldInitializer(member, isStatic)) {
+ numFields++;
+ } else if (NeedsAccessorInitializer(member, isStatic)) {
+ numPrivateInitializers++;
+ hasPrivateBrand = true;
+ } else if (NeedsPrivateBrand(member)) {
+ hasPrivateBrand = true;
+ }
+ }
+
+ // If there are more initializers than can be represented, return invalid.
+ if (numFields + numPrivateInitializers >
+ MemberInitializers::MaxInitializers) {
+ return Nothing();
+ }
+ return Some(
+ MemberInitializers(hasPrivateBrand, numFields + numPrivateInitializers));
+}
+
+// Purpose of .fieldKeys:
+// Computed field names (`["x"] = 2;`) must be ran at class-evaluation time,
+// not object construction time. The transformation to do so is roughly as
+// follows:
+//
+// class C {
+// [keyExpr] = valueExpr;
+// }
+// -->
+// let .fieldKeys = [keyExpr];
+// let .initializers = [
+// () => {
+// this[.fieldKeys[0]] = valueExpr;
+// }
+// ];
+// class C {
+// constructor() {
+// .initializers[0]();
+// }
+// }
+//
+// BytecodeEmitter::emitCreateFieldKeys does `let .fieldKeys = [...];`
+// BytecodeEmitter::emitPropertyList fills in the elements of the array.
+// See GeneralParser::fieldInitializer for the `this[.fieldKeys[0]]` part.
+bool BytecodeEmitter::emitCreateFieldKeys(ListNode* obj,
+ FieldPlacement placement) {
+ bool isStatic = placement == FieldPlacement::Static;
+ auto isFieldWithComputedName = [isStatic](ParseNode* propdef) {
+ return propdef->is<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;
+ }
+
+ auto fieldKeys = isStatic
+ ? TaggedParserAtomIndex::WellKnown::dotStaticFieldKeys()
+ : TaggedParserAtomIndex::WellKnown::dotFieldKeys();
+ NameOpEmitter noe(this, fieldKeys, NameOpEmitter::Kind::Initialize);
+ if (!noe.prepareForRhs()) {
+ return false;
+ }
+
+ if (!emitUint32Operand(JSOp::NewArray, numFieldKeys)) {
+ // [stack] ARRAY
+ return false;
+ }
+
+ if (!noe.emitAssignment()) {
+ // [stack] ARRAY
+ return false;
+ }
+
+ if (!emit1(JSOp::Pop)) {
+ // [stack]
+ return false;
+ }
+
+ return true;
+}
+
+static bool HasInitializer(ParseNode* node, bool isStaticContext) {
+ return (node->is<ClassField>() &&
+ node->as<ClassField>().isStatic() == isStaticContext) ||
+ (isStaticContext && node->is<StaticClassBlock>());
+}
+
+static FunctionNode* GetInitializer(ParseNode* node, bool isStaticContext) {
+ MOZ_ASSERT(HasInitializer(node, isStaticContext));
+ MOZ_ASSERT_IF(!node->is<ClassField>(), isStaticContext);
+ return node->is<ClassField>() ? node->as<ClassField>().initializer()
+ : node->as<StaticClassBlock>().function();
+}
+
+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(fc);
+ return false;
+ }
+
+ size_t numInitializers = memberInitializers->numMemberInitializers;
+ if (numInitializers == 0) {
+ return true;
+ }
+
+ bool isStatic = placement == FieldPlacement::Static;
+ if (!ce.prepareForMemberInitializers(numInitializers, isStatic)) {
+ // [stack] HOMEOBJ HERITAGE? ARRAY
+ // or:
+ // [stack] CTOR HOMEOBJ ARRAY
+ return false;
+ }
+
+ // Private accessors could be used in the field initializers, so make sure
+ // accessor initializers appear earlier in the .initializers array so they
+ // run first. Static private methods are not initialized using initializers
+ // (emitPropertyList emits bytecode to stamp them onto the constructor), so
+ // skip this step if isStatic.
+ if (!isStatic) {
+ if (!emitPrivateMethodInitializers(ce, obj)) {
+ return false;
+ }
+ }
+
+ for (ParseNode* propdef : obj->contents()) {
+ if (!HasInitializer(propdef, isStatic)) {
+ continue;
+ }
+
+ FunctionNode* initializer = GetInitializer(propdef, isStatic);
+
+ if (!ce.prepareForMemberInitializer()) {
+ return false;
+ }
+ if (!emitTree(initializer)) {
+ // [stack] HOMEOBJ HERITAGE? ARRAY LAMBDA
+ // or:
+ // [stack] CTOR HOMEOBJ ARRAY LAMBDA
+ return false;
+ }
+ if (initializer->funbox()->needsHomeObject()) {
+ MOZ_ASSERT(initializer->funbox()->allowSuperProperty());
+ if (!ce.emitMemberInitializerHomeObject(isStatic)) {
+ // [stack] HOMEOBJ HERITAGE? ARRAY LAMBDA
+ // or:
+ // [stack] CTOR HOMEOBJ ARRAY LAMBDA
+ return false;
+ }
+ }
+ if (!ce.emitStoreMemberInitializer()) {
+ // [stack] HOMEOBJ HERITAGE? ARRAY
+ // or:
+ // [stack] CTOR HOMEOBJ ARRAY
+ return false;
+ }
+ }
+
+#ifdef ENABLE_DECORATORS
+ // Index to use to append new initializers returned by decorators to the array
+ if (!emitNumberOp(numInitializers)) {
+ // [stack] HOMEOBJ HERITAGE? ARRAY INDEX
+ // or:
+ // [stack] CTOR HOMEOBJ ARRAY INDEX
+ return false;
+ }
+
+ for (ParseNode* propdef : obj->contents()) {
+ if (!propdef->is<ClassField>()) {
+ continue;
+ }
+ ClassField* field = &propdef->as<ClassField>();
+ if (placement == FieldPlacement::Static && !field->isStatic()) {
+ continue;
+ }
+ if (field->decorators() && !field->decorators()->empty()) {
+ DecoratorEmitter de(this);
+ if (!de.emitApplyDecoratorsToFieldDefinition(
+ &field->name(), field->decorators(), field->isStatic())) {
+ // [stack] HOMEOBJ HERITAGE? ARRAY INDEX INITIALIZERS
+ // or:
+ // [stack] CTOR HOMEOBJ ARRAY INDEX INITIALIZERS
+ return false;
+ }
+
+ if (!emit1(JSOp::InitElemInc)) {
+ // [stack] HOMEOBJ HERITAGE? ARRAY INDEX
+ // or:
+ // [stack] CTOR HOMEOBJ ARRAY INDEX
+ return false;
+ }
+ }
+ }
+
+ // Pop INDEX
+ if (!emitPopN(1)) {
+ // [stack] HOMEOBJ HERITAGE? ARRAY
+ // or:
+ // [stack] CTOR HOMEOBJ ARRAY
+ return false;
+ }
+#endif
+
+ if (!ce.emitMemberInitializersEnd()) {
+ // [stack] HOMEOBJ HERITAGE?
+ // or:
+ // [stack] CTOR HOMEOBJ
+ return false;
+ }
+
+ return true;
+}
+
+static bool IsPrivateInstanceAccessor(const ClassMethod* classMethod) {
+ return !classMethod->isStatic() &&
+ classMethod->name().isKind(ParseNodeKind::PrivateName) &&
+ classMethod->accessorType() != AccessorType::None;
+}
+
+bool BytecodeEmitter::emitPrivateMethodInitializers(ClassEmitter& ce,
+ ListNode* obj) {
+ for (ParseNode* propdef : obj->contents()) {
+ if (!propdef->is<ClassMethod>()) {
+ continue;
+ }
+ auto* classMethod = &propdef->as<ClassMethod>();
+
+ // Skip over anything which isn't a private instance accessor.
+ if (!IsPrivateInstanceAccessor(classMethod)) {
+ continue;
+ }
+
+ if (!ce.prepareForMemberInitializer()) {
+ // [stack] HOMEOBJ HERITAGE? ARRAY
+ // or:
+ // [stack] CTOR HOMEOBJ ARRAY
+ return false;
+ }
+
+ // Synthesize a name for the lexical variable that will store the
+ // private method body.
+ TaggedParserAtomIndex name = classMethod->name().as<NameNode>().atom();
+ AccessorType accessorType = classMethod->accessorType();
+ StringBuffer storedMethodName(fc);
+ if (!storedMethodName.append(parserAtoms(), name)) {
+ return false;
+ }
+ if (!storedMethodName.append(
+ accessorType == AccessorType::Getter ? ".getter" : ".setter")) {
+ return false;
+ }
+ auto storedMethodAtom =
+ storedMethodName.finishParserAtom(parserAtoms(), fc);
+
+ // Emit the private method body and store it as a lexical var.
+ if (!emitFunction(&classMethod->method())) {
+ // [stack] HOMEOBJ HERITAGE? ARRAY METHOD
+ // or:
+ // [stack] CTOR HOMEOBJ ARRAY METHOD
+ return false;
+ }
+ // The private method body needs to access the home object,
+ // and the CE knows where that is on the stack.
+ if (classMethod->method().funbox()->needsHomeObject()) {
+ if (!ce.emitMemberInitializerHomeObject(false)) {
+ // [stack] HOMEOBJ HERITAGE? ARRAY METHOD
+ // or:
+ // [stack] CTOR HOMEOBJ ARRAY METHOD
+ return false;
+ }
+ }
+ if (!emitLexicalInitialization(storedMethodAtom)) {
+ // [stack] HOMEOBJ HERITAGE? ARRAY METHOD
+ // or:
+ // [stack] CTOR HOMEOBJ ARRAY METHOD
+ return false;
+ }
+ if (!emit1(JSOp::Pop)) {
+ // [stack] HOMEOBJ HERITAGE? ARRAY
+ // or:
+ // [stack] CTOR HOMEOBJ ARRAY
+ return false;
+ }
+
+ if (!emitPrivateMethodInitializer(classMethod, storedMethodAtom)) {
+ // [stack] HOMEOBJ HERITAGE? ARRAY
+ // or:
+ // [stack] CTOR HOMEOBJ ARRAY
+ return false;
+ }
+
+ // Store the emitted initializer function into the .initializers array.
+ if (!ce.emitStoreMemberInitializer()) {
+ // [stack] HOMEOBJ HERITAGE? ARRAY
+ // or:
+ // [stack] CTOR HOMEOBJ ARRAY
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool BytecodeEmitter::emitPrivateMethodInitializer(
+ ClassMethod* classMethod, TaggedParserAtomIndex storedMethodAtom) {
+ MOZ_ASSERT(IsPrivateInstanceAccessor(classMethod));
+
+ auto* name = &classMethod->name().as<NameNode>();
+
+ // Emit the synthesized initializer function.
+ FunctionNode* funNode = classMethod->initializerIfPrivate();
+ MOZ_ASSERT(funNode);
+ FunctionBox* funbox = funNode->funbox();
+ FunctionEmitter fe(this, funbox, funNode->syntaxKind(),
+ FunctionEmitter::IsHoisted::No);
+ if (!fe.prepareForNonLazy()) {
+ // [stack]
+ return false;
+ }
+
+ BytecodeEmitter bce2(this, funbox);
+ if (!bce2.init(funNode->pn_pos)) {
+ return false;
+ }
+ ParamsBodyNode* paramsBody = funNode->body();
+ FunctionScriptEmitter fse(&bce2, funbox, Nothing(), Nothing());
+ if (!fse.prepareForParameters()) {
+ // [stack]
+ return false;
+ }
+ if (!bce2.emitFunctionFormalParameters(paramsBody)) {
+ // [stack]
+ return false;
+ }
+ if (!fse.prepareForBody()) {
+ // [stack]
+ return false;
+ }
+
+ if (!bce2.emit1(JSOp::FunctionThis)) {
+ // [stack] THIS
+ return false;
+ }
+ if (!bce2.emitGetPrivateName(name)) {
+ // [stack] THIS NAME
+ return false;
+ }
+ if (!bce2.emitGetName(storedMethodAtom)) {
+ // [stack] THIS NAME METHOD
+ return false;
+ }
+
+ switch (name->privateNameKind()) {
+ case PrivateNameKind::Setter:
+ if (!bce2.emit1(JSOp::InitHiddenElemSetter)) {
+ // [stack] THIS
+ return false;
+ }
+ if (!bce2.emitGetPrivateName(name)) {
+ // [stack] THIS NAME
+ return false;
+ }
+ if (!bce2.emitAtomOp(
+ JSOp::GetIntrinsic,
+ TaggedParserAtomIndex::WellKnown::NoPrivateGetter())) {
+ // [stack] THIS NAME FUN
+ return false;
+ }
+ if (!bce2.emit1(JSOp::InitHiddenElemGetter)) {
+ // [stack] THIS
+ return false;
+ }
+ break;
+ case PrivateNameKind::Getter:
+ case PrivateNameKind::GetterSetter:
+ if (classMethod->accessorType() == AccessorType::Getter) {
+ if (!bce2.emit1(JSOp::InitHiddenElemGetter)) {
+ // [stack] THIS
+ return false;
+ }
+ } else {
+ if (!bce2.emit1(JSOp::InitHiddenElemSetter)) {
+ // [stack] THIS
+ return false;
+ }
+ }
+ break;
+ default:
+ MOZ_CRASH("Invalid op");
+ }
+
+ // Pop remaining THIS.
+ if (!bce2.emit1(JSOp::Pop)) {
+ // [stack]
+ return false;
+ }
+
+ if (!fse.emitEndBody()) {
+ // [stack]
+ return false;
+ }
+ if (!fse.intoStencil()) {
+ return false;
+ }
+
+ if (!fe.emitNonLazyEnd()) {
+ // [stack] HOMEOBJ HERITAGE? ARRAY FUN
+ // or:
+ // [stack] CTOR HOMEOBJ ARRAY FUN
+ return false;
+ }
+
+ return true;
+}
+
+const MemberInitializers& BytecodeEmitter::findMemberInitializersForCall() {
+ for (BytecodeEmitter* current = this; current; current = current->parent) {
+ if (current->sc->isFunctionBox()) {
+ FunctionBox* funbox = current->sc->asFunctionBox();
+
+ if (funbox->isArrow()) {
+ continue;
+ }
+
+ // If we found a non-arrow / non-constructor we were never allowed to
+ // expect fields in the first place.
+ MOZ_RELEASE_ASSERT(funbox->isClassConstructor());
+
+ return funbox->useMemberInitializers() ? funbox->memberInitializers()
+ : MemberInitializers::Empty();
+ }
+ }
+
+ MOZ_RELEASE_ASSERT(compilationState.scopeContext.memberInitializers);
+ return *compilationState.scopeContext.memberInitializers;
+}
+
+bool BytecodeEmitter::emitInitializeInstanceMembers(
+ bool isDerivedClassConstructor) {
+ const MemberInitializers& memberInitializers =
+ findMemberInitializersForCall();
+ MOZ_ASSERT(memberInitializers.valid);
+
+ if (memberInitializers.hasPrivateBrand) {
+ // This is guaranteed to run after super(), so we don't need TDZ checks.
+ if (!emitGetName(TaggedParserAtomIndex::WellKnown::dotThis())) {
+ // [stack] THIS
+ return false;
+ }
+ if (!emitGetName(TaggedParserAtomIndex::WellKnown::dotPrivateBrand())) {
+ // [stack] THIS BRAND
+ return false;
+ }
+ if (isDerivedClassConstructor) {
+ if (!emitCheckPrivateField(ThrowCondition::ThrowHas,
+ ThrowMsgKind::PrivateBrandDoubleInit)) {
+ // [stack] THIS BRAND BOOL
+ return false;
+ }
+ if (!emit1(JSOp::Pop)) {
+ // [stack] THIS BRAND
+ return false;
+ }
+ }
+ if (!emit1(JSOp::Null)) {
+ // [stack] THIS BRAND NULL
+ return false;
+ }
+ if (!emit1(JSOp::InitHiddenElem)) {
+ // [stack] THIS
+ return false;
+ }
+ if (!emit1(JSOp::Pop)) {
+ // [stack]
+ return false;
+ }
+ }
+
+ size_t numInitializers = memberInitializers.numMemberInitializers;
+ if (numInitializers > 0) {
+ if (!emitGetName(TaggedParserAtomIndex::WellKnown::dotInitializers())) {
+ // [stack] ARRAY
+ return false;
+ }
+
+ for (size_t index = 0; index < numInitializers; index++) {
+ if (index < numInitializers - 1) {
+ // We Dup to keep the array around (it is consumed in the bytecode
+ // below) for next iterations of this loop, except for the last
+ // iteration, which avoids an extra Pop at the end of the loop.
+ if (!emit1(JSOp::Dup)) {
+ // [stack] ARRAY ARRAY
+ return false;
+ }
+ }
+
+ if (!emitNumberOp(index)) {
+ // [stack] ARRAY? ARRAY INDEX
+ return false;
+ }
+
+ if (!emit1(JSOp::GetElem)) {
+ // [stack] ARRAY? FUNC
+ return false;
+ }
+
+ // This is guaranteed to run after super(), so we don't need TDZ checks.
+ if (!emitGetName(TaggedParserAtomIndex::WellKnown::dotThis())) {
+ // [stack] ARRAY? FUNC THIS
+ return false;
+ }
+
+ // Callee is always internal function.
+ if (!emitCall(JSOp::CallIgnoresRv, 0)) {
+ // [stack] ARRAY? RVAL
+ return false;
+ }
+
+ if (!emit1(JSOp::Pop)) {
+ // [stack] ARRAY?
+ return false;
+ }
+ }
+#ifdef ENABLE_DECORATORS
+ // Decorators Proposal
+ // https://arai-a.github.io/ecma262-compare/?pr=2417&id=sec-initializeinstanceelements
+ // 4. For each element e of elements, do
+ // 4.a. If elementRecord.[[Kind]] is field or accessor, then
+ // 4.a.i. Perform ? InitializeFieldOrAccessor(O, elementRecord).
+ //
+
+ // TODO: (See Bug 1817993) At the moment, we're applying the initialization
+ // logic in two steps. The pre-decorator initialization code runs, stores
+ // the initial value, and then we retrieve it here and apply the
+ // initializers added by decorators. We should unify these two steps.
+ if (!emitGetName(TaggedParserAtomIndex::WellKnown::dotInitializers())) {
+ // [stack] ARRAY
+ return false;
+ }
+
+ if (!emit1(JSOp::Dup)) {
+ // [stack] ARRAY ARRAY
+ return false;
+ }
+
+ if (!emitAtomOp(JSOp::GetProp,
+ TaggedParserAtomIndex::WellKnown::length())) {
+ // [stack] ARRAY LENGTH
+ return false;
+ }
+
+ if (!emitNumberOp(static_cast<double>(numInitializers))) {
+ // [stack] ARRAY LENGTH INDEX
+ return false;
+ }
+
+ WhileEmitter wh(this);
+ // At this point, we have no context to determine offsets in the
+ // code for this while statement. Ideally, it would correspond to
+ // the field we're initializing.
+ if (!wh.emitCond(0, 0, 0)) {
+ // [stack] ARRAY LENGTH INDEX
+ return false;
+ }
+
+ if (!emit1(JSOp::Dup)) {
+ // [stack] ARRAY LENGTH INDEX INDEX
+ return false;
+ }
+
+ if (!emitDupAt(2)) {
+ // [stack] ARRAY LENGTH INDEX INDEX LENGTH
+ return false;
+ }
+
+ if (!emit1(JSOp::Lt)) {
+ // [stack] ARRAY LENGTH INDEX BOOL
+ return false;
+ }
+
+ if (!wh.emitBody()) {
+ // [stack] ARRAY LENGTH INDEX
+ return false;
+ }
+
+ if (!emitDupAt(2)) {
+ // [stack] ARRAY LENGTH INDEX ARRAY
+ return false;
+ }
+
+ if (!emitDupAt(1)) {
+ // [stack] ARRAY LENGTH INDEX ARRAY INDEX
+ return false;
+ }
+
+ // Retrieve initializers for this field
+ if (!emit1(JSOp::GetElem)) {
+ // [stack] ARRAY LENGTH INDEX INITIALIZERS
+ return false;
+ }
+
+ // This is guaranteed to run after super(), so we don't need TDZ checks.
+ if (!emitGetName(TaggedParserAtomIndex::WellKnown::dotThis())) {
+ // [stack] ARRAY LENGTH INDEX INITIALIZERS THIS
+ return false;
+ }
+
+ if (!emit1(JSOp::Swap)) {
+ // [stack] ARRAY LENGTH INDEX THIS INITIALIZERS
+ return false;
+ }
+
+ DecoratorEmitter de(this);
+ if (!de.emitInitializeFieldOrAccessor()) {
+ // [stack] ARRAY LENGTH INDEX
+ return false;
+ }
+
+ if (!emit1(JSOp::Inc)) {
+ // [stack] ARRAY LENGTH INDEX
+ return false;
+ }
+
+ if (!wh.emitEnd()) {
+ // [stack] ARRAY LENGTH INDEX
+ return false;
+ }
+
+ if (!emitPopN(3)) {
+ // [stack]
+ return false;
+ }
+ // 5. Return unused.
+#endif
+ }
+ return true;
+}
+
+bool BytecodeEmitter::emitInitializeStaticFields(ListNode* classMembers) {
+ auto isStaticField = [](ParseNode* propdef) {
+ return HasInitializer(propdef, true);
+ };
+ size_t numFields =
+ std::count_if(classMembers->contents().begin(),
+ classMembers->contents().end(), isStaticField);
+
+ if (numFields == 0) {
+ return true;
+ }
+
+ if (!emitGetName(TaggedParserAtomIndex::WellKnown::dotStaticInitializers())) {
+ // [stack] CTOR ARRAY
+ return false;
+ }
+
+ for (size_t fieldIndex = 0; fieldIndex < numFields; fieldIndex++) {
+ bool hasNext = fieldIndex < numFields - 1;
+ if (hasNext) {
+ // We Dup to keep the array around (it is consumed in the bytecode below)
+ // for next iterations of this loop, except for the last iteration, which
+ // avoids an extra Pop at the end of the loop.
+ if (!emit1(JSOp::Dup)) {
+ // [stack] CTOR ARRAY ARRAY
+ return false;
+ }
+ }
+
+ if (!emitNumberOp(fieldIndex)) {
+ // [stack] CTOR ARRAY? ARRAY INDEX
+ return false;
+ }
+
+ if (!emit1(JSOp::GetElem)) {
+ // [stack] CTOR ARRAY? FUNC
+ return false;
+ }
+
+ if (!emitDupAt(1 + hasNext)) {
+ // [stack] CTOR ARRAY? FUNC CTOR
+ return false;
+ }
+
+ // Callee is always internal function.
+ if (!emitCall(JSOp::CallIgnoresRv, 0)) {
+ // [stack] CTOR ARRAY? RVAL
+ return false;
+ }
+
+ if (!emit1(JSOp::Pop)) {
+ // [stack] CTOR ARRAY?
+ return false;
+ }
+ }
+
+ // Overwrite |.staticInitializers| and |.staticFieldKeys| with undefined to
+ // avoid keeping the arrays alive indefinitely.
+ auto clearStaticFieldSlot = [&](TaggedParserAtomIndex name) {
+ NameOpEmitter noe(this, name, NameOpEmitter::Kind::SimpleAssignment);
+ if (!noe.prepareForRhs()) {
+ // [stack] ENV? VAL?
+ return false;
+ }
+
+ if (!emit1(JSOp::Undefined)) {
+ // [stack] ENV? VAL? UNDEFINED
+ return false;
+ }
+
+ if (!noe.emitAssignment()) {
+ // [stack] VAL
+ return false;
+ }
+
+ if (!emit1(JSOp::Pop)) {
+ // [stack]
+ return false;
+ }
+
+ return true;
+ };
+
+ if (!clearStaticFieldSlot(
+ TaggedParserAtomIndex::WellKnown::dotStaticInitializers())) {
+ return false;
+ }
+
+ auto isStaticFieldWithComputedName = [](ParseNode* propdef) {
+ return propdef->is<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(
+ TaggedParserAtomIndex::WellKnown::dotStaticFieldKeys())) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+// Using MOZ_NEVER_INLINE in here is a workaround for llvm.org/pr14047. See
+// the comment on emitSwitch.
+MOZ_NEVER_INLINE bool BytecodeEmitter::emitObject(ListNode* objNode) {
+ // Note: this method uses the ObjLiteralWriter and emits ObjLiteralStencil
+ // objects into the GCThingList, which will evaluate them into real GC objects
+ // or shapes during JSScript::fullyInitFromEmitter. Eventually we want
+ // OBJLITERAL to be a real opcode, but for now, performance constraints limit
+ // us to evaluating object literals at the end of parse, when we're allowed to
+ // allocate GC things.
+ //
+ // There are four cases here, in descending order of preference:
+ //
+ // 1. The list of property names is "normal" and constant (no computed
+ // values, no integer indices), the values are all simple constants
+ // (numbers, booleans, strings), *and* this occurs in a run-once
+ // (singleton) context. In this case, we can emit ObjLiteral
+ // instructions to build an object with values, and the object will be
+ // attached to a JSOp::Object opcode, whose semantics are for the backend
+ // to simply steal the object from the script.
+ //
+ // 2. The list of property names is "normal" and constant as above, *and* this
+ // occurs in a run-once (singleton) context, but some values are complex
+ // (computed expressions, sub-objects, functions, etc.). In this case, we
+ // can still use JSOp::Object (because singleton context), but the object
+ // has |undefined| property values and InitProp ops are emitted to set the
+ // values.
+ //
+ // 3. The list of property names is "normal" and constant as above, but this
+ // occurs in a non-run-once (non-singleton) context. In this case, we can
+ // use the ObjLiteral functionality to describe an *empty* object (all
+ // values left undefined) with the right fields, which will become a
+ // JSOp::NewObject opcode using the object's shape to speed up the creation
+ // of the object each time it executes. The emitted bytecode still needs
+ // InitProp ops to set the values in this case.
+ //
+ // 4. Any other case. As a fallback, we use NewInit to create a new, empty
+ // object (i.e., `{}`) and then emit bytecode to initialize its properties
+ // one-by-one.
+
+ bool useObjLiteral = false;
+ bool useObjLiteralValues = false;
+ isPropertyListObjLiteralCompatible(objNode, &useObjLiteralValues,
+ &useObjLiteral);
+
+ // [stack]
+ //
+ ObjectEmitter oe(this);
+ if (useObjLiteral) {
+ bool singleton = checkSingletonContext() &&
+ !objNode->hasNonConstInitializer() && objNode->head();
+ JSOp op;
+ if (singleton) {
+ // Case 1 or 2.
+ op = JSOp::Object;
+ } else {
+ // Case 3.
+ useObjLiteralValues = false;
+ op = JSOp::NewObject;
+ }
+
+ // Use an ObjLiteral op. This will record ObjLiteral insns in the
+ // objLiteralWriter's buffer and add a fixup to the list of ObjLiteral
+ // fixups so that at GC-publish time at the end of parse, the full object
+ // (case 1 or 2) or shape (case 3) can be allocated and the bytecode can be
+ // patched to refer to it.
+ if (!emitPropertyListObjLiteral(objNode, op, useObjLiteralValues)) {
+ // [stack] OBJ
+ return false;
+ }
+ // Put the ObjectEmitter in the right state. This tells it that there will
+ // already be an object on the stack as a result of the (eventual)
+ // NewObject or Object op, and prepares it to emit values if needed.
+ if (!oe.emitObjectWithTemplateOnStack()) {
+ // [stack] OBJ
+ return false;
+ }
+ if (!useObjLiteralValues) {
+ // Case 2 or 3 above: we still need to emit bytecode to fill in the
+ // object's property values.
+ if (!emitPropertyList(objNode, oe, ObjectLiteral)) {
+ // [stack] OBJ
+ return false;
+ }
+ }
+ } else {
+ // Case 4 above: no ObjLiteral use, just bytecode to build the object from
+ // scratch.
+ if (!oe.emitObject(objNode->count())) {
+ // [stack] OBJ
+ return false;
+ }
+ if (!emitPropertyList(objNode, oe, ObjectLiteral)) {
+ // [stack] OBJ
+ return false;
+ }
+ }
+
+ if (!oe.emitEnd()) {
+ // [stack] OBJ
+ return false;
+ }
+
+ return true;
+}
+
+bool BytecodeEmitter::emitArrayLiteral(ListNode* array) {
+ // Emit JSOp::Object if the array consists entirely of primitive values and we
+ // are in a singleton context.
+ if (checkSingletonContext() && !array->hasNonConstInitializer() &&
+ !array->empty() && isArrayObjLiteralCompatible(array)) {
+ return emitObjLiteralArray(array);
+ }
+
+ return emitArray(array);
+}
+
+bool BytecodeEmitter::emitArray(ListNode* array) {
+ /*
+ * Emit code for [a, b, c] that is equivalent to constructing a new
+ * array and in source order evaluating each element value and adding
+ * it to the array, without invoking latent setters. We use the
+ * JSOp::NewInit and JSOp::InitElemArray bytecodes to ignore setters and
+ * to avoid dup'ing and popping the array as each element is added, as
+ * JSOp::SetElem/JSOp::SetProp would do.
+ */
+
+ uint32_t nspread = 0;
+ for (ParseNode* elem : array->contents()) {
+ if (elem->isKind(ParseNodeKind::Spread)) {
+ nspread++;
+ }
+ }
+
+ // Array literal's length is limited to NELEMENTS_LIMIT in parser.
+ static_assert(NativeObject::MAX_DENSE_ELEMENTS_COUNT <= INT32_MAX,
+ "array literals' maximum length must not exceed limits "
+ "required by BaselineCompiler::emit_NewArray, "
+ "BaselineCompiler::emit_InitElemArray, "
+ "and DoSetElemFallback's handling of JSOp::InitElemArray");
+
+ uint32_t count = array->count();
+ MOZ_ASSERT(count >= nspread);
+ MOZ_ASSERT(count <= NativeObject::MAX_DENSE_ELEMENTS_COUNT,
+ "the parser must throw an error if the array exceeds maximum "
+ "length");
+
+ // For arrays with spread, this is a very pessimistic allocation, the
+ // minimum possible final size.
+ if (!emitUint32Operand(JSOp::NewArray, count - nspread)) {
+ // [stack] ARRAY
+ return false;
+ }
+
+ uint32_t index = 0;
+ bool afterSpread = false;
+ for (ParseNode* elem : array->contents()) {
+ if (elem->isKind(ParseNodeKind::Spread)) {
+ if (!afterSpread) {
+ afterSpread = true;
+ if (!emitNumberOp(index)) {
+ // [stack] ARRAY INDEX
+ return false;
+ }
+ }
+
+ ParseNode* expr = elem->as<UnaryNode>().kid();
+ SelfHostedIter selfHostedIter = getSelfHostedIterFor(expr);
+
+ if (!updateSourceCoordNotes(elem->pn_pos.begin)) {
+ return false;
+ }
+ if (!emitTree(expr, ValueUsage::WantValue)) {
+ // [stack] ARRAY INDEX VALUE
+ return false;
+ }
+ if (!emitIterator(selfHostedIter)) {
+ // [stack] ARRAY INDEX NEXT ITER
+ return false;
+ }
+ if (!emit2(JSOp::Pick, 3)) {
+ // [stack] INDEX NEXT ITER ARRAY
+ return false;
+ }
+ if (!emit2(JSOp::Pick, 3)) {
+ // [stack] NEXT ITER ARRAY INDEX
+ return false;
+ }
+ if (!emitSpread(selfHostedIter)) {
+ // [stack] ARRAY INDEX
+ return false;
+ }
+ } else {
+ if (!updateSourceCoordNotes(elem->pn_pos.begin)) {
+ return false;
+ }
+ if (elem->isKind(ParseNodeKind::Elision)) {
+ if (!emit1(JSOp::Hole)) {
+ return false;
+ }
+ } else {
+ if (!emitTree(elem, ValueUsage::WantValue)) {
+ // [stack] ARRAY INDEX? VALUE
+ return false;
+ }
+ }
+
+ if (afterSpread) {
+ if (!emit1(JSOp::InitElemInc)) {
+ // [stack] ARRAY (INDEX+1)
+ return false;
+ }
+ } else {
+ if (!emitUint32Operand(JSOp::InitElemArray, index)) {
+ // [stack] ARRAY
+ return false;
+ }
+ }
+ }
+
+ index++;
+ }
+ MOZ_ASSERT(index == count);
+ if (afterSpread) {
+ if (!emit1(JSOp::Pop)) {
+ // [stack] ARRAY
+ return false;
+ }
+ }
+ return true;
+}
+
+bool BytecodeEmitter::emitSpreadIntoArray(UnaryNode* elem) {
+ MOZ_ASSERT(elem->isKind(ParseNodeKind::Spread));
+
+ if (!updateSourceCoordNotes(elem->pn_pos.begin)) {
+ // [stack] VALUE
+ return false;
+ }
+
+ SelfHostedIter selfHostedIter = getSelfHostedIterFor(elem->kid());
+ if (!emitIterator(selfHostedIter)) {
+ // [stack] NEXT ITER
+ return false;
+ }
+
+ if (!emitUint32Operand(JSOp::NewArray, 0)) {
+ // [stack] NEXT ITER ARRAY
+ return false;
+ }
+
+ if (!emitNumberOp(0)) {
+ // [stack] NEXT ITER ARRAY INDEX
+ return false;
+ }
+
+ if (!emitSpread(selfHostedIter)) {
+ // [stack] ARRAY INDEX
+ return false;
+ }
+
+ if (!emit1(JSOp::Pop)) {
+ // [stack] ARRAY
+ return false;
+ }
+ return true;
+}
+
+#ifdef ENABLE_RECORD_TUPLE
+bool BytecodeEmitter::emitRecordLiteral(ListNode* record) {
+ if (!emitUint32Operand(JSOp::InitRecord, record->count())) {
+ // [stack] RECORD
+ return false;
+ }
+
+ for (ParseNode* propdef : record->contents()) {
+ if (propdef->isKind(ParseNodeKind::Spread)) {
+ if (!emitTree(propdef->as<UnaryNode>().kid())) {
+ // [stack] RECORD SPREADEE
+ return false;
+ }
+ if (!emit1(JSOp::AddRecordSpread)) {
+ // [stack] RECORD
+ return false;
+ }
+ } else {
+ BinaryNode* prop = &propdef->as<BinaryNode>();
+
+ ParseNode* key = prop->left();
+ ParseNode* value = prop->right();
+
+ switch (key->getKind()) {
+ case ParseNodeKind::ObjectPropertyName:
+ if (!emitStringOp(JSOp::String, key->as<NameNode>().atom())) {
+ return false;
+ }
+ break;
+ case ParseNodeKind::ComputedName:
+ if (!emitTree(key->as<UnaryNode>().kid())) {
+ return false;
+ }
+ break;
+ default:
+ MOZ_ASSERT(key->isKind(ParseNodeKind::StringExpr) ||
+ key->isKind(ParseNodeKind::NumberExpr) ||
+ key->isKind(ParseNodeKind::BigIntExpr));
+ if (!emitTree(key)) {
+ return false;
+ }
+ break;
+ }
+ // [stack] RECORD KEY
+
+ if (!emitTree(value)) {
+ // [stack] RECORD KEY VALUE
+ return false;
+ }
+
+ if (!emit1(JSOp::AddRecordProperty)) {
+ // [stack] RECORD
+ return false;
+ }
+ }
+ }
+
+ if (!emit1(JSOp::FinishRecord)) {
+ // [stack] RECORD
+ return false;
+ }
+
+ return true;
+}
+
+bool BytecodeEmitter::emitTupleLiteral(ListNode* tuple) {
+ if (!emitUint32Operand(JSOp::InitTuple, tuple->count())) {
+ // [stack] TUPLE
+ return false;
+ }
+
+ for (ParseNode* elt : tuple->contents()) {
+ if (elt->isKind(ParseNodeKind::Spread)) {
+ ParseNode* expr = elt->as<UnaryNode>().kid();
+
+ if (!emitTree(expr)) {
+ // [stack] TUPLE VALUE
+ return false;
+ }
+ if (!emitIterator()) {
+ // [stack] TUPLE NEXT ITER
+ return false;
+ }
+ if (!emit2(JSOp::Pick, 2)) {
+ // [stack] NEXT ITER TUPLE
+ return false;
+ }
+ if (!emitSpread(getSelfHostedIterFor(expr), /* spreadeeStackItems = */ 1,
+ JSOp::AddTupleElement)) {
+ // [stack] TUPLE
+ return false;
+ }
+ } else {
+ if (!emitTree(elt)) {
+ // [stack] TUPLE VALUE
+ return false;
+ }
+
+ // Update location to throw errors about non-primitive elements
+ // in the correct position.
+ if (!updateSourceCoordNotes(elt->pn_pos.begin)) {
+ return false;
+ }
+
+ if (!emit1(JSOp::AddTupleElement)) {
+ // [stack] TUPLE
+ return false;
+ }
+ }
+ }
+
+ if (!emit1(JSOp::FinishTuple)) {
+ // [stack] TUPLE
+ return false;
+ }
+
+ return true;
+}
+#endif
+
+static inline JSOp UnaryOpParseNodeKindToJSOp(ParseNodeKind pnk) {
+ switch (pnk) {
+ case ParseNodeKind::ThrowStmt:
+ return JSOp::Throw;
+ case ParseNodeKind::VoidExpr:
+ return JSOp::Void;
+ case ParseNodeKind::NotExpr:
+ return JSOp::Not;
+ case ParseNodeKind::BitNotExpr:
+ return JSOp::BitNot;
+ case ParseNodeKind::PosExpr:
+ return JSOp::Pos;
+ case ParseNodeKind::NegExpr:
+ return JSOp::Neg;
+ default:
+ MOZ_CRASH("unexpected unary op");
+ }
+}
+
+bool BytecodeEmitter::emitUnary(UnaryNode* unaryNode) {
+ if (!updateSourceCoordNotes(unaryNode->pn_pos.begin)) {
+ return false;
+ }
+
+ JSOp op = UnaryOpParseNodeKindToJSOp(unaryNode->getKind());
+ ValueUsage valueUsage =
+ op == JSOp::Void ? ValueUsage::IgnoreValue : ValueUsage::WantValue;
+ if (!emitTree(unaryNode->kid(), valueUsage)) {
+ return false;
+ }
+ return emit1(op);
+}
+
+bool BytecodeEmitter::emitTypeof(UnaryNode* typeofNode, JSOp op) {
+ MOZ_ASSERT(op == JSOp::Typeof || op == JSOp::TypeofExpr);
+
+ if (!updateSourceCoordNotes(typeofNode->pn_pos.begin)) {
+ return false;
+ }
+
+ if (!emitTree(typeofNode->kid())) {
+ return false;
+ }
+
+ return emit1(op);
+}
+
+bool BytecodeEmitter::emitFunctionFormalParameters(ParamsBodyNode* paramsBody) {
+ FunctionBox* funbox = sc->asFunctionBox();
+
+ bool hasRest = funbox->hasRest();
+
+ FunctionParamsEmitter fpe(this, funbox);
+ for (ParseNode* arg : paramsBody->parameters()) {
+ ParseNode* bindingElement = arg;
+ ParseNode* initializer = nullptr;
+ if (arg->isKind(ParseNodeKind::AssignExpr)) {
+ bindingElement = arg->as<BinaryNode>().left();
+ initializer = arg->as<BinaryNode>().right();
+ }
+ bool hasInitializer = !!initializer;
+ bool isRest =
+ hasRest && arg->pn_next == *std::end(paramsBody->parameters());
+ bool isDestructuring = !bindingElement->isKind(ParseNodeKind::Name);
+
+ // Left-hand sides are either simple names or destructuring patterns.
+ MOZ_ASSERT(bindingElement->isKind(ParseNodeKind::Name) ||
+ bindingElement->isKind(ParseNodeKind::ArrayExpr) ||
+ bindingElement->isKind(ParseNodeKind::ObjectExpr));
+
+ auto emitDefaultInitializer = [this, &initializer, &bindingElement]() {
+ // [stack]
+
+ if (!this->emitInitializer(initializer, bindingElement)) {
+ // [stack] DEFAULT
+ return false;
+ }
+ return true;
+ };
+
+ auto emitDestructuring = [this, &bindingElement]() {
+ // [stack] ARG
+
+ if (!this->emitDestructuringOps(&bindingElement->as<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 {
+ auto 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;
+ }
+ auto paramName = bindingElement->as<NameNode>().name();
+ if (!fpe.emitDefaultEnd(paramName)) {
+ // [stack]
+ return false;
+ }
+
+ continue;
+ }
+
+ auto 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, TaggedParserAtomIndex name, JSOp op) {
+ // A special name must be slotful, either on the frame or on the
+ // call environment.
+ MOZ_ASSERT(bce->lookupName(name).hasKnownSlot());
+
+ NameOpEmitter noe(bce, name, NameOpEmitter::Kind::Initialize);
+ if (!noe.prepareForRhs()) {
+ // [stack]
+ return false;
+ }
+ if (!bce->emit1(op)) {
+ // [stack] THIS/ARGUMENTS/NEW.TARGET
+ return false;
+ }
+ if (!noe.emitAssignment()) {
+ // [stack] THIS/ARGUMENTS/NEW.TARGET
+ return false;
+ }
+ if (!bce->emit1(JSOp::Pop)) {
+ // [stack]
+ return false;
+ }
+
+ return true;
+ };
+
+ // Do nothing if the function doesn't have an arguments binding.
+ if (funbox->needsArgsObj()) {
+ // Self-hosted code should use the more efficient ArgumentsLength and
+ // GetArgument intrinsics instead of `arguments`.
+ MOZ_ASSERT(emitterMode != BytecodeEmitter::SelfHosting);
+ if (!emitInitializeFunctionSpecialName(
+ this, TaggedParserAtomIndex::WellKnown::arguments(),
+ JSOp::Arguments)) {
+ // [stack]
+ return false;
+ }
+ }
+
+ // Do nothing if the function doesn't have a this-binding (this
+ // happens for instance if it doesn't use this/eval or if it's an
+ // arrow function).
+ if (funbox->functionHasThisBinding()) {
+ if (!emitInitializeFunctionSpecialName(
+ this, TaggedParserAtomIndex::WellKnown::dotThis(),
+ JSOp::FunctionThis)) {
+ return false;
+ }
+ }
+
+ // Do nothing if the function doesn't have a new.target-binding (this happens
+ // for instance if it doesn't use new.target/eval or if it's an arrow
+ // function).
+ if (funbox->functionHasNewTargetBinding()) {
+ if (!emitInitializeFunctionSpecialName(
+ this, TaggedParserAtomIndex::WellKnown::dotNewTarget(),
+ JSOp::NewTarget)) {
+ return false;
+ }
+ }
+
+ // Do nothing if the function doesn't implicitly return a promise result.
+ if (funbox->needsPromiseResult()) {
+ if (!emitInitializeFunctionSpecialName(
+ this, TaggedParserAtomIndex::WellKnown::dotGenerator(),
+ JSOp::Generator)) {
+ // [stack]
+ return false;
+ }
+ }
+ return true;
+}
+
+bool BytecodeEmitter::emitLexicalInitialization(NameNode* name) {
+ return emitLexicalInitialization(name->name());
+}
+
+bool BytecodeEmitter::emitLexicalInitialization(TaggedParserAtomIndex name) {
+ NameOpEmitter noe(this, name, NameOpEmitter::Kind::Initialize);
+ if (!noe.prepareForRhs()) {
+ return false;
+ }
+
+ // The caller has pushed the RHS to the top of the stack. Assert that the
+ // binding can be initialized without a binding object on the stack, and that
+ // no BIND[G]NAME ops were emitted.
+ MOZ_ASSERT(noe.loc().isLexical() || noe.loc().isSynthetic() ||
+ noe.loc().isPrivateMethod());
+ MOZ_ASSERT(!noe.emittedBindOp());
+
+ if (!noe.emitAssignment()) {
+ return false;
+ }
+
+ return true;
+}
+
+static MOZ_ALWAYS_INLINE ParseNode* FindConstructor(ListNode* classMethods) {
+ for (ParseNode* classElement : classMethods->contents()) {
+ ParseNode* unwrappedElement = classElement;
+ if (unwrappedElement->is<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() ==
+ TaggedParserAtomIndex::WellKnown::constructor()) {
+ return classElement;
+ }
+ }
+ }
+ return nullptr;
+}
+
+bool BytecodeEmitter::emitNewPrivateName(TaggedParserAtomIndex bindingName,
+ TaggedParserAtomIndex symbolName) {
+ if (!emitAtomOp(JSOp::NewPrivateName, symbolName)) {
+ // [stack] HERITAGE PRIVATENAME
+ return false;
+ }
+
+ // Add a binding for #name => privatename
+ if (!emitLexicalInitialization(bindingName)) {
+ // [stack] HERITAGE PRIVATENAME
+ return false;
+ }
+
+ // Pop Private name off the stack.
+ if (!emit1(JSOp::Pop)) {
+ // [stack] HERITAGE
+ return false;
+ }
+
+ return true;
+}
+
+bool BytecodeEmitter::emitNewPrivateNames(
+ TaggedParserAtomIndex privateBrandName, ListNode* classMembers) {
+ bool hasPrivateBrand = false;
+
+ for (ParseNode* classElement : classMembers->contents()) {
+ ParseNode* elementName;
+ if (classElement->is<ClassMethod>()) {
+ elementName = &classElement->as<ClassMethod>().name();
+ } else if (classElement->is<ClassField>()) {
+ elementName = &classElement->as<ClassField>().name();
+ } else {
+ continue;
+ }
+
+ if (!elementName->isKind(ParseNodeKind::PrivateName)) {
+ continue;
+ }
+
+ // Non-static private methods' private names are optimized away.
+ bool isOptimized = false;
+ if (classElement->is<ClassMethod>() &&
+ !classElement->as<ClassMethod>().isStatic()) {
+ hasPrivateBrand = true;
+ if (classElement->as<ClassMethod>().accessorType() ==
+ AccessorType::None) {
+ isOptimized = true;
+ }
+ }
+
+ if (!isOptimized) {
+ auto privateName = elementName->as<NameNode>().name();
+ if (!emitNewPrivateName(privateName, privateName)) {
+ return false;
+ }
+ }
+ }
+
+ if (hasPrivateBrand) {
+ // We don't make a private name for every optimized method, but we need one
+ // private name per class, the `.privateBrand`.
+ if (!emitNewPrivateName(TaggedParserAtomIndex::WellKnown::dotPrivateBrand(),
+ privateBrandName)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+// This follows ES6 14.5.14 (ClassDefinitionEvaluation) and ES6 14.5.15
+// (BindingClassDeclarationEvaluation).
+bool BytecodeEmitter::emitClass(
+ ClassNode* classNode,
+ ClassNameKind nameKind /* = ClassNameKind::BindingName */,
+ TaggedParserAtomIndex
+ nameForAnonymousClass /* = TaggedParserAtomIndex::null() */) {
+ MOZ_ASSERT((nameKind == ClassNameKind::InferredName) ==
+ bool(nameForAnonymousClass));
+
+ ParseNode* heritageExpression = classNode->heritage();
+ ListNode* classMembers = classNode->memberList();
+ ParseNode* constructor = FindConstructor(classMembers);
+
+ // If |nameKind != ClassNameKind::ComputedName|
+ // [stack]
+ // Else
+ // [stack] NAME
+
+ ClassEmitter ce(this);
+ TaggedParserAtomIndex innerName;
+ ClassEmitter::Kind kind = ClassEmitter::Kind::Expression;
+ if (ClassNames* names = classNode->names()) {
+ MOZ_ASSERT(nameKind == ClassNameKind::BindingName);
+ innerName = names->innerBinding()->name();
+ MOZ_ASSERT(innerName);
+
+ if (names->outerBinding()) {
+ MOZ_ASSERT(names->outerBinding()->name());
+ MOZ_ASSERT(names->outerBinding()->name() == innerName);
+ kind = ClassEmitter::Kind::Declaration;
+ }
+ }
+
+ if (LexicalScopeNode* scopeBindings = classNode->scopeBindings()) {
+ if (!ce.emitScope(scopeBindings->scopeBindings())) {
+ // [stack]
+ return false;
+ }
+ }
+
+ bool isDerived = !!heritageExpression;
+ if (isDerived) {
+ if (!updateSourceCoordNotes(classNode->pn_pos.begin)) {
+ return false;
+ }
+ if (!markStepBreakpoint()) {
+ return false;
+ }
+ if (!emitTree(heritageExpression)) {
+ // [stack] HERITAGE
+ return false;
+ }
+ }
+
+ // The class body scope holds any private names. Those mustn't be visible in
+ // the heritage expression and hence the scope must be emitted after the
+ // heritage expression.
+ if (ClassBodyScopeNode* bodyScopeBindings = classNode->bodyScopeBindings()) {
+ if (!ce.emitBodyScope(bodyScopeBindings->scopeBindings())) {
+ // [stack] HERITAGE
+ return false;
+ }
+
+ // The spec does not say anything about private brands being symbols. It's
+ // an implementation detail. So we can give the special private brand
+ // symbol any description we want and users won't normally see it. For
+ // debugging, use the class name.
+ auto privateBrandName = innerName;
+ if (!innerName) {
+ privateBrandName = nameForAnonymousClass
+ ? nameForAnonymousClass
+ : TaggedParserAtomIndex::WellKnown::anonymous();
+ }
+ if (!emitNewPrivateNames(privateBrandName, classMembers)) {
+ return false;
+ }
+ }
+
+ bool hasNameOnStack = nameKind == ClassNameKind::ComputedName;
+ if (isDerived) {
+ if (!ce.emitDerivedClass(innerName, nameForAnonymousClass,
+ hasNameOnStack)) {
+ // [stack] HERITAGE HOMEOBJ
+ return false;
+ }
+ } else {
+ if (!ce.emitClass(innerName, nameForAnonymousClass, hasNameOnStack)) {
+ // [stack] HOMEOBJ
+ return false;
+ }
+ }
+
+ // Stack currently has HOMEOBJ followed by optional HERITAGE. When HERITAGE
+ // is not used, an implicit value of %FunctionPrototype% is implied.
+
+ // See |Parser::classMember(...)| for the reason why |.initializers| is
+ // created within its own scope.
+ Maybe<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()->length == 1);
+ MOZ_ASSERT(GetScopeDataTrailingNames(constructorScope->scopeBindings())[0]
+ .name() ==
+ TaggedParserAtomIndex::WellKnown::dotInitializers());
+
+ auto needsInitializer = [](ParseNode* propdef) {
+ return NeedsFieldInitializer(propdef, false) ||
+ NeedsAccessorInitializer(propdef, false);
+ };
+
+ // As an optimization omit the |.initializers| binding when no instance
+ // fields or private methods are present.
+ bool needsInitializers =
+ std::any_of(classMembers->contents().begin(),
+ classMembers->contents().end(), needsInitializer);
+ if (needsInitializers) {
+ lse.emplace(this);
+ if (!lse->emitScope(ScopeKind::Lexical,
+ constructorScope->scopeBindings())) {
+ return false;
+ }
+
+ // Any class with field initializers will have a constructor
+ if (!emitCreateMemberInitializers(ce, classMembers,
+ FieldPlacement::Instance)) {
+ return false;
+ }
+ }
+
+ ctor = &constructorScope->scopeBody()->as<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)) {
+ // [stack] HOMEOBJ CTOR
+ return false;
+ }
+ if (lse.isSome()) {
+ if (!lse->emitEnd()) {
+ return false;
+ }
+ lse.reset();
+ }
+ if (!ce.emitInitConstructor(needsHomeObject)) {
+ // [stack] CTOR HOMEOBJ
+ return false;
+ }
+
+ if (!emitCreateFieldKeys(classMembers, FieldPlacement::Instance)) {
+ return false;
+ }
+
+ if (!emitCreateMemberInitializers(ce, classMembers, FieldPlacement::Static)) {
+ return false;
+ }
+
+ if (!emitCreateFieldKeys(classMembers, FieldPlacement::Static)) {
+ return false;
+ }
+
+ if (!emitPropertyList(classMembers, ce, ClassBody)) {
+ // [stack] CTOR HOMEOBJ
+ return false;
+ }
+
+ if (!ce.emitBinding()) {
+ // [stack] CTOR
+ return false;
+ }
+
+ if (!emitInitializeStaticFields(classMembers)) {
+ // [stack] CTOR
+ return false;
+ }
+
+ if (!ce.emitEnd(kind)) {
+ // [stack] # class declaration
+ // [stack]
+ // [stack] # class expression
+ // [stack] CTOR
+ return false;
+ }
+
+ return true;
+}
+
+bool BytecodeEmitter::emitExportDefault(BinaryNode* exportNode) {
+ MOZ_ASSERT(exportNode->isKind(ParseNodeKind::ExportDefaultStmt));
+
+ ParseNode* valueNode = exportNode->left();
+ if (valueNode->isDirectRHSAnonFunction()) {
+ MOZ_ASSERT(exportNode->right());
+
+ if (!emitAnonymousFunctionWithName(
+ valueNode, TaggedParserAtomIndex::WellKnown::default_())) {
+ return false;
+ }
+ } else {
+ if (!emitTree(valueNode)) {
+ return false;
+ }
+ }
+
+ if (ParseNode* binding = exportNode->right()) {
+ if (!emitLexicalInitialization(&binding->as<NameNode>())) {
+ return false;
+ }
+
+ if (!emit1(JSOp::Pop)) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool BytecodeEmitter::emitTree(
+ ParseNode* pn, ValueUsage valueUsage /* = ValueUsage::WantValue */,
+ EmitLineNumberNote emitLineNote /* = EMIT_LINENOTE */) {
+ AutoCheckRecursionLimit recursion(fc);
+ if (!recursion.check(fc)) {
+ return false;
+ }
+
+ /* Emit notes to tell the current bytecode's source line number.
+ However, a couple trees require special treatment; see the
+ relevant emitter functions for details. */
+ if (emitLineNote == EMIT_LINENOTE &&
+ !ParseNodeRequiresSpecialLineNumberNotes(pn)) {
+ if (!updateLineNumberNotes(pn->pn_pos.begin)) {
+ return false;
+ }
+ }
+
+ switch (pn->getKind()) {
+ case ParseNodeKind::Function:
+ if (!emitFunction(&pn->as<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>(), valueUsage)) {
+ return false;
+ }
+ break;
+
+ case ParseNodeKind::AddExpr:
+ case ParseNodeKind::SubExpr:
+ case ParseNodeKind::BitOrExpr:
+ case ParseNodeKind::BitXorExpr:
+ case ParseNodeKind::BitAndExpr:
+ case ParseNodeKind::StrictEqExpr:
+ case ParseNodeKind::EqExpr:
+ case ParseNodeKind::StrictNeExpr:
+ case ParseNodeKind::NeExpr:
+ case ParseNodeKind::LtExpr:
+ case ParseNodeKind::LeExpr:
+ case ParseNodeKind::GtExpr:
+ case ParseNodeKind::GeExpr:
+ case ParseNodeKind::InExpr:
+ case ParseNodeKind::InstanceOfExpr:
+ case ParseNodeKind::LshExpr:
+ case ParseNodeKind::RshExpr:
+ case ParseNodeKind::UrshExpr:
+ case ParseNodeKind::MulExpr:
+ case ParseNodeKind::DivExpr:
+ case ParseNodeKind::ModExpr:
+ if (!emitLeftAssociative(&pn->as<ListNode>())) {
+ return false;
+ }
+ break;
+
+ case ParseNodeKind::PrivateInExpr:
+ if (!emitPrivateInExpr(&pn->as<ListNode>())) {
+ return false;
+ }
+ break;
+
+ case ParseNodeKind::PowExpr:
+ if (!emitRightAssociative(&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>(), valueUsage)) {
+ 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();
+ MOZ_ASSERT(!elem->key().isKind(ParseNodeKind::PrivateName));
+ ElemOpEmitter eoe(this, ElemOpEmitter::Kind::Get,
+ isSuper ? ElemOpEmitter::ObjKind::Super
+ : ElemOpEmitter::ObjKind::Other);
+ if (!emitElemObjAndKey(elem, isSuper, eoe)) {
+ // [stack] # if Super
+ // [stack] THIS KEY
+ // [stack] # otherwise
+ // [stack] OBJ KEY
+ return false;
+ }
+ if (!eoe.emitGet()) {
+ // [stack] ELEM
+ return false;
+ }
+
+ break;
+ }
+
+ case ParseNodeKind::PrivateMemberExpr: {
+ PrivateMemberAccess* privateExpr = &pn->as<PrivateMemberAccess>();
+ PrivateOpEmitter xoe(this, PrivateOpEmitter::Kind::Get,
+ privateExpr->privateName().name());
+ if (!emitTree(&privateExpr->expression())) {
+ // [stack] OBJ
+ return false;
+ }
+ if (!xoe.emitReference()) {
+ // [stack] OBJ NAME
+ return false;
+ }
+ if (!xoe.emitGet()) {
+ // [stack] VALUE
+ return false;
+ }
+
+ break;
+ }
+
+ case ParseNodeKind::NewExpr:
+ case ParseNodeKind::TaggedTemplateExpr:
+ case ParseNodeKind::CallExpr:
+ case ParseNodeKind::SuperCallExpr:
+ if (!emitCallOrNew(&pn->as<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 (!emitStringOp(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 (!emitNewTarget(&pn->as<NewTargetNode>())) {
+ return false;
+ }
+ break;
+
+ case ParseNodeKind::ImportMetaExpr:
+ if (!emit1(JSOp::ImportMeta)) {
+ return false;
+ }
+ break;
+
+ case ParseNodeKind::CallImportExpr: {
+ BinaryNode* spec = &pn->as<BinaryNode>().right()->as<BinaryNode>();
+
+ if (!emitTree(spec->left())) {
+ // [stack] specifier
+ return false;
+ }
+
+ if (!spec->right()->isKind(ParseNodeKind::PosHolder)) {
+ // [stack] specifier options
+ if (!emitTree(spec->right())) {
+ return false;
+ }
+ } else {
+ // [stack] specifier undefined
+ if (!emit1(JSOp::Undefined)) {
+ return false;
+ }
+ }
+
+ if (!emit1(JSOp::DynamicImport)) {
+ return false;
+ }
+
+ break;
+ }
+
+ case ParseNodeKind::SetThis:
+ if (!emitSetThis(&pn->as<BinaryNode>())) {
+ return false;
+ }
+ break;
+
+#ifdef ENABLE_RECORD_TUPLE
+ case ParseNodeKind::RecordExpr:
+ if (!emitRecordLiteral(&pn->as<ListNode>())) {
+ return false;
+ }
+ break;
+
+ case ParseNodeKind::TupleExpr:
+ if (!emitTupleLiteral(&pn->as<ListNode>())) {
+ return false;
+ }
+ break;
+#endif
+
+ case ParseNodeKind::PropertyNameExpr:
+ case ParseNodeKind::PosHolder:
+ MOZ_FALLTHROUGH_ASSERT(
+ "Should never try to emit ParseNodeKind::PosHolder or ::Property");
+
+ default:
+ MOZ_ASSERT(0);
+ }
+
+ return true;
+}
+
+static bool AllocSrcNote(FrontendContext* fc, SrcNotesVector& notes,
+ unsigned size, unsigned* index) {
+ size_t oldLength = notes.length();
+
+ if (MOZ_UNLIKELY(oldLength + size > MaxSrcNotesLength)) {
+ ReportAllocationOverflow(fc);
+ return false;
+ }
+
+ if (!notes.growByUninitialized(size)) {
+ return false;
+ }
+
+ *index = oldLength;
+ return true;
+}
+
+bool BytecodeEmitter::addTryNote(TryNoteKind kind, uint32_t stackDepth,
+ BytecodeOffset start, BytecodeOffset end) {
+ MOZ_ASSERT(!inPrologue());
+ return bytecodeSection().tryNoteList().append(kind, stackDepth, start, end);
+}
+
+bool BytecodeEmitter::newSrcNote(SrcNoteType type, unsigned* indexp) {
+ // Non-gettable source notes such as column/lineno and debugger should not be
+ // emitted for prologue / self-hosted.
+ MOZ_ASSERT_IF(skipLocationSrcNotes() || skipBreakpointSrcNotes(),
+ type <= SrcNoteType::LastGettable);
+
+ SrcNotesVector& notes = bytecodeSection().notes();
+ unsigned index;
+
+ /*
+ * Compute delta from the last annotated bytecode's offset. If it's too
+ * big to fit in sn, allocate one or more xdelta notes and reset sn.
+ */
+ BytecodeOffset offset = bytecodeSection().offset();
+ ptrdiff_t delta = (offset - bytecodeSection().lastNoteOffset()).value();
+ bytecodeSection().setLastNoteOffset(offset);
+
+ auto allocator = [&](unsigned size) -> SrcNote* {
+ if (!AllocSrcNote(fc, notes, size, &index)) {
+ return nullptr;
+ }
+ return &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(fc, notes, size, &index)) {
+ return nullptr;
+ }
+ return &notes[index];
+ };
+
+ return SrcNoteWriter::writeOperand(operand, allocator);
+}
+
+bool BytecodeEmitter::intoScriptStencil(ScriptIndex scriptIndex) {
+ js::UniquePtr<ImmutableScriptData> immutableScriptData =
+ createImmutableScriptData();
+ if (!immutableScriptData) {
+ return false;
+ }
+
+ MOZ_ASSERT(outermostScope().hasNonSyntacticScopeOnChain() ==
+ sc->hasNonSyntacticScope());
+
+ auto& things = perScriptData().gcThingList().objects();
+ if (!compilationState.appendGCThings(fc, scriptIndex, things)) {
+ return false;
+ }
+
+ // Hand over the ImmutableScriptData instance generated by BCE.
+ auto* sharedData =
+ SharedImmutableScriptData::createWith(fc, std::move(immutableScriptData));
+ if (!sharedData) {
+ return false;
+ }
+
+ // De-duplicate the bytecode within the runtime.
+ if (!compilationState.sharedData.addAndShare(fc, scriptIndex, sharedData)) {
+ return false;
+ }
+
+ ScriptStencil& script = compilationState.scriptData[scriptIndex];
+ script.setHasSharedData();
+
+ // Update flags specific to functions.
+ if (sc->isFunctionBox()) {
+ FunctionBox* funbox = sc->asFunctionBox();
+ MOZ_ASSERT(&script == &funbox->functionStencil());
+ funbox->copyUpdatedImmutableFlags();
+ MOZ_ASSERT(script.isFunction());
+ } else {
+ ScriptStencilExtra& scriptExtra = compilationState.scriptExtra[scriptIndex];
+ sc->copyScriptExtraFields(scriptExtra);
+ }
+
+ return true;
+}
+
+SelfHostedIter BytecodeEmitter::getSelfHostedIterFor(ParseNode* parseNode) {
+ if (emitterMode == BytecodeEmitter::SelfHosting &&
+ parseNode->isKind(ParseNodeKind::CallExpr) &&
+ (parseNode->as<BinaryNode>().left()->isName(
+ TaggedParserAtomIndex::WellKnown::allowContentIter()) ||
+ parseNode->as<BinaryNode>().left()->isName(
+ TaggedParserAtomIndex::WellKnown::allowContentIterWith()))) {
+ return SelfHostedIter::Allow;
+ }
+
+ return SelfHostedIter::Deny;
+}
+
+#if defined(DEBUG) || defined(JS_JITSPEW)
+void BytecodeEmitter::dumpAtom(TaggedParserAtomIndex index) const {
+ parserAtoms().dump(index);
+}
+#endif
diff --git a/js/src/frontend/BytecodeEmitter.h b/js/src/frontend/BytecodeEmitter.h
new file mode 100644
index 0000000000..6d9c6eed0a
--- /dev/null
+++ b/js/src/frontend/BytecodeEmitter.h
@@ -0,0 +1,1095 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* JS bytecode generation. */
+
+#ifndef frontend_BytecodeEmitter_h
+#define frontend_BytecodeEmitter_h
+
+#include "mozilla/Assertions.h" // MOZ_ASSERT
+#include "mozilla/Attributes.h" // MOZ_STACK_CLASS, MOZ_ALWAYS_INLINE, MOZ_NEVER_INLINE, MOZ_RAII
+#include "mozilla/Maybe.h" // mozilla::Maybe, mozilla::Some
+#include "mozilla/Saturate.h" // mozilla::SaturateUint8
+#include "mozilla/Span.h" // mozilla::Span
+
+#include <stddef.h> // ptrdiff_t
+#include <stdint.h> // uint16_t, uint32_t
+
+#include "frontend/AbstractScopePtr.h" // ScopeIndex
+#include "frontend/BytecodeSection.h" // BytecodeSection, PerScriptData, CGScopeList
+#include "frontend/DestructuringFlavor.h" // DestructuringFlavor
+#include "frontend/EitherParser.h" // EitherParser
+#include "frontend/IteratorKind.h" // IteratorKind
+#include "frontend/JumpList.h" // JumpList, JumpTarget
+#include "frontend/NameAnalysisTypes.h" // NameLocation
+#include "frontend/NameCollections.h" // AtomIndexMap
+#include "frontend/ParseNode.h" // ParseNode and subclasses
+#include "frontend/Parser.h" // Parser, PropListType
+#include "frontend/ParserAtom.h" // TaggedParserAtomIndex, ParserAtom
+#include "frontend/ScriptIndex.h" // ScriptIndex
+#include "frontend/SelfHostedIter.h" // SelfHostedIter
+#include "frontend/SourceNotes.h" // SrcNoteType
+#include "frontend/ValueUsage.h" // ValueUsage
+#include "js/AllocPolicy.h" // ReportOutOfMemory
+#include "js/TypeDecls.h" // jsbytecode
+#include "vm/BuiltinObjectKind.h" // BuiltinObjectKind
+#include "vm/CheckIsObjectKind.h" // CheckIsObjectKind
+#include "vm/CompletionKind.h" // CompletionKind
+#include "vm/FunctionPrefixKind.h" // FunctionPrefixKind
+#include "vm/GeneratorResumeKind.h" // GeneratorResumeKind
+#include "vm/Opcodes.h" // JSOp
+#include "vm/SharedStencil.h" // GCThingIndex, MemberInitializers
+#include "vm/StencilEnums.h" // TryNoteKind
+#include "vm/ThrowMsgKind.h" // ThrowMsgKind, ThrowCondition
+
+namespace js {
+
+class FrontendContext;
+
+namespace frontend {
+
+class BytecodeOffset;
+class CallOrNewEmitter;
+class ClassEmitter;
+class ElemOpEmitter;
+class EmitterScope;
+class ErrorReporter;
+class FullParseHandler;
+class NestableControl;
+class PrivateOpEmitter;
+class PropertyEmitter;
+class PropOpEmitter;
+class OptionalEmitter;
+class SharedContext;
+class TDZCheckCache;
+class TryEmitter;
+
+struct TokenPos;
+
+enum class ValueIsOnStack { Yes, No };
+
+// [SMDOC] Bytecode emission
+//
+// Bytecode emitter class and helper classes for generating bytecode and related
+// stencil data from AST generated by JS parser.
+//
+//
+// BytecodeEmitter
+// ---------------
+//
+// BytecodeEmitter receives an AST, and utilizes helper classes to generate the
+// bytecode sequence, and related stencil data.
+//
+// BytecodeEmitter can be nested, in order to emit inner non-lazy function
+// scripts.
+//
+//
+// Bytecode structures
+// -------------------
+//
+// While bytecode is being emitted, it is separated into 2 parts, the prologue
+// and the main part. The prologue part contains instantiation of the declared
+// variables, functions, and special names in function. The main part contains
+// the remaining part of the bytecode.
+//
+// The generated bytecode is stored into the following 2 classes, before
+// converting them into stencil data (See ImmutableScriptData and
+// BytecodeEmitter::createImmutableScriptData):
+//
+// * BytecodeSection
+// * PerScriptData
+//
+// BytecodeSection stores the bytecode sequence and data directly associated
+// with opcode or index inside the bytecode sequence.
+//
+// PerScriptData contains data referred from the bytecode, that is mostly the
+// list of GC things.
+//
+//
+// Bindings
+// --------
+//
+// # Scope and bindings
+//
+// When emitting AST node that's associated with a given scope, EmitterScope is
+// allocated to store/cache the bindings information.
+//
+// This information is used when emitting an opcode that accesses bindings, to
+// determine where the binding is stored, and how the binding should be
+// accessed, including which opcode to use and what operand to use for it.
+//
+//
+// # Temporal Dead Zone (TDZ) check cache
+//
+// The spec requires TDZ check for all lexical variable access, but emitting
+// TDZ check for all operation increases the bytecode size and affects the
+// performance. TDZCheckCache is a cache to optimize away unnecessary TDZ check
+// operations.
+//
+// See comments for TDZCheckCache for more details.
+//
+//
+// Control structures
+// ------------------
+//
+// # Jump list
+//
+// When emitting jump-related bytecode (if-else, break/continue, try-catch),
+// forward jump is tracked by JumpList class, in order to patch the jump
+// after the jump target is emitted.
+//
+// See the comment above JumpList class for mode details.
+//
+//
+// # Loop and label
+//
+// Control structure related to break/continue is handled by NestableControl and
+// its subclasses. Those classes handle jump with labelled and un-labelled
+// break/continue, stack balancing around them, TDZ check cache for the
+// loop's basic block, and association between the control and the scope.
+//
+//
+// Emitter helpers
+// ---------------
+//
+// Bytecode sequence or structure specific to certain syntax (e.g. if, for, try)
+// are handled by emitter helper classes.
+//
+// Each emitter helper is defined in *Emitter.{cpp,h} in this directory.
+//
+// Emitter helpers should meet the following requirements:
+// * helper classes should be ParseNode-agnostic
+// * helper classes shouldn't contain `JS::Rooted` field, given they can be
+// held in `mozilla::Maybe` in the consumer or other helper classes
+// * instantiation (ctor/dtor) of the emitter helper class shouldn't
+// modify BytecodeEmitter, except for nestable controls
+// * instantiation (ctor/dtor) of the emitter helper class shouldn't
+// read BytecodeEmitter field that can change before the first method call.
+// Such data should be explicitly passed as parameter, or be accessed inside
+// the method
+// * methods that emits bytecode should be named `emit*` or `prepareFor*`
+// * methods and their names shouldn't require the consumer knowing the
+// details of the bytecode sequence/structure that the helper emits
+// * implicit branch or scope/control handling should be hidden from the
+// consumer
+// * If there are multiple operations between bytecode that the consumer
+// emits, they should be wrapped into single `emit*` or `prepareFor*`
+// method
+// e.g.
+// // Bad!
+// helper.emitJumpAroundA();
+// helper.allocateScopeForA();
+// ... // emit bytecode for A here
+// helper.deallocateScopeForA();
+// helper.emitJumpAroundB();
+// helper.allocateScopeForB();
+// ... // emit bytecode for B here
+// helper.deallocateScopeForB();
+// helper.emitJumpTarget();
+//
+// // Good!
+// helper.prepareForA();
+// ... // emit bytecode for A here
+// helper.prepareForB();
+// ... // emit bytecode for B here
+// helper.emitEnd();
+// * helper classes should track state transition and assert it in each
+// method call, to avoid misuse
+// * it's recommended to defer receiving parameter until the parameter value
+// is actually used in the method, instead of receiving and storing them
+// into instance fields
+//
+// See comment block above each helper class for more details and example usage.
+
+struct MOZ_STACK_CLASS BytecodeEmitter {
+ // Context shared between parsing and bytecode generation.
+ SharedContext* const sc = nullptr;
+
+ FrontendContext* const fc = nullptr;
+
+ // Enclosing function or global context.
+ BytecodeEmitter* const parent = nullptr;
+
+ BytecodeSection bytecodeSection_;
+
+ public:
+ BytecodeSection& bytecodeSection() { return bytecodeSection_; }
+ const BytecodeSection& bytecodeSection() const { return bytecodeSection_; }
+
+ private:
+ PerScriptData perScriptData_;
+
+ public:
+ PerScriptData& perScriptData() { return perScriptData_; }
+ const PerScriptData& perScriptData() const { return perScriptData_; }
+
+ private:
+ // switchToMain sets this to the bytecode offset of the main section.
+ mozilla::Maybe<uint32_t> mainOffset_ = {};
+
+ // Private storage for parser wrapper. DO NOT REFERENCE INTERNALLY. May not be
+ // initialized.
+ mozilla::Maybe<EitherParser> ep_ = {};
+
+ const ErrorReporter& errorReporter_;
+
+ public:
+ CompilationState& compilationState;
+
+ uint32_t maxFixedSlots = 0; /* maximum number of fixed frame slots so far */
+
+ // Index into scopeList of the body scope.
+ GCThingIndex bodyScopeIndex = ScopeNote::NoScopeIndex;
+
+ EmitterScope* varEmitterScope = nullptr;
+ NestableControl* innermostNestableControl = nullptr;
+ EmitterScope* innermostEmitterScope_ = nullptr;
+ TDZCheckCache* innermostTDZCheckCache = nullptr;
+
+ // When compiling in self-hosted mode, we have special intrinsics that act as
+ // decorators for exported functions. To keeps things simple, we only allow
+ // these to target the last top-level function emitted. This field tracks that
+ // function.
+ FunctionBox* prevSelfHostedTopLevelFunction = nullptr;
+
+#ifdef DEBUG
+ bool unstableEmitterScope = false;
+
+ friend class AutoCheckUnstableEmitterScope;
+#endif
+
+ const ErrorReporter& errorReporter() const { return errorReporter_; }
+
+ ParserAtomsTable& parserAtoms() { return compilationState.parserAtoms; }
+ const ParserAtomsTable& parserAtoms() const {
+ return compilationState.parserAtoms;
+ }
+
+ EmitterScope* innermostEmitterScope() const {
+ MOZ_ASSERT(!unstableEmitterScope);
+ return innermostEmitterScopeNoCheck();
+ }
+ EmitterScope* innermostEmitterScopeNoCheck() const {
+ return innermostEmitterScope_;
+ }
+
+ // When parsing internal code such as self-hosted functions or synthetic
+ // class constructors, we do not emit breakpoint and srcnote data since there
+ // is no direcly corresponding user-visible sources.
+ const bool suppressBreakpointsAndSourceNotes = false;
+
+ // Script contains finally block.
+ bool hasTryFinally = false;
+
+ enum EmitterMode {
+ Normal,
+
+ // Emit JSOp::GetIntrinsic instead of JSOp::GetName and assert that
+ // JSOp::GetName and JSOp::*GName don't ever get emitted. See the comment
+ // for the field |selfHostingMode| in Parser.h for details.
+ SelfHosting,
+
+ // Check the static scope chain of the root function for resolving free
+ // variable accesses in the script.
+ LazyFunction
+ };
+
+ const EmitterMode emitterMode = Normal;
+
+ mozilla::Maybe<uint32_t> scriptStartOffset = {};
+
+ // The end location of a function body that is being emitted.
+ mozilla::Maybe<uint32_t> functionBodyEndPos = {};
+
+ // Jump target just before the final CheckReturn opcode in a derived class
+ // constructor body.
+ JumpList endOfDerivedClassConstructorBody = {};
+
+ // Jump target just before the final yield in a generator or async function.
+ JumpList finalYields = {};
+
+ // In order to heuristically determine the size of the allocation if this is a
+ // constructor function, we track expressions which add properties in the
+ // constructor.
+ mozilla::SaturateUint8 propertyAdditionEstimate = {};
+
+ /*
+ * Note that BytecodeEmitters are magic: they own the arena "top-of-stack"
+ * space above their tempMark points. This means that you cannot alloc from
+ * tempLifoAlloc and save the pointer beyond the next BytecodeEmitter
+ * destruction.
+ */
+ private:
+ // Internal constructor, for delegation use only.
+ BytecodeEmitter(BytecodeEmitter* parent, FrontendContext* fc,
+ SharedContext* sc, const ErrorReporter& errorReporter,
+ CompilationState& compilationState, EmitterMode emitterMode);
+
+ BytecodeEmitter(BytecodeEmitter* parent, SharedContext* sc);
+
+ void initFromBodyPosition(TokenPos bodyPosition);
+
+ public:
+ BytecodeEmitter(FrontendContext* fc, const EitherParser& parser,
+ SharedContext* sc, CompilationState& compilationState,
+ EmitterMode emitterMode = Normal);
+
+ template <typename Unit>
+ BytecodeEmitter(FrontendContext* fc, Parser<FullParseHandler, Unit>* parser,
+ SharedContext* sc, CompilationState& compilationState,
+ EmitterMode emitterMode = Normal)
+ : BytecodeEmitter(fc, EitherParser(parser), sc, compilationState,
+ emitterMode) {}
+
+ [[nodiscard]] bool init();
+ [[nodiscard]] bool init(TokenPos bodyPosition);
+
+ template <typename T>
+ T* findInnermostNestableControl() const;
+
+ template <typename T, typename Predicate /* (T*) -> bool */>
+ T* findInnermostNestableControl(Predicate predicate) const;
+
+ NameLocation lookupName(TaggedParserAtomIndex name);
+
+ // See EmitterScope::lookupPrivate for details around brandLoc
+ void lookupPrivate(TaggedParserAtomIndex name, NameLocation& loc,
+ mozilla::Maybe<NameLocation>& brandLoc);
+
+ // To implement Annex B and the formal parameter defaults scope semantics
+ // requires accessing names that would otherwise be shadowed. This method
+ // returns the access location of a name that is known to be bound in a
+ // target scope.
+ mozilla::Maybe<NameLocation> locationOfNameBoundInScope(
+ TaggedParserAtomIndex name, EmitterScope* target);
+
+ // Get the location of a name known to be bound in a given scope,
+ // starting at the source scope.
+ template <typename T>
+ mozilla::Maybe<NameLocation> locationOfNameBoundInScopeType(
+ TaggedParserAtomIndex name, EmitterScope* source);
+
+ // Get the location of a name known to be bound in the function scope,
+ // starting at the source scope.
+ mozilla::Maybe<NameLocation> locationOfNameBoundInFunctionScope(
+ TaggedParserAtomIndex 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;
+
+ [[nodiscard]] MOZ_ALWAYS_INLINE bool makeAtomIndex(
+ TaggedParserAtomIndex atom, ParserAtom::Atomize atomize,
+ GCThingIndex* indexp) {
+ MOZ_ASSERT(perScriptData().atomIndices());
+ AtomIndexMap::AddPtr p = perScriptData().atomIndices()->lookupForAdd(atom);
+ if (p) {
+ compilationState.parserAtoms.markAtomize(atom, atomize);
+ *indexp = GCThingIndex(p->value());
+ return true;
+ }
+
+ GCThingIndex index;
+ if (!perScriptData().gcThingList().append(atom, atomize, &index)) {
+ return false;
+ }
+
+ // `atomIndices()` uses uint32_t instead of GCThingIndex, because
+ // GCThingIndex isn't trivial type.
+ if (!perScriptData().atomIndices()->add(p, atom, index.index)) {
+ ReportOutOfMemory(fc);
+ return false;
+ }
+
+ *indexp = index;
+ return true;
+ }
+
+ bool isInLoop();
+ [[nodiscard]] bool checkSingletonContext();
+
+ bool needsImplicitThis();
+
+ size_t countThisEnvironmentHops();
+ [[nodiscard]] bool emitThisEnvironmentCallee();
+ [[nodiscard]] bool emitSuperBase();
+
+ uint32_t mainOffset() const { return *mainOffset_; }
+
+ bool inPrologue() const { return mainOffset_.isNothing(); }
+
+ void switchToMain() {
+ MOZ_ASSERT(inPrologue());
+ mainOffset_.emplace(bytecodeSection().code().length());
+ }
+
+ void setFunctionBodyEndPos(uint32_t pos) {
+ functionBodyEndPos = mozilla::Some(pos);
+ }
+
+ void setScriptStartOffsetIfUnset(uint32_t pos) {
+ if (scriptStartOffset.isNothing()) {
+ scriptStartOffset = mozilla::Some(pos);
+ }
+ }
+
+ void reportError(ParseNode* pn, unsigned errorNumber, ...);
+ void reportError(uint32_t offset, unsigned errorNumber, ...);
+
+ // Fill in a ScriptStencil using this BCE data.
+ bool intoScriptStencil(ScriptIndex scriptIndex);
+
+ // If pn contains a useful expression, return true with *answer set to true.
+ // If pn contains a useless expression, return true with *answer set to
+ // false. Return false on error.
+ //
+ // The caller should initialize *answer to false and invoke this function on
+ // an expression statement or similar subtree to decide whether the tree
+ // could produce code that has any side effects. For an expression
+ // statement, we define useless code as code with no side effects, because
+ // the main effect, the value left on the stack after the code executes,
+ // will be discarded by a pop bytecode.
+ [[nodiscard]] bool checkSideEffects(ParseNode* pn, bool* answer);
+
+#ifdef DEBUG
+ [[nodiscard]] bool checkStrictOrSloppy(JSOp op);
+#endif
+
+ // Add TryNote to the tryNoteList array. The start and end offset are
+ // relative to current section.
+ [[nodiscard]] bool addTryNote(TryNoteKind kind, uint32_t stackDepth,
+ BytecodeOffset start, BytecodeOffset end);
+
+ // Indicates the emitter should not generate location or debugger source
+ // notes. This lets us avoid generating notes for non-user code.
+ bool skipLocationSrcNotes() const {
+ return inPrologue() || suppressBreakpointsAndSourceNotes;
+ }
+ bool skipBreakpointSrcNotes() const {
+ return inPrologue() || suppressBreakpointsAndSourceNotes;
+ }
+
+ // Append a new source note of the given type (and therefore size) to the
+ // notes dynamic array, updating noteCount. Return the new note's index
+ // within the array pointed at by current->notes as outparam.
+ [[nodiscard]] bool newSrcNote(SrcNoteType type, unsigned* indexp = nullptr);
+ [[nodiscard]] bool newSrcNote2(SrcNoteType type, ptrdiff_t operand,
+ unsigned* indexp = nullptr);
+
+ [[nodiscard]] bool newSrcNoteOperand(ptrdiff_t operand);
+
+ // Control whether emitTree emits a line number note.
+ enum EmitLineNumberNote { EMIT_LINENOTE, SUPPRESS_LINENOTE };
+
+ // Emit code for the tree rooted at pn.
+ [[nodiscard]] bool emitTree(ParseNode* pn,
+ ValueUsage valueUsage = ValueUsage::WantValue,
+ EmitLineNumberNote emitLineNote = EMIT_LINENOTE);
+
+ [[nodiscard]] bool emitOptionalTree(
+ ParseNode* pn, OptionalEmitter& oe,
+ ValueUsage valueUsage = ValueUsage::WantValue);
+
+ [[nodiscard]] bool emitDeclarationInstantiation(ParseNode* body);
+
+ // Emit global, eval, or module code for tree rooted at body. Always
+ // encompasses the entire source.
+ [[nodiscard]] bool emitScript(ParseNode* body);
+
+ // Calculate the `nslots` value for BCEScriptStencil constructor parameter.
+ // Fails if it overflows.
+ [[nodiscard]] bool getNslots(uint32_t* nslots);
+
+ // Emit function code for the tree rooted at body.
+ [[nodiscard]] bool emitFunctionScript(FunctionNode* funNode);
+
+ [[nodiscard]] bool markStepBreakpoint();
+ [[nodiscard]] bool markSimpleBreakpoint();
+ [[nodiscard]] bool updateLineNumberNotes(uint32_t offset);
+ [[nodiscard]] bool updateSourceCoordNotes(uint32_t offset);
+
+ JSOp strictifySetNameOp(JSOp op);
+
+ [[nodiscard]] bool emitCheck(JSOp op, ptrdiff_t delta,
+ BytecodeOffset* offset);
+
+ // Emit one bytecode.
+ [[nodiscard]] bool emit1(JSOp op);
+
+ // Emit two bytecodes, an opcode (op) with a byte of immediate operand
+ // (op1).
+ [[nodiscard]] bool emit2(JSOp op, uint8_t op1);
+
+ // Emit three bytecodes, an opcode with two bytes of immediate operands.
+ [[nodiscard]] bool emit3(JSOp op, jsbytecode op1, jsbytecode op2);
+
+ // Helper to duplicate one or more stack values. |slotFromTop| is the value's
+ // depth on the JS stack, as measured from the top. |count| is the number of
+ // values to duplicate, in theiro original order.
+ [[nodiscard]] bool emitDupAt(unsigned slotFromTop, unsigned count = 1);
+
+ // Helper to emit JSOp::Pop or JSOp::PopN.
+ [[nodiscard]] bool emitPopN(unsigned n);
+
+ // Helper to emit JSOp::Swap or JSOp::Pick.
+ [[nodiscard]] bool emitPickN(uint8_t n);
+
+ // Helper to emit JSOp::Swap or JSOp::Unpick.
+ [[nodiscard]] bool emitUnpickN(uint8_t n);
+
+ // Helper to emit JSOp::CheckIsObj.
+ [[nodiscard]] bool emitCheckIsObj(CheckIsObjectKind kind);
+
+ // Helper to emit JSOp::BuiltinObject.
+ [[nodiscard]] bool emitBuiltinObject(BuiltinObjectKind kind);
+
+ // Emit a bytecode followed by an uint16 immediate operand stored in
+ // big-endian order.
+ [[nodiscard]] bool emitUint16Operand(JSOp op, uint32_t operand);
+
+ // Emit a bytecode followed by an uint32 immediate operand.
+ [[nodiscard]] bool emitUint32Operand(JSOp op, uint32_t operand);
+
+ // Emit (1 + extra) bytecodes, for N bytes of op and its immediate operand.
+ [[nodiscard]] bool emitN(JSOp op, size_t extra,
+ BytecodeOffset* offset = nullptr);
+
+ [[nodiscard]] bool emitDouble(double dval);
+ [[nodiscard]] bool emitNumberOp(double dval);
+
+ [[nodiscard]] bool emitBigIntOp(BigIntLiteral* bigint);
+
+ [[nodiscard]] bool emitThisLiteral(ThisLiteral* pn);
+ [[nodiscard]] bool emitGetFunctionThis(NameNode* thisName);
+ [[nodiscard]] bool emitGetThisForSuperBase(UnaryNode* superBase);
+ [[nodiscard]] bool emitSetThis(BinaryNode* setThisNode);
+ [[nodiscard]] bool emitCheckDerivedClassConstructorReturn();
+
+ private:
+ [[nodiscard]] bool emitNewTarget();
+
+ public:
+ [[nodiscard]] bool emitNewTarget(NewTargetNode* pn);
+ [[nodiscard]] bool emitNewTarget(CallNode* pn);
+
+ // Handle jump opcodes and jump targets.
+ [[nodiscard]] bool emitJumpTargetOp(JSOp op, BytecodeOffset* off);
+ [[nodiscard]] bool emitJumpTarget(JumpTarget* target);
+ [[nodiscard]] bool emitJumpNoFallthrough(JSOp op, JumpList* jump);
+ [[nodiscard]] bool emitJump(JSOp op, JumpList* jump);
+ void patchJumpsToTarget(JumpList jump, JumpTarget target);
+ [[nodiscard]] bool emitJumpTargetAndPatch(JumpList jump);
+
+ [[nodiscard]] bool emitCall(
+ JSOp op, uint16_t argc,
+ const mozilla::Maybe<uint32_t>& sourceCoordOffset);
+ [[nodiscard]] bool emitCall(JSOp op, uint16_t argc, ParseNode* pn = nullptr);
+ [[nodiscard]] bool emitCallIncDec(UnaryNode* incDec);
+
+ uint32_t getOffsetForLoop(ParseNode* nextpn);
+
+ enum class GotoKind { Break, Continue };
+ [[nodiscard]] bool emitGoto(NestableControl* target, GotoKind kind);
+
+ [[nodiscard]] bool emitGCIndexOp(JSOp op, GCThingIndex index);
+
+ [[nodiscard]] bool emitAtomOp(JSOp op, TaggedParserAtomIndex atom);
+ [[nodiscard]] bool emitAtomOp(JSOp op, GCThingIndex atomIndex);
+
+ [[nodiscard]] bool emitStringOp(JSOp op, TaggedParserAtomIndex atom);
+ [[nodiscard]] bool emitStringOp(JSOp op, GCThingIndex atomIndex);
+
+ [[nodiscard]] bool emitArrayLiteral(ListNode* array);
+ [[nodiscard]] bool emitArray(ListNode* array);
+ [[nodiscard]] bool emitSpreadIntoArray(UnaryNode* elem);
+
+ [[nodiscard]] bool emitInternedScopeOp(GCThingIndex index, JSOp op);
+ [[nodiscard]] bool emitInternedObjectOp(GCThingIndex index, JSOp op);
+ [[nodiscard]] bool emitRegExp(GCThingIndex index);
+
+ [[nodiscard]] MOZ_NEVER_INLINE bool emitFunction(FunctionNode* funNode,
+ bool needsProto = false);
+ [[nodiscard]] MOZ_NEVER_INLINE bool emitObject(ListNode* objNode);
+
+ [[nodiscard]] bool emitHoistedFunctionsInList(ListNode* stmtList);
+
+ // Can we use the object-literal writer either in singleton-object mode (with
+ // values) or in template mode (field names only, no values) for the property
+ // list?
+ void isPropertyListObjLiteralCompatible(ListNode* obj, bool* withValues,
+ bool* withoutValues);
+ bool isArrayObjLiteralCompatible(ListNode* array);
+
+ [[nodiscard]] bool emitPropertyList(ListNode* obj, PropertyEmitter& pe,
+ PropListType type);
+
+ [[nodiscard]] bool emitPropertyListObjLiteral(ListNode* obj, JSOp op,
+ bool useObjLiteralValues);
+
+ [[nodiscard]] bool emitDestructuringRestExclusionSetObjLiteral(
+ ListNode* pattern);
+
+ [[nodiscard]] bool emitObjLiteralArray(ListNode* array);
+
+ // Is a field value OBJLITERAL-compatible?
+ [[nodiscard]] bool isRHSObjLiteralCompatible(ParseNode* value);
+
+ [[nodiscard]] bool emitObjLiteralValue(ObjLiteralWriter& writer,
+ ParseNode* value);
+
+ mozilla::Maybe<MemberInitializers> setupMemberInitializers(
+ ListNode* classMembers, FieldPlacement placement);
+ [[nodiscard]] bool emitCreateFieldKeys(ListNode* obj,
+ FieldPlacement placement);
+ [[nodiscard]] bool emitCreateMemberInitializers(ClassEmitter& ce,
+ ListNode* obj,
+ FieldPlacement placement);
+ const MemberInitializers& findMemberInitializersForCall();
+ [[nodiscard]] bool emitInitializeInstanceMembers(
+ bool isDerivedClassConstructor);
+ [[nodiscard]] bool emitInitializeStaticFields(ListNode* classMembers);
+
+ [[nodiscard]] bool emitPrivateMethodInitializers(ClassEmitter& ce,
+ ListNode* obj);
+ [[nodiscard]] bool emitPrivateMethodInitializer(
+ ClassMethod* classMethod, TaggedParserAtomIndex storedMethodAtom);
+
+ // To catch accidental misuse, emitUint16Operand/emit3 assert that they are
+ // not used to unconditionally emit JSOp::GetLocal. Variable access should
+ // instead be emitted using EmitVarOp. In special cases, when the caller
+ // definitely knows that a given local slot is unaliased, this function may be
+ // used as a non-asserting version of emitUint16Operand.
+ [[nodiscard]] bool emitLocalOp(JSOp op, uint32_t slot);
+
+ [[nodiscard]] bool emitArgOp(JSOp op, uint16_t slot);
+ [[nodiscard]] bool emitEnvCoordOp(JSOp op, EnvironmentCoordinate ec);
+
+ [[nodiscard]] bool emitGetNameAtLocation(TaggedParserAtomIndex name,
+ const NameLocation& loc);
+ [[nodiscard]] bool emitGetName(TaggedParserAtomIndex name) {
+ return emitGetNameAtLocation(name, lookupName(name));
+ }
+ [[nodiscard]] bool emitGetName(NameNode* name);
+ [[nodiscard]] bool emitGetPrivateName(NameNode* name);
+ [[nodiscard]] bool emitGetPrivateName(TaggedParserAtomIndex name);
+
+ [[nodiscard]] bool emitTDZCheckIfNeeded(TaggedParserAtomIndex name,
+ const NameLocation& loc,
+ ValueIsOnStack isOnStack);
+
+ [[nodiscard]] bool emitNameIncDec(UnaryNode* incDec, ValueUsage valueUsage);
+
+ [[nodiscard]] bool emitDeclarationList(ListNode* declList);
+ [[nodiscard]] bool emitSingleDeclaration(ListNode* declList, NameNode* decl,
+ ParseNode* initializer);
+ [[nodiscard]] bool emitAssignmentRhs(ParseNode* rhs,
+ TaggedParserAtomIndex anonFunctionName);
+ [[nodiscard]] bool emitAssignmentRhs(uint8_t offset);
+
+ [[nodiscard]] bool emitPrepareIteratorResult();
+ [[nodiscard]] bool emitFinishIteratorResult(bool done);
+
+ // Convert and add `writer` data to stencil.
+ // Iff it suceeds, `outIndex` out parameter is initialized to the index of the
+ // object in GC things vector.
+ [[nodiscard]] bool addObjLiteralData(ObjLiteralWriter& writer,
+ GCThingIndex* outIndex);
+
+ [[nodiscard]] bool emitGetDotGeneratorInInnermostScope() {
+ return emitGetDotGeneratorInScope(*innermostEmitterScope());
+ }
+ [[nodiscard]] bool emitGetDotGeneratorInScope(EmitterScope& currentScope);
+
+ [[nodiscard]] bool allocateResumeIndex(BytecodeOffset offset,
+ uint32_t* resumeIndex);
+ [[nodiscard]] bool allocateResumeIndexRange(
+ mozilla::Span<BytecodeOffset> offsets, uint32_t* firstResumeIndex);
+
+ [[nodiscard]] bool emitInitialYield(UnaryNode* yieldNode);
+ [[nodiscard]] bool emitYield(UnaryNode* yieldNode);
+ [[nodiscard]] bool emitYieldOp(JSOp op);
+ [[nodiscard]] bool emitYieldStar(ParseNode* iter);
+ [[nodiscard]] bool emitAwaitInInnermostScope() {
+ return emitAwaitInScope(*innermostEmitterScope());
+ }
+ [[nodiscard]] bool emitAwaitInInnermostScope(UnaryNode* awaitNode);
+ [[nodiscard]] bool emitAwaitInScope(EmitterScope& currentScope);
+
+ [[nodiscard]] bool emitPushResumeKind(GeneratorResumeKind kind);
+
+ [[nodiscard]] bool emitPropLHS(PropertyAccess* prop);
+ [[nodiscard]] bool emitPropIncDec(UnaryNode* incDec, ValueUsage valueUsage);
+
+ [[nodiscard]] bool emitComputedPropertyName(UnaryNode* computedPropName);
+
+ [[nodiscard]] bool emitObjAndKey(ParseNode* exprOrSuper, ParseNode* key,
+ ElemOpEmitter& eoe);
+
+ // Emit bytecode to put operands for a JSOp::GetElem/CallElem/SetElem/DelElem
+ // opcode onto the stack in the right order. In the case of SetElem, the
+ // value to be assigned must already be pushed.
+ enum class EmitElemOption { Get, Call, IncDec, CompoundAssign, Ref };
+ [[nodiscard]] bool emitElemOperands(PropertyByValue* elem,
+ EmitElemOption opts);
+
+ [[nodiscard]] bool emitElemObjAndKey(PropertyByValue* elem, bool isSuper,
+ ElemOpEmitter& eoe);
+ [[nodiscard]] bool emitElemOpBase(JSOp op);
+
+ [[nodiscard]] bool emitElemIncDec(UnaryNode* incDec, ValueUsage valueUsage);
+ [[nodiscard]] bool emitObjAndPrivateName(PrivateMemberAccess* elem,
+ ElemOpEmitter& eoe);
+ [[nodiscard]] bool emitPrivateIncDec(UnaryNode* incDec,
+ ValueUsage valueUsage);
+
+ [[nodiscard]] bool emitCatch(BinaryNode* catchClause);
+ [[nodiscard]] bool emitIf(TernaryNode* ifNode);
+ [[nodiscard]] bool emitWith(BinaryNode* withNode);
+
+ [[nodiscard]] MOZ_NEVER_INLINE bool emitLabeledStatement(
+ const LabeledStatement* labeledStmt);
+ [[nodiscard]] MOZ_NEVER_INLINE bool emitLexicalScope(
+ LexicalScopeNode* lexicalScope);
+ [[nodiscard]] bool emitLexicalScopeBody(
+ ParseNode* body, EmitLineNumberNote emitLineNote = EMIT_LINENOTE);
+ [[nodiscard]] MOZ_NEVER_INLINE bool emitSwitch(SwitchStatement* switchStmt);
+ [[nodiscard]] MOZ_NEVER_INLINE bool emitTry(TryNode* tryNode);
+
+ [[nodiscard]] bool emitJumpToFinally(JumpList* jump, uint32_t idx);
+
+ // emitDestructuringLHSRef emits the lhs expression's reference.
+ // If the lhs expression is object property |OBJ.prop|, it emits |OBJ|.
+ // If it's object element |OBJ[ELEM]|, it emits |OBJ| and |ELEM|.
+ // If there's nothing to evaluate for the reference, it emits nothing.
+ // |emitted| parameter receives the number of values pushed onto the stack.
+ [[nodiscard]] bool emitDestructuringLHSRef(ParseNode* target,
+ size_t* emitted);
+
+ // emitSetOrInitializeDestructuring assumes the lhs expression's reference
+ // and the to-be-destructured value has been pushed on the stack. It emits
+ // code to destructure a single lhs expression (either a name or a compound
+ // []/{} expression).
+ [[nodiscard]] bool emitSetOrInitializeDestructuring(ParseNode* target,
+ DestructuringFlavor flav);
+
+ // emitDestructuringObjRestExclusionSet emits the property exclusion set
+ // for the rest-property in an object pattern.
+ [[nodiscard]] bool emitDestructuringObjRestExclusionSet(ListNode* pattern);
+
+ // emitDestructuringOps assumes the to-be-destructured value has been
+ // pushed on the stack and emits code to destructure each part of a [] or
+ // {} lhs expression.
+ [[nodiscard]] bool emitDestructuringOps(ListNode* pattern,
+ DestructuringFlavor flav);
+ [[nodiscard]] bool emitDestructuringOpsArray(ListNode* pattern,
+ DestructuringFlavor flav);
+ [[nodiscard]] bool emitDestructuringOpsObject(ListNode* pattern,
+ DestructuringFlavor flav);
+
+ enum class CopyOption { Filtered, Unfiltered };
+
+ // Calls either the |CopyDataProperties| or the
+ // |CopyDataPropertiesUnfiltered| intrinsic function, consumes three (or
+ // two in the latter case) elements from the stack.
+ [[nodiscard]] bool emitCopyDataProperties(CopyOption option);
+
+ JSOp getIterCallOp(JSOp callOp, SelfHostedIter selfHostedIter);
+
+ // emitIterator expects the iterable to already be on the stack.
+ // It will replace that stack value with the corresponding iterator
+ [[nodiscard]] bool emitIterator(
+ SelfHostedIter selfHostedIter = SelfHostedIter::Deny,
+ bool isIteratorMethodOnStack = false);
+
+ [[nodiscard]] bool emitAsyncIterator(
+ SelfHostedIter selfHostedIter = SelfHostedIter::Deny,
+ bool isIteratorMethodOnStack = false);
+
+ // Pops iterator from the top of the stack. Pushes the result of |.next()|
+ // onto the stack.
+ [[nodiscard]] bool emitIteratorNext(
+ const mozilla::Maybe<uint32_t>& callSourceCoordOffset,
+ IteratorKind kind = IteratorKind::Sync,
+ SelfHostedIter selfHostedIter = SelfHostedIter::Deny);
+ [[nodiscard]] bool emitIteratorCloseInScope(
+ EmitterScope& currentScope, IteratorKind iterKind = IteratorKind::Sync,
+ CompletionKind completionKind = CompletionKind::Normal,
+ SelfHostedIter selfHostedIter = SelfHostedIter::Deny);
+ [[nodiscard]] bool emitIteratorCloseInInnermostScope(
+ IteratorKind iterKind = IteratorKind::Sync,
+ CompletionKind completionKind = CompletionKind::Normal,
+ SelfHostedIter selfHostedIter = SelfHostedIter::Deny) {
+ return emitIteratorCloseInScope(*innermostEmitterScope(), iterKind,
+ completionKind, selfHostedIter);
+ }
+
+ template <typename InnerEmitter>
+ [[nodiscard]] bool wrapWithDestructuringTryNote(int32_t iterDepth,
+ InnerEmitter emitter);
+
+ [[nodiscard]] bool defineHoistedTopLevelFunctions(ParseNode* body);
+
+ // Check if the value on top of the stack is "undefined". If so, replace
+ // that value on the stack with the value defined by |defaultExpr|.
+ // |pattern| is a lhs node of the default expression. If it's an
+ // identifier and |defaultExpr| is an anonymous function, |SetFunctionName|
+ // is called at compile time.
+ [[nodiscard]] bool emitDefault(ParseNode* defaultExpr, ParseNode* pattern);
+
+ [[nodiscard]] bool emitAnonymousFunctionWithName(ParseNode* node,
+ TaggedParserAtomIndex name);
+
+ [[nodiscard]] bool emitAnonymousFunctionWithComputedName(
+ ParseNode* node, FunctionPrefixKind prefixKind);
+
+ [[nodiscard]] bool setFunName(FunctionBox* fun, TaggedParserAtomIndex name);
+ [[nodiscard]] bool emitInitializer(ParseNode* initializer,
+ ParseNode* pattern);
+
+ [[nodiscard]] bool emitCallSiteObjectArray(ObjLiteralWriter& writer,
+ ListNode* cookedOrRaw,
+ ParseNode* head, uint32_t count);
+ [[nodiscard]] bool emitCallSiteObject(CallSiteNode* callSiteObj);
+ [[nodiscard]] bool emitTemplateString(ListNode* templateString);
+ [[nodiscard]] bool emitAssignmentOrInit(ParseNodeKind kind, ParseNode* lhs,
+ ParseNode* rhs);
+ [[nodiscard]] bool emitShortCircuitAssignment(AssignmentNode* node);
+
+ [[nodiscard]] bool emitReturn(UnaryNode* returnNode);
+ [[nodiscard]] bool finishReturn(BytecodeOffset setRvalOffset);
+
+ [[nodiscard]] bool emitExpressionStatement(UnaryNode* exprStmt);
+ [[nodiscard]] bool emitStatementList(ListNode* stmtList);
+
+ [[nodiscard]] bool emitDeleteName(UnaryNode* deleteNode);
+ [[nodiscard]] bool emitDeleteProperty(UnaryNode* deleteNode);
+ [[nodiscard]] bool emitDeleteElement(UnaryNode* deleteNode);
+ [[nodiscard]] bool emitDeleteExpression(UnaryNode* deleteNode);
+
+ // Optional methods which emit Optional Jump Target
+ [[nodiscard]] bool emitOptionalChain(UnaryNode* expr, ValueUsage valueUsage);
+ [[nodiscard]] bool emitCalleeAndThisForOptionalChain(UnaryNode* expr,
+ CallNode* callNode,
+ CallOrNewEmitter& cone);
+ [[nodiscard]] bool emitDeleteOptionalChain(UnaryNode* deleteNode);
+
+ // Optional methods which emit a shortCircuit jump. They need to be called by
+ // a method which emits an Optional Jump Target, see below.
+ [[nodiscard]] bool emitOptionalDotExpression(PropertyAccessBase* expr,
+ PropOpEmitter& poe, bool isSuper,
+ OptionalEmitter& oe);
+ [[nodiscard]] bool emitOptionalElemExpression(PropertyByValueBase* elem,
+ ElemOpEmitter& eoe,
+ bool isSuper,
+ OptionalEmitter& oe);
+ [[nodiscard]] bool emitOptionalPrivateExpression(
+ PrivateMemberAccessBase* privateExpr, PrivateOpEmitter& xoe,
+ OptionalEmitter& oe);
+ [[nodiscard]] bool emitOptionalCall(CallNode* callNode, OptionalEmitter& oe,
+ ValueUsage valueUsage);
+ [[nodiscard]] bool emitDeletePropertyInOptChain(PropertyAccessBase* propExpr,
+ OptionalEmitter& oe);
+ [[nodiscard]] bool emitDeleteElementInOptChain(PropertyByValueBase* elemExpr,
+ OptionalEmitter& oe);
+
+ // |op| must be JSOp::Typeof or JSOp::TypeofExpr.
+ [[nodiscard]] bool emitTypeof(UnaryNode* typeofNode, JSOp op);
+
+ [[nodiscard]] bool emitUnary(UnaryNode* unaryNode);
+ [[nodiscard]] bool emitRightAssociative(ListNode* node);
+ [[nodiscard]] bool emitLeftAssociative(ListNode* node);
+ [[nodiscard]] bool emitPrivateInExpr(ListNode* node);
+ [[nodiscard]] bool emitShortCircuit(ListNode* node, ValueUsage valueUsage);
+ [[nodiscard]] bool emitSequenceExpr(ListNode* node, ValueUsage valueUsage);
+
+ [[nodiscard]] MOZ_NEVER_INLINE bool emitIncOrDec(UnaryNode* incDec,
+ ValueUsage valueUsage);
+
+ [[nodiscard]] bool emitConditionalExpression(
+ ConditionalExpression& conditional, ValueUsage valueUsage);
+
+ [[nodiscard]] ParseNode* getCoordNode(ParseNode* callNode,
+ ParseNode* calleeNode, JSOp op,
+ ListNode* argsList);
+
+ [[nodiscard]] bool emitArguments(ListNode* argsList, bool isCall,
+ bool isSpread, CallOrNewEmitter& cone);
+ [[nodiscard]] bool emitCallOrNew(CallNode* callNode, ValueUsage valueUsage);
+ [[nodiscard]] bool emitDebugCheckSelfHosted();
+ [[nodiscard]] bool emitSelfHostedCallFunction(CallNode* callNode, JSOp op);
+ [[nodiscard]] bool emitSelfHostedResumeGenerator(CallNode* callNode);
+ [[nodiscard]] bool emitSelfHostedForceInterpreter();
+ [[nodiscard]] bool emitSelfHostedAllowContentIter(CallNode* callNode);
+ [[nodiscard]] bool emitSelfHostedAllowContentIterWith(CallNode* callNode);
+ [[nodiscard]] bool emitSelfHostedDefineDataProperty(CallNode* callNode);
+ [[nodiscard]] bool emitSelfHostedGetPropertySuper(CallNode* callNode);
+ [[nodiscard]] bool emitSelfHostedHasOwn(CallNode* callNode);
+ [[nodiscard]] bool emitSelfHostedToNumeric(CallNode* callNode);
+ [[nodiscard]] bool emitSelfHostedToString(CallNode* callNode);
+ [[nodiscard]] bool emitSelfHostedIsNullOrUndefined(CallNode* callNode);
+ [[nodiscard]] bool emitSelfHostedGetBuiltinConstructor(CallNode* callNode);
+ [[nodiscard]] bool emitSelfHostedGetBuiltinPrototype(CallNode* callNode);
+ [[nodiscard]] bool emitSelfHostedGetBuiltinSymbol(CallNode* callNode);
+ [[nodiscard]] bool emitSelfHostedSetIsInlinableLargeFunction(
+ CallNode* callNode);
+ [[nodiscard]] bool emitSelfHostedSetCanonicalName(CallNode* callNode);
+ [[nodiscard]] bool emitSelfHostedArgumentsLength(CallNode* callNode);
+ [[nodiscard]] bool emitSelfHostedGetArgument(CallNode* callNode);
+#ifdef DEBUG
+ void assertSelfHostedExpectedTopLevel(ParseNode* node);
+ void assertSelfHostedUnsafeGetReservedSlot(ListNode* argsList);
+ void assertSelfHostedUnsafeSetReservedSlot(ListNode* argsList);
+#endif
+
+ [[nodiscard]] bool emitDo(BinaryNode* doNode);
+ [[nodiscard]] bool emitWhile(BinaryNode* whileNode);
+
+ [[nodiscard]] bool emitFor(
+ ForNode* forNode, const EmitterScope* headLexicalEmitterScope = nullptr);
+ [[nodiscard]] bool emitCStyleFor(ForNode* forNode,
+ const EmitterScope* headLexicalEmitterScope);
+ [[nodiscard]] bool emitForIn(ForNode* forNode,
+ const EmitterScope* headLexicalEmitterScope);
+ [[nodiscard]] bool emitForOf(ForNode* forNode,
+ const EmitterScope* headLexicalEmitterScope);
+
+ [[nodiscard]] bool emitInitializeForInOrOfTarget(TernaryNode* forHead);
+
+ [[nodiscard]] bool emitBreak(TaggedParserAtomIndex label);
+ [[nodiscard]] bool emitContinue(TaggedParserAtomIndex label);
+
+ [[nodiscard]] bool emitFunctionFormalParameters(ParamsBodyNode* paramsBody);
+ [[nodiscard]] bool emitInitializeFunctionSpecialNames();
+ [[nodiscard]] bool emitLexicalInitialization(NameNode* name);
+ [[nodiscard]] bool emitLexicalInitialization(TaggedParserAtomIndex name);
+
+ // Emit bytecode for the spread operator.
+ //
+ // emitSpread expects some values representing the spread target (an array or
+ // a tuple), the iterator and it's next() method to be on the stack in that
+ // order (iterator's next() on the bottom).
+ // The number of values representing the spread target is
+ // `spreadeeStackItems`: it's 2 for arrays (one for the array and one for the
+ // index) and 1 for tuples (the tuple itself).
+ // Since arrays and tuples use different opcodes to initialize new elements,
+ // it must be specified using `storeElementOp`.
+ // When emitSpread() finishes, the stack only contains the values representing
+ // the spread target.
+ [[nodiscard]] bool emitSpread(SelfHostedIter selfHostedIter,
+ int spreadeeStackItems, JSOp storeElementOp);
+ // This shortcut can be used when spreading into arrays, as it assumes
+ // `spreadeeStackItems = 2` (|ARRAY INDEX|) and `storeElementOp =
+ // JSOp::InitElemInc`
+ [[nodiscard]] bool emitSpread(
+ SelfHostedIter selfHostedIter = SelfHostedIter::Deny);
+
+ enum class ClassNameKind {
+ // The class name is defined through its BindingIdentifier, if present.
+ BindingName,
+
+ // The class is anonymous and has a statically inferred name.
+ InferredName,
+
+ // The class is anonymous and has a dynamically computed name.
+ ComputedName
+ };
+
+ [[nodiscard]] bool emitClass(
+ ClassNode* classNode, ClassNameKind nameKind = ClassNameKind::BindingName,
+ TaggedParserAtomIndex nameForAnonymousClass =
+ TaggedParserAtomIndex::null());
+
+ [[nodiscard]] bool emitSuperElemOperands(
+ PropertyByValue* elem, EmitElemOption opts = EmitElemOption::Get);
+ [[nodiscard]] bool emitSuperGetElem(PropertyByValue* elem,
+ bool isCall = false);
+
+ [[nodiscard]] bool emitCalleeAndThis(ParseNode* callee, ParseNode* call,
+ CallOrNewEmitter& cone);
+
+ [[nodiscard]] bool emitOptionalCalleeAndThis(ParseNode* callee,
+ CallNode* call,
+ CallOrNewEmitter& cone,
+ OptionalEmitter& oe);
+
+#ifdef ENABLE_RECORD_TUPLE
+ [[nodiscard]] bool emitRecordLiteral(ListNode* record);
+ [[nodiscard]] bool emitTupleLiteral(ListNode* tuple);
+#endif
+
+ [[nodiscard]] bool emitExportDefault(BinaryNode* exportNode);
+
+ [[nodiscard]] bool emitReturnRval() { return emit1(JSOp::RetRval); }
+
+ [[nodiscard]] bool emitCheckPrivateField(ThrowCondition throwCondition,
+ ThrowMsgKind msgKind) {
+ return emit3(JSOp::CheckPrivateField, uint8_t(throwCondition),
+ uint8_t(msgKind));
+ }
+
+ [[nodiscard]] bool emitNewPrivateName(TaggedParserAtomIndex bindingName,
+ TaggedParserAtomIndex symbolName);
+
+ template <class ClassMemberType>
+ [[nodiscard]] bool emitNewPrivateNames(ListNode* classMembers);
+
+ [[nodiscard]] bool emitNewPrivateNames(TaggedParserAtomIndex privateBrandName,
+ ListNode* classMembers);
+
+ [[nodiscard]] js::UniquePtr<ImmutableScriptData> createImmutableScriptData();
+
+ private:
+ [[nodiscard]] SelfHostedIter getSelfHostedIterFor(ParseNode* parseNode);
+
+ [[nodiscard]] bool emitSelfHostedGetBuiltinConstructorOrPrototype(
+ CallNode* callNode, bool isConstructor);
+
+ public:
+#if defined(DEBUG) || defined(JS_JITSPEW)
+ void dumpAtom(TaggedParserAtomIndex index) const;
+#endif
+};
+
+class MOZ_RAII AutoCheckUnstableEmitterScope {
+#ifdef DEBUG
+ bool prev_;
+ BytecodeEmitter* bce_;
+#endif
+
+ public:
+ AutoCheckUnstableEmitterScope() = delete;
+ explicit AutoCheckUnstableEmitterScope(BytecodeEmitter* bce)
+#ifdef DEBUG
+ : bce_(bce)
+#endif
+ {
+#ifdef DEBUG
+ prev_ = bce_->unstableEmitterScope;
+ bce_->unstableEmitterScope = true;
+#endif
+ }
+ ~AutoCheckUnstableEmitterScope() {
+#ifdef DEBUG
+ bce_->unstableEmitterScope = prev_;
+#endif
+ }
+};
+
+} /* namespace frontend */
+} /* namespace js */
+
+#endif /* frontend_BytecodeEmitter_h */
diff --git a/js/src/frontend/BytecodeOffset.h b/js/src/frontend/BytecodeOffset.h
new file mode 100644
index 0000000000..cb13e9e79a
--- /dev/null
+++ b/js/src/frontend/BytecodeOffset.h
@@ -0,0 +1,153 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef frontend_BytecodeOffset_h
+#define frontend_BytecodeOffset_h
+
+#include "mozilla/Assertions.h" // MOZ_ASSERT
+#include "mozilla/CheckedInt.h" // mozilla::CheckedInt
+
+#include <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..95ba28566d
--- /dev/null
+++ b/js/src/frontend/BytecodeSection.cpp
@@ -0,0 +1,195 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "frontend/BytecodeSection.h"
+
+#include "mozilla/Assertions.h" // MOZ_ASSERT
+
+#include "frontend/AbstractScopePtr.h" // ScopeIndex
+#include "frontend/CompilationStencil.h" // CompilationStencil
+#include "frontend/FrontendContext.h" // FrontendContext
+#include "frontend/SharedContext.h" // FunctionBox
+#include "vm/BytecodeUtil.h" // INDEX_LIMIT, StackUses, StackDefs
+#include "vm/GlobalObject.h"
+#include "vm/JSContext.h" // JSContext
+#include "vm/RegExpObject.h" // RegexpObject
+#include "vm/Scope.h" // GlobalScope
+
+using namespace js;
+using namespace js::frontend;
+
+bool GCThingList::append(FunctionBox* funbox, GCThingIndex* index) {
+ // Append the function to the vector and return the index in *index.
+ *index = GCThingIndex(vector.length());
+
+ if (!vector.emplaceBack(funbox->index())) {
+ return false;
+ }
+ return true;
+}
+
+AbstractScopePtr GCThingList::getScope(size_t index) const {
+ const TaggedScriptThingIndex& elem = vector[index];
+ if (elem.isEmptyGlobalScope()) {
+ // The empty enclosing scope should be stored by
+ // CompilationInput::initForSelfHostingGlobal.
+ return AbstractScopePtr::compilationEnclosingScope(compilationState);
+ }
+ return AbstractScopePtr(compilationState, elem.toScope());
+}
+
+mozilla::Maybe<ScopeIndex> GCThingList::getScopeIndex(size_t index) const {
+ const TaggedScriptThingIndex& elem = vector[index];
+ if (elem.isEmptyGlobalScope()) {
+ return mozilla::Nothing();
+ }
+ return mozilla::Some(vector[index].toScope());
+}
+
+TaggedParserAtomIndex GCThingList::getAtom(size_t index) const {
+ const TaggedScriptThingIndex& elem = vector[index];
+ return elem.toAtom();
+}
+
+bool js::frontend::EmitScriptThingsVector(
+ JSContext* cx, const CompilationAtomCache& atomCache,
+ const CompilationStencil& stencil, CompilationGCOutput& gcOutput,
+ mozilla::Span<const TaggedScriptThingIndex> things,
+ mozilla::Span<JS::GCCellPtr> output) {
+ MOZ_ASSERT(things.size() <= INDEX_LIMIT);
+ MOZ_ASSERT(things.size() == output.size());
+
+ for (uint32_t i = 0; i < things.size(); i++) {
+ const auto& thing = things[i];
+ switch (thing.tag()) {
+ case TaggedScriptThingIndex::Kind::ParserAtomIndex:
+ case TaggedScriptThingIndex::Kind::WellKnown: {
+ JSString* str = atomCache.getExistingStringAt(cx, thing.toAtom());
+ MOZ_ASSERT(str);
+ output[i] = JS::GCCellPtr(str);
+ break;
+ }
+ case TaggedScriptThingIndex::Kind::Null:
+ output[i] = JS::GCCellPtr(nullptr);
+ break;
+ case TaggedScriptThingIndex::Kind::BigInt: {
+ const BigIntStencil& data = stencil.bigIntData[thing.toBigInt()];
+ BigInt* bi = data.createBigInt(cx);
+ if (!bi) {
+ return false;
+ }
+ output[i] = JS::GCCellPtr(bi);
+ break;
+ }
+ case TaggedScriptThingIndex::Kind::ObjLiteral: {
+ const ObjLiteralStencil& data =
+ stencil.objLiteralData[thing.toObjLiteral()];
+ JS::GCCellPtr ptr = data.create(cx, atomCache);
+ if (!ptr) {
+ return false;
+ }
+ output[i] = ptr;
+ break;
+ }
+ case TaggedScriptThingIndex::Kind::RegExp: {
+ RegExpStencil& data = stencil.regExpData[thing.toRegExp()];
+ RegExpObject* regexp = data.createRegExp(cx, atomCache);
+ if (!regexp) {
+ return false;
+ }
+ output[i] = JS::GCCellPtr(regexp);
+ break;
+ }
+ case TaggedScriptThingIndex::Kind::Scope:
+ output[i] = JS::GCCellPtr(gcOutput.getScope(thing.toScope()));
+ break;
+ case TaggedScriptThingIndex::Kind::Function:
+ output[i] = JS::GCCellPtr(gcOutput.getFunction(thing.toFunction()));
+ break;
+ case TaggedScriptThingIndex::Kind::EmptyGlobalScope: {
+ Scope* scope = &cx->global()->emptyGlobalScope();
+ output[i] = JS::GCCellPtr(scope);
+ break;
+ }
+ }
+ }
+
+ return true;
+}
+
+bool CGTryNoteList::append(TryNoteKind kind, uint32_t stackDepth,
+ BytecodeOffset start, BytecodeOffset end) {
+ MOZ_ASSERT(start <= end);
+
+ // Offsets are given relative to sections, but we only expect main-section
+ // to have TryNotes. In finish() we will fixup base offset.
+
+ TryNote note(uint32_t(kind), stackDepth, start.toUint32(),
+ (end - start).toUint32());
+
+ return list.append(note);
+}
+
+bool CGScopeNoteList::append(GCThingIndex scopeIndex, BytecodeOffset offset,
+ uint32_t parent) {
+ ScopeNote note;
+ note.index = scopeIndex;
+ note.start = offset.toUint32();
+ note.length = 0;
+ note.parent = parent;
+
+ return list.append(note);
+}
+
+void CGScopeNoteList::recordEnd(uint32_t index, BytecodeOffset offset) {
+ recordEndImpl(index, offset.toUint32());
+}
+
+void CGScopeNoteList::recordEndFunctionBodyVar(uint32_t index) {
+ recordEndImpl(index, UINT32_MAX);
+}
+
+void CGScopeNoteList::recordEndImpl(uint32_t index, uint32_t offset) {
+ MOZ_ASSERT(index < length());
+ MOZ_ASSERT(list[index].length == 0);
+ MOZ_ASSERT(offset >= list[index].start);
+ list[index].length = offset - list[index].start;
+}
+
+BytecodeSection::BytecodeSection(FrontendContext* fc, uint32_t lineNum,
+ uint32_t column)
+ : code_(fc),
+ notes_(fc),
+ lastNoteOffset_(0),
+ tryNoteList_(fc),
+ scopeNoteList_(fc),
+ resumeOffsetList_(fc),
+ currentLine_(lineNum),
+ lastColumn_(column) {}
+
+void BytecodeSection::updateDepth(JSOp op, BytecodeOffset target) {
+ jsbytecode* pc = code(target);
+
+ int nuses = StackUses(op, pc);
+ int ndefs = StackDefs(op);
+
+ stackDepth_ -= nuses;
+ MOZ_ASSERT(stackDepth_ >= 0);
+ stackDepth_ += ndefs;
+
+ if (uint32_t(stackDepth_) > maxStackDepth_) {
+ maxStackDepth_ = stackDepth_;
+ }
+}
+
+PerScriptData::PerScriptData(FrontendContext* fc,
+ frontend::CompilationState& compilationState)
+ : gcThingList_(fc, compilationState),
+ atomIndices_(fc->nameCollectionPool()) {}
+
+bool PerScriptData::init(FrontendContext* fc) {
+ return atomIndices_.acquire(fc);
+}
diff --git a/js/src/frontend/BytecodeSection.h b/js/src/frontend/BytecodeSection.h
new file mode 100644
index 0000000000..955d6fc748
--- /dev/null
+++ b/js/src/frontend/BytecodeSection.h
@@ -0,0 +1,393 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef frontend_BytecodeSection_h
+#define frontend_BytecodeSection_h
+
+#include "mozilla/Attributes.h" // MOZ_STACK_CLASS
+#include "mozilla/Maybe.h" // mozilla::Maybe
+#include "mozilla/Span.h" // mozilla::Span
+
+#include <stddef.h> // ptrdiff_t, size_t
+#include <stdint.h> // uint16_t, int32_t, uint32_t
+
+#include "frontend/AbstractScopePtr.h" // AbstractScopePtr, ScopeIndex
+#include "frontend/BytecodeOffset.h" // BytecodeOffset
+#include "frontend/CompilationStencil.h" // CompilationStencil, CompilationGCOutput, CompilationAtomCache
+#include "frontend/FrontendContext.h" // FrontendContext
+#include "frontend/JumpList.h" // JumpTarget
+#include "frontend/NameCollections.h" // AtomIndexMap, PooledMapPtr
+#include "frontend/ParseNode.h" // BigIntLiteral
+#include "frontend/ParserAtom.h" // ParserAtomsTable, TaggedParserAtomIndex, ParserAtom
+#include "frontend/SourceNotes.h" // SrcNote
+#include "frontend/Stencil.h" // Stencils
+#include "js/TypeDecls.h" // jsbytecode, JSContext
+#include "js/Vector.h" // Vector
+#include "vm/SharedStencil.h" // TryNote, ScopeNote, GCThingIndex
+#include "vm/StencilEnums.h" // TryNoteKind
+
+namespace js {
+namespace frontend {
+
+class FunctionBox;
+
+struct MOZ_STACK_CLASS GCThingList {
+ // The BCE accumulates TaggedScriptThingIndex items so use a vector type. We
+ // reserve some stack slots to avoid allocating for most small scripts.
+ using ScriptThingsStackVector = Vector<TaggedScriptThingIndex, 8>;
+
+ CompilationState& compilationState;
+ ScriptThingsStackVector vector;
+
+ // Index of the first scope in the vector.
+ mozilla::Maybe<GCThingIndex> firstScopeIndex;
+
+ explicit GCThingList(FrontendContext* fc, CompilationState& compilationState)
+ : compilationState(compilationState), vector(fc) {}
+
+ [[nodiscard]] bool append(TaggedParserAtomIndex atom,
+ ParserAtom::Atomize atomize, GCThingIndex* index) {
+ *index = GCThingIndex(vector.length());
+ compilationState.parserAtoms.markUsedByStencil(atom, atomize);
+ if (!vector.emplaceBack(atom)) {
+ return false;
+ }
+ return true;
+ }
+ [[nodiscard]] bool append(ScopeIndex scope, GCThingIndex* index) {
+ *index = GCThingIndex(vector.length());
+ if (!vector.emplaceBack(scope)) {
+ return false;
+ }
+ if (!firstScopeIndex) {
+ firstScopeIndex.emplace(*index);
+ }
+ return true;
+ }
+ [[nodiscard]] bool append(BigIntLiteral* literal, GCThingIndex* index) {
+ *index = GCThingIndex(vector.length());
+ if (!vector.emplaceBack(literal->index())) {
+ return false;
+ }
+ return true;
+ }
+ [[nodiscard]] bool append(RegExpLiteral* literal, GCThingIndex* index) {
+ *index = GCThingIndex(vector.length());
+ if (!vector.emplaceBack(literal->index())) {
+ return false;
+ }
+ return true;
+ }
+ [[nodiscard]] bool append(ObjLiteralIndex objlit, GCThingIndex* index) {
+ *index = GCThingIndex(vector.length());
+ if (!vector.emplaceBack(objlit)) {
+ return false;
+ }
+ return true;
+ }
+ [[nodiscard]] bool append(FunctionBox* funbox, GCThingIndex* index);
+
+ [[nodiscard]] bool appendEmptyGlobalScope(GCThingIndex* index) {
+ *index = GCThingIndex(vector.length());
+ EmptyGlobalScopeType emptyGlobalScope;
+ if (!vector.emplaceBack(emptyGlobalScope)) {
+ return false;
+ }
+ if (!firstScopeIndex) {
+ firstScopeIndex.emplace(*index);
+ }
+ return true;
+ }
+
+ uint32_t length() const { return vector.length(); }
+
+ const ScriptThingsStackVector& objects() { return vector; }
+
+ AbstractScopePtr getScope(size_t index) const;
+
+ // Index of scope within CompilationStencil or Nothing is the scope is
+ // EmptyGlobalScopeType.
+ mozilla::Maybe<ScopeIndex> getScopeIndex(size_t index) const;
+
+ TaggedParserAtomIndex getAtom(size_t index) const;
+
+ AbstractScopePtr firstScope() const {
+ MOZ_ASSERT(firstScopeIndex.isSome());
+ return getScope(*firstScopeIndex);
+ }
+};
+
+[[nodiscard]] bool EmitScriptThingsVector(
+ JSContext* cx, const CompilationAtomCache& atomCache,
+ const CompilationStencil& stencil, CompilationGCOutput& gcOutput,
+ mozilla::Span<const TaggedScriptThingIndex> things,
+ mozilla::Span<JS::GCCellPtr> output);
+
+struct CGTryNoteList {
+ Vector<TryNote, 0> list;
+ explicit CGTryNoteList(FrontendContext* fc) : list(fc) {}
+
+ [[nodiscard]] 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(FrontendContext* fc) : list(fc) {}
+
+ [[nodiscard]] bool append(GCThingIndex scopeIndex, BytecodeOffset offset,
+ uint32_t parent);
+ void recordEnd(uint32_t index, BytecodeOffset offset);
+ void recordEndFunctionBodyVar(uint32_t index);
+ mozilla::Span<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(FrontendContext* fc) : list(fc) {}
+
+ [[nodiscard]] 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(FrontendContext* fc, uint32_t lineNum, uint32_t column);
+
+ // ---- Bytecode ----
+
+ BytecodeVector& code() { return code_; }
+ const BytecodeVector& code() const { return code_; }
+
+ jsbytecode* code(BytecodeOffset offset) {
+ return code_.begin() + offset.value();
+ }
+ BytecodeOffset offset() const {
+ return BytecodeOffset(code_.end() - code_.begin());
+ }
+
+ // ---- Source notes ----
+
+ SrcNotesVector& notes() { return notes_; }
+ const SrcNotesVector& notes() const { return notes_; }
+
+ BytecodeOffset lastNoteOffset() const { return lastNoteOffset_; }
+ void setLastNoteOffset(BytecodeOffset offset) { lastNoteOffset_ = offset; }
+
+ // ---- Jump ----
+
+ BytecodeOffset lastTargetOffset() const { return lastTarget_.offset; }
+ void setLastTargetOffset(BytecodeOffset offset) {
+ lastTarget_.offset = offset;
+ }
+
+ // ---- Stack ----
+
+ int32_t stackDepth() const { return stackDepth_; }
+ void setStackDepth(int32_t depth) { stackDepth_ = depth; }
+
+ uint32_t maxStackDepth() const { return maxStackDepth_; }
+
+ void updateDepth(JSOp op, BytecodeOffset target);
+
+ // ---- Try notes ----
+
+ CGTryNoteList& tryNoteList() { return tryNoteList_; };
+ const CGTryNoteList& tryNoteList() const { return tryNoteList_; };
+
+ // ---- Scope ----
+
+ CGScopeNoteList& scopeNoteList() { return scopeNoteList_; };
+ const CGScopeNoteList& scopeNoteList() const { return scopeNoteList_; };
+
+ // ---- Generator ----
+
+ CGResumeOffsetList& resumeOffsetList() { return resumeOffsetList_; }
+ const CGResumeOffsetList& resumeOffsetList() const {
+ return resumeOffsetList_;
+ }
+
+ uint32_t numYields() const { return numYields_; }
+ void addNumYields() { numYields_++; }
+
+ // ---- Line and column ----
+
+ uint32_t currentLine() const { return currentLine_; }
+ uint32_t lastColumn() const { return lastColumn_; }
+ void setCurrentLine(uint32_t line, uint32_t sourceOffset) {
+ currentLine_ = line;
+ lastColumn_ = 0;
+ lastSourceOffset_ = sourceOffset;
+ }
+
+ void setLastColumn(uint32_t column, uint32_t offset) {
+ lastColumn_ = column;
+ lastSourceOffset_ = offset;
+ }
+
+ void updateSeparatorPosition() {
+ lastSeparatorCodeOffset_ = code().length();
+ lastSeparatorSourceOffset_ = lastSourceOffset_;
+ lastSeparatorLine_ = currentLine_;
+ lastSeparatorColumn_ = lastColumn_;
+ }
+
+ void updateSeparatorPositionIfPresent() {
+ if (lastSeparatorCodeOffset_ == code().length()) {
+ lastSeparatorSourceOffset_ = lastSourceOffset_;
+ lastSeparatorLine_ = currentLine_;
+ lastSeparatorColumn_ = lastColumn_;
+ }
+ }
+
+ bool isDuplicateLocation() const {
+ return lastSeparatorLine_ == currentLine_ &&
+ lastSeparatorColumn_ == lastColumn_;
+ }
+
+ bool atSeparator(uint32_t offset) const {
+ return lastSeparatorSourceOffset_ == offset;
+ }
+
+ // ---- JIT ----
+
+ uint32_t numICEntries() const { return numICEntries_; }
+ void incrementNumICEntries() {
+ MOZ_ASSERT(numICEntries_ != UINT32_MAX, "Shouldn't overflow");
+ numICEntries_++;
+ }
+ void setNumICEntries(uint32_t entries) { numICEntries_ = entries; }
+
+ private:
+ // ---- Bytecode ----
+
+ // Bytecode.
+ BytecodeVector code_;
+
+ // ---- Source notes ----
+
+ // Source notes
+ SrcNotesVector notes_;
+
+ // Code offset for last source note
+ BytecodeOffset lastNoteOffset_;
+
+ // ---- Jump ----
+
+ // Last jump target emitted.
+ JumpTarget lastTarget_;
+
+ // ---- Stack ----
+
+ // Maximum number of expression stack slots so far.
+ uint32_t maxStackDepth_ = 0;
+
+ // Current stack depth in script frame.
+ int32_t stackDepth_ = 0;
+
+ // ---- Try notes ----
+
+ // List of emitted try notes.
+ CGTryNoteList tryNoteList_;
+
+ // ---- Scope ----
+
+ // List of emitted block scope notes.
+ CGScopeNoteList scopeNoteList_;
+
+ // ---- Generator ----
+
+ // Certain ops (yield, await) have an entry in the script's resumeOffsets
+ // list. This can be used to map from the op's resumeIndex to the bytecode
+ // offset of the next pc. This indirection makes it easy to resume in the JIT
+ // (because BaselineScript stores a resumeIndex => native code array).
+ CGResumeOffsetList resumeOffsetList_;
+
+ // Number of yield instructions emitted. Does not include JSOp::Await.
+ uint32_t numYields_ = 0;
+
+ // ---- Line and column ----
+
+ // Line number for srcnotes.
+ //
+ // WARNING: If this becomes out of sync with already-emitted srcnotes,
+ // we can get undefined behavior.
+ uint32_t currentLine_;
+
+ // Zero-based column index on currentLine_ of last
+ // SrcNoteType::ColSpan-annotated opcode.
+ //
+ // WARNING: If this becomes out of sync with already-emitted srcnotes,
+ // we can get undefined behavior.
+ uint32_t lastColumn_ = 0;
+
+ // The last code unit used for srcnotes.
+ uint32_t lastSourceOffset_ = 0;
+
+ // The offset, line and column numbers of the last opcode for the
+ // breakpoint for step execution.
+ uint32_t lastSeparatorCodeOffset_ = 0;
+ uint32_t lastSeparatorSourceOffset_ = 0;
+ uint32_t lastSeparatorLine_ = 0;
+ uint32_t lastSeparatorColumn_ = 0;
+
+ // ---- JIT ----
+
+ // Number of ICEntries in the script. There's one ICEntry for each JOF_IC op
+ // and, if the script is a function, for |this| and each formal argument.
+ uint32_t numICEntries_ = 0;
+};
+
+// Data that is not directly associated with specific opcode/index inside
+// bytecode, but referred from bytecode is stored in this class.
+class PerScriptData {
+ public:
+ PerScriptData(FrontendContext* fc,
+ frontend::CompilationState& compilationState);
+
+ [[nodiscard]] bool init(FrontendContext* fc);
+
+ GCThingList& gcThingList() { return gcThingList_; }
+ const GCThingList& gcThingList() const { return gcThingList_; }
+
+ PooledMapPtr<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..4fccde0e88
--- /dev/null
+++ b/js/src/frontend/CForEmitter.cpp
@@ -0,0 +1,179 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "frontend/CForEmitter.h"
+
+#include "frontend/BytecodeEmitter.h" // BytecodeEmitter
+#include "frontend/EmitterScope.h" // EmitterScope
+#include "vm/Opcodes.h" // JSOp
+#include "vm/ScopeKind.h" // ScopeKind
+#include "vm/StencilEnums.h" // TryNoteKind
+
+using namespace js;
+using namespace js::frontend;
+
+using mozilla::Maybe;
+
+CForEmitter::CForEmitter(BytecodeEmitter* bce,
+ const EmitterScope* headLexicalEmitterScopeForLet)
+ : bce_(bce),
+ headLexicalEmitterScopeForLet_(headLexicalEmitterScopeForLet) {}
+
+bool CForEmitter::emitInit(const Maybe<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_->emitInternedScopeOp(headLexicalEmitterScopeForLet_->index(),
+ JSOp::FreshenLexicalEnv)) {
+ return false;
+ }
+ }
+ }
+
+ if (!loopInfo_->emitLoopHead(bce_, condPos)) {
+ // [stack]
+ return false;
+ }
+
+#ifdef DEBUG
+ state_ = State::Cond;
+#endif
+ return true;
+}
+
+bool CForEmitter::emitBody(Cond cond) {
+ MOZ_ASSERT(state_ == State::Cond);
+ cond_ = cond;
+
+ if (cond_ == Cond::Present) {
+ if (!bce_->emitJump(JSOp::JumpIfFalse, &loopInfo_->breaks)) {
+ return false;
+ }
+ }
+
+ tdzCache_.emplace(bce_);
+
+#ifdef DEBUG
+ state_ = State::Body;
+#endif
+ return true;
+}
+
+bool CForEmitter::emitUpdate(Update update, const Maybe<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_->emitInternedScopeOp(headLexicalEmitterScopeForLet_->index(),
+ JSOp::FreshenLexicalEnv)) {
+ return false;
+ }
+ }
+ }
+
+ // The update code may not be executed at all; it needs its own TDZ
+ // cache.
+ if (update_ == Update::Present) {
+ tdzCache_.emplace(bce_);
+
+ if (updatePos) {
+ if (!bce_->updateSourceCoordNotes(*updatePos)) {
+ return false;
+ }
+ }
+ }
+
+#ifdef DEBUG
+ state_ = State::Update;
+#endif
+ return true;
+}
+
+bool CForEmitter::emitEnd(uint32_t forPos) {
+ MOZ_ASSERT(state_ == State::Update);
+
+ if (update_ == Update::Present) {
+ tdzCache_.reset();
+
+ // [stack] UPDATE
+
+ if (!bce_->emit1(JSOp::Pop)) {
+ // [stack]
+ return false;
+ }
+ }
+
+ if (cond_ == Cond::Missing && update_ == Update::Missing) {
+ // If there is no condition clause and no update clause, mark
+ // the loop-ending "goto" with the location of the "for".
+ // This ensures that the debugger will stop on each loop
+ // iteration.
+ if (!bce_->updateSourceCoordNotes(forPos)) {
+ return false;
+ }
+ }
+
+ // Emit the loop-closing jump.
+ if (!loopInfo_->emitLoopEnd(bce_, JSOp::Goto, TryNoteKind::Loop)) {
+ // [stack]
+ return false;
+ }
+
+ loopInfo_.reset();
+
+#ifdef DEBUG
+ state_ = State::End;
+#endif
+ return true;
+}
diff --git a/js/src/frontend/CForEmitter.h b/js/src/frontend/CForEmitter.h
new file mode 100644
index 0000000000..231dd318be
--- /dev/null
+++ b/js/src/frontend/CForEmitter.h
@@ -0,0 +1,175 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef frontend_CForEmitter_h
+#define frontend_CForEmitter_h
+
+#include "mozilla/Attributes.h" // MOZ_STACK_CLASS
+#include "mozilla/Maybe.h" // mozilla::Maybe
+
+#include <stdint.h> // uint32_t
+
+#include "frontend/BytecodeControlStructures.h" // LoopControl
+#include "frontend/TDZCheckCache.h" // TDZCheckCache
+
+namespace js {
+namespace frontend {
+
+struct BytecodeEmitter;
+class EmitterScope;
+
+// Class for emitting bytecode for c-style for block.
+//
+// Usage: (check for the return value is omitted for simplicity)
+//
+// `for (init; cond; update) body`
+// CForEmitter cfor(this, headLexicalEmitterScopeForLet or nullptr);
+// cfor.emitInit(Some(offset_of_init));
+// emit(init); // without pushing value
+// cfor.emitCond(Some(offset_of_cond));
+// emit(cond);
+// cfor.emitBody(CForEmitter::Cond::Present);
+// emit(body);
+// cfor.emitUpdate(CForEmitter::Update::Present, Some(offset_of_update)));
+// emit(update);
+// cfor.emitEnd(offset_of_for);
+//
+// `for (;;) body`
+// CForEmitter cfor(this, nullptr);
+// cfor.emitInit(Nothing());
+// cfor.emitCond(Nothing());
+// cfor.emitBody(CForEmitter::Cond::Missing);
+// emit(body);
+// cfor.emitUpdate(CForEmitter::Update::Missing, Nothing());
+// cfor.emitEnd(offset_of_for);
+//
+class MOZ_STACK_CLASS CForEmitter {
+ // Basic structure of the bytecode (not complete).
+ //
+ // If `cond` is not empty:
+ // {init}
+ // loop:
+ // JSOp::LoopHead
+ // {cond}
+ // JSOp::JumpIfFalse break
+ // {body}
+ // continue:
+ // {update}
+ // JSOp::Goto loop
+ // break:
+ //
+ // If `cond` is empty:
+ // {init}
+ // loop:
+ // JSOp::LoopHead
+ // {body}
+ // continue:
+ // {update}
+ // JSOp::Goto loop
+ // break:
+ //
+ public:
+ enum class Cond { Missing, Present };
+ enum class Update { Missing, Present };
+
+ private:
+ BytecodeEmitter* bce_;
+
+ // Whether the c-style for loop has `cond` and `update`.
+ Cond cond_ = Cond::Missing;
+ Update update_ = Update::Missing;
+
+ mozilla::Maybe<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.
+ [[nodiscard]] bool emitInit(const mozilla::Maybe<uint32_t>& initPos);
+ [[nodiscard]] bool emitCond(const mozilla::Maybe<uint32_t>& condPos);
+ [[nodiscard]] bool emitBody(Cond cond);
+ [[nodiscard]] bool emitUpdate(Update update,
+ const mozilla::Maybe<uint32_t>& updatePos);
+ [[nodiscard]] bool emitEnd(uint32_t forPos);
+};
+
+} /* namespace frontend */
+} /* namespace js */
+
+#endif /* frontend_CForEmitter_h */
diff --git a/js/src/frontend/CallOrNewEmitter.cpp b/js/src/frontend/CallOrNewEmitter.cpp
new file mode 100644
index 0000000000..fc4e449d56
--- /dev/null
+++ b/js/src/frontend/CallOrNewEmitter.cpp
@@ -0,0 +1,365 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "frontend/CallOrNewEmitter.h"
+
+#include "frontend/BytecodeEmitter.h"
+#include "frontend/NameOpEmitter.h"
+#include "vm/Opcodes.h"
+
+using namespace js;
+using namespace js::frontend;
+
+CallOrNewEmitter::CallOrNewEmitter(BytecodeEmitter* bce, JSOp op,
+ ArgumentsKind argumentsKind,
+ ValueUsage valueUsage)
+ : bce_(bce), op_(op), argumentsKind_(argumentsKind) {
+ if (op_ == JSOp::Call && valueUsage == ValueUsage::IgnoreValue) {
+ op_ = JSOp::CallIgnoresRv;
+ }
+
+ MOZ_ASSERT(isCall() || isNew() || isSuperCall());
+}
+
+bool CallOrNewEmitter::emitNameCallee(TaggedParserAtomIndex name) {
+ MOZ_ASSERT(state_ == State::Start);
+
+ // [stack]
+
+ NameOpEmitter noe(
+ bce_, name,
+ isCall() ? NameOpEmitter::Kind::Call : NameOpEmitter::Kind::Get);
+ if (!noe.emitGet()) {
+ // [stack] # if isCall()
+ // [stack] CALLEE THIS
+ // [stack] # if isNew() or isSuperCall()
+ // [stack] CALLEE
+ return false;
+ }
+
+ state_ = State::NameCallee;
+ return true;
+}
+
+[[nodiscard]] PropOpEmitter& CallOrNewEmitter::prepareForPropCallee(
+ bool isSuperProp) {
+ MOZ_ASSERT(state_ == State::Start);
+ MOZ_ASSERT(bce_->emitterMode != BytecodeEmitter::SelfHosting);
+
+ // [stack]
+
+ poe_.emplace(bce_,
+ isCall() ? PropOpEmitter::Kind::Call : PropOpEmitter::Kind::Get,
+ isSuperProp ? PropOpEmitter::ObjKind::Super
+ : PropOpEmitter::ObjKind::Other);
+
+ state_ = State::PropCallee;
+ return *poe_;
+}
+
+[[nodiscard]] ElemOpEmitter& CallOrNewEmitter::prepareForElemCallee(
+ bool isSuperElem) {
+ MOZ_ASSERT(state_ == State::Start);
+ MOZ_ASSERT(bce_->emitterMode != BytecodeEmitter::SelfHosting);
+
+ // [stack]
+
+ eoe_.emplace(bce_,
+ isCall() ? ElemOpEmitter::Kind::Call : ElemOpEmitter::Kind::Get,
+ isSuperElem ? ElemOpEmitter::ObjKind::Super
+ : ElemOpEmitter::ObjKind::Other);
+
+ state_ = State::ElemCallee;
+ return *eoe_;
+}
+
+PrivateOpEmitter& CallOrNewEmitter::prepareForPrivateCallee(
+ TaggedParserAtomIndex privateName) {
+ MOZ_ASSERT(state_ == State::Start);
+ MOZ_ASSERT(bce_->emitterMode != BytecodeEmitter::SelfHosting);
+
+ // [stack]
+
+ xoe_.emplace(
+ bce_,
+ isCall() ? PrivateOpEmitter::Kind::Call : PrivateOpEmitter::Kind::Get,
+ privateName);
+ state_ = State::PrivateCallee;
+ return *xoe_;
+}
+
+bool CallOrNewEmitter::prepareForFunctionCallee() {
+ MOZ_ASSERT(state_ == State::Start);
+ MOZ_ASSERT(bce_->emitterMode != BytecodeEmitter::SelfHosting);
+
+ // [stack]
+
+ state_ = State::FunctionCallee;
+ return true;
+}
+
+bool CallOrNewEmitter::emitSuperCallee() {
+ MOZ_ASSERT(state_ == State::Start);
+ MOZ_ASSERT(bce_->emitterMode != BytecodeEmitter::SelfHosting);
+
+ // [stack]
+
+ if (!bce_->emitThisEnvironmentCallee()) {
+ // [stack] CALLEE
+ return false;
+ }
+ if (!bce_->emit1(JSOp::SuperFun)) {
+ // [stack] SUPER_FUN
+ return false;
+ }
+ if (!bce_->emit1(JSOp::IsConstructing)) {
+ // [stack] SUPER_FUN IS_CONSTRUCTING
+ return false;
+ }
+
+ state_ = State::SuperCallee;
+ return true;
+}
+
+bool CallOrNewEmitter::prepareForOtherCallee() {
+ MOZ_ASSERT(state_ == State::Start);
+ MOZ_ASSERT(bce_->emitterMode != BytecodeEmitter::SelfHosting);
+
+ // [stack]
+
+ state_ = State::OtherCallee;
+ return true;
+}
+
+bool CallOrNewEmitter::emitThis() {
+ MOZ_ASSERT(state_ == State::NameCallee || state_ == State::PropCallee ||
+ state_ == State::ElemCallee || state_ == State::PrivateCallee ||
+ state_ == State::FunctionCallee || state_ == State::SuperCallee ||
+ state_ == State::OtherCallee);
+
+ // [stack] # if isCall()
+ // [stack] CALLEE THIS?
+ // [stack] # if isNew() or isSuperCall()
+ // [stack] CALLEE
+
+ bool needsThis = false;
+ switch (state_) {
+ case State::NameCallee:
+ if (!isCall()) {
+ needsThis = true;
+ }
+ break;
+ case State::PropCallee:
+ poe_.reset();
+ if (!isCall()) {
+ needsThis = true;
+ }
+ break;
+ case State::ElemCallee:
+ eoe_.reset();
+ if (!isCall()) {
+ needsThis = true;
+ }
+ break;
+ case State::PrivateCallee:
+ xoe_.reset();
+ if (!isCall()) {
+ needsThis = true;
+ }
+ break;
+ case State::FunctionCallee:
+ needsThis = true;
+ break;
+ case State::SuperCallee:
+ break;
+ case State::OtherCallee:
+ needsThis = true;
+ break;
+ default:;
+ }
+ if (needsThis) {
+ if (isNew() || isSuperCall()) {
+ if (!bce_->emit1(JSOp::IsConstructing)) {
+ // [stack] CALLEE IS_CONSTRUCTING
+ return false;
+ }
+ } else {
+ if (!bce_->emit1(JSOp::Undefined)) {
+ // [stack] CALLEE THIS
+ return false;
+ }
+ }
+ }
+
+ // [stack] CALLEE THIS
+
+ state_ = State::This;
+ return true;
+}
+
+bool CallOrNewEmitter::prepareForNonSpreadArguments() {
+ MOZ_ASSERT(state_ == State::This);
+ MOZ_ASSERT(!isSpread());
+
+ // [stack] CALLEE THIS
+
+ state_ = State::Arguments;
+ return true;
+}
+
+// See the usage in the comment at the top of the class.
+bool CallOrNewEmitter::wantSpreadOperand() {
+ MOZ_ASSERT(state_ == State::This);
+ MOZ_ASSERT(isSpread());
+
+ // [stack] CALLEE THIS
+
+ state_ = State::WantSpreadOperand;
+ return isSingleSpread() || isPassthroughRest();
+}
+
+bool CallOrNewEmitter::prepareForSpreadArguments() {
+ MOZ_ASSERT(state_ == State::WantSpreadOperand);
+ MOZ_ASSERT(isSpread());
+ MOZ_ASSERT(!isSingleSpread() && !isPassthroughRest());
+
+ // [stack] CALLEE THIS
+
+ state_ = State::Arguments;
+ return true;
+}
+
+bool CallOrNewEmitter::emitSpreadArgumentsTest() {
+ // Caller should check wantSpreadOperand before this.
+ MOZ_ASSERT(state_ == State::WantSpreadOperand);
+ MOZ_ASSERT(isSpread());
+ MOZ_ASSERT(isSingleSpread() || isPassthroughRest());
+
+ // [stack] CALLEE THIS ARG0
+
+ if (isSingleSpread()) {
+ // Emit a preparation code to optimize the spread call:
+ //
+ // g(...args);
+ //
+ // If the spread operand is a packed array, skip the spread
+ // operation and pass it directly to spread call operation.
+ // See the comment in OptimizeSpreadCall in Interpreter.cpp
+ // for the optimizable conditions.
+ // [stack] CALLEE THIS ARG0
+
+ ifNotOptimizable_.emplace(bce_);
+ if (!bce_->emit1(JSOp::Dup)) {
+ // [stack] CALLEE THIS ARG0 ARG0
+ return false;
+ }
+ if (!bce_->emit1(JSOp::OptimizeSpreadCall)) {
+ // [stack] CALLEE THIS ARG0 ARRAY_OR_UNDEF
+ return false;
+ }
+
+ if (!bce_->emit1(JSOp::Dup)) {
+ // [stack] CALLEE THIS ARG0 ARRAY_OR_UNDEF ARRAY_OR_UNDEF
+ return false;
+ }
+ if (!bce_->emit1(JSOp::Undefined)) {
+ // [stack] CALLEE THIS ARG0 ARRAY_OR_UNDEF ARRAY_OR_UNDEF UNDEF
+ return false;
+ }
+ if (!bce_->emit1(JSOp::StrictEq)) {
+ // [stack] CALLEE THIS ARG0 ARRAY_OR_UNDEF EQ
+ return false;
+ }
+
+ if (!ifNotOptimizable_->emitThenElse()) {
+ // [stack] CALLEE THIS ARG0 ARRAY_OR_UNDEF
+ return false;
+ }
+ if (!bce_->emit1(JSOp::Pop)) {
+ // [stack] CALLEE THIS ARG0
+ return false;
+ }
+ }
+
+ state_ = State::SpreadArgumentsTest;
+ return true;
+}
+
+bool CallOrNewEmitter::wantSpreadIteration() {
+ MOZ_ASSERT(state_ == State::SpreadArgumentsTest);
+ MOZ_ASSERT(isSpread());
+
+ state_ = State::SpreadIteration;
+ return !isPassthroughRest();
+}
+
+bool CallOrNewEmitter::emitSpreadArgumentsTestEnd() {
+ MOZ_ASSERT(state_ == State::SpreadIteration);
+ MOZ_ASSERT(isSpread());
+
+ if (isSingleSpread()) {
+ if (!ifNotOptimizable_->emitElse()) {
+ // [stack] CALLEE THIS ARG0 ARRAY_OR_UNDEF
+ return false;
+ }
+ if (!bce_->emit1(JSOp::Swap)) {
+ // [stack] CALLEE THIS ARRAY_OR_UNDEF ARG0
+ return false;
+ }
+ if (!bce_->emit1(JSOp::Pop)) {
+ // [stack] CALLEE THIS ARRAY_OR_UNDEF
+ return false;
+ }
+
+ if (!ifNotOptimizable_->emitEnd()) {
+ // [stack] CALLEE THIS ARR
+ return false;
+ }
+
+ ifNotOptimizable_.reset();
+ }
+
+ state_ = State::Arguments;
+ return true;
+}
+
+bool CallOrNewEmitter::emitEnd(uint32_t argc, uint32_t beginPos) {
+ MOZ_ASSERT(state_ == State::Arguments);
+
+ // [stack] # if isCall()
+ // [stack] CALLEE THIS ARG0 ... ARGN
+ // [stack] # if isNew() or isSuperCall()
+ // [stack] CALLEE IS_CONSTRUCTING ARG0 ... ARGN NEW.TARGET?
+
+ if (!bce_->updateSourceCoordNotes(beginPos)) {
+ return false;
+ }
+ if (!bce_->markSimpleBreakpoint()) {
+ return false;
+ }
+ if (!isSpread()) {
+ if (!bce_->emitCall(op_, argc)) {
+ // [stack] RVAL
+ return false;
+ }
+ } else {
+ if (!bce_->emit1(op_)) {
+ // [stack] RVAL
+ return false;
+ }
+ }
+
+ if (isEval()) {
+ uint32_t lineNum = bce_->errorReporter().lineAt(beginPos);
+ if (!bce_->emitUint32Operand(JSOp::Lineno, lineNum)) {
+ // [stack] RVAL
+ return false;
+ }
+ }
+
+ state_ = State::End;
+ return true;
+}
diff --git a/js/src/frontend/CallOrNewEmitter.h b/js/src/frontend/CallOrNewEmitter.h
new file mode 100644
index 0000000000..5abf644b70
--- /dev/null
+++ b/js/src/frontend/CallOrNewEmitter.h
@@ -0,0 +1,352 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef frontend_CallOrNewEmitter_h
+#define frontend_CallOrNewEmitter_h
+
+#include "mozilla/Attributes.h"
+#include "mozilla/Maybe.h"
+
+#include <stdint.h>
+
+#include "frontend/ElemOpEmitter.h"
+#include "frontend/IfEmitter.h"
+#include "frontend/PrivateOpEmitter.h"
+#include "frontend/PropOpEmitter.h"
+#include "frontend/ValueUsage.h"
+#include "vm/BytecodeUtil.h"
+#include "vm/Opcodes.h"
+
+namespace js {
+namespace frontend {
+
+struct BytecodeEmitter;
+class TaggedParserAtomIndex;
+
+// Class for emitting bytecode for call or new expression.
+//
+// Usage: (check for the return value is omitted for simplicity)
+//
+// `print(arg);`
+// CallOrNewEmitter cone(this, JSOp::Call,
+// CallOrNewEmitter::ArgumentsKind::Other,
+// ValueUsage::WantValue);
+// cone.emitNameCallee(print);
+// cone.emitThis();
+// cone.prepareForNonSpreadArguments();
+// emit(arg);
+// cone.emitEnd(1, offset_of_callee);
+//
+// `callee.prop(arg1, arg2);`
+// CallOrNewEmitter cone(this, JSOp::Call,
+// CallOrNewEmitter::ArgumentsKind::Other,
+// ValueUsage::WantValue);
+// PropOpEmitter& poe = cone.prepareForPropCallee(false);
+// ... emit `callee.prop` with `poe` here...
+// cone.emitThis();
+// cone.prepareForNonSpreadArguments();
+// emit(arg1);
+// emit(arg2);
+// cone.emitEnd(2, offset_of_callee);
+//
+// `callee[key](arg);`
+// CallOrNewEmitter cone(this, JSOp::Call,
+// CallOrNewEmitter::ArgumentsKind::Other,
+// ValueUsage::WantValue);
+// ElemOpEmitter& eoe = cone.prepareForElemCallee(false);
+// ... emit `callee[key]` with `eoe` here...
+// cone.emitThis();
+// cone.prepareForNonSpreadArguments();
+// emit(arg);
+// cone.emitEnd(1, offset_of_callee);
+//
+// `callee.#method(arg);`
+// CallOrNewEmitter cone(this, JSOp::Call,
+// CallOrNewEmitter::ArgumentsKind::Other,
+// ValueUsage::WantValue);
+// PrivateOpEmitter& xoe = cone.prepareForPrivateCallee();
+// ... emit `callee.#method` with `xoe` here...
+// cone.prepareForNonSpreadArguments();
+// emit(arg);
+// cone.emitEnd(1, offset_of_callee);
+//
+// `(function() { ... })(arg);`
+// CallOrNewEmitter cone(this, JSOp::Call,
+// CallOrNewEmitter::ArgumentsKind::Other,
+// ValueUsage::WantValue);
+// cone.prepareForFunctionCallee();
+// emit(function);
+// cone.emitThis();
+// cone.prepareForNonSpreadArguments();
+// emit(arg);
+// cone.emitEnd(1, offset_of_callee);
+//
+// `super(arg);`
+// CallOrNewEmitter cone(this, JSOp::Call,
+// CallOrNewEmitter::ArgumentsKind::Other,
+// ValueUsage::WantValue);
+// cone.emitSuperCallee();
+// cone.emitThis();
+// cone.prepareForNonSpreadArguments();
+// emit(arg);
+// cone.emitEnd(1, offset_of_callee);
+//
+// `(some_other_expression)(arg);`
+// CallOrNewEmitter cone(this, JSOp::Call,
+// CallOrNewEmitter::ArgumentsKind::Other,
+// ValueUsage::WantValue);
+// cone.prepareForOtherCallee();
+// emit(some_other_expression);
+// cone.emitThis();
+// cone.prepareForNonSpreadArguments();
+// emit(arg);
+// cone.emitEnd(1, offset_of_callee);
+//
+// `print(...arg);`
+// CallOrNewEmitter cone(this, JSOp::SpreadCall,
+// CallOrNewEmitter::ArgumentsKind::SingleSpread,
+// ValueUsage::WantValue);
+// cone.emitNameCallee(print);
+// cone.emitThis();
+// if (cone.wantSpreadOperand()) {
+// emit(arg)
+// }
+// cone.emitSpreadArgumentsTest();
+// emit([...arg]);
+// cone.emitEnd(1, offset_of_callee);
+//
+// `new f(arg);`
+// CallOrNewEmitter cone(this, JSOp::New,
+// CallOrNewEmitter::ArgumentsKind::Other,
+// ValueUsage::WantValue);
+// cone.emitNameCallee(f);
+// cone.emitThis();
+// cone.prepareForNonSpreadArguments();
+// emit(arg);
+// cone.emitEnd(1, offset_of_callee);
+//
+class MOZ_STACK_CLASS CallOrNewEmitter {
+ public:
+ enum class ArgumentsKind {
+ Other,
+
+ // Specify this for the following case:
+ //
+ // g(...input);
+ //
+ // This enables optimization to avoid allocating an intermediate array
+ // for spread operation.
+ //
+ // wantSpreadOperand() returns true when this is specified.
+ SingleSpread,
+
+ // Used for default derived class constructors:
+ //
+ // constructor(...args) {
+ // super(...args);
+ // }
+ //
+ // The rest-parameter is directly passed through to the `super` call without
+ // using the iteration protocol.
+ PassthroughRest,
+ };
+
+ private:
+ BytecodeEmitter* bce_;
+
+ // The opcode for the call or new.
+ JSOp op_;
+
+ // Whether the call is a spread call with single parameter or not.
+ // See the comment in emitSpreadArgumentsTest for more details.
+ ArgumentsKind argumentsKind_;
+
+ // The branch for spread call optimization.
+ mozilla::Maybe<InternalIfEmitter> ifNotOptimizable_;
+
+ mozilla::Maybe<PropOpEmitter> poe_;
+ mozilla::Maybe<ElemOpEmitter> eoe_;
+ mozilla::Maybe<PrivateOpEmitter> xoe_;
+
+ // The state of this emitter.
+ //
+ // +-------+ emitNameCallee +------------+
+ // | Start |-+------------------------->| NameCallee |------+
+ // +-------+ | +------------+ |
+ // | |
+ // | prepareForPropCallee +------------+ v
+ // +------------------------->| PropCallee |----->+
+ // | +------------+ |
+ // | |
+ // | prepareForElemCallee +------------+ v
+ // +------------------------->| ElemCallee |----->+
+ // | +------------+ |
+ // | |
+ // | prepareForPrivateCallee +---------------+ v
+ // +------------------------->| PrivateCallee |-->+
+ // | +---------------+ |
+ // | |
+ // | prepareForFunctionCallee +----------------+ v
+ // +------------------------->| FunctionCallee |->+
+ // | +----------------+ |
+ // | |
+ // | emitSuperCallee +-------------+ v
+ // +------------------------->| SuperCallee |---->+
+ // | +-------------+ |
+ // | |
+ // | prepareForOtherCallee +-------------+ v
+ // +------------------------->| OtherCallee |---->+
+ // +-------------+ |
+ // |
+ // +--------------------------------------------------------+
+ // |
+ // | emitThis +------+
+ // +--------->| This |-+
+ // +------+ |
+ // |
+ // +-------------------+
+ // |
+ // | [!isSpread]
+ // | prepareForNonSpreadArguments +-----------+ emitEnd +-----+
+ // +------------------------------->+->| Arguments |-------->| End |
+ // | ^ +-----------+ +-----+
+ // | |
+ // | +<------------------------------------+
+ // | | |
+ // | | emitSpreadArgumentsTestEnd |
+ // | | |
+ // | | +-----------------+ |
+ // | +---------| SpreadIteration |------+ |
+ // | +-----------------+ | |
+ // | +----------------------------------+ |
+ // | | |
+ // | | wantSpreadIteration |
+ // | | |
+ // | | +---------------------+ |
+ // | +---------| SpreadArgumentsTest |--+ |
+ // | +---------------------+ | |
+ // | [isSpread] | |
+ // | wantSpreadOperand +-------------------+ emitSpreadArgumentsTest | |
+ // +-------------------->| WantSpreadOperand |-------------------------+ |
+ // | +-------------------+ |
+ // | |
+ // | |
+ // | |
+ // | [isSpread] |
+ // | prepareForSpreadArguments |
+ // +----------------------------------------------------------------------+
+ enum class State {
+ // The initial state.
+ Start,
+
+ // After calling emitNameCallee.
+ NameCallee,
+
+ // After calling prepareForPropCallee.
+ PropCallee,
+
+ // After calling prepareForElemCallee.
+ ElemCallee,
+
+ // After calling prepareForPrivateCallee.
+ PrivateCallee,
+
+ // After calling prepareForFunctionCallee.
+ FunctionCallee,
+
+ // After calling emitSuperCallee.
+ SuperCallee,
+
+ // After calling prepareForOtherCallee.
+ OtherCallee,
+
+ // After calling emitThis.
+ This,
+
+ // After calling wantSpreadOperand.
+ WantSpreadOperand,
+
+ // After calling emitSpreadArgumentsTest.
+ SpreadArgumentsTest,
+
+ // After calling wantSpreadIteration.
+ SpreadIteration,
+
+ // After calling prepareForNonSpreadArguments.
+ Arguments,
+
+ // After calling emitEnd.
+ End
+ };
+ State state_ = State::Start;
+
+ public:
+ CallOrNewEmitter(BytecodeEmitter* bce, JSOp op, ArgumentsKind argumentsKind,
+ ValueUsage valueUsage);
+
+ private:
+ [[nodiscard]] bool isCall() const {
+ return op_ == JSOp::Call || op_ == JSOp::CallIgnoresRv ||
+ op_ == JSOp::SpreadCall || isEval();
+ }
+
+ [[nodiscard]] bool isNew() const {
+ return op_ == JSOp::New || op_ == JSOp::SpreadNew;
+ }
+
+ [[nodiscard]] bool isSuperCall() const {
+ return op_ == JSOp::SuperCall || op_ == JSOp::SpreadSuperCall;
+ }
+
+ [[nodiscard]] bool isEval() const {
+ return op_ == JSOp::Eval || op_ == JSOp::StrictEval ||
+ op_ == JSOp::SpreadEval || op_ == JSOp::StrictSpreadEval;
+ }
+
+ [[nodiscard]] bool isSpread() const { return IsSpreadOp(op_); }
+
+ [[nodiscard]] bool isSingleSpread() const {
+ return argumentsKind_ == ArgumentsKind::SingleSpread;
+ }
+
+ [[nodiscard]] bool isPassthroughRest() const {
+ return argumentsKind_ == ArgumentsKind::PassthroughRest;
+ }
+
+ public:
+ [[nodiscard]] bool emitNameCallee(TaggedParserAtomIndex name);
+ [[nodiscard]] PropOpEmitter& prepareForPropCallee(bool isSuperProp);
+ [[nodiscard]] ElemOpEmitter& prepareForElemCallee(bool isSuperElem);
+ [[nodiscard]] PrivateOpEmitter& prepareForPrivateCallee(
+ TaggedParserAtomIndex privateName);
+ [[nodiscard]] bool prepareForFunctionCallee();
+ [[nodiscard]] bool emitSuperCallee();
+ [[nodiscard]] bool prepareForOtherCallee();
+
+ [[nodiscard]] bool emitThis();
+
+ [[nodiscard]] bool prepareForNonSpreadArguments();
+ [[nodiscard]] bool prepareForSpreadArguments();
+
+ // See the usage in the comment at the top of the class.
+ [[nodiscard]] bool wantSpreadOperand();
+ [[nodiscard]] bool emitSpreadArgumentsTest();
+ [[nodiscard]] bool emitSpreadArgumentsTestEnd();
+ [[nodiscard]] bool wantSpreadIteration();
+
+ // Parameters are the offset in the source code for each character below:
+ //
+ // callee(arg);
+ // ^
+ // |
+ // beginPos
+ [[nodiscard]] bool emitEnd(uint32_t argc, uint32_t beginPos);
+};
+
+} /* namespace frontend */
+} /* namespace js */
+
+#endif /* frontend_CallOrNewEmitter_h */
diff --git a/js/src/frontend/CompilationStencil.h b/js/src/frontend/CompilationStencil.h
new file mode 100644
index 0000000000..617a3dd487
--- /dev/null
+++ b/js/src/frontend/CompilationStencil.h
@@ -0,0 +1,1842 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef frontend_CompilationStencil_h
+#define frontend_CompilationStencil_h
+
+#include "mozilla/AlreadyAddRefed.h" // already_AddRefed
+#include "mozilla/Assertions.h" // MOZ_ASSERT
+#include "mozilla/Atomics.h" // mozilla::Atomic
+#include "mozilla/Attributes.h" // MOZ_RAII
+#include "mozilla/HashTable.h" // mozilla::HashMap
+#include "mozilla/Maybe.h" // mozilla::Maybe
+#include "mozilla/MemoryReporting.h" // mozilla::MallocSizeOf
+#include "mozilla/RefPtr.h" // RefPtr
+#include "mozilla/Span.h"
+#include "mozilla/Variant.h" // mozilla::Variant
+
+#include "ds/LifoAlloc.h"
+#include "frontend/FrontendContext.h" // AutoReportFrontendContext
+#include "frontend/NameAnalysisTypes.h" // EnvironmentCoordinate
+#include "frontend/ParserAtom.h" // ParserAtomsTable, TaggedParserAtomIndex
+#include "frontend/ScriptIndex.h" // ScriptIndex
+#include "frontend/SharedContext.h"
+#include "frontend/Stencil.h"
+#include "frontend/TaggedParserAtomIndexHasher.h" // TaggedParserAtomIndexHasher
+#include "frontend/UsedNameTracker.h"
+#include "js/GCVector.h"
+#include "js/HashTable.h"
+#include "js/RefCounted.h" // AtomicRefCounted
+#include "js/Transcoding.h"
+#include "js/UniquePtr.h" // js::UniquePtr
+#include "js/Vector.h"
+#include "js/WasmModule.h"
+#include "vm/GlobalObject.h" // GlobalObject
+#include "vm/JSContext.h"
+#include "vm/JSFunction.h" // JSFunction
+#include "vm/JSScript.h" // SourceExtent
+#include "vm/Realm.h"
+#include "vm/ScopeKind.h" // ScopeKind
+#include "vm/SharedStencil.h" // SharedImmutableScriptData
+
+class JSAtom;
+class JSString;
+
+namespace JS {
+class JS_PUBLIC_API ReadOnlyCompileOptions;
+}
+
+namespace js {
+
+class AtomSet;
+class JSONPrinter;
+class ModuleObject;
+
+namespace frontend {
+
+struct CompilationInput;
+struct CompilationStencil;
+struct CompilationGCOutput;
+class ScriptStencilIterable;
+struct InputName;
+class ScopeBindingCache;
+
+// Reference to a Scope within a CompilationStencil.
+struct ScopeStencilRef {
+ const CompilationStencil& context_;
+ const ScopeIndex scopeIndex_;
+
+ // Lookup the ScopeStencil referenced by this ScopeStencilRef.
+ inline const ScopeStencil& scope() const;
+};
+
+// Wraps a scope for a CompilationInput. The scope is either as a GC pointer to
+// an instantiated scope, or as a reference to a CompilationStencil.
+//
+// Note: A scope reference may be nullptr/InvalidIndex if there is no such
+// scope, such as the enclosingScope at the end of a scope chain. See `isNull`
+// helper.
+class InputScope {
+ using InputScopeStorage = mozilla::Variant<Scope*, ScopeStencilRef>;
+ InputScopeStorage scope_;
+
+ public:
+ // Create an InputScope given an instantiated scope.
+ explicit InputScope(Scope* ptr) : scope_(ptr) {}
+
+ // Create an InputScope given a CompilationStencil and the ScopeIndex which is
+ // an offset within the same CompilationStencil given as argument.
+ InputScope(const CompilationStencil& context, ScopeIndex scopeIndex)
+ : scope_(ScopeStencilRef{context, scopeIndex}) {}
+
+ // Returns the variant used by the InputScope. This can be useful for complex
+ // cases where the following accessors are not enough.
+ const InputScopeStorage& variant() const { return scope_; }
+ InputScopeStorage& variant() { return scope_; }
+
+ // This match function will unwrap the variant for each type, and will
+ // specialize and call the Matcher given as argument with the type and value
+ // of the stored pointer / reference.
+ //
+ // This is useful for cases where the code is literally identical despite
+ // having different specializations. This is achiveable by relying on
+ // function overloading when the usage differ between the 2 types.
+ //
+ // Example:
+ // inputScope.match([](auto& scope) {
+ // // scope is either a `Scope*` or a `ScopeStencilRef`.
+ // for (auto bi = InputBindingIter(scope); bi; bi++) {
+ // InputName name(scope, bi.name());
+ // // ...
+ // }
+ // });
+ template <typename Matcher>
+ decltype(auto) match(Matcher&& matcher) const& {
+ return scope_.match(std::forward<Matcher>(matcher));
+ }
+ template <typename Matcher>
+ decltype(auto) match(Matcher&& matcher) & {
+ return scope_.match(std::forward<Matcher>(matcher));
+ }
+
+ bool isNull() const {
+ return scope_.match(
+ [](const Scope* ptr) { return !ptr; },
+ [](const ScopeStencilRef& ref) { return !ref.scopeIndex_.isValid(); });
+ }
+
+ ScopeKind kind() const {
+ return scope_.match(
+ [](const Scope* ptr) { return ptr->kind(); },
+ [](const ScopeStencilRef& ref) { return ref.scope().kind(); });
+ };
+ bool hasEnvironment() const {
+ return scope_.match([](const Scope* ptr) { return ptr->hasEnvironment(); },
+ [](const ScopeStencilRef& ref) {
+ return ref.scope().hasEnvironment();
+ });
+ };
+ inline InputScope enclosing() const;
+ bool hasOnChain(ScopeKind kind) const {
+ return scope_.match(
+ [=](const Scope* ptr) { return ptr->hasOnChain(kind); },
+ [=](const ScopeStencilRef& ref) {
+ ScopeStencilRef it = ref;
+ while (true) {
+ const ScopeStencil& scope = it.scope();
+ if (scope.kind() == kind) {
+ return true;
+ }
+ if (!scope.hasEnclosing()) {
+ break;
+ }
+ new (&it) ScopeStencilRef{ref.context_, scope.enclosing()};
+ }
+ return false;
+ });
+ }
+ uint32_t environmentChainLength() const {
+ return scope_.match(
+ [](const Scope* ptr) { return ptr->environmentChainLength(); },
+ [](const ScopeStencilRef& ref) {
+ uint32_t length = 0;
+ ScopeStencilRef it = ref;
+ while (true) {
+ const ScopeStencil& scope = it.scope();
+ if (scope.hasEnvironment() &&
+ scope.kind() != ScopeKind::NonSyntactic) {
+ length++;
+ }
+ if (!scope.hasEnclosing()) {
+ break;
+ }
+ new (&it) ScopeStencilRef{ref.context_, scope.enclosing()};
+ }
+ return length;
+ });
+ }
+ void trace(JSTracer* trc);
+ bool isStencil() const { return scope_.is<ScopeStencilRef>(); };
+
+ // Various accessors which are valid only when the InputScope is a
+ // FunctionScope. Some of these accessors are returning values associated with
+ // the canonical function.
+ private:
+ inline FunctionFlags functionFlags() const;
+ inline ImmutableScriptFlags immutableFlags() const;
+
+ public:
+ inline MemberInitializers getMemberInitializers() const;
+ RO_IMMUTABLE_SCRIPT_FLAGS(immutableFlags())
+ bool isArrow() const { return functionFlags().isArrow(); }
+ bool allowSuperProperty() const {
+ return functionFlags().allowSuperProperty();
+ }
+ bool isClassConstructor() const {
+ return functionFlags().isClassConstructor();
+ }
+};
+
+// Reference to a Script within a CompilationStencil.
+struct ScriptStencilRef {
+ const CompilationStencil& context_;
+ const ScriptIndex scriptIndex_;
+
+ inline const ScriptStencil& scriptData() const;
+ inline const ScriptStencilExtra& scriptExtra() const;
+};
+
+// Wraps a script for a CompilationInput. The script is either as a BaseScript
+// pointer to an instantiated script, or as a reference to a CompilationStencil.
+class InputScript {
+ using InputScriptStorage = mozilla::Variant<BaseScript*, ScriptStencilRef>;
+ InputScriptStorage script_;
+
+ public:
+ // Create an InputScript given an instantiated BaseScript pointer.
+ explicit InputScript(BaseScript* ptr) : script_(ptr) {}
+
+ // Create an InputScript given a CompilationStencil and the ScriptIndex which
+ // is an offset within the same CompilationStencil given as argument.
+ InputScript(const CompilationStencil& context, ScriptIndex scriptIndex)
+ : script_(ScriptStencilRef{context, scriptIndex}) {}
+
+ const InputScriptStorage& raw() const { return script_; }
+ InputScriptStorage& raw() { return script_; }
+
+ SourceExtent extent() const {
+ return script_.match(
+ [](const BaseScript* ptr) { return ptr->extent(); },
+ [](const ScriptStencilRef& ref) { return ref.scriptExtra().extent; });
+ }
+ ImmutableScriptFlags immutableFlags() const {
+ return script_.match(
+ [](const BaseScript* ptr) { return ptr->immutableFlags(); },
+ [](const ScriptStencilRef& ref) {
+ return ref.scriptExtra().immutableFlags;
+ });
+ }
+ RO_IMMUTABLE_SCRIPT_FLAGS(immutableFlags())
+ FunctionFlags functionFlags() const {
+ return script_.match(
+ [](const BaseScript* ptr) { return ptr->function()->flags(); },
+ [](const ScriptStencilRef& ref) {
+ return ref.scriptData().functionFlags;
+ });
+ }
+ bool hasPrivateScriptData() const {
+ return script_.match(
+ [](const BaseScript* ptr) { return ptr->hasPrivateScriptData(); },
+ [](const ScriptStencilRef& ref) {
+ // See BaseScript::CreateRawLazy.
+ return ref.scriptData().hasGCThings() ||
+ ref.scriptExtra().useMemberInitializers();
+ });
+ }
+ InputScope enclosingScope() const {
+ return script_.match(
+ [](const BaseScript* ptr) {
+ return InputScope(ptr->function()->enclosingScope());
+ },
+ [](const ScriptStencilRef& ref) {
+ // The ScriptStencilRef only reference lazy Script, otherwise we
+ // should fetch the enclosing scope using the bodyScope field of the
+ // immutable data which is a reference to the vector of gc-things.
+ MOZ_RELEASE_ASSERT(!ref.scriptData().hasSharedData());
+ MOZ_ASSERT(ref.scriptData().hasLazyFunctionEnclosingScopeIndex());
+ auto scopeIndex = ref.scriptData().lazyFunctionEnclosingScopeIndex();
+ return InputScope(ref.context_, scopeIndex);
+ });
+ }
+ MemberInitializers getMemberInitializers() const {
+ return script_.match(
+ [](const BaseScript* ptr) { return ptr->getMemberInitializers(); },
+ [](const ScriptStencilRef& ref) {
+ return ref.scriptExtra().memberInitializers();
+ });
+ }
+
+ InputName displayAtom() const;
+ void trace(JSTracer* trc);
+ bool isNull() const {
+ return script_.match([](const BaseScript* ptr) { return !ptr; },
+ [](const ScriptStencilRef& ref) { return false; });
+ }
+ bool isStencil() const {
+ return script_.match([](const BaseScript* ptr) { return false; },
+ [](const ScriptStencilRef&) { return true; });
+ };
+};
+
+// Iterator for walking the scope chain, this is identical to ScopeIter but
+// accept an InputScope instead of a Scope pointer.
+//
+// It may be placed in GC containers; for example:
+//
+// for (Rooted<InputScopeIter> si(cx, InputScopeIter(scope)); si; si++) {
+// use(si);
+// SomeMayGCOperation();
+// use(si);
+// }
+//
+class MOZ_STACK_CLASS InputScopeIter {
+ InputScope scope_;
+
+ public:
+ explicit InputScopeIter(const InputScope& scope) : scope_(scope) {}
+
+ InputScope& scope() {
+ MOZ_ASSERT(!done());
+ return scope_;
+ }
+
+ const InputScope& scope() const {
+ MOZ_ASSERT(!done());
+ return scope_;
+ }
+
+ bool done() const { return scope_.isNull(); }
+ explicit operator bool() const { return !done(); }
+ void operator++(int) { scope_ = scope_.enclosing(); }
+ ScopeKind kind() const { return scope_.kind(); }
+
+ // Returns whether this scope has a syntactic environment (i.e., an
+ // Environment that isn't a non-syntactic With or NonSyntacticVariables)
+ // on the environment chain.
+ bool hasSyntacticEnvironment() const {
+ return scope_.hasEnvironment() && scope_.kind() != ScopeKind::NonSyntactic;
+ }
+
+ void trace(JSTracer* trc) { scope_.trace(trc); }
+};
+
+// Reference to a Binding Name within an existing CompilationStencil.
+// TaggedParserAtomIndex are in some cases indexes in the parserAtomData of the
+// CompilationStencil.
+struct NameStencilRef {
+ const CompilationStencil& context_;
+ const TaggedParserAtomIndex atomIndex_;
+};
+
+// Wraps a name for a CompilationInput. The name is either as a GC pointer to
+// a JSAtom, or a TaggedParserAtomIndex which might reference to a non-included.
+//
+// The constructor for this class are using an InputScope as argument. This
+// InputScope is made to fetch back the CompilationStencil associated with the
+// TaggedParserAtomIndex when using a Stencil as input.
+struct InputName {
+ using InputNameStorage = mozilla::Variant<JSAtom*, NameStencilRef>;
+ InputNameStorage variant_;
+
+ InputName(Scope*, JSAtom* ptr) : variant_(ptr) {}
+ InputName(const ScopeStencilRef& scope, TaggedParserAtomIndex index)
+ : variant_(NameStencilRef{scope.context_, index}) {}
+ InputName(BaseScript*, JSAtom* ptr) : variant_(ptr) {}
+ InputName(const ScriptStencilRef& script, TaggedParserAtomIndex index)
+ : variant_(NameStencilRef{script.context_, index}) {}
+
+ // The InputName is either from an instantiated name, or from another
+ // CompilationStencil. This method interns the current name in the parser atom
+ // table of a CompilationState, which has a corresponding CompilationInput.
+ TaggedParserAtomIndex internInto(FrontendContext* fc,
+ ParserAtomsTable& parserAtoms,
+ CompilationAtomCache& atomCache);
+
+ // Compare an InputName, which is not yet interned, with `other` is either an
+ // interned name or a well-known or static string.
+ //
+ // The `otherCached` argument should be a reference to a JSAtom*, initialized
+ // to nullptr, which is used to cache the JSAtom representation of the `other`
+ // argument if needed. If a different `other` parameter is provided, the
+ // `otherCached` argument should be reset to nullptr.
+ bool isEqualTo(FrontendContext* fc, ParserAtomsTable& parserAtoms,
+ CompilationAtomCache& atomCache, TaggedParserAtomIndex other,
+ JSAtom** otherCached) const;
+
+ bool isNull() const {
+ return variant_.match(
+ [](JSAtom* ptr) { return !ptr; },
+ [](const NameStencilRef& ref) { return !ref.atomIndex_; });
+ }
+};
+
+// ScopeContext holds information derived from the scope and environment chains
+// to try to avoid the parser needing to traverse VM structures directly.
+struct ScopeContext {
+ // Cache: Scope -> (JSAtom/TaggedParserAtomIndex -> NameLocation)
+ //
+ // This cache maps the scope to a hash table which can lookup a name of the
+ // scope to the equivalent NameLocation.
+ ScopeBindingCache* scopeCache = nullptr;
+
+ // Generation number of the `scopeCache` collected before filling the cache
+ // with enclosing scope information.
+ //
+ // The generation number, obtained from `scopeCache->getCurrentGeneration()`
+ // is incremented each time the GC invalidate the content of the cache. The
+ // `scopeCache` can only be used when the generation number collected before
+ // filling the cache is identical to the generation number seen when querying
+ // the cached content.
+ size_t scopeCacheGen = 0;
+
+ // Class field initializer info if we are nested within a class constructor.
+ // We may be an combination of arrow and eval context within the constructor.
+ mozilla::Maybe<MemberInitializers> memberInitializers = {};
+
+ enum class EnclosingLexicalBindingKind {
+ Let,
+ Const,
+ CatchParameter,
+ Synthetic,
+ PrivateMethod,
+ };
+
+ using EnclosingLexicalBindingCache =
+ mozilla::HashMap<TaggedParserAtomIndex, EnclosingLexicalBindingKind,
+ TaggedParserAtomIndexHasher>;
+
+ // Cache of enclosing lexical bindings.
+ // Used only for eval.
+ mozilla::Maybe<EnclosingLexicalBindingCache> enclosingLexicalBindingCache_;
+
+ // A map of private names to NameLocations used to allow evals to
+ // provide correct private name semantics (particularly around early
+ // errors and private brand lookup).
+ using EffectiveScopePrivateFieldCache =
+ mozilla::HashMap<TaggedParserAtomIndex, NameLocation,
+ TaggedParserAtomIndexHasher>;
+
+ // Cache of enclosing class's private fields.
+ // Used only for eval.
+ mozilla::Maybe<EffectiveScopePrivateFieldCache>
+ effectiveScopePrivateFieldCache_;
+
+#ifdef DEBUG
+ bool enclosingEnvironmentIsDebugProxy_ = false;
+#endif
+
+ // How many hops required to navigate from 'enclosingScope' to effective
+ // scope.
+ uint32_t effectiveScopeHops = 0;
+
+ uint32_t enclosingScopeEnvironmentChainLength = 0;
+
+ // Eval and arrow scripts also inherit the "this" environment -- used by
+ // `super` expressions -- from their enclosing script. We count the number of
+ // environment hops needed to get from enclosing scope to the nearest
+ // appropriate environment. This value is undefined if the script we are
+ // compiling is not an eval or arrow-function.
+ uint32_t enclosingThisEnvironmentHops = 0;
+
+ // The kind of enclosing scope.
+ ScopeKind enclosingScopeKind = ScopeKind::Global;
+
+ // The type of binding required for `this` of the top level context, as
+ // indicated by the enclosing scopes of this parse.
+ //
+ // NOTE: This is computed based on the effective scope (defined above).
+ ThisBinding thisBinding = ThisBinding::Global;
+
+ // Eval and arrow scripts inherit certain syntax allowances from their
+ // enclosing scripts.
+ bool allowNewTarget = false;
+ bool allowSuperProperty = false;
+ bool allowSuperCall = false;
+ bool allowArguments = true;
+
+ // Indicates there is a 'class' or 'with' scope on enclosing scope chain.
+ bool inClass = false;
+ bool inWith = false;
+
+ // True if the enclosing scope is for FunctionScope of arrow function.
+ bool enclosingScopeIsArrow = false;
+
+ // True if the enclosing scope has environment.
+ bool enclosingScopeHasEnvironment = false;
+
+#ifdef DEBUG
+ // True if the enclosing scope has non-syntactic scope on chain.
+ bool hasNonSyntacticScopeOnChain = false;
+
+ // True if the enclosing scope has function scope where the function needs
+ // home object.
+ bool hasFunctionNeedsHomeObjectOnChain = false;
+#endif
+
+ bool init(FrontendContext* fc, CompilationInput& input,
+ ParserAtomsTable& parserAtoms, ScopeBindingCache* scopeCache,
+ InheritThis inheritThis, JSObject* enclosingEnv);
+
+ mozilla::Maybe<EnclosingLexicalBindingKind>
+ lookupLexicalBindingInEnclosingScope(TaggedParserAtomIndex name);
+
+ NameLocation searchInEnclosingScope(FrontendContext* fc,
+ CompilationInput& input,
+ ParserAtomsTable& parserAtoms,
+ TaggedParserAtomIndex name);
+
+ bool effectiveScopePrivateFieldCacheHas(TaggedParserAtomIndex name);
+ mozilla::Maybe<NameLocation> getPrivateFieldLocation(
+ TaggedParserAtomIndex name);
+
+ private:
+ void computeThisBinding(const InputScope& scope);
+ void computeThisEnvironment(const InputScope& enclosingScope);
+ void computeInScope(const InputScope& enclosingScope);
+ void cacheEnclosingScope(const InputScope& enclosingScope);
+ NameLocation searchInEnclosingScopeWithCache(FrontendContext* fc,
+ CompilationInput& input,
+ ParserAtomsTable& parserAtoms,
+ TaggedParserAtomIndex name);
+ NameLocation searchInEnclosingScopeNoCache(FrontendContext* fc,
+ CompilationInput& input,
+ ParserAtomsTable& parserAtoms,
+ TaggedParserAtomIndex name);
+
+ InputScope determineEffectiveScope(InputScope& scope, JSObject* environment);
+
+ bool cachePrivateFieldsForEval(FrontendContext* fc, CompilationInput& input,
+ JSObject* enclosingEnvironment,
+ const InputScope& effectiveScope,
+ ParserAtomsTable& parserAtoms);
+
+ bool cacheEnclosingScopeBindingForEval(FrontendContext* fc,
+ CompilationInput& input,
+ ParserAtomsTable& parserAtoms);
+
+ bool addToEnclosingLexicalBindingCache(FrontendContext* fc,
+ ParserAtomsTable& parserAtoms,
+ CompilationAtomCache& atomCache,
+ InputName& name,
+ EnclosingLexicalBindingKind kind);
+};
+
+struct CompilationAtomCache {
+ public:
+ using AtomCacheVector = JS::GCVector<JSString*, 0, js::SystemAllocPolicy>;
+
+ private:
+ // Atoms lowered into or converted from CompilationStencil.parserAtomData.
+ //
+ // This field is here instead of in CompilationGCOutput because atoms lowered
+ // from JSAtom is part of input (enclosing scope bindings, lazy function name,
+ // etc), and having 2 vectors in both input/output is error prone.
+ AtomCacheVector atoms_;
+
+ public:
+ JSString* getExistingStringAt(ParserAtomIndex index) const;
+ JSString* getExistingStringAt(JSContext* cx,
+ TaggedParserAtomIndex taggedIndex) const;
+ JSString* getStringAt(ParserAtomIndex index) const;
+
+ JSAtom* getExistingAtomAt(ParserAtomIndex index) const;
+ JSAtom* getExistingAtomAt(JSContext* cx,
+ TaggedParserAtomIndex taggedIndex) const;
+ JSAtom* getAtomAt(ParserAtomIndex index) const;
+
+ bool hasAtomAt(ParserAtomIndex index) const;
+ bool setAtomAt(FrontendContext* fc, ParserAtomIndex index, JSString* atom);
+ bool allocate(FrontendContext* fc, size_t length);
+
+ bool empty() const { return atoms_.empty(); }
+ size_t size() const { return atoms_.length(); }
+
+ void stealBuffer(AtomCacheVector& atoms);
+ void releaseBuffer(AtomCacheVector& atoms);
+
+ void trace(JSTracer* trc);
+
+ size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const {
+ return atoms_.sizeOfExcludingThis(mallocSizeOf);
+ }
+};
+
+// Input of the compilation, including source and enclosing context.
+struct CompilationInput {
+ enum class CompilationTarget {
+ Global,
+ SelfHosting,
+ StandaloneFunction,
+ StandaloneFunctionInNonSyntacticScope,
+ Eval,
+ Module,
+ Delazification,
+ };
+ CompilationTarget target = CompilationTarget::Global;
+
+ const JS::ReadOnlyCompileOptions& options;
+
+ CompilationAtomCache atomCache;
+
+ private:
+ InputScript lazy_ = InputScript(nullptr);
+
+ public:
+ RefPtr<ScriptSource> source;
+
+ // * If the target is Global, null.
+ // * If the target is SelfHosting, null. Instantiation code for self-hosting
+ // will ignore this and use the appropriate empty global scope instead.
+ // * If the target is StandaloneFunction, an empty global scope.
+ // * If the target is StandaloneFunctionInNonSyntacticScope, the non-null
+ // enclosing scope of the function
+ // * If the target is Eval, the non-null enclosing scope of the `eval`.
+ // * If the target is Module, null that means empty global scope
+ // (See EmitterScope::checkEnvironmentChainLength)
+ // * If the target is Delazification, the non-null enclosing scope of
+ // the function
+ InputScope enclosingScope = InputScope(nullptr);
+
+ explicit CompilationInput(const JS::ReadOnlyCompileOptions& options)
+ : options(options) {}
+
+ private:
+ bool initScriptSource(FrontendContext* fc);
+
+ public:
+ bool initForGlobal(FrontendContext* fc) {
+ target = CompilationTarget::Global;
+ return initScriptSource(fc);
+ }
+
+ bool initForSelfHostingGlobal(FrontendContext* fc) {
+ target = CompilationTarget::SelfHosting;
+ return initScriptSource(fc);
+ }
+
+ bool initForStandaloneFunction(JSContext* cx, FrontendContext* fc) {
+ target = CompilationTarget::StandaloneFunction;
+ if (!initScriptSource(fc)) {
+ return false;
+ }
+ enclosingScope = InputScope(&cx->global()->emptyGlobalScope());
+ return true;
+ }
+
+ bool initForStandaloneFunctionInNonSyntacticScope(
+ FrontendContext* fc, Handle<Scope*> functionEnclosingScope);
+
+ bool initForEval(FrontendContext* fc, Handle<Scope*> evalEnclosingScope) {
+ target = CompilationTarget::Eval;
+ if (!initScriptSource(fc)) {
+ return false;
+ }
+ enclosingScope = InputScope(evalEnclosingScope);
+ return true;
+ }
+
+ bool initForModule(FrontendContext* fc) {
+ target = CompilationTarget::Module;
+ if (!initScriptSource(fc)) {
+ return false;
+ }
+ // The `enclosingScope` is the emptyGlobalScope.
+ return true;
+ }
+
+ void initFromLazy(JSContext* cx, BaseScript* lazyScript, ScriptSource* ss) {
+ MOZ_ASSERT(cx->compartment() == lazyScript->compartment());
+
+ // We can only compile functions whose parents have previously been
+ // compiled, because compilation requires full information about the
+ // function's immediately enclosing scope.
+ MOZ_ASSERT(lazyScript->isReadyForDelazification());
+ target = CompilationTarget::Delazification;
+ lazy_ = InputScript(lazyScript);
+ source = ss;
+ enclosingScope = lazy_.enclosingScope();
+ }
+
+ void initFromStencil(CompilationStencil& context, ScriptIndex scriptIndex,
+ ScriptSource* ss) {
+ target = CompilationTarget::Delazification;
+ lazy_ = InputScript(context, scriptIndex);
+ source = ss;
+ enclosingScope = lazy_.enclosingScope();
+ }
+
+ // Returns true if enclosingScope field is provided to init* function,
+ // instead of setting to empty global internally.
+ bool hasNonDefaultEnclosingScope() const {
+ return target == CompilationTarget::StandaloneFunctionInNonSyntacticScope ||
+ target == CompilationTarget::Eval ||
+ target == CompilationTarget::Delazification;
+ }
+
+ // Returns the enclosing scope provided to init* function.
+ // nullptr otherwise.
+ InputScope maybeNonDefaultEnclosingScope() const {
+ if (hasNonDefaultEnclosingScope()) {
+ return enclosingScope;
+ }
+ return InputScope(nullptr);
+ }
+
+ // The BaseScript* is needed when instantiating a lazy function.
+ // See InstantiateTopLevel and FunctionsFromExistingLazy.
+ InputScript lazyOuterScript() { return lazy_; }
+ BaseScript* lazyOuterBaseScript() { return lazy_.raw().as<BaseScript*>(); }
+
+ // The JSFunction* is needed when instantiating a lazy function.
+ // See FunctionsFromExistingLazy.
+ JSFunction* function() const {
+ return lazy_.raw().as<BaseScript*>()->function();
+ }
+
+ // When compiling an inner function, we want to know the unique identifier
+ // which identify a function. This is computed from the source extend.
+ SourceExtent extent() const { return lazy_.extent(); }
+
+ // See `BaseScript::immutableFlags_`.
+ ImmutableScriptFlags immutableFlags() const { return lazy_.immutableFlags(); }
+
+ RO_IMMUTABLE_SCRIPT_FLAGS(immutableFlags())
+
+ FunctionFlags functionFlags() const { return lazy_.functionFlags(); }
+
+ // When delazifying, return the kind of function which is defined.
+ FunctionSyntaxKind functionSyntaxKind() const;
+
+ bool hasPrivateScriptData() const {
+ // This is equivalent to: ngcthings != 0 || useMemberInitializers()
+ // See BaseScript::CreateRawLazy.
+ return lazy_.hasPrivateScriptData();
+ }
+
+ // Whether this CompilationInput is parsing the top-level of a script, or
+ // false if we are parsing an inner function.
+ bool isInitialStencil() { return lazy_.isNull(); }
+
+ // Whether this CompilationInput is parsing a specific function with already
+ // pre-parsed contextual information.
+ bool isDelazifying() { return target == CompilationTarget::Delazification; }
+
+ void trace(JSTracer* trc);
+
+ // Size of dynamic data. Note that GC data is counted by GC and not here. We
+ // also ignore ScriptSource which is a shared RefPtr.
+ size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const {
+ return atomCache.sizeOfExcludingThis(mallocSizeOf);
+ }
+ size_t sizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const {
+ return mallocSizeOf(this) + sizeOfExcludingThis(mallocSizeOf);
+ }
+
+#if defined(DEBUG) || defined(JS_JITSPEW)
+ void dump() const;
+ void dump(js::JSONPrinter& json) const;
+ void dumpFields(js::JSONPrinter& json) const;
+#endif
+};
+
+// When compiling a function which was previously Syntaxly Parsed, we generated
+// some information which made it possible to skip over some parsing phases,
+// such as computing closed over bindings as well as parsing inner functions.
+// This class contains all information which is generated by the SyntaxParse and
+// reused in the FullParse.
+class CompilationSyntaxParseCache {
+ // When delazifying, we should prepare an array which contains all
+ // stencil-like gc-things such that it can be used by the parser.
+ //
+ // When compiling from a Stencil, this will alias the existing Stencil.
+ mozilla::Span<TaggedScriptThingIndex> cachedGCThings_;
+
+ // When delazifying, we should perpare an array which contains all
+ // stencil-like information about scripts, such that it can be used by the
+ // parser.
+ //
+ // When compiling from a Stencil, these will alias the existing Stencil.
+ mozilla::Span<ScriptStencil> cachedScriptData_;
+ mozilla::Span<ScriptStencilExtra> cachedScriptExtra_;
+
+ // When delazifying, we copy the atom, either from JSAtom, or from another
+ // Stencil into TaggedParserAtomIndex which are valid in this current
+ // CompilationState.
+ mozilla::Span<TaggedParserAtomIndex> closedOverBindings_;
+
+ // Atom of the function being compiled. This atom index is valid in the
+ // current CompilationState.
+ TaggedParserAtomIndex displayAtom_;
+
+ // Stencil-like data about the function which is being compiled.
+ ScriptStencilExtra funExtra_;
+
+#ifdef DEBUG
+ // Whether any of these data should be considered or not.
+ bool isInitialized = false;
+#endif
+
+ public:
+ // When doing a full-parse of an incomplete BaseScript*, we have to iterate
+ // over functions and closed-over bindings, to avoid costly recursive decent
+ // in inner functions. This function will clone the BaseScript* information to
+ // make it available as a stencil-like data to the full-parser.
+ mozilla::Span<TaggedParserAtomIndex> closedOverBindings() const {
+ MOZ_ASSERT(isInitialized);
+ return closedOverBindings_;
+ }
+ const ScriptStencil& scriptData(size_t functionIndex) const {
+ return cachedScriptData_[scriptIndex(functionIndex)];
+ }
+ const ScriptStencilExtra& scriptExtra(size_t functionIndex) const {
+ return cachedScriptExtra_[scriptIndex(functionIndex)];
+ }
+
+ // Return the name of the function being delazified, if any.
+ TaggedParserAtomIndex displayAtom() const {
+ MOZ_ASSERT(isInitialized);
+ return displayAtom_;
+ }
+
+ // Return the extra information about the function being delazified, if any.
+ const ScriptStencilExtra& funExtra() const {
+ MOZ_ASSERT(isInitialized);
+ return funExtra_;
+ }
+
+ // Initialize the SynaxParse cache given a LifoAlloc. The JSContext is only
+ // used for reporting allocation errors.
+ [[nodiscard]] bool init(FrontendContext* fc, LifoAlloc& alloc,
+ ParserAtomsTable& parseAtoms,
+ CompilationAtomCache& atomCache,
+ const InputScript& lazy);
+
+ private:
+ // Return the script index of a given inner function.
+ //
+ // WARNING: The ScriptIndex returned by this function corresponds to the index
+ // in the cachedScriptExtra_ and cachedScriptData_ spans. With the
+ // cachedGCThings_ span, these might be reference to an actual Stencil from
+ // another compilation. Thus, the ScriptIndex returned by this function should
+ // not be confused with any ScriptIndex from the CompilationState.
+ ScriptIndex scriptIndex(size_t functionIndex) const {
+ MOZ_ASSERT(isInitialized);
+ auto taggedScriptIndex = cachedGCThings_[functionIndex];
+ MOZ_ASSERT(taggedScriptIndex.isFunction());
+ return taggedScriptIndex.toFunction();
+ }
+
+ [[nodiscard]] bool copyFunctionInfo(FrontendContext* fc,
+ ParserAtomsTable& parseAtoms,
+ CompilationAtomCache& atomCache,
+ const InputScript& lazy);
+ [[nodiscard]] bool copyScriptInfo(FrontendContext* fc, LifoAlloc& alloc,
+ ParserAtomsTable& parseAtoms,
+ CompilationAtomCache& atomCache,
+ BaseScript* lazy);
+ [[nodiscard]] bool copyScriptInfo(FrontendContext* fc, LifoAlloc& alloc,
+ ParserAtomsTable& parseAtoms,
+ CompilationAtomCache& atomCache,
+ const ScriptStencilRef& lazy);
+ [[nodiscard]] bool copyClosedOverBindings(FrontendContext* fc,
+ LifoAlloc& alloc,
+ ParserAtomsTable& parseAtoms,
+ CompilationAtomCache& atomCache,
+ BaseScript* lazy);
+ [[nodiscard]] bool copyClosedOverBindings(FrontendContext* fc,
+ LifoAlloc& alloc,
+ ParserAtomsTable& parseAtoms,
+ CompilationAtomCache& atomCache,
+ const ScriptStencilRef& lazy);
+};
+
+// AsmJS scripts are very rare on-average, so we use a HashMap to associate
+// data with a ScriptStencil. The ScriptStencil has a flag to indicate if we
+// need to even do this lookup.
+using StencilAsmJSMap =
+ HashMap<ScriptIndex, RefPtr<const JS::WasmModule>,
+ mozilla::DefaultHasher<ScriptIndex>, js::SystemAllocPolicy>;
+
+struct StencilAsmJSContainer
+ : public js::AtomicRefCounted<StencilAsmJSContainer> {
+ StencilAsmJSMap moduleMap;
+
+ StencilAsmJSContainer() = default;
+
+ size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const {
+ return moduleMap.shallowSizeOfExcludingThis(mallocSizeOf);
+ }
+ size_t sizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const {
+ return mallocSizeOf(this) + sizeOfExcludingThis(mallocSizeOf);
+ }
+};
+
+// Store shared data for non-lazy script.
+struct SharedDataContainer {
+ // NOTE: While stored, we must hold a ref-count and care must be taken when
+ // updating or clearing the pointer.
+ using SingleSharedDataPtr = SharedImmutableScriptData*;
+
+ using SharedDataVector =
+ Vector<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,
+ BorrowTag = 3,
+
+ TagMask = 3,
+ };
+
+ uintptr_t data_ = 0;
+
+ public:
+ // Defaults to SingleSharedData.
+ SharedDataContainer() = default;
+
+ SharedDataContainer(const SharedDataContainer&) = delete;
+ SharedDataContainer(SharedDataContainer&& other) noexcept {
+ std::swap(data_, other.data_);
+ MOZ_ASSERT(other.isEmpty());
+ }
+
+ SharedDataContainer& operator=(const SharedDataContainer&) = delete;
+ SharedDataContainer& operator=(SharedDataContainer&& other) noexcept {
+ std::swap(data_, other.data_);
+ MOZ_ASSERT(other.isEmpty());
+ return *this;
+ }
+
+ ~SharedDataContainer();
+
+ [[nodiscard]] bool initVector(FrontendContext* fc);
+ [[nodiscard]] bool initMap(FrontendContext* fc);
+
+ private:
+ [[nodiscard]] bool convertFromSingleToMap(FrontendContext* fc);
+
+ public:
+ bool isEmpty() const { return (data_) == SingleTag; }
+ bool isSingle() const { return (data_ & TagMask) == SingleTag; }
+ bool isVector() const { return (data_ & TagMask) == VectorTag; }
+ bool isMap() const { return (data_ & TagMask) == MapTag; }
+ bool isBorrow() const { return (data_ & TagMask) == BorrowTag; }
+
+ void setSingle(already_AddRefed<SharedImmutableScriptData>&& data) {
+ MOZ_ASSERT(isEmpty());
+ data_ = reinterpret_cast<uintptr_t>(data.take());
+ MOZ_ASSERT(isSingle());
+ MOZ_ASSERT(!isEmpty());
+ }
+
+ void setBorrow(SharedDataContainer* sharedData) {
+ MOZ_ASSERT(isEmpty());
+ data_ = reinterpret_cast<uintptr_t>(sharedData) | BorrowTag;
+ MOZ_ASSERT(isBorrow());
+ }
+
+ SingleSharedDataPtr asSingle() const {
+ MOZ_ASSERT(isSingle());
+ MOZ_ASSERT(!isEmpty());
+ static_assert(SingleTag == 0);
+ return reinterpret_cast<SingleSharedDataPtr>(data_);
+ }
+ SharedDataVectorPtr asVector() const {
+ MOZ_ASSERT(isVector());
+ return reinterpret_cast<SharedDataVectorPtr>(data_ & ~TagMask);
+ }
+ SharedDataMapPtr asMap() const {
+ MOZ_ASSERT(isMap());
+ return reinterpret_cast<SharedDataMapPtr>(data_ & ~TagMask);
+ }
+ SharedDataContainer* asBorrow() const {
+ MOZ_ASSERT(isBorrow());
+ return reinterpret_cast<SharedDataContainer*>(data_ & ~TagMask);
+ }
+
+ [[nodiscard]] bool prepareStorageFor(FrontendContext* fc,
+ size_t nonLazyScriptCount,
+ size_t allScriptCount);
+ [[nodiscard]] bool cloneFrom(FrontendContext* fc,
+ const SharedDataContainer& other);
+
+ // Returns index-th script's shared data, or nullptr if it doesn't have.
+ js::SharedImmutableScriptData* get(ScriptIndex index) const;
+
+ // Add data for index-th script and share it with VM.
+ [[nodiscard]] bool addAndShare(FrontendContext* fc, ScriptIndex index,
+ js::SharedImmutableScriptData* data);
+
+ // Add data for index-th script without sharing it with VM.
+ // The data should already be shared with VM.
+ //
+ // The data is supposed to be added from delazification.
+ [[nodiscard]] bool addExtraWithoutShare(FrontendContext* fc,
+ ScriptIndex index,
+ js::SharedImmutableScriptData* data);
+
+ // Dynamic memory associated with this container. Does not include the
+ // SharedImmutableScriptData since we are not the unique owner of it.
+ size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const {
+ if (isVector()) {
+ return asVector()->sizeOfIncludingThis(mallocSizeOf);
+ }
+ if (isMap()) {
+ return asMap()->shallowSizeOfIncludingThis(mallocSizeOf);
+ }
+ MOZ_ASSERT(isSingle() || isBorrow());
+ return 0;
+ }
+
+#if defined(DEBUG) || defined(JS_JITSPEW)
+ void dump() const;
+ void dump(js::JSONPrinter& json) const;
+ void dumpFields(js::JSONPrinter& json) const;
+#endif
+};
+
+struct ExtensibleCompilationStencil;
+
+// The top level struct of stencil specialized for non-extensible case.
+// Used as the compilation output, and also XDR decode output.
+//
+// In XDR decode output case, the span and not-owning pointer fields point
+// the internal LifoAlloc and the external XDR buffer.
+//
+// In BorrowingCompilationStencil usage, span and not-owning pointer fields
+// point the ExtensibleCompilationStencil and its LifoAlloc.
+//
+// The dependent XDR buffer or ExtensibleCompilationStencil must be kept
+// alive manually.
+//
+// See SMDOC in Stencil.h for more info.
+struct CompilationStencil {
+ friend struct ExtensibleCompilationStencil;
+
+ static constexpr ScriptIndex TopLevelIndex = ScriptIndex(0);
+
+ static constexpr size_t LifoAllocChunkSize = 512;
+
+ // The lifetime of this CompilationStencil may be managed by stack allocation,
+ // UniquePtr<T>, or RefPtr<T>. If a RefPtr is used, this ref-count will track
+ // the lifetime, otherwise it is ignored.
+ //
+ // NOTE: Internal code and public APIs use a mix of these different allocation
+ // modes.
+ //
+ // See: JS::StencilAddRef/Release
+ mutable mozilla::Atomic<uintptr_t> refCount{0};
+
+ private:
+ // On-heap ExtensibleCompilationStencil that this CompilationStencil owns,
+ // and this CompilationStencil borrows each data from.
+ UniquePtr<ExtensibleCompilationStencil> ownedBorrowStencil;
+
+ public:
+ enum class StorageType {
+ // Pointers and spans point LifoAlloc or owned buffer.
+ Owned,
+
+ // Pointers and spans point external data, such as XDR buffer, or not-owned
+ // ExtensibleCompilationStencil (see BorrowingCompilationStencil).
+ Borrowed,
+
+ // Pointers and spans point data owned by ownedBorrowStencil.
+ OwnedExtensible,
+ };
+ StorageType storageType = StorageType::Owned;
+
+ // Value of CanLazilyParse(CompilationInput) on compilation.
+ // Used during instantiation.
+ bool canLazilyParse = false;
+
+ // If this stencil is a delazification, this identifies location of the
+ // function in the source text.
+ using FunctionKey = SourceExtent::FunctionKey;
+ FunctionKey functionKey = SourceExtent::NullFunctionKey;
+
+ // This holds allocations that do not require destructors to be run but are
+ // live until the stencil is released.
+ LifoAlloc alloc;
+
+ // The source text holder for the script. This may be an empty placeholder if
+ // the code will fully parsed and options indicate the source will never be
+ // needed again.
+ RefPtr<ScriptSource> source;
+
+ // Stencil for all function and non-function scripts. The TopLevelIndex is
+ // reserved for the top-level script. This top-level may or may not be a
+ // function.
+ mozilla::Span<ScriptStencil> scriptData;
+
+ // Immutable data computed during initial compilation and never updated during
+ // delazification.
+ mozilla::Span<ScriptStencilExtra> scriptExtra;
+
+ 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;
+
+ // Hold onto the RegExpStencil, BigIntStencil, and ObjLiteralStencil that are
+ // allocated during parse to ensure correct destruction.
+ mozilla::Span<RegExpStencil> regExpData;
+ mozilla::Span<BigIntStencil> bigIntData;
+ mozilla::Span<ObjLiteralStencil> objLiteralData;
+
+ // List of parser atoms for this compilation. This may contain nullptr entries
+ // when round-tripping with XDR if the atom was generated in original parse
+ // but not used by stencil.
+ ParserAtomSpan parserAtomData;
+
+ // Variable sized container for bytecode and other immutable data. A valid
+ // stencil always contains at least an entry for `TopLevelIndex` script.
+ SharedDataContainer sharedData;
+
+ // Module metadata if this is a module compile.
+ RefPtr<StencilModuleMetadata> moduleMetadata;
+
+ // AsmJS modules generated by parsing. These scripts are never lazy and
+ // therefore only generated during initial parse.
+ RefPtr<StencilAsmJSContainer> asmJS;
+
+ // End of fields.
+
+ // Construct a CompilationStencil
+ explicit CompilationStencil(ScriptSource* source)
+ : alloc(LifoAllocChunkSize), source(source) {}
+
+ // Take the ownership of on-heap ExtensibleCompilationStencil and
+ // borrow from it.
+ explicit CompilationStencil(
+ UniquePtr<ExtensibleCompilationStencil>&& extensibleStencil);
+
+ protected:
+ void borrowFromExtensibleCompilationStencil(
+ ExtensibleCompilationStencil& extensibleStencil);
+
+#ifdef DEBUG
+ void assertBorrowingFromExtensibleCompilationStencil(
+ const ExtensibleCompilationStencil& extensibleStencil) const;
+#endif
+
+ public:
+ bool isInitialStencil() const {
+ return functionKey == SourceExtent::NullFunctionKey;
+ }
+
+ [[nodiscard]] static bool instantiateStencilAfterPreparation(
+ JSContext* cx, CompilationInput& input, const CompilationStencil& stencil,
+ CompilationGCOutput& gcOutput);
+
+ [[nodiscard]] static bool prepareForInstantiate(
+ FrontendContext* fc, CompilationAtomCache& atomCache,
+ const CompilationStencil& stencil, CompilationGCOutput& gcOutput);
+
+ [[nodiscard]] static bool instantiateStencils(
+ JSContext* cx, CompilationInput& input, const CompilationStencil& stencil,
+ CompilationGCOutput& gcOutput);
+
+ // Decode the special self-hosted stencil
+ [[nodiscard]] bool instantiateSelfHostedAtoms(
+ JSContext* cx, AtomSet& atomSet, CompilationAtomCache& atomCache) const;
+ [[nodiscard]] JSScript* instantiateSelfHostedTopLevelForRealm(
+ JSContext* cx, CompilationInput& input);
+ [[nodiscard]] JSFunction* instantiateSelfHostedLazyFunction(
+ JSContext* cx, CompilationAtomCache& atomCache, ScriptIndex index,
+ Handle<JSAtom*> name);
+ [[nodiscard]] bool delazifySelfHostedFunction(JSContext* cx,
+ CompilationAtomCache& atomCache,
+ ScriptIndexRange range,
+ HandleFunction fun);
+
+ [[nodiscard]] bool serializeStencils(JSContext* cx, CompilationInput& input,
+ JS::TranscodeBuffer& buf,
+ bool* succeededOut = nullptr) const;
+ [[nodiscard]] bool deserializeStencils(
+ FrontendContext* fc, const JS::ReadOnlyCompileOptions& options,
+ const JS::TranscodeRange& range, bool* succeededOut = nullptr);
+
+ // To avoid any misuses, make sure this is neither copyable or assignable.
+ CompilationStencil(const CompilationStencil&) = delete;
+ CompilationStencil(CompilationStencil&&) = delete;
+ CompilationStencil& operator=(const CompilationStencil&) = delete;
+ CompilationStencil& operator=(CompilationStencil&&) = delete;
+#ifdef DEBUG
+ ~CompilationStencil() {
+ // We can mix UniquePtr<..> and RefPtr<..>. This asserts that a UniquePtr
+ // does not delete a reference-counted stencil.
+ MOZ_ASSERT(!refCount);
+ }
+#endif
+
+ static inline ScriptStencilIterable functionScriptStencils(
+ const CompilationStencil& stencil, CompilationGCOutput& gcOutput);
+
+ void setFunctionKey(BaseScript* lazy) {
+ functionKey = lazy->extent().toFunctionKey();
+ }
+
+ inline size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const;
+ size_t sizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const {
+ return mallocSizeOf(this) + sizeOfExcludingThis(mallocSizeOf);
+ }
+
+ const ParserAtomSpan& parserAtomsSpan() const { return parserAtomData; }
+
+ bool isModule() const;
+
+ bool hasMultipleReference() const { return refCount > 1; }
+
+ bool hasOwnedBorrow() const {
+ return storageType == StorageType::OwnedExtensible;
+ }
+
+ ExtensibleCompilationStencil* takeOwnedBorrow() {
+ MOZ_ASSERT(!hasMultipleReference());
+ MOZ_ASSERT(hasOwnedBorrow());
+ return ownedBorrowStencil.release();
+ }
+
+#ifdef DEBUG
+ void assertNoExternalDependency() const;
+#endif
+
+#if defined(DEBUG) || defined(JS_JITSPEW)
+ void dump() const;
+ void dump(js::JSONPrinter& json) const;
+ void dumpFields(js::JSONPrinter& json) const;
+
+ void dumpAtom(TaggedParserAtomIndex index) const;
+#endif
+};
+
+// The top level struct of stencil specialized for extensible case.
+// Used as the temporary storage during compilation, an the compilation output.
+//
+// All not-owning pointer fields point the internal LifoAlloc.
+//
+// See CompilationStencil for each field's description.
+//
+// Also see SMDOC in Stencil.h for more info.
+struct ExtensibleCompilationStencil {
+ bool canLazilyParse = false;
+
+ using FunctionKey = SourceExtent::FunctionKey;
+
+ FunctionKey functionKey = SourceExtent::NullFunctionKey;
+
+ // Data pointed by other fields are allocated in this LifoAlloc,
+ // and moved to `CompilationStencil.alloc`.
+ LifoAlloc alloc;
+
+ RefPtr<ScriptSource> source;
+
+ // NOTE: We reserve a modest amount of inline storage in order to reduce
+ // allocations in the most common delazification cases. These common
+ // cases have one script and scope, as well as a handful of gcthings.
+ // For complex pages this covers about 75% of delazifications.
+
+ Vector<ScriptStencil, 1, js::SystemAllocPolicy> scriptData;
+ Vector<ScriptStencilExtra, 0, js::SystemAllocPolicy> scriptExtra;
+
+ Vector<TaggedScriptThingIndex, 8, js::SystemAllocPolicy> gcThingData;
+
+ Vector<ScopeStencil, 1, js::SystemAllocPolicy> scopeData;
+ Vector<BaseParserScopeData*, 1, js::SystemAllocPolicy> scopeNames;
+
+ Vector<RegExpStencil, 0, js::SystemAllocPolicy> regExpData;
+ Vector<BigIntStencil, 0, js::SystemAllocPolicy> bigIntData;
+ Vector<ObjLiteralStencil, 0, js::SystemAllocPolicy> objLiteralData;
+
+ // Table of parser atoms for this compilation.
+ ParserAtomsTable parserAtoms;
+
+ SharedDataContainer sharedData;
+
+ RefPtr<StencilModuleMetadata> moduleMetadata;
+
+ RefPtr<StencilAsmJSContainer> asmJS;
+
+ explicit ExtensibleCompilationStencil(ScriptSource* source);
+
+ explicit ExtensibleCompilationStencil(CompilationInput& input);
+ ExtensibleCompilationStencil(const JS::ReadOnlyCompileOptions& options,
+ RefPtr<ScriptSource> source);
+
+ ExtensibleCompilationStencil(ExtensibleCompilationStencil&& other) noexcept
+ : canLazilyParse(other.canLazilyParse),
+ functionKey(other.functionKey),
+ alloc(CompilationStencil::LifoAllocChunkSize),
+ source(std::move(other.source)),
+ scriptData(std::move(other.scriptData)),
+ scriptExtra(std::move(other.scriptExtra)),
+ gcThingData(std::move(other.gcThingData)),
+ scopeData(std::move(other.scopeData)),
+ scopeNames(std::move(other.scopeNames)),
+ regExpData(std::move(other.regExpData)),
+ bigIntData(std::move(other.bigIntData)),
+ objLiteralData(std::move(other.objLiteralData)),
+ parserAtoms(std::move(other.parserAtoms)),
+ sharedData(std::move(other.sharedData)),
+ moduleMetadata(std::move(other.moduleMetadata)),
+ asmJS(std::move(other.asmJS)) {
+ alloc.steal(&other.alloc);
+ parserAtoms.fixupAlloc(alloc);
+ }
+
+ ExtensibleCompilationStencil& operator=(
+ ExtensibleCompilationStencil&& other) noexcept {
+ MOZ_ASSERT(alloc.isEmpty());
+
+ canLazilyParse = other.canLazilyParse;
+ functionKey = other.functionKey;
+ source = std::move(other.source);
+ scriptData = std::move(other.scriptData);
+ scriptExtra = std::move(other.scriptExtra);
+ gcThingData = std::move(other.gcThingData);
+ scopeData = std::move(other.scopeData);
+ scopeNames = std::move(other.scopeNames);
+ regExpData = std::move(other.regExpData);
+ bigIntData = std::move(other.bigIntData);
+ objLiteralData = std::move(other.objLiteralData);
+ parserAtoms = std::move(other.parserAtoms);
+ sharedData = std::move(other.sharedData);
+ moduleMetadata = std::move(other.moduleMetadata);
+ asmJS = std::move(other.asmJS);
+
+ alloc.steal(&other.alloc);
+ parserAtoms.fixupAlloc(alloc);
+
+ return *this;
+ }
+
+ void setFunctionKey(const SourceExtent& extent) {
+ functionKey = extent.toFunctionKey();
+ }
+
+ bool isInitialStencil() const {
+ return functionKey == SourceExtent::NullFunctionKey;
+ }
+
+ // Steal CompilationStencil content.
+ [[nodiscard]] bool steal(FrontendContext* fc,
+ RefPtr<CompilationStencil>&& other);
+
+ // Clone ExtensibleCompilationStencil content.
+ [[nodiscard]] bool cloneFrom(FrontendContext* fc,
+ const CompilationStencil& other);
+ [[nodiscard]] bool cloneFrom(FrontendContext* fc,
+ const ExtensibleCompilationStencil& other);
+
+ private:
+ template <typename Stencil>
+ [[nodiscard]] bool cloneFromImpl(FrontendContext* fc, const Stencil& other);
+
+ public:
+ const ParserAtomVector& parserAtomsSpan() const {
+ return parserAtoms.entries();
+ }
+
+ bool isModule() const;
+
+ inline size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const;
+ size_t sizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const {
+ return mallocSizeOf(this) + sizeOfExcludingThis(mallocSizeOf);
+ }
+
+#ifdef DEBUG
+ void assertNoExternalDependency() const;
+#endif
+
+#if defined(DEBUG) || defined(JS_JITSPEW)
+ void dump();
+ void dump(js::JSONPrinter& json);
+ void dumpFields(js::JSONPrinter& json);
+
+ void dumpAtom(TaggedParserAtomIndex index);
+#endif
+};
+
+// The internal state of the compilation.
+struct MOZ_RAII CompilationState : public ExtensibleCompilationStencil {
+ Directives directives;
+
+ ScopeContext scopeContext;
+
+ UsedNameTracker usedNames;
+
+ // LifoAlloc scope used by Parser for allocating AST etc.
+ //
+ // NOTE: This is not used for ExtensibleCompilationStencil.alloc.
+ LifoAllocScope& parserAllocScope;
+
+ CompilationInput& input;
+ CompilationSyntaxParseCache previousParseCache;
+
+ // The number of functions that *will* have bytecode.
+ // This doesn't count top-level non-function script.
+ //
+ // This should be counted while parsing, and should be passed to
+ // SharedDataContainer.prepareStorageFor *before* start emitting bytecode.
+ size_t nonLazyFunctionCount = 0;
+
+ // End of fields.
+
+ CompilationState(FrontendContext* fc, LifoAllocScope& parserAllocScope,
+ CompilationInput& input);
+
+ bool init(FrontendContext* fc, ScopeBindingCache* scopeCache,
+ InheritThis inheritThis = InheritThis::No,
+ JSObject* enclosingEnv = nullptr) {
+ if (!scopeContext.init(fc, input, parserAtoms, scopeCache, inheritThis,
+ enclosingEnv)) {
+ return false;
+ }
+
+ // gcThings is later used by the full parser initialization.
+ if (input.isDelazifying()) {
+ InputScript lazy = input.lazyOuterScript();
+ auto& atomCache = input.atomCache;
+ if (!previousParseCache.init(fc, alloc, parserAtoms, atomCache, lazy)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ // Track the state of key allocations and roll them back as parts of parsing
+ // get retried. This ensures iteration during stencil instantiation does not
+ // encounter discarded frontend state.
+ struct CompilationStatePosition {
+ // Temporarily share this token struct with CompilationState.
+ size_t scriptDataLength = 0;
+
+ size_t asmJSCount = 0;
+ };
+
+ bool prepareSharedDataStorage(FrontendContext* fc);
+
+ CompilationStatePosition getPosition();
+ void rewind(const CompilationStatePosition& pos);
+
+ // When parsing arrow function, parameter is parsed twice, and if there are
+ // functions inside parameter expression, stencils will be created for them.
+ //
+ // Those functions exist only for lazy parsing.
+ // Mark them "ghost", so that they don't affect other parts.
+ //
+ // See GHOST_FUNCTION in FunctionFlags.h for more details.
+ void markGhost(const CompilationStatePosition& pos);
+
+ // Allocate space for `length` gcthings, and return the address of the
+ // first element to `cursor` to initialize on the caller.
+ bool allocateGCThingsUninitialized(FrontendContext* fc,
+ ScriptIndex scriptIndex, size_t length,
+ TaggedScriptThingIndex** cursor);
+
+ bool appendScriptStencilAndData(FrontendContext* fc);
+
+ bool appendGCThings(FrontendContext* fc, ScriptIndex scriptIndex,
+ mozilla::Span<const TaggedScriptThingIndex> things);
+};
+
+// A temporary CompilationStencil instance that borrows
+// ExtensibleCompilationStencil data.
+// Ensure that this instance does not outlive the ExtensibleCompilationStencil.
+class MOZ_STACK_CLASS BorrowingCompilationStencil : public CompilationStencil {
+ public:
+ explicit BorrowingCompilationStencil(
+ ExtensibleCompilationStencil& extensibleStencil);
+};
+
+// Size of dynamic data. Ignores Spans (unless their contents are in the
+// LifoAlloc) and RefPtrs since we are not the unique owner.
+inline size_t CompilationStencil::sizeOfExcludingThis(
+ mozilla::MallocSizeOf mallocSizeOf) const {
+ if (ownedBorrowStencil) {
+ return ownedBorrowStencil->sizeOfIncludingThis(mallocSizeOf);
+ }
+
+ size_t moduleMetadataSize =
+ moduleMetadata ? moduleMetadata->sizeOfIncludingThis(mallocSizeOf) : 0;
+ size_t asmJSSize = asmJS ? asmJS->sizeOfIncludingThis(mallocSizeOf) : 0;
+
+ return alloc.sizeOfExcludingThis(mallocSizeOf) +
+ sharedData.sizeOfExcludingThis(mallocSizeOf) + moduleMetadataSize +
+ asmJSSize;
+}
+
+inline size_t ExtensibleCompilationStencil::sizeOfExcludingThis(
+ mozilla::MallocSizeOf mallocSizeOf) const {
+ size_t moduleMetadataSize =
+ moduleMetadata ? moduleMetadata->sizeOfIncludingThis(mallocSizeOf) : 0;
+ size_t asmJSSize = asmJS ? asmJS->sizeOfIncludingThis(mallocSizeOf) : 0;
+
+ return alloc.sizeOfExcludingThis(mallocSizeOf) +
+ scriptData.sizeOfExcludingThis(mallocSizeOf) +
+ scriptExtra.sizeOfExcludingThis(mallocSizeOf) +
+ gcThingData.sizeOfExcludingThis(mallocSizeOf) +
+ scopeData.sizeOfExcludingThis(mallocSizeOf) +
+ scopeNames.sizeOfExcludingThis(mallocSizeOf) +
+ regExpData.sizeOfExcludingThis(mallocSizeOf) +
+ bigIntData.sizeOfExcludingThis(mallocSizeOf) +
+ objLiteralData.sizeOfExcludingThis(mallocSizeOf) +
+ parserAtoms.sizeOfExcludingThis(mallocSizeOf) +
+ sharedData.sizeOfExcludingThis(mallocSizeOf) + moduleMetadataSize +
+ asmJSSize;
+}
+
+// The output of GC allocation from stencil.
+struct CompilationGCOutput {
+ // The resulting outermost script for the compilation powered
+ // by this CompilationStencil.
+ JSScript* script = nullptr;
+
+ // The resulting module object if there is one.
+ ModuleObject* module = nullptr;
+
+ // A Rooted vector to handle tracing of JSFunction* and Atoms within.
+ //
+ // If the top level script isn't a function, the item at TopLevelIndex is
+ // nullptr.
+ JS::GCVector<JSFunction*, 1, js::SystemAllocPolicy> functions;
+
+ // References to scopes are controlled via AbstractScopePtr, which holds onto
+ // an index (and CompilationStencil reference).
+ JS::GCVector<js::Scope*, 1, js::SystemAllocPolicy> scopes;
+
+ // The result ScriptSourceObject. This is unused in delazifying parses.
+ ScriptSourceObject* sourceObject = nullptr;
+
+ private:
+ // If we are only instantiating part of a stencil, we can reduce allocations
+ // by setting a base index and reserving only the vector capacity we need.
+ // This applies to both the `functions` and `scopes` arrays. These fields are
+ // initialized by `ensureReservedWithBaseIndex` which also reserves the vector
+ // sizes appropriately.
+ //
+ // Note: These are only used for self-hosted delazification currently.
+ ScriptIndex functionsBaseIndex{};
+ ScopeIndex scopesBaseIndex{};
+
+ // End of fields.
+
+ public:
+ CompilationGCOutput() = default;
+
+ // Helper to access the `functions` vector. The NoBaseIndex version is used if
+ // the caller never uses a base index.
+ JSFunction*& getFunction(ScriptIndex index) {
+ return functions[index - functionsBaseIndex];
+ }
+ JSFunction*& getFunctionNoBaseIndex(ScriptIndex index) {
+ MOZ_ASSERT(!functionsBaseIndex);
+ return functions[index];
+ }
+
+ // Helper accessors for the `scopes` vector.
+ js::Scope*& getScope(ScopeIndex index) {
+ return scopes[index - scopesBaseIndex];
+ }
+ js::Scope*& getScopeNoBaseIndex(ScopeIndex index) {
+ MOZ_ASSERT(!scopesBaseIndex);
+ return scopes[index];
+ }
+ js::Scope* getScopeNoBaseIndex(ScopeIndex index) const {
+ MOZ_ASSERT(!scopesBaseIndex);
+ return scopes[index];
+ }
+
+ // Reserve output vector capacity. This may be called before instantiate to do
+ // allocations ahead of time (off thread). The stencil instantiation code will
+ // also run this to ensure the vectors are ready.
+ [[nodiscard]] bool ensureReserved(FrontendContext* fc,
+ size_t scriptDataLength,
+ size_t scopeDataLength) {
+ if (!functions.reserve(scriptDataLength)) {
+ ReportOutOfMemory(fc);
+ return false;
+ }
+ if (!scopes.reserve(scopeDataLength)) {
+ ReportOutOfMemory(fc);
+ return false;
+ }
+ return true;
+ }
+
+ // A variant of `ensureReserved` that sets a base index for the function and
+ // scope arrays. This is used when instantiating only a subset of the stencil.
+ // Currently this only applies to self-hosted delazification. The ranges
+ // include the start index and exclude the limit index.
+ [[nodiscard]] bool ensureReservedWithBaseIndex(FrontendContext* fc,
+ ScriptIndex scriptStart,
+ ScriptIndex scriptLimit,
+ ScopeIndex scopeStart,
+ ScopeIndex scopeLimit) {
+ this->functionsBaseIndex = scriptStart;
+ this->scopesBaseIndex = scopeStart;
+
+ return ensureReserved(fc, scriptLimit - scriptStart,
+ scopeLimit - scopeStart);
+ }
+
+ // Size of dynamic data. Note that GC data is counted by GC and not here.
+ size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const {
+ return functions.sizeOfExcludingThis(mallocSizeOf) +
+ scopes.sizeOfExcludingThis(mallocSizeOf);
+ }
+
+ void trace(JSTracer* trc);
+};
+
+// Iterator over functions that make up a CompilationStencil. This abstracts
+// over the parallel arrays in stencil and gc-output that use the same index
+// system.
+class ScriptStencilIterable {
+ public:
+ class ScriptAndFunction {
+ public:
+ const ScriptStencil& script;
+ const ScriptStencilExtra* scriptExtra;
+ JSFunction* function;
+ ScriptIndex index;
+
+ ScriptAndFunction() = delete;
+ ScriptAndFunction(const ScriptStencil& script,
+ const ScriptStencilExtra* scriptExtra,
+ JSFunction* function, ScriptIndex index)
+ : script(script),
+ scriptExtra(scriptExtra),
+ function(function),
+ index(index) {}
+ };
+
+ class Iterator {
+ size_t index_ = 0;
+ const CompilationStencil& stencil_;
+ CompilationGCOutput& gcOutput_;
+
+ Iterator(const CompilationStencil& stencil, CompilationGCOutput& gcOutput,
+ size_t index)
+ : index_(index), stencil_(stencil), gcOutput_(gcOutput) {
+ MOZ_ASSERT(index == stencil.scriptData.size());
+ }
+
+ public:
+ explicit Iterator(const CompilationStencil& stencil,
+ CompilationGCOutput& gcOutput)
+ : stencil_(stencil), gcOutput_(gcOutput) {
+ skipTopLevelNonFunction();
+ }
+
+ Iterator operator++() {
+ next();
+ assertFunction();
+ return *this;
+ }
+
+ void next() {
+ MOZ_ASSERT(index_ < stencil_.scriptData.size());
+ index_++;
+ }
+
+ void assertFunction() {
+ if (index_ < stencil_.scriptData.size()) {
+ MOZ_ASSERT(stencil_.scriptData[index_].isFunction());
+ }
+ }
+
+ void skipTopLevelNonFunction() {
+ MOZ_ASSERT(index_ == 0);
+ if (stencil_.scriptData.size()) {
+ if (!stencil_.scriptData[0].isFunction()) {
+ next();
+ assertFunction();
+ }
+ }
+ }
+
+ bool operator!=(const Iterator& other) const {
+ return index_ != other.index_;
+ }
+
+ ScriptAndFunction operator*() {
+ ScriptIndex index = ScriptIndex(index_);
+ const ScriptStencil& script = stencil_.scriptData[index];
+ const ScriptStencilExtra* scriptExtra = nullptr;
+ if (stencil_.isInitialStencil()) {
+ scriptExtra = &stencil_.scriptExtra[index];
+ }
+ return ScriptAndFunction(script, scriptExtra,
+ gcOutput_.getFunctionNoBaseIndex(index), index);
+ }
+
+ static Iterator end(const CompilationStencil& stencil,
+ CompilationGCOutput& gcOutput) {
+ return Iterator(stencil, gcOutput, stencil.scriptData.size());
+ }
+ };
+
+ const CompilationStencil& stencil_;
+ CompilationGCOutput& gcOutput_;
+
+ explicit ScriptStencilIterable(const CompilationStencil& stencil,
+ CompilationGCOutput& gcOutput)
+ : stencil_(stencil), gcOutput_(gcOutput) {}
+
+ Iterator begin() const { return Iterator(stencil_, gcOutput_); }
+
+ Iterator end() const { return Iterator::end(stencil_, gcOutput_); }
+};
+
+inline ScriptStencilIterable CompilationStencil::functionScriptStencils(
+ const CompilationStencil& stencil, CompilationGCOutput& gcOutput) {
+ return ScriptStencilIterable(stencil, gcOutput);
+}
+
+// Merge CompilationStencil for delazification into initial
+// ExtensibleCompilationStencil.
+struct CompilationStencilMerger {
+ private:
+ using FunctionKey = SourceExtent::FunctionKey;
+
+ // The stencil for the initial compilation.
+ // Delazifications are merged into this.
+ //
+ // If any failure happens during merge operation, this field is reset to
+ // nullptr.
+ UniquePtr<ExtensibleCompilationStencil> initial_;
+
+ // A Map from function key to the ScriptIndex in the initial stencil.
+ using FunctionKeyToScriptIndexMap =
+ HashMap<FunctionKey, ScriptIndex, mozilla::DefaultHasher<FunctionKey>,
+ js::SystemAllocPolicy>;
+ FunctionKeyToScriptIndexMap functionKeyToInitialScriptIndex_;
+
+ [[nodiscard]] bool buildFunctionKeyToIndex(FrontendContext* fc);
+
+ ScriptIndex getInitialScriptIndexFor(
+ const CompilationStencil& delazification) const;
+
+ // A map from delazification's ParserAtomIndex to
+ // initial's TaggedParserAtomIndex
+ using AtomIndexMap = Vector<TaggedParserAtomIndex, 0, js::SystemAllocPolicy>;
+
+ [[nodiscard]] bool buildAtomIndexMap(FrontendContext* fc,
+ const CompilationStencil& delazification,
+ AtomIndexMap& atomIndexMap);
+
+ public:
+ CompilationStencilMerger() = default;
+
+ // Set the initial stencil and prepare for merging.
+ [[nodiscard]] bool setInitial(
+ FrontendContext* fc, UniquePtr<ExtensibleCompilationStencil>&& initial);
+
+ // Merge the delazification stencil into the initial stencil.
+ [[nodiscard]] bool addDelazification(
+ FrontendContext* fc, const CompilationStencil& delazification);
+
+ ExtensibleCompilationStencil& getResult() const { return *initial_; }
+ UniquePtr<ExtensibleCompilationStencil> takeResult() {
+ return std::move(initial_);
+ }
+};
+
+const ScopeStencil& ScopeStencilRef::scope() const {
+ return context_.scopeData[scopeIndex_];
+}
+
+InputScope InputScope::enclosing() const {
+ return scope_.match(
+ [](const Scope* ptr) {
+ // This may return a nullptr Scope pointer.
+ return InputScope(ptr->enclosing());
+ },
+ [](const ScopeStencilRef& ref) {
+ if (ref.scope().hasEnclosing()) {
+ return InputScope(ref.context_, ref.scope().enclosing());
+ }
+ return InputScope(nullptr);
+ });
+}
+
+FunctionFlags InputScope::functionFlags() const {
+ return scope_.match(
+ [](const Scope* ptr) {
+ JSFunction* fun = ptr->as<FunctionScope>().canonicalFunction();
+ return fun->flags();
+ },
+ [](const ScopeStencilRef& ref) {
+ MOZ_ASSERT(ref.scope().isFunction());
+ ScriptIndex scriptIndex = ref.scope().functionIndex();
+ ScriptStencil& data = ref.context_.scriptData[scriptIndex];
+ return data.functionFlags;
+ });
+}
+
+ImmutableScriptFlags InputScope::immutableFlags() const {
+ return scope_.match(
+ [](const Scope* ptr) {
+ JSFunction* fun = ptr->as<FunctionScope>().canonicalFunction();
+ return fun->baseScript()->immutableFlags();
+ },
+ [](const ScopeStencilRef& ref) {
+ MOZ_ASSERT(ref.scope().isFunction());
+ ScriptIndex scriptIndex = ref.scope().functionIndex();
+ ScriptStencilExtra& extra = ref.context_.scriptExtra[scriptIndex];
+ return extra.immutableFlags;
+ });
+}
+
+MemberInitializers InputScope::getMemberInitializers() const {
+ return scope_.match(
+ [](const Scope* ptr) {
+ JSFunction* fun = ptr->as<FunctionScope>().canonicalFunction();
+ return fun->baseScript()->getMemberInitializers();
+ },
+ [](const ScopeStencilRef& ref) {
+ MOZ_ASSERT(ref.scope().isFunction());
+ ScriptIndex scriptIndex = ref.scope().functionIndex();
+ ScriptStencilExtra& extra = ref.context_.scriptExtra[scriptIndex];
+ return extra.memberInitializers();
+ });
+}
+
+const ScriptStencil& ScriptStencilRef::scriptData() const {
+ return context_.scriptData[scriptIndex_];
+}
+
+const ScriptStencilExtra& ScriptStencilRef::scriptExtra() const {
+ return context_.scriptExtra[scriptIndex_];
+}
+
+} // namespace frontend
+} // namespace js
+
+#endif // frontend_CompilationStencil_h
diff --git a/js/src/frontend/CompileScript.cpp b/js/src/frontend/CompileScript.cpp
new file mode 100644
index 0000000000..0c75b40ccb
--- /dev/null
+++ b/js/src/frontend/CompileScript.cpp
@@ -0,0 +1,175 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "js/experimental/CompileScript.h"
+
+#include "frontend/BytecodeCompilation.h" // frontend::CompileGlobalScriptToStencil
+#include "frontend/BytecodeCompiler.h" // frontend::ParseModuleToStencil
+#include "frontend/CompilationStencil.h" // frontend::{CompilationStencil,CompilationInput}
+#include "frontend/FrontendContext.h" // frontend::FrontendContext
+#include "frontend/ScopeBindingCache.h" // frontend::NoScopeBindingCache
+#include "js/SourceText.h" // JS::SourceText
+
+using namespace js;
+using namespace js::frontend;
+
+JS_PUBLIC_API FrontendContext* JS::NewFrontendContext() {
+ MOZ_ASSERT(JS::detail::libraryInitState == JS::detail::InitState::Running,
+ "must call JS_Init prior to creating any FrontendContexts");
+
+ return js::NewFrontendContext();
+}
+
+JS_PUBLIC_API void JS::DestroyFrontendContext(FrontendContext* fc) {
+ return js::DestroyFrontendContext(fc);
+}
+
+JS_PUBLIC_API void JS::SetNativeStackQuota(JS::FrontendContext* fc,
+ JS::NativeStackSize stackSize) {
+ fc->setStackQuota(stackSize);
+}
+
+JS_PUBLIC_API bool JS::SetSupportedImportAssertions(
+ FrontendContext* fc,
+ const JS::ImportAssertionVector& supportedImportAssertions) {
+ return fc->setSupportedImportAssertions(supportedImportAssertions);
+}
+
+JS::CompilationStorage::~CompilationStorage() {
+ if (input_ && !isBorrowed_) {
+ js_delete(input_);
+ input_ = nullptr;
+ }
+}
+
+size_t JS::CompilationStorage::sizeOfIncludingThis(
+ mozilla::MallocSizeOf mallocSizeOf) const {
+ size_t sizeOfCompilationInput =
+ input_ ? input_->sizeOfExcludingThis(mallocSizeOf) : 0;
+ return mallocSizeOf(this) + sizeOfCompilationInput;
+}
+
+bool JS::CompilationStorage::allocateInput(
+ FrontendContext* fc, const JS::ReadOnlyCompileOptions& options) {
+ MOZ_ASSERT(!input_);
+ input_ = fc->getAllocator()->new_<frontend::CompilationInput>(options);
+ return !!input_;
+}
+
+void JS::CompilationStorage::trace(JSTracer* trc) {
+ if (input_) {
+ input_->trace(trc);
+ }
+}
+
+template <typename CharT>
+static already_AddRefed<JS::Stencil> CompileGlobalScriptToStencilImpl(
+ JS::FrontendContext* fc, const JS::ReadOnlyCompileOptions& options,
+ JS::SourceText<CharT>& srcBuf, JS::CompilationStorage& compilationStorage) {
+ ScopeKind scopeKind =
+ options.nonSyntacticScope ? ScopeKind::NonSyntactic : ScopeKind::Global;
+
+ JS::SourceText<CharT> data(std::move(srcBuf));
+
+ compilationStorage.allocateInput(fc, options);
+ if (!compilationStorage.hasInput()) {
+ return nullptr;
+ }
+
+ frontend::NoScopeBindingCache scopeCache;
+ LifoAlloc tempLifoAlloc(JSContext::TEMP_LIFO_ALLOC_PRIMARY_CHUNK_SIZE);
+ RefPtr<frontend::CompilationStencil> stencil_ =
+ frontend::CompileGlobalScriptToStencil(nullptr, fc, tempLifoAlloc,
+ compilationStorage.getInput(),
+ &scopeCache, data, scopeKind);
+ return stencil_.forget();
+}
+
+template <typename CharT>
+static already_AddRefed<JS::Stencil> CompileModuleScriptToStencilImpl(
+ JS::FrontendContext* fc, const JS::ReadOnlyCompileOptions& optionsInput,
+ JS::SourceText<CharT>& srcBuf, JS::CompilationStorage& compilationStorage) {
+ JS::CompileOptions options(nullptr, optionsInput);
+ options.setModule();
+
+ compilationStorage.allocateInput(fc, options);
+ if (!compilationStorage.hasInput()) {
+ return nullptr;
+ }
+
+ NoScopeBindingCache scopeCache;
+ js::LifoAlloc tempLifoAlloc(JSContext::TEMP_LIFO_ALLOC_PRIMARY_CHUNK_SIZE);
+ RefPtr<JS::Stencil> stencil =
+ ParseModuleToStencil(nullptr, fc, tempLifoAlloc,
+ compilationStorage.getInput(), &scopeCache, srcBuf);
+ if (!stencil) {
+ return nullptr;
+ }
+
+ // Convert the UniquePtr to a RefPtr and increment the count (to 1).
+ return stencil.forget();
+}
+
+already_AddRefed<JS::Stencil> JS::CompileGlobalScriptToStencil(
+ JS::FrontendContext* fc, const JS::ReadOnlyCompileOptions& options,
+ JS::SourceText<mozilla::Utf8Unit>& srcBuf,
+ JS::CompilationStorage& compileStorage) {
+ return CompileGlobalScriptToStencilImpl(fc, options, srcBuf, compileStorage);
+}
+
+already_AddRefed<JS::Stencil> JS::CompileGlobalScriptToStencil(
+ JS::FrontendContext* fc, const JS::ReadOnlyCompileOptions& options,
+ JS::SourceText<char16_t>& srcBuf, JS::CompilationStorage& compileStorage) {
+ return CompileGlobalScriptToStencilImpl(fc, options, srcBuf, compileStorage);
+}
+
+already_AddRefed<JS::Stencil> JS::CompileModuleScriptToStencil(
+ JS::FrontendContext* fc, const JS::ReadOnlyCompileOptions& optionsInput,
+ JS::SourceText<mozilla::Utf8Unit>& srcBuf,
+ JS::CompilationStorage& compileStorage) {
+ return CompileModuleScriptToStencilImpl(fc, optionsInput, srcBuf,
+ compileStorage);
+}
+
+already_AddRefed<JS::Stencil> JS::CompileModuleScriptToStencil(
+ JS::FrontendContext* fc, const JS::ReadOnlyCompileOptions& optionsInput,
+ JS::SourceText<char16_t>& srcBuf, JS::CompilationStorage& compileStorage) {
+ return CompileModuleScriptToStencilImpl(fc, optionsInput, srcBuf,
+ compileStorage);
+}
+
+#ifdef DEBUG
+// We don't need to worry about GC if the CompilationInput has no GC pointers
+static bool isGCSafe(js::frontend::CompilationInput& input) {
+ bool isGlobalOrModule =
+ input.target == CompilationInput::CompilationTarget::Global ||
+ input.target == CompilationInput::CompilationTarget::Module;
+ bool scopeHasNoGC =
+ input.enclosingScope.isStencil() || input.enclosingScope.isNull();
+ bool scriptHasNoGC =
+ input.lazyOuterScript().isStencil() || input.lazyOuterScript().isNull();
+ bool cacheHasNoGC = input.atomCache.empty();
+
+ return isGlobalOrModule && scopeHasNoGC && scriptHasNoGC && cacheHasNoGC;
+}
+#endif // DEBUG
+
+bool JS::PrepareForInstantiate(JS::FrontendContext* fc,
+ JS::CompilationStorage& compileStorage,
+ JS::Stencil& stencil,
+ JS::InstantiationStorage& storage) {
+ MOZ_ASSERT(compileStorage.hasInput());
+ MOZ_ASSERT(isGCSafe(compileStorage.getInput()));
+ if (!storage.gcOutput_) {
+ storage.gcOutput_ =
+ fc->getAllocator()->new_<js::frontend::CompilationGCOutput>();
+ if (!storage.gcOutput_) {
+ return false;
+ }
+ }
+ return CompilationStencil::prepareForInstantiate(
+ fc, compileStorage.getInput().atomCache, stencil, *storage.gcOutput_);
+}
diff --git a/js/src/frontend/DecoratorEmitter.cpp b/js/src/frontend/DecoratorEmitter.cpp
new file mode 100644
index 0000000000..f5771ba9e4
--- /dev/null
+++ b/js/src/frontend/DecoratorEmitter.cpp
@@ -0,0 +1,840 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "frontend/DecoratorEmitter.h"
+
+#include "frontend/BytecodeEmitter.h"
+#include "frontend/CallOrNewEmitter.h"
+#include "frontend/IfEmitter.h"
+#include "frontend/NameAnalysisTypes.h"
+#include "frontend/ObjectEmitter.h"
+#include "frontend/ParseNode.h"
+#include "frontend/ParserAtom.h"
+#include "frontend/WhileEmitter.h"
+#include "vm/ThrowMsgKind.h"
+
+using namespace js;
+using namespace js::frontend;
+
+DecoratorEmitter::DecoratorEmitter(BytecodeEmitter* bce) : bce_(bce) {}
+
+bool DecoratorEmitter::emitApplyDecoratorsToElementDefinition(
+ DecoratorEmitter::Kind kind, ParseNode* key, ListNode* decorators,
+ bool isStatic) {
+ MOZ_ASSERT(kind != Kind::Field);
+
+ // The DecoratorEmitter expects the value to be decorated to be at the top
+ // of the stack prior to this call. It will apply the decorators to this
+ // value, possibly replacing the value with a value returned by a decorator.
+ // [stack] VAL
+
+ // Decorators Proposal
+ // https://arai-a.github.io/ecma262-compare/?pr=2417&id=sec-applydecoratorstoelementdefinition.
+ // 1. Let decorators be elementRecord.[[Decorators]].
+ // 2. If decorators is empty, return unused.
+ // This is checked by the caller.
+ MOZ_ASSERT(!decorators->empty());
+
+ // 3. Let key be elementRecord.[[Key]].
+ // 4. Let kind be elementRecord.[[Kind]].
+ // 5. For each element decorator of decorators, do
+ for (ParseNode* decorator : decorators->contents()) {
+ // 5.a. Let decorationState be the Record { [[Finished]]: false }.
+ if (!emitDecorationState()) {
+ return false;
+ }
+
+ if (!emitCallDecorator(kind, key, isStatic, decorator)) {
+ return false;
+ }
+
+ // 5.i. Set decorationState.[[Finished]] to true.
+ if (!emitUpdateDecorationState()) {
+ return false;
+ }
+
+ // We need to check if the decorator returned undefined, a callable value,
+ // or any other value.
+ IfEmitter ie(bce_);
+ if (!ie.emitIf(mozilla::Nothing())) {
+ return false;
+ }
+
+ if (!emitCheckIsUndefined()) {
+ // [stack] VAL RETVAL ISUNDEFINED
+ return false;
+ }
+
+ if (!ie.emitThenElse()) {
+ // [stack] VAL RETVAL
+ return false;
+ }
+
+ // Pop the undefined RETVAL from the stack, leaving the original value in
+ // place.
+ if (!bce_->emitPopN(1)) {
+ // [stack] VAL
+ return false;
+ }
+
+ if (!ie.emitElseIf(mozilla::Nothing())) {
+ return false;
+ }
+
+ // 5.l.i. If IsCallable(newValue) is true, then
+ if (!emitCheckIsCallable()) {
+ // [stack] VAL RETVAL ISCALLABLE_RESULT
+ return false;
+ }
+
+ if (!ie.emitThenElse()) {
+ // [stack] VAL RETVAL
+ return false;
+ }
+
+ // clang-format off
+ // 5.k. Else if kind is accessor, then
+ // 5.k.i. If newValue is an Object, then
+ // 5.k.i.1. Let newGetter be ? Get(newValue, "get").
+ // 5.k.i.2. If IsCallable(newGetter) is true, set elementRecord.[[Get]] to newGetter.
+ // 5.k.i.3. Else if newGetter is not undefined, throw a TypeError exception.
+ // 5.k.i.4. Let newSetter be ? Get(newValue, "set").
+ // 5.k.i.5. If IsCallable(newSetter) is true, set elementRecord.[[Set]] to newSetter.
+ // 5.k.i.6. Else if newSetter is not undefined, throw a TypeError exception.
+ // 5.k.i.7. Let initializer be ? Get(newValue, "init").
+ // 5.k.i.8. If IsCallable(initializer) is true, append initializer to elementRecord.[[Initializers]].
+ // 5.k.i.9. Else if initializer is not undefined, throw a TypeError exception.
+ // 5.k.ii. Else if newValue is not undefined, throw a TypeError exception.
+ // TODO: See https://bugzilla.mozilla.org/show_bug.cgi?id=1793961.
+ // clang-format on
+ // 5.l. Else,
+
+ // 5.l.i.1. Perform MakeMethod(newValue, homeObject).
+ // MakeMethod occurs in the caller, here we just drop the original method
+ // which was an argument to the decorator, and leave the new method
+ // returned by the decorator on the stack.
+ if (!bce_->emit1(JSOp::Swap)) {
+ // [stack] RETVAL VAL
+ return false;
+ }
+ if (!bce_->emitPopN(1)) {
+ // [stack] RETVAL
+ return false;
+ }
+ // 5.j.ii. Else if initializer is not undefined, throw a TypeError
+ // exception. 5.l.ii. Else if newValue is not undefined, throw a
+ // TypeError exception.
+ if (!ie.emitElse()) {
+ return false;
+ }
+
+ if (!bce_->emitPopN(1)) {
+ // [stack] RETVAL
+ return false;
+ }
+
+ if (!bce_->emit2(JSOp::ThrowMsg,
+ uint8_t(ThrowMsgKind::DecoratorInvalidReturnType))) {
+ return false;
+ }
+
+ if (!ie.emitEnd()) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool DecoratorEmitter::emitApplyDecoratorsToFieldDefinition(
+ ParseNode* key, ListNode* decorators, bool isStatic) {
+ // This method creates a new array to contain initializers added by decorators
+ // to the stack. start:
+ // [stack]
+ // end:
+ // [stack] ARRAY
+
+ // Decorators Proposal
+ // https://arai-a.github.io/ecma262-compare/?pr=2417&id=sec-applydecoratorstoelementdefinition.
+ // 1. Let decorators be elementRecord.[[Decorators]].
+ // 2. If decorators is empty, return unused.
+ // This is checked by the caller.
+ MOZ_ASSERT(!decorators->empty());
+
+ // If we're apply decorators to a field, we'll push a new array to the stack
+ // to hold newly created initializers.
+ if (!bce_->emitUint32Operand(JSOp::NewArray, 1)) {
+ // [stack] ARRAY
+ return false;
+ }
+
+ if (!emitPropertyKey(key)) {
+ // [stack] ARRAY NAME
+ return false;
+ }
+
+ if (!bce_->emitUint32Operand(JSOp::InitElemArray, 0)) {
+ // [stack] ARRAY
+ return false;
+ }
+
+ if (!bce_->emit1(JSOp::One)) {
+ // [stack] ARRAY INDEX
+ return false;
+ }
+
+ // 3. Let key be elementRecord.[[Key]].
+ // 4. Let kind be elementRecord.[[Kind]].
+ // 5. For each element decorator of decorators, do
+ for (ParseNode* decorator : decorators->contents()) {
+ // 5.a. Let decorationState be the Record { [[Finished]]: false }.
+ if (!emitDecorationState()) {
+ return false;
+ }
+
+ if (!emitCallDecorator(Kind::Field, key, isStatic, decorator)) {
+ // [stack] ARRAY INDEX RETVAL
+ return false;
+ }
+
+ // 5.i. Set decorationState.[[Finished]] to true.
+ if (!emitUpdateDecorationState()) {
+ // [stack] ARRAY INDEX RETVAL
+ return false;
+ }
+
+ // We need to check if the decorator returned undefined, a callable value,
+ // or any other value.
+ IfEmitter ie(bce_);
+ if (!ie.emitIf(mozilla::Nothing())) {
+ return false;
+ }
+
+ if (!emitCheckIsUndefined()) {
+ // [stack] ARRAY INDEX RETVAL ISUNDEFINED
+ return false;
+ }
+
+ if (!ie.emitThenElse()) {
+ // [stack] ARRAY INDEX RETVAL
+ return false;
+ }
+
+ // Pop the undefined RETVAL from the stack, leaving the original value in
+ // place.
+ if (!bce_->emitPopN(1)) {
+ // [stack] ARRAY INDEX
+ return false;
+ }
+
+ if (!ie.emitElseIf(mozilla::Nothing())) {
+ return false;
+ }
+
+ // 5.l.i. If IsCallable(newValue) is true, then
+ if (!emitCheckIsCallable()) {
+ // [stack] ARRAY INDEX RETVAL ISCALLABLE_RESULT
+ return false;
+ }
+
+ if (!ie.emitThenElse()) {
+ // [stack] ARRAY INDEX RETVAL
+ return false;
+ }
+
+ // 5.j. If kind is field, then
+ // 5.j.i. If IsCallable(initializer) is true, append initializer
+ // to elementRecord.[[Initializers]].
+ if (!bce_->emit1(JSOp::InitElemInc)) {
+ // [stack] ARRAY INDEX
+ return false;
+ }
+
+ // 5.j.ii. Else if initializer is not undefined, throw a TypeError
+ // exception. 5.l.ii. Else if newValue is not undefined, throw a
+ // TypeError exception.
+ if (!ie.emitElse()) {
+ return false;
+ }
+
+ if (!bce_->emitPopN(1)) {
+ // [stack] ARRAY INDEX
+ return false;
+ }
+
+ if (!bce_->emit2(JSOp::ThrowMsg,
+ uint8_t(ThrowMsgKind::DecoratorInvalidReturnType))) {
+ return false;
+ }
+
+ if (!ie.emitEnd()) {
+ return false;
+ }
+ }
+
+ // Pop INDEX
+ return bce_->emitPopN(1);
+ // [stack] ARRAY
+}
+
+bool DecoratorEmitter::emitInitializeFieldOrAccessor() {
+ // [stack] THIS INITIALIZERS
+
+ // Decorators Proposal
+ // https://arai-a.github.io/ecma262-compare/?pr=2417&id=sec-applydecoratorstoelementdefinition.
+ //
+ // 1. Assert: elementRecord.[[Kind]] is field or accessor.
+ // 2. If elementRecord.[[BackingStorageKey]] is present, let fieldName be
+ // elementRecord.[[BackingStorageKey]].
+ // 3. Else, let fieldName be elementRecord.[[Key]].
+ // We've stored the fieldname in the first element of the initializers array.
+ if (!bce_->emit1(JSOp::Dup)) {
+ // [stack] THIS INITIALIZERS INITIALIZERS
+ return false;
+ }
+
+ if (!bce_->emit1(JSOp::Zero)) {
+ // [stack] THIS INITIALIZERS INITIALIZERS INDEX
+ return false;
+ }
+
+ if (!bce_->emit1(JSOp::GetElem)) {
+ // [stack] THIS INITIALIZERS FIELDNAME
+ return false;
+ }
+
+ // Retrieve initial value of the field
+ if (!bce_->emit1(JSOp::Dup)) {
+ // [stack] THIS INITIALIZERS FIELDNAME FIELDNAME
+ return false;
+ }
+
+ if (!bce_->emitDupAt(3)) {
+ // [stack] THIS INITIALIZERS FIELDNAME FIELDNAME THIS
+ return false;
+ }
+
+ if (!bce_->emit1(JSOp::Swap)) {
+ // [stack] THIS INITIALIZERS FIELDNAME THIS FIELDNAME
+ return false;
+ }
+
+ // 4. Let initValue be undefined.
+ // TODO: (See Bug 1817993) At the moment, we're applying the initialization
+ // logic in two steps. The pre-decorator initialization code runs, stores
+ // the initial value, and then we retrieve it here and apply the initializers
+ // added by decorators. We should unify these two steps.
+ if (!bce_->emit1(JSOp::GetElem)) {
+ // [stack] THIS INITIALIZERS FIELDNAME VALUE
+ return false;
+ }
+
+ if (!bce_->emit2(JSOp::Pick, 2)) {
+ // [stack] THIS FIELDNAME VALUE INITIALIZERS
+ return false;
+ }
+
+ // Retrieve the length of the initializers array.
+ if (!bce_->emit1(JSOp::Dup)) {
+ // [stack] THIS FIELDNAME VALUE INITIALIZERS INITIALIZERS
+ return false;
+ }
+
+ if (!bce_->emitAtomOp(JSOp::GetProp,
+ TaggedParserAtomIndex::WellKnown::length())) {
+ // [stack] THIS FIELDNAME VALUE INITIALIZERS LENGTH
+ return false;
+ }
+
+ if (!bce_->emit1(JSOp::One)) {
+ // [stack] THIS FIELDNAME VALUE INITIALIZERS LENGTH INDEX
+ return false;
+ }
+
+ // 5. For each element initializer of elementRecord.[[Initializers]], do
+ WhileEmitter wh(bce_);
+ // At this point, we have no context to determine offsets in the
+ // code for this while statement. Ideally, it would correspond to
+ // the field we're initializing.
+ if (!wh.emitCond(0, 0, 0)) {
+ // [stack] THIS FIELDNAME VALUE INITIALIZERS LENGTH INDEX
+ return false;
+ }
+
+ if (!bce_->emit1(JSOp::Dup)) {
+ // [stack] THIS FIELDNAME VALUE INITIALIZERS LENGTH INDEX INDEX
+ return false;
+ }
+
+ if (!bce_->emitDupAt(2)) {
+ // [stack] THIS FIELDNAME VALUE INITIALIZERS LENGTH INDEX INDEX
+ // LENGTH
+ return false;
+ }
+
+ if (!bce_->emit1(JSOp::Lt)) {
+ // [stack] THIS FIELDNAME VALUE INITIALIZERS LENGTH INDEX BOOL
+ return false;
+ }
+
+ // a. Set initValue to ? Call(initializer, receiver, « initValue »).
+ if (!wh.emitBody()) {
+ // [stack] THIS FIELDNAME VALUE INITIALIZERS LENGTH INDEX
+ return false;
+ }
+
+ if (!bce_->emitDupAt(2)) {
+ // [stack] THIS FIELDNAME VALUE INITIALIZERS LENGTH INDEX
+ // INITIALIZERS
+ return false;
+ }
+
+ if (!bce_->emitDupAt(1)) {
+ // [stack] THIS FIELDNAME VALUE INITIALIZERS LENGTH INDEX
+ // INITIALIZERS INDEX
+ return false;
+ }
+
+ if (!bce_->emit1(JSOp::GetElem)) {
+ // [stack] THIS FIELDNAME VALUE INITIALIZERS LENGTH INDEX FUNC
+ return false;
+ }
+
+ // This is guaranteed to run after super(), so we don't need TDZ checks.
+ if (!bce_->emitGetName(TaggedParserAtomIndex::WellKnown::dotThis())) {
+ // [stack] THIS FIELDNAME VALUE INITIALIZERS LENGTH INDEX FUNC
+ // THIS
+ return false;
+ }
+
+ // Pass value in as argument to the initializer
+ if (!bce_->emit2(JSOp::Pick, 5)) {
+ // [stack] THIS FIELDNAME INITIALIZERS LENGTH INDEX FUNC THIS
+ // VALUE
+ return false;
+ }
+
+ // Callee is always internal function.
+ if (!bce_->emitCall(JSOp::Call, 1)) {
+ // [stack] THIS FIELDNAME INITIALIZERS LENGTH INDEX RVAL
+ return false;
+ }
+
+ // Store returned value for next iteration
+ if (!bce_->emit2(JSOp::Unpick, 3)) {
+ // [stack] THIS FIELDNAME VALUE INITIALIZERS LENGTH INDEX
+ return false;
+ }
+
+ if (!bce_->emit1(JSOp::Inc)) {
+ // [stack] THIS FIELDNAME VALUE INITIALIZERS LENGTH INDEX
+ return false;
+ }
+
+ if (!wh.emitEnd()) {
+ // [stack] THIS FIELDNAME VALUE INITIALIZERS LENGTH INDEX
+ return false;
+ }
+
+ // 6. If fieldName is a Private Name, then
+ // 6.a. Perform ? PrivateFieldAdd(receiver, fieldName, initValue).
+ // 7. Else,
+ // 6.a. Assert: IsPropertyKey(fieldName) is true.
+ // 6.b. Perform ? CreateDataPropertyOrThrow(receiver, fieldName,
+ // initValue).
+ // TODO: (See Bug 1817993) Because the field already exists, we just store the
+ // updated value here.
+ if (!bce_->emitPopN(3)) {
+ // [stack] THIS FIELDNAME VALUE
+ return false;
+ }
+
+ if (!bce_->emit1(JSOp::InitElem)) {
+ // [stack] THIS
+ return false;
+ }
+
+ // 8. Return unused.
+ return bce_->emitPopN(1);
+ // [stack]
+}
+
+bool DecoratorEmitter::emitPropertyKey(ParseNode* key) {
+ if (key->is<NameNode>()) {
+ NameNode* keyAsNameNode = &key->as<NameNode>();
+ if (keyAsNameNode->privateNameKind() == PrivateNameKind::None) {
+ if (!bce_->emitStringOp(JSOp::String, keyAsNameNode->atom())) {
+ // [stack] NAME
+ return false;
+ }
+ } else {
+ MOZ_ASSERT(keyAsNameNode->privateNameKind() == PrivateNameKind::Field);
+ if (!bce_->emitGetPrivateName(keyAsNameNode)) {
+ // [stack] NAME
+ return false;
+ }
+ }
+ } else if (key->isKind(ParseNodeKind::NumberExpr)) {
+ if (!bce_->emitNumberOp(key->as<NumericLiteral>().value())) {
+ // [stack] NAME
+ return false;
+ }
+ } else {
+ // Otherwise this is a computed property name. BigInt keys are parsed
+ // as (synthetic) computed property names, too.
+ MOZ_ASSERT(key->isKind(ParseNodeKind::ComputedName));
+
+ if (!bce_->emitComputedPropertyName(&key->as<UnaryNode>())) {
+ // [stack] NAME
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool DecoratorEmitter::emitDecorationState() {
+ // TODO: See https://bugzilla.mozilla.org/show_bug.cgi?id=1800724.
+ return true;
+}
+
+bool DecoratorEmitter::emitUpdateDecorationState() {
+ // TODO: See https://bugzilla.mozilla.org/show_bug.cgi?id=1800724.
+ return true;
+}
+
+[[nodiscard]] bool DecoratorEmitter::emitCallDecorator(Kind kind,
+ ParseNode* key,
+ bool isStatic,
+ ParseNode* decorator) {
+ // Prepare to call decorator
+ CallOrNewEmitter cone(bce_, JSOp::Call,
+ CallOrNewEmitter::ArgumentsKind::Other,
+ ValueUsage::WantValue);
+
+ // DecoratorMemberExpression: IdentifierReference e.g. @dec
+ if (decorator->is<NameNode>()) {
+ if (!cone.emitNameCallee(decorator->as<NameNode>().name())) {
+ // [stack] VAL? CALLEE THIS?
+ return false;
+ }
+ } else if (decorator->is<ListNode>()) {
+ // DecoratorMemberExpression: DecoratorMemberExpression . IdentifierName
+ // e.g. @decorators.nested.dec
+ PropOpEmitter& poe = cone.prepareForPropCallee(false);
+ if (!poe.prepareForObj()) {
+ return false;
+ }
+
+ ListNode* ln = &decorator->as<ListNode>();
+ bool first = true;
+ for (ParseNode* node : ln->contentsTo(ln->last())) {
+ // We should have only placed NameNode instances in this list while
+ // parsing.
+ MOZ_ASSERT(node->is<NameNode>());
+
+ if (first) {
+ NameNode* obj = &node->as<NameNode>();
+ if (!bce_->emitGetName(obj)) {
+ return false;
+ }
+ first = false;
+ } else {
+ NameNode* prop = &node->as<NameNode>();
+ GCThingIndex propAtomIndex;
+
+ if (!bce_->makeAtomIndex(prop->atom(), ParserAtom::Atomize::Yes,
+ &propAtomIndex)) {
+ return false;
+ }
+
+ if (!bce_->emitAtomOp(JSOp::GetProp, propAtomIndex)) {
+ return false;
+ }
+ }
+ }
+
+ NameNode* prop = &ln->last()->as<NameNode>();
+ if (!poe.emitGet(prop->atom())) {
+ return false;
+ }
+ } else {
+ // DecoratorCallExpression | DecoratorParenthesizedExpression,
+ // e.g. @dec('argument') | @((value, context) => value)
+ if (!cone.prepareForFunctionCallee()) {
+ return false;
+ }
+
+ if (!bce_->emitTree(decorator)) {
+ return false;
+ }
+ }
+
+ if (!cone.emitThis()) {
+ // [stack] VAL? CALLEE THIS
+ return false;
+ }
+
+ if (!cone.prepareForNonSpreadArguments()) {
+ return false;
+ }
+
+ if (kind == Kind::Field) {
+ // 5.c. Let value be undefined.
+ if (!bce_->emit1(JSOp::Undefined)) {
+ // [stack] VAL? CALLEE THIS undefined
+ return false;
+ }
+ } else if (kind == Kind::Getter || kind == Kind::Method ||
+ kind == Kind::Setter) {
+ // 5.d. If kind is method, set value to elementRecord.[[Value]].
+ // 5.e. Else if kind is getter, set value to elementRecord.[[Get]].
+ // 5.f. Else if kind is setter, set value to elementRecord.[[Set]].
+ // The DecoratorEmitter expects the method to already be on the stack.
+ // We dup the value here so we can use it as an argument to the decorator.
+ if (!bce_->emitDupAt(2)) {
+ // [stack] VAL CALLEE THIS VAL
+ return false;
+ }
+ }
+ // 5.g. Else if kind is accessor, then
+ // 5.g.i. Set value to OrdinaryObjectCreate(%Object.prototype%).
+ // 5.g.ii. Perform ! CreateDataPropertyOrThrow(accessor, "get",
+ // elementRecord.[[Get]]). 5.g.iii. Perform
+ // ! CreateDataPropertyOrThrow(accessor, "set",
+ // elementRecord.[[Set]]).
+ // TODO: See https://bugzilla.mozilla.org/show_bug.cgi?id=1793961.
+ // 5.b. Let context be CreateDecoratorContextObject(kind, key,
+ // extraInitializers, decorationState, isStatic).
+ if (!emitCreateDecoratorContextObject(kind, key, isStatic,
+ decorator->pn_pos)) {
+ // [stack] VAL? CALLEE THIS VAL
+ // context
+ return false;
+ }
+
+ // 5.h. Let newValue be ? Call(decorator, undefined, « value, context
+ // »).
+ return cone.emitEnd(2, decorator->pn_pos.begin);
+ // [stack] VAL? RETVAL
+}
+
+bool DecoratorEmitter::emitCreateDecoratorAccessObject() {
+ // TODO: See https://bugzilla.mozilla.org/show_bug.cgi?id=1800725.
+ ObjectEmitter oe(bce_);
+ if (!oe.emitObject(0)) {
+ return false;
+ }
+ return oe.emitEnd();
+}
+
+bool DecoratorEmitter::emitCheckIsUndefined() {
+ // This emits code to check if the value at the top of the stack is
+ // undefined. The value is left on the stack.
+ // [stack] VAL
+ if (!bce_->emit1(JSOp::Dup)) {
+ // [stack] VAL VAL
+ return false;
+ }
+ if (!bce_->emit1(JSOp::Undefined)) {
+ // [stack] VAL VAL undefined
+ return false;
+ }
+ return bce_->emit1(JSOp::Eq);
+ // [stack] VAL ISUNDEFINED
+}
+
+bool DecoratorEmitter::emitCheckIsCallable() {
+ // This emits code to check if the value at the top of the stack is
+ // callable. The value is left on the stack.
+ // [stack] VAL
+ if (!bce_->emitAtomOp(JSOp::GetIntrinsic,
+ TaggedParserAtomIndex::WellKnown::IsCallable())) {
+ // [stack] VAL ISCALLABLE
+ return false;
+ }
+ if (!bce_->emit1(JSOp::Undefined)) {
+ // [stack] VAL ISCALLABLE UNDEFINED
+ return false;
+ }
+ if (!bce_->emitDupAt(2)) {
+ // [stack] VAL ISCALLABLE UNDEFINED VAL
+ return false;
+ }
+ return bce_->emitCall(JSOp::Call, 1);
+ // [stack] VAL ISCALLABLE_RESULT
+}
+
+bool DecoratorEmitter::emitCreateAddInitializerFunction() {
+ // TODO: See https://bugzilla.mozilla.org/show_bug.cgi?id=1800724.
+ ObjectEmitter oe(bce_);
+ if (!oe.emitObject(0)) {
+ return false;
+ }
+ return oe.emitEnd();
+}
+
+bool DecoratorEmitter::emitCreateDecoratorContextObject(Kind kind,
+ ParseNode* key,
+ bool isStatic,
+ TokenPos pos) {
+ // 1. Let contextObj be OrdinaryObjectCreate(%Object.prototype%).
+ ObjectEmitter oe(bce_);
+ if (!oe.emitObject(/* propertyCount */ 6)) {
+ // [stack] context
+ return false;
+ }
+ if (!oe.prepareForPropValue(pos.begin, PropertyEmitter::Kind::Prototype)) {
+ return false;
+ }
+
+ if (kind == Kind::Method) {
+ // 2. If kind is method, let kindStr be "method".
+ if (!bce_->emitStringOp(
+ JSOp::String,
+ frontend::TaggedParserAtomIndex::WellKnown::method())) {
+ // [stack] context "method"
+ return false;
+ }
+ } else if (kind == Kind::Getter) {
+ // 3. Else if kind is getter, let kindStr be "getter".
+ if (!bce_->emitStringOp(
+ JSOp::String,
+ frontend::TaggedParserAtomIndex::WellKnown::getter())) {
+ // [stack] context "getter"
+ return false;
+ }
+ } else if (kind == Kind::Setter) {
+ // 4. Else if kind is setter, let kindStr be "setter".
+ if (!bce_->emitStringOp(
+ JSOp::String,
+ frontend::TaggedParserAtomIndex::WellKnown::setter())) {
+ // [stack] context "setter"
+ return false;
+ }
+ } else if (kind == Kind::Field) {
+ // 6. Else if kind is field, let kindStr be "field".
+ if (!bce_->emitStringOp(
+ JSOp::String,
+ frontend::TaggedParserAtomIndex::WellKnown::field())) {
+ // [stack] context "field"
+ return false;
+ }
+ } else {
+ // clang-format off
+
+
+ // TODO: See https://bugzilla.mozilla.org/show_bug.cgi?id=1793960.
+ // 5. Else if kind is accessor, let kindStr be "accessor".
+ // TODO: See https://bugzilla.mozilla.org/show_bug.cgi?id=1793961.
+ // 7. Else,
+ // a. Assert: kind is class.
+ // b. Let kindStr be "class".
+ // TODO: See https://bugzilla.mozilla.org/show_bug.cgi?id=1793963.
+ // clang-format on
+ return false;
+ }
+
+ // 8. Perform ! CreateDataPropertyOrThrow(contextObj, "kind", kindStr).
+ if (!oe.emitInit(frontend::AccessorType::None,
+ frontend::TaggedParserAtomIndex::WellKnown::kind())) {
+ // [stack] context
+ return false;
+ }
+ // 9. If kind is not class, then
+ if (kind != Kind::Class) {
+ // 9.a. Perform ! CreateDataPropertyOrThrow(contextObj, "access",
+ // CreateDecoratorAccessObject(kind, name)).
+ if (!oe.prepareForPropValue(pos.begin, PropertyEmitter::Kind::Prototype)) {
+ return false;
+ }
+ if (!emitCreateDecoratorAccessObject()) {
+ return false;
+ }
+ if (!oe.emitInit(frontend::AccessorType::None,
+ frontend::TaggedParserAtomIndex::WellKnown::access())) {
+ // [stack] context
+ return false;
+ }
+ // 9.b. If isStatic is present, perform
+ // ! CreateDataPropertyOrThrow(contextObj, "static", isStatic).
+ if (!oe.prepareForPropValue(pos.begin, PropertyEmitter::Kind::Prototype)) {
+ return false;
+ }
+ if (!bce_->emit1(isStatic ? JSOp::True : JSOp::False)) {
+ // [stack] context isStatic
+ return false;
+ }
+ if (!oe.emitInit(frontend::AccessorType::None,
+ frontend::TaggedParserAtomIndex::WellKnown::static_())) {
+ // [stack] context
+ return false;
+ }
+ // 9.c. If name is a Private Name, then
+ // 9.c.i. Perform ! CreateDataPropertyOrThrow(contextObj, "private",
+ // true).
+ // 9.d. Else,
+ // 9.d.i. Perform ! CreateDataPropertyOrThrow(contextObj, "private",
+ // false).
+ if (!oe.prepareForPropValue(pos.begin, PropertyEmitter::Kind::Prototype)) {
+ return false;
+ }
+ if (!bce_->emit1(key->isKind(ParseNodeKind::PrivateName) ? JSOp::True
+ : JSOp::False)) {
+ // [stack] context private
+ return false;
+ }
+ if (!oe.emitInit(frontend::AccessorType::None,
+ frontend::TaggedParserAtomIndex::WellKnown::private_())) {
+ // [stack] context
+ return false;
+ }
+ // 9.c.ii. Perform ! CreateDataPropertyOrThrow(contextObj, "name",
+ // name.[[Description]]).
+ //
+ // 9.d.ii. Perform
+ // ! CreateDataPropertyOrThrow(contextObj, "name", name).)
+ if (!oe.prepareForPropValue(pos.begin, PropertyEmitter::Kind::Prototype)) {
+ return false;
+ }
+ if (key->is<NameNode>()) {
+ if (!bce_->emitStringOp(JSOp::String, key->as<NameNode>().atom())) {
+ return false;
+ }
+ } else {
+ if (!emitPropertyKey(key)) {
+ return false;
+ }
+ }
+ if (!oe.emitInit(frontend::AccessorType::None,
+ frontend::TaggedParserAtomIndex::WellKnown::name())) {
+ // [stack] context
+ return false;
+ }
+ } else {
+ // 10. Else,
+ // 10.a. Perform ! CreateDataPropertyOrThrow(contextObj, "name", name).
+ // TODO: See https://bugzilla.mozilla.org/show_bug.cgi?id=1793963
+ return false;
+ }
+ // 11. Let addInitializer be CreateAddInitializerFunction(initializers,
+ // decorationState).
+ if (!oe.prepareForPropValue(pos.begin, PropertyEmitter::Kind::Prototype)) {
+ return false;
+ }
+ if (!emitCreateAddInitializerFunction()) {
+ // [stack] context addInitializer
+ return false;
+ }
+ // 12. Perform ! CreateDataPropertyOrThrow(contextObj, "addInitializer",
+ // addInitializer).
+ if (!oe.emitInit(
+ frontend::AccessorType::None,
+ frontend::TaggedParserAtomIndex::WellKnown::addInitializer())) {
+ // [stack] context
+ return false;
+ }
+ // 13. Return contextObj.
+ return oe.emitEnd();
+}
diff --git a/js/src/frontend/DecoratorEmitter.h b/js/src/frontend/DecoratorEmitter.h
new file mode 100644
index 0000000000..42cad2e81c
--- /dev/null
+++ b/js/src/frontend/DecoratorEmitter.h
@@ -0,0 +1,59 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef frontend_DecoratorEmitter_h
+#define frontend_DecoratorEmitter_h
+
+#include "mozilla/Attributes.h"
+
+#include "frontend/ParseNode.h"
+
+namespace js::frontend {
+
+struct BytecodeEmitter;
+
+class MOZ_STACK_CLASS DecoratorEmitter {
+ private:
+ BytecodeEmitter* bce_;
+
+ public:
+ enum Kind { Method, Getter, Setter, Field, Accessor, Class };
+
+ explicit DecoratorEmitter(BytecodeEmitter* bce);
+
+ [[nodiscard]] bool emitApplyDecoratorsToElementDefinition(
+ Kind kind, ParseNode* key, ListNode* decorators, bool isStatic);
+
+ [[nodiscard]] bool emitApplyDecoratorsToFieldDefinition(ParseNode* key,
+ ListNode* decorators,
+ bool isStatic);
+
+ [[nodiscard]] bool emitInitializeFieldOrAccessor();
+
+ private:
+ [[nodiscard]] bool emitPropertyKey(ParseNode* key);
+
+ [[nodiscard]] bool emitDecorationState();
+
+ [[nodiscard]] bool emitUpdateDecorationState();
+
+ [[nodiscard]] bool emitCallDecorator(Kind kind, ParseNode* key, bool isStatic,
+ ParseNode* decorator);
+
+ [[nodiscard]] bool emitCreateDecoratorAccessObject();
+
+ [[nodiscard]] bool emitCheckIsUndefined();
+
+ [[nodiscard]] bool emitCheckIsCallable();
+
+ [[nodiscard]] bool emitCreateAddInitializerFunction();
+
+ [[nodiscard]] bool emitCreateDecoratorContextObject(Kind kind, ParseNode* key,
+ bool isStatic,
+ TokenPos pos);
+};
+
+} /* namespace js::frontend */
+
+#endif /* frontend_DecoratorEmitter_h */
diff --git a/js/src/frontend/DefaultEmitter.cpp b/js/src/frontend/DefaultEmitter.cpp
new file mode 100644
index 0000000000..1377e4b7ad
--- /dev/null
+++ b/js/src/frontend/DefaultEmitter.cpp
@@ -0,0 +1,75 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "frontend/DefaultEmitter.h"
+
+#include "mozilla/Assertions.h" // MOZ_ASSERT
+
+#include "frontend/BytecodeEmitter.h" // BytecodeEmitter
+#include "vm/Opcodes.h" // JSOp
+
+using namespace js;
+using namespace js::frontend;
+
+using mozilla::Nothing;
+
+DefaultEmitter::DefaultEmitter(BytecodeEmitter* bce) : bce_(bce) {}
+
+bool DefaultEmitter::prepareForDefault() {
+ MOZ_ASSERT(state_ == State::Start);
+
+ // [stack] VALUE
+
+ ifUndefined_.emplace(bce_);
+ if (!ifUndefined_->emitIf(Nothing())) {
+ return false;
+ }
+
+ if (!bce_->emit1(JSOp::Dup)) {
+ // [stack] VALUE VALUE
+ return false;
+ }
+ if (!bce_->emit1(JSOp::Undefined)) {
+ // [stack] VALUE VALUE UNDEFINED
+ return false;
+ }
+ if (!bce_->emit1(JSOp::StrictEq)) {
+ // [stack] VALUE EQ?
+ return false;
+ }
+
+ if (!ifUndefined_->emitThen()) {
+ // [stack] VALUE
+ return false;
+ }
+
+ if (!bce_->emit1(JSOp::Pop)) {
+ // [stack]
+ return false;
+ }
+
+#ifdef DEBUG
+ state_ = State::Default;
+#endif
+ return true;
+}
+
+bool DefaultEmitter::emitEnd() {
+ MOZ_ASSERT(state_ == State::Default);
+
+ // [stack] DEFAULTVALUE
+
+ if (!ifUndefined_->emitEnd()) {
+ // [stack] VALUE/DEFAULTVALUE
+ return false;
+ }
+ ifUndefined_.reset();
+
+#ifdef DEBUG
+ state_ = State::End;
+#endif
+ return true;
+}
diff --git a/js/src/frontend/DefaultEmitter.h b/js/src/frontend/DefaultEmitter.h
new file mode 100644
index 0000000000..3dc9aba21b
--- /dev/null
+++ b/js/src/frontend/DefaultEmitter.h
@@ -0,0 +1,65 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef frontend_DefaultEmitter_h
+#define frontend_DefaultEmitter_h
+
+#include "mozilla/Attributes.h" // MOZ_STACK_CLASS
+#include "mozilla/Maybe.h" // Maybe
+
+#include "frontend/IfEmitter.h" // IfEmitter
+
+namespace js {
+namespace frontend {
+
+struct BytecodeEmitter;
+
+// Class for emitting default parameter or default value.
+//
+// Usage: (check for the return value is omitted for simplicity)
+//
+// `x = 10` in `function (x = 10) {}`
+// // the value of arguments[0] is on the stack
+// DefaultEmitter de(this);
+// de.prepareForDefault();
+// emit(10);
+// de.emitEnd();
+//
+class MOZ_STACK_CLASS DefaultEmitter {
+ BytecodeEmitter* bce_;
+
+ mozilla::Maybe<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);
+
+ [[nodiscard]] bool prepareForDefault();
+ [[nodiscard]] bool emitEnd();
+};
+
+} /* namespace frontend */
+} /* namespace js */
+
+#endif /* frontend_LabelEmitter_h */
diff --git a/js/src/frontend/DestructuringFlavor.h b/js/src/frontend/DestructuringFlavor.h
new file mode 100644
index 0000000000..d9d8fb26e1
--- /dev/null
+++ b/js/src/frontend/DestructuringFlavor.h
@@ -0,0 +1,24 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef frontend_DestructuringFlavor_h
+#define frontend_DestructuringFlavor_h
+
+namespace js {
+namespace frontend {
+
+enum class DestructuringFlavor {
+ // Destructuring into a declaration.
+ Declaration,
+
+ // Destructuring as part of an AssignmentExpression.
+ Assignment
+};
+
+} /* namespace frontend */
+} /* namespace js */
+
+#endif /* frontend_DestructuringFlavor_h */
diff --git a/js/src/frontend/DoWhileEmitter.cpp b/js/src/frontend/DoWhileEmitter.cpp
new file mode 100644
index 0000000000..acdd8074b6
--- /dev/null
+++ b/js/src/frontend/DoWhileEmitter.cpp
@@ -0,0 +1,69 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "frontend/DoWhileEmitter.h"
+
+#include "frontend/BytecodeEmitter.h"
+#include "vm/Opcodes.h"
+#include "vm/StencilEnums.h" // TryNoteKind
+
+using namespace js;
+using namespace js::frontend;
+
+DoWhileEmitter::DoWhileEmitter(BytecodeEmitter* bce) : bce_(bce) {}
+
+bool DoWhileEmitter::emitBody(uint32_t doPos, uint32_t bodyPos) {
+ MOZ_ASSERT(state_ == State::Start);
+
+ // Ensure that the column of the 'do' is set properly.
+ if (!bce_->updateSourceCoordNotes(doPos)) {
+ return false;
+ }
+
+ // We need a nop here to make it possible to set a breakpoint on `do`.
+ if (!bce_->emit1(JSOp::Nop)) {
+ return false;
+ }
+
+ loopInfo_.emplace(bce_, StatementKind::DoLoop);
+
+ if (!loopInfo_->emitLoopHead(bce_, mozilla::Some(bodyPos))) {
+ return false;
+ }
+
+#ifdef DEBUG
+ state_ = State::Body;
+#endif
+ return true;
+}
+
+bool DoWhileEmitter::emitCond() {
+ MOZ_ASSERT(state_ == State::Body);
+
+ if (!loopInfo_->emitContinueTarget(bce_)) {
+ return false;
+ }
+
+#ifdef DEBUG
+ state_ = State::Cond;
+#endif
+ return true;
+}
+
+bool DoWhileEmitter::emitEnd() {
+ MOZ_ASSERT(state_ == State::Cond);
+
+ if (!loopInfo_->emitLoopEnd(bce_, JSOp::JumpIfTrue, TryNoteKind::Loop)) {
+ return false;
+ }
+
+ loopInfo_.reset();
+
+#ifdef DEBUG
+ state_ = State::End;
+#endif
+ return true;
+}
diff --git a/js/src/frontend/DoWhileEmitter.h b/js/src/frontend/DoWhileEmitter.h
new file mode 100644
index 0000000000..7b970a4595
--- /dev/null
+++ b/js/src/frontend/DoWhileEmitter.h
@@ -0,0 +1,80 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef frontend_DoWhileEmitter_h
+#define frontend_DoWhileEmitter_h
+
+#include "mozilla/Attributes.h"
+#include "mozilla/Maybe.h"
+
+#include <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(offset_of_do, 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
+ [[nodiscard]] bool emitBody(uint32_t doPos, uint32_t bodyPos);
+ [[nodiscard]] bool emitCond();
+ [[nodiscard]] bool emitEnd();
+};
+
+} /* namespace frontend */
+} /* namespace js */
+
+#endif /* frontend_DoWhileEmitter_h */
diff --git a/js/src/frontend/EitherParser.h b/js/src/frontend/EitherParser.h
new file mode 100644
index 0000000000..a5201eedcd
--- /dev/null
+++ b/js/src/frontend/EitherParser.h
@@ -0,0 +1,58 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * A variant-like class abstracting operations on a Parser with a given
+ * ParseHandler but unspecified character type.
+ */
+
+#ifndef frontend_EitherParser_h
+#define frontend_EitherParser_h
+
+#include "mozilla/Utf8.h"
+#include "mozilla/Variant.h"
+
+#include <type_traits>
+#include <utility>
+
+#include "frontend/Parser.h"
+
+namespace js::frontend {
+
+class EitherParser final {
+ // Leave this as a variant, to promote good form until 8-bit parser
+ // integration.
+ mozilla::Variant<Parser<FullParseHandler, char16_t>* const,
+ Parser<FullParseHandler, mozilla::Utf8Unit>* const>
+ parser;
+
+ public:
+ template <class Parser>
+ explicit EitherParser(Parser* parser) : parser(parser) {}
+
+ const ErrorReporter& errorReporter() const {
+ return parser.match([](auto* parser) -> const frontend::ErrorReporter& {
+ return parser->tokenStream;
+ });
+ }
+
+ void computeLineAndColumn(uint32_t offset, uint32_t* line,
+ uint32_t* column) const {
+ return parser.match([offset, line, column](auto* parser) -> void {
+ parser->tokenStream.computeLineAndColumn(offset, line, column);
+ });
+ }
+
+ ParserAtomsTable& parserAtoms() {
+ auto& base = parser.match(
+ [](auto* parser) -> frontend::ParserSharedBase& { return *parser; });
+ return base.parserAtoms();
+ }
+};
+
+} /* namespace js::frontend */
+
+#endif /* frontend_EitherParser_h */
diff --git a/js/src/frontend/ElemOpEmitter.cpp b/js/src/frontend/ElemOpEmitter.cpp
new file mode 100644
index 0000000000..0d563e466d
--- /dev/null
+++ b/js/src/frontend/ElemOpEmitter.cpp
@@ -0,0 +1,257 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "frontend/ElemOpEmitter.h"
+
+#include "frontend/BytecodeEmitter.h"
+#include "frontend/SharedContext.h"
+#include "vm/Opcodes.h"
+#include "vm/ThrowMsgKind.h" // ThrowMsgKind
+
+using namespace js;
+using namespace js::frontend;
+
+ElemOpEmitter::ElemOpEmitter(BytecodeEmitter* bce, Kind kind, ObjKind objKind)
+ : bce_(bce), kind_(kind), objKind_(objKind) {}
+
+bool ElemOpEmitter::prepareForObj() {
+ MOZ_ASSERT(state_ == State::Start);
+
+#ifdef DEBUG
+ state_ = State::Obj;
+#endif
+ return true;
+}
+
+bool ElemOpEmitter::prepareForKey() {
+ MOZ_ASSERT(state_ == State::Obj);
+
+ if (isCall()) {
+ if (!bce_->emit1(JSOp::Dup)) {
+ // [stack] # if Super
+ // [stack] THIS THIS
+ // [stack] # otherwise
+ // [stack] OBJ OBJ
+ return false;
+ }
+ }
+
+#ifdef DEBUG
+ state_ = State::Key;
+#endif
+ return true;
+}
+
+bool ElemOpEmitter::emitGet() {
+ MOZ_ASSERT(state_ == State::Key);
+
+ // Inc/dec and compound assignment use the KEY twice, but if it's an object,
+ // it must be converted ToPropertyKey only once, per spec.
+ if (isIncDec() || isCompoundAssignment()) {
+ if (!bce_->emit1(JSOp::ToPropertyKey)) {
+ // [stack] # if Super
+ // [stack] THIS KEY
+ // [stack] # otherwise
+ // [stack] OBJ KEY
+ return false;
+ }
+ }
+
+ if (isSuper()) {
+ if (!bce_->emitSuperBase()) {
+ // [stack] THIS? THIS KEY SUPERBASE
+ return false;
+ }
+ }
+ if (isIncDec() || isCompoundAssignment()) {
+ if (isSuper()) {
+ if (!bce_->emitDupAt(2, 3)) {
+ // [stack] THIS KEY SUPERBASE THIS KEY SUPERBASE
+ return false;
+ }
+ } else {
+ if (!bce_->emit1(JSOp::Dup2)) {
+ // [stack] OBJ KEY OBJ KEY
+ return false;
+ }
+ }
+ }
+
+ JSOp op;
+ if (isSuper()) {
+ op = JSOp::GetElemSuper;
+ } else {
+ op = JSOp::GetElem;
+ }
+ if (!bce_->emitElemOpBase(op)) {
+ // [stack] # if Get
+ // [stack] ELEM
+ // [stack] # if Call
+ // [stack] THIS ELEM
+ // [stack] # if Inc/Dec/Assignment, with Super
+ // [stack] THIS KEY SUPERBASE ELEM
+ // [stack] # if Inc/Dec/Assignment, other
+ // [stack] OBJ KEY ELEM
+ return false;
+ }
+ if (isCall()) {
+ if (!bce_->emit1(JSOp::Swap)) {
+ // [stack] ELEM THIS
+ return false;
+ }
+ }
+
+#ifdef DEBUG
+ state_ = State::Get;
+#endif
+ return true;
+}
+
+bool ElemOpEmitter::prepareForRhs() {
+ MOZ_ASSERT(isSimpleAssignment() || isPropInit() || isCompoundAssignment());
+ MOZ_ASSERT_IF(isSimpleAssignment() || isPropInit(), state_ == State::Key);
+ MOZ_ASSERT_IF(isCompoundAssignment(), state_ == State::Get);
+
+ if (isSimpleAssignment() || isPropInit()) {
+ // For CompoundAssignment, SuperBase is already emitted by emitGet.
+ if (isSuper()) {
+ if (!bce_->emitSuperBase()) {
+ // [stack] THIS KEY SUPERBASE
+ return false;
+ }
+ }
+ }
+
+#ifdef DEBUG
+ state_ = State::Rhs;
+#endif
+ return true;
+}
+
+bool ElemOpEmitter::skipObjAndKeyAndRhs() {
+ MOZ_ASSERT(state_ == State::Start);
+ MOZ_ASSERT(isSimpleAssignment() || isPropInit());
+
+#ifdef DEBUG
+ state_ = State::Rhs;
+#endif
+ return true;
+}
+
+bool ElemOpEmitter::emitDelete() {
+ MOZ_ASSERT(state_ == State::Key);
+ MOZ_ASSERT(isDelete());
+
+ if (isSuper()) {
+ if (!bce_->emit1(JSOp::ToPropertyKey)) {
+ // [stack] THIS KEY
+ return false;
+ }
+ if (!bce_->emitSuperBase()) {
+ // [stack] THIS KEY SUPERBASE
+ return false;
+ }
+
+ // Unconditionally throw when attempting to delete a super-reference.
+ if (!bce_->emit2(JSOp::ThrowMsg, uint8_t(ThrowMsgKind::CantDeleteSuper))) {
+ // [stack] THIS KEY SUPERBASE
+ return false;
+ }
+
+ // Another wrinkle: Balance the stack from the emitter's point of view.
+ // Execution will not reach here, as the last bytecode threw.
+ if (!bce_->emitPopN(2)) {
+ // [stack] THIS
+ return false;
+ }
+ } else {
+ JSOp op = bce_->sc->strict() ? JSOp::StrictDelElem : JSOp::DelElem;
+ if (!bce_->emitElemOpBase(op)) {
+ // SUCCEEDED
+ return false;
+ }
+ }
+
+#ifdef DEBUG
+ state_ = State::Delete;
+#endif
+ return true;
+}
+
+bool ElemOpEmitter::emitAssignment() {
+ MOZ_ASSERT(isSimpleAssignment() || isPropInit() || isCompoundAssignment());
+ MOZ_ASSERT(state_ == State::Rhs);
+
+ MOZ_ASSERT_IF(isPropInit(), !isSuper());
+
+ JSOp setOp = isPropInit() ? JSOp::InitElem
+ : isSuper() ? bce_->sc->strict() ? JSOp::StrictSetElemSuper
+ : JSOp::SetElemSuper
+ : bce_->sc->strict() ? JSOp::StrictSetElem
+ : JSOp::SetElem;
+ if (!bce_->emitElemOpBase(setOp)) {
+ // [stack] ELEM
+ return false;
+ }
+
+#ifdef DEBUG
+ state_ = State::Assignment;
+#endif
+ return true;
+}
+
+bool ElemOpEmitter::emitIncDec(ValueUsage valueUsage) {
+ MOZ_ASSERT(state_ == State::Key);
+ MOZ_ASSERT(isIncDec());
+
+ if (!emitGet()) {
+ // [stack] ... ELEM
+ return false;
+ }
+
+ MOZ_ASSERT(state_ == State::Get);
+
+ JSOp incOp = isInc() ? JSOp::Inc : JSOp::Dec;
+ if (!bce_->emit1(JSOp::ToNumeric)) {
+ // [stack] ... N
+ return false;
+ }
+ if (isPostIncDec() && valueUsage == ValueUsage::WantValue) {
+ // [stack] OBJ KEY SUPERBASE? N
+ if (!bce_->emit1(JSOp::Dup)) {
+ // [stack] ... N N
+ return false;
+ }
+ if (!bce_->emit2(JSOp::Unpick, 3 + isSuper())) {
+ // [stack] N OBJ KEY SUPERBASE? N
+ return false;
+ }
+ }
+ if (!bce_->emit1(incOp)) {
+ // [stack] ... N+1
+ return false;
+ }
+
+ JSOp setOp =
+ isSuper()
+ ? (bce_->sc->strict() ? JSOp::StrictSetElemSuper : JSOp::SetElemSuper)
+ : (bce_->sc->strict() ? JSOp::StrictSetElem : JSOp::SetElem);
+ if (!bce_->emitElemOpBase(setOp)) {
+ // [stack] N? N+1
+ return false;
+ }
+ if (isPostIncDec() && valueUsage == ValueUsage::WantValue) {
+ if (!bce_->emit1(JSOp::Pop)) {
+ // [stack] N
+ return false;
+ }
+ }
+
+#ifdef DEBUG
+ state_ = State::IncDec;
+#endif
+ return true;
+}
diff --git a/js/src/frontend/ElemOpEmitter.h b/js/src/frontend/ElemOpEmitter.h
new file mode 100644
index 0000000000..cf528bf312
--- /dev/null
+++ b/js/src/frontend/ElemOpEmitter.h
@@ -0,0 +1,267 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef frontend_ElemOpEmitter_h
+#define frontend_ElemOpEmitter_h
+
+#include "mozilla/Attributes.h"
+
+namespace js {
+namespace frontend {
+
+struct BytecodeEmitter;
+enum class ValueUsage;
+
+// Class for emitting bytecode for element operation.
+//
+// Usage: (check for the return value is omitted for simplicity)
+//
+// `obj[key];`
+// ElemOpEmitter eoe(this,
+// ElemOpEmitter::Kind::Get,
+// ElemOpEmitter::ObjKind::Other);
+// eoe.prepareForObj();
+// emit(obj);
+// eoe.prepareForKey();
+// emit(key);
+// eoe.emitGet();
+//
+// `super[key];`
+// ElemOpEmitter eoe(this,
+// ElemOpEmitter::Kind::Get,
+// ElemOpEmitter::ObjKind::Super);
+// eoe.prepareForObj();
+// emit(this_for_super);
+// eoe.prepareForKey();
+// emit(key);
+// eoe.emitGet();
+//
+// `obj[key]();`
+// ElemOpEmitter eoe(this,
+// ElemOpEmitter::Kind::Call,
+// ElemOpEmitter::ObjKind::Other);
+// eoe.prepareForObj();
+// emit(obj);
+// eoe.prepareForKey();
+// emit(key);
+// eoe.emitGet();
+// emit_call_here();
+//
+// `new obj[key]();`
+// ElemOpEmitter eoe(this,
+// ElemOpEmitter::Kind::Call,
+// ElemOpEmitter::ObjKind::Other);
+// eoe.prepareForObj();
+// emit(obj);
+// eoe.prepareForKey();
+// emit(key);
+// eoe.emitGet();
+// emit_call_here();
+//
+// `delete obj[key];`
+// ElemOpEmitter eoe(this,
+// ElemOpEmitter::Kind::Delete,
+// ElemOpEmitter::ObjKind::Other);
+// eoe.prepareForObj();
+// emit(obj);
+// eoe.prepareForKey();
+// emit(key);
+// eoe.emitDelete();
+//
+// `delete super[key];`
+// ElemOpEmitter eoe(this,
+// ElemOpEmitter::Kind::Delete,
+// ElemOpEmitter::ObjKind::Super);
+// eoe.prepareForObj();
+// emit(this_for_super);
+// eoe.prepareForKey();
+// emit(key);
+// eoe.emitDelete();
+//
+// `obj[key]++;`
+// ElemOpEmitter eoe(this,
+// ElemOpEmitter::Kind::PostIncrement,
+// ElemOpEmitter::ObjKind::Other);
+// eoe.prepareForObj();
+// emit(obj);
+// eoe.prepareForKey();
+// emit(key);
+// eoe.emitIncDec();
+//
+// `obj[key] = value;`
+// ElemOpEmitter eoe(this,
+// ElemOpEmitter::Kind::SimpleAssignment,
+// ElemOpEmitter::ObjKind::Other);
+// eoe.prepareForObj();
+// emit(obj);
+// eoe.prepareForKey();
+// emit(key);
+// eoe.prepareForRhs();
+// emit(value);
+// eoe.emitAssignment();
+//
+// `obj[key] += value;`
+// ElemOpEmitter eoe(this,
+// ElemOpEmitter::Kind::CompoundAssignment,
+// ElemOpEmitter::ObjKind::Other);
+// eoe.prepareForObj();
+// emit(obj);
+// eoe.prepareForKey();
+// emit(key);
+// eoe.emitGet();
+// eoe.prepareForRhs();
+// emit(value);
+// emit_add_op_here();
+// eoe.emitAssignment();
+//
+class MOZ_STACK_CLASS ElemOpEmitter {
+ public:
+ enum class Kind {
+ Get,
+ Call,
+ Delete,
+ PostIncrement,
+ PreIncrement,
+ PostDecrement,
+ PreDecrement,
+ SimpleAssignment,
+ PropInit,
+ CompoundAssignment
+ };
+ enum class ObjKind { Super, Other };
+
+ private:
+ BytecodeEmitter* bce_;
+
+ Kind kind_;
+ ObjKind objKind_;
+
+#ifdef DEBUG
+ // The state of this emitter.
+ //
+ // skipObjAndKeyAndRhs
+ // +------------------------------------------------+
+ // | |
+ // +-------+ | prepareForObj +-----+ prepareForKey +-----+ |
+ // | Start |-+-------------->| Obj |-------------->| Key |-+ |
+ // +-------+ +-----+ +-----+ | |
+ // | |
+ // +-------------------------------------------------------+ |
+ // | |
+ // | [Get] |
+ // | [Call] |
+ // | emitGet +-----+ |
+ // +---------->| Get | |
+ // | +-----+ |
+ // | |
+ // | [Delete] |
+ // | emitDelete +--------+ |
+ // +------------->| Delete | |
+ // | +--------+ |
+ // | |
+ // | [PostIncrement] |
+ // | [PreIncrement] |
+ // | [PostDecrement] |
+ // | [PreDecrement] |
+ // | emitIncDec +--------+ |
+ // +------------->| IncDec | |
+ // | +--------+ |
+ // | +-------------------+
+ // | [SimpleAssignment] |
+ // | [PropInit] |
+ // | prepareForRhs v +-----+
+ // +--------------------->+-------------->+->| Rhs |-+
+ // | ^ +-----+ |
+ // | | |
+ // | | +-------------+
+ // | [CompoundAssignment] | |
+ // | emitGet +-----+ | | emitAssignment +------------+
+ // +---------->| Get |----+ +--------------->| Assignment |
+ // +-----+ +------------+
+ enum class State {
+ // The initial state.
+ Start,
+
+ // After calling prepareForObj.
+ Obj,
+
+ // After calling emitKey.
+ Key,
+
+ // After calling emitGet.
+ Get,
+
+ // After calling emitDelete.
+ Delete,
+
+ // After calling emitIncDec.
+ IncDec,
+
+ // After calling prepareForRhs or skipObjAndKeyAndRhs.
+ Rhs,
+
+ // After calling emitAssignment.
+ Assignment,
+ };
+ State state_ = State::Start;
+#endif
+
+ public:
+ ElemOpEmitter(BytecodeEmitter* bce, Kind kind, ObjKind objKind);
+
+ private:
+ [[nodiscard]] bool isCall() const { return kind_ == Kind::Call; }
+
+ [[nodiscard]] bool isSimpleAssignment() const {
+ return kind_ == Kind::SimpleAssignment;
+ }
+
+ [[nodiscard]] bool isPropInit() const { return kind_ == Kind::PropInit; }
+
+ [[nodiscard]] bool isDelete() const { return kind_ == Kind::Delete; }
+
+ [[nodiscard]] bool isCompoundAssignment() const {
+ return kind_ == Kind::CompoundAssignment;
+ }
+
+ [[nodiscard]] bool isIncDec() const {
+ return isPostIncDec() || isPreIncDec();
+ }
+
+ [[nodiscard]] bool isPostIncDec() const {
+ return kind_ == Kind::PostIncrement || kind_ == Kind::PostDecrement;
+ }
+
+ [[nodiscard]] bool isPreIncDec() const {
+ return kind_ == Kind::PreIncrement || kind_ == Kind::PreDecrement;
+ }
+
+ [[nodiscard]] bool isInc() const {
+ return kind_ == Kind::PostIncrement || kind_ == Kind::PreIncrement;
+ }
+
+ [[nodiscard]] bool isSuper() const { return objKind_ == ObjKind::Super; }
+
+ public:
+ [[nodiscard]] bool prepareForObj();
+ [[nodiscard]] bool prepareForKey();
+
+ [[nodiscard]] bool emitGet();
+
+ [[nodiscard]] bool prepareForRhs();
+ [[nodiscard]] bool skipObjAndKeyAndRhs();
+
+ [[nodiscard]] bool emitDelete();
+
+ [[nodiscard]] bool emitAssignment();
+
+ [[nodiscard]] bool emitIncDec(ValueUsage valueUsage);
+};
+
+} /* namespace frontend */
+} // namespace js
+
+#endif /* frontend_ElemOpEmitter_h */
diff --git a/js/src/frontend/EmitterScope.cpp b/js/src/frontend/EmitterScope.cpp
new file mode 100644
index 0000000000..96cb8a08b2
--- /dev/null
+++ b/js/src/frontend/EmitterScope.cpp
@@ -0,0 +1,1117 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "frontend/EmitterScope.h"
+
+#include "frontend/AbstractScopePtr.h"
+#include "frontend/BytecodeEmitter.h"
+#include "frontend/ModuleSharedContext.h"
+#include "frontend/TDZCheckCache.h"
+#include "js/friend/ErrorMessages.h" // JSMSG_*
+#include "vm/EnvironmentObject.h" // ClassBodyLexicalEnvironmentObject
+#include "vm/WellKnownAtom.h" // js_*_str
+
+using namespace js;
+using namespace js::frontend;
+
+using mozilla::DebugOnly;
+using mozilla::Maybe;
+using mozilla::Nothing;
+using mozilla::Some;
+
+EmitterScope::EmitterScope(BytecodeEmitter* bce)
+ : Nestable<EmitterScope>(&bce->innermostEmitterScope_),
+ nameCache_(bce->fc->nameCollectionPool()),
+ hasEnvironment_(false),
+ environmentChainLength_(0),
+ nextFrameSlot_(0),
+ scopeIndex_(ScopeNote::NoScopeIndex),
+ noteIndex_(ScopeNote::NoScopeNoteIndex) {}
+
+bool EmitterScope::ensureCache(BytecodeEmitter* bce) {
+ return nameCache_.acquire(bce->fc);
+}
+
+bool EmitterScope::checkSlotLimits(BytecodeEmitter* bce,
+ const ParserBindingIter& bi) {
+ if (bi.nextFrameSlot() >= LOCALNO_LIMIT ||
+ bi.nextEnvironmentSlot() >= ENVCOORD_SLOT_LIMIT) {
+ bce->reportError(nullptr, JSMSG_TOO_MANY_LOCALS);
+ return false;
+ }
+ return true;
+}
+
+bool EmitterScope::checkEnvironmentChainLength(BytecodeEmitter* bce) {
+ uint32_t hops;
+ if (EmitterScope* emitterScope = enclosing(&bce)) {
+ hops = emitterScope->environmentChainLength_;
+ } else if (!bce->compilationState.input.enclosingScope.isNull()) {
+ hops =
+ bce->compilationState.scopeContext.enclosingScopeEnvironmentChainLength;
+ } else {
+ // If we're compiling module, enclosingScope is nullptr and it means empty
+ // global scope.
+ // See also the assertion in CompilationStencil::instantiateStencils.
+ //
+ // Global script also uses enclosingScope == nullptr, but it shouldn't call
+ // checkEnvironmentChainLength.
+ MOZ_ASSERT(bce->sc->isModule());
+ hops = ModuleScope::EnclosingEnvironmentChainLength;
+ }
+
+ if (hops >= ENVCOORD_HOPS_LIMIT - 1) {
+ bce->reportError(nullptr, JSMSG_TOO_DEEP, js_function_str);
+ return false;
+ }
+
+ environmentChainLength_ = mozilla::AssertedCast<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,
+ TaggedParserAtomIndex name,
+ NameLocation loc) {
+ NameLocationMap& cache = *nameCache_;
+ NameLocationMap::AddPtr p = cache.lookupForAdd(name);
+ MOZ_ASSERT(!p);
+ if (!cache.add(p, name, loc)) {
+ ReportOutOfMemory(bce->fc);
+ return false;
+ }
+ return true;
+}
+
+Maybe<NameLocation> EmitterScope::lookupInCache(BytecodeEmitter* bce,
+ TaggedParserAtomIndex name) {
+ if (NameLocationMap::Ptr p = nameCache_->lookup(name)) {
+ return Some(p->value().wrapped);
+ }
+ if (fallbackFreeNameLocation_ && nameCanBeFree(bce, name)) {
+ return fallbackFreeNameLocation_;
+ }
+ return Nothing();
+}
+
+EmitterScope* EmitterScope::enclosing(BytecodeEmitter** bce) const {
+ // There is an enclosing scope with access to the same frame.
+ if (EmitterScope* inFrame = enclosingInFrame()) {
+ return inFrame;
+ }
+
+ // We are currently compiling the enclosing script, look in the
+ // enclosing BCE.
+ if ((*bce)->parent) {
+ *bce = (*bce)->parent;
+ return (*bce)->innermostEmitterScopeNoCheck();
+ }
+
+ return nullptr;
+}
+
+mozilla::Maybe<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,
+ TaggedParserAtomIndex name) {
+ // '.generator' cannot be accessed by name.
+ return name != TaggedParserAtomIndex::WellKnown::dotGenerator();
+}
+
+NameLocation EmitterScope::searchAndCache(BytecodeEmitter* bce,
+ TaggedParserAtomIndex 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) {
+ MOZ_ASSERT(bce->compilationState.input.target ==
+ CompilationInput::CompilationTarget::Delazification ||
+ bce->compilationState.input.target ==
+ CompilationInput::CompilationTarget::Eval);
+ inCurrentScript = false;
+ loc = Some(bce->compilationState.scopeContext.searchInEnclosingScope(
+ bce->fc, bce->compilationState.input, bce->parserAtoms(), name));
+ if (loc->kind() == NameLocation::Kind::EnvironmentCoordinate) {
+ *loc = loc->addHops(hops);
+ }
+ }
+
+ // Each script has its own frame. A free name that is accessed
+ // from an inner script must not be a frame slot access. If this
+ // assertion is hit, it is a bug in the free name analysis in the
+ // parser.
+ MOZ_ASSERT_IF(!inCurrentScript, loc->kind() != NameLocation::Kind::FrameSlot);
+
+ // It is always correct to not cache the location. Ignore OOMs to make
+ // lookups infallible.
+ if (!putNameInCache(bce, name, *loc)) {
+ bce->fc->recoverFromOutOfMemory();
+ }
+
+ return *loc;
+}
+
+bool EmitterScope::internEmptyGlobalScopeAsBody(BytecodeEmitter* bce) {
+ // Only the self-hosted top-level script uses this. If this changes, you must
+ // update ScopeStencil::enclosing.
+ MOZ_ASSERT(bce->emitterMode == BytecodeEmitter::SelfHosting);
+
+ hasEnvironment_ = Scope::hasEnvironment(ScopeKind::Global);
+
+ bce->bodyScopeIndex =
+ GCThingIndex(bce->perScriptData().gcThingList().length());
+ return bce->perScriptData().gcThingList().appendEmptyGlobalScope(
+ &scopeIndex_);
+}
+
+bool EmitterScope::internScopeStencil(BytecodeEmitter* bce,
+ ScopeIndex scopeIndex) {
+ ScopeStencil& scope = bce->compilationState.scopeData[scopeIndex.index];
+ hasEnvironment_ = scope.hasEnvironment();
+ return bce->perScriptData().gcThingList().append(scopeIndex, &scopeIndex_);
+}
+
+bool EmitterScope::internBodyScopeStencil(BytecodeEmitter* bce,
+ ScopeIndex scopeIndex) {
+ MOZ_ASSERT(bce->bodyScopeIndex == ScopeNote::NoScopeIndex,
+ "There can be only one body scope");
+ bce->bodyScopeIndex =
+ GCThingIndex(bce->perScriptData().gcThingList().length());
+ return internScopeStencil(bce, scopeIndex);
+}
+
+bool EmitterScope::appendScopeNote(BytecodeEmitter* bce) {
+ MOZ_ASSERT(ScopeKindIsInBody(scope(bce).kind()) && enclosingInFrame(),
+ "Scope notes are not needed for body-level scopes.");
+ noteIndex_ = bce->bytecodeSection().scopeNoteList().length();
+ return bce->bytecodeSection().scopeNoteList().append(
+ index(), bce->bytecodeSection().offset(),
+ enclosingInFrame() ? enclosingInFrame()->noteIndex()
+ : ScopeNote::NoScopeNoteIndex);
+}
+
+bool EmitterScope::clearFrameSlotRange(BytecodeEmitter* bce, JSOp opcode,
+ uint32_t slotStart,
+ uint32_t slotEnd) const {
+ MOZ_ASSERT(opcode == JSOp::Uninitialized || opcode == JSOp::Undefined);
+
+ // Lexical bindings throw ReferenceErrors if they are used before
+ // initialization. See ES6 8.1.1.1.6.
+ //
+ // For completeness, lexical bindings are initialized in ES6 by calling
+ // InitializeBinding, after which touching the binding will no longer
+ // throw reference errors. See 13.1.11, 9.2.13, 13.6.3.4, 13.6.4.6,
+ // 13.6.4.8, 13.14.5, 15.1.8, and 15.2.0.15.
+ //
+ // This code is also used to reset `var`s to `undefined` when entering an
+ // extra body var scope; and to clear slots when leaving a block, in
+ // generators and async functions, to avoid keeping garbage alive
+ // indefinitely.
+ if (slotStart != slotEnd) {
+ if (!bce->emit1(opcode)) {
+ return false;
+ }
+ for (uint32_t slot = slotStart; slot < slotEnd; slot++) {
+ if (!bce->emitLocalOp(JSOp::InitLexical, slot)) {
+ return false;
+ }
+ }
+ if (!bce->emit1(JSOp::Pop)) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+void EmitterScope::dump(BytecodeEmitter* bce) {
+ fprintf(stdout, "EmitterScope [%s] %p\n", ScopeKindString(scope(bce).kind()),
+ this);
+
+ for (NameLocationMap::Range r = nameCache_->all(); !r.empty(); r.popFront()) {
+ const NameLocation& l = r.front().value();
+
+ auto atom = r.front().key();
+ UniqueChars bytes = bce->parserAtoms().toPrintableString(atom);
+ if (!bytes) {
+ ReportOutOfMemory(bce->fc);
+ return;
+ }
+ if (l.kind() != NameLocation::Kind::Dynamic) {
+ fprintf(stdout, " %s %s ", BindingKindString(l.bindingKind()),
+ bytes.get());
+ } else {
+ fprintf(stdout, " %s ", bytes.get());
+ }
+
+ switch (l.kind()) {
+ case NameLocation::Kind::Dynamic:
+ fprintf(stdout, "dynamic\n");
+ break;
+ case NameLocation::Kind::Global:
+ fprintf(stdout, "global\n");
+ break;
+ case NameLocation::Kind::Intrinsic:
+ fprintf(stdout, "intrinsic\n");
+ break;
+ case NameLocation::Kind::NamedLambdaCallee:
+ fprintf(stdout, "named lambda callee\n");
+ break;
+ case NameLocation::Kind::Import:
+ fprintf(stdout, "import\n");
+ break;
+ case NameLocation::Kind::ArgumentSlot:
+ fprintf(stdout, "arg slot=%u\n", l.argumentSlot());
+ break;
+ case NameLocation::Kind::FrameSlot:
+ fprintf(stdout, "frame slot=%u\n", l.frameSlot());
+ break;
+ case NameLocation::Kind::EnvironmentCoordinate:
+ fprintf(stdout, "environment hops=%u slot=%u\n",
+ l.environmentCoordinate().hops(),
+ l.environmentCoordinate().slot());
+ break;
+ case NameLocation::Kind::DebugEnvironmentCoordinate:
+ fprintf(stdout, "debugEnvironment hops=%u slot=%u\n",
+ l.environmentCoordinate().hops(),
+ l.environmentCoordinate().slot());
+ break;
+ case NameLocation::Kind::DynamicAnnexBVar:
+ fprintf(stdout, "dynamic annex b var\n");
+ break;
+ }
+ }
+
+ fprintf(stdout, "\n");
+}
+
+bool EmitterScope::enterLexical(BytecodeEmitter* bce, ScopeKind kind,
+ LexicalScope::ParserData* bindings) {
+ MOZ_ASSERT(kind != ScopeKind::NamedLambda &&
+ kind != ScopeKind::StrictNamedLambda);
+ MOZ_ASSERT(this == bce->innermostEmitterScopeNoCheck());
+
+ if (!ensureCache(bce)) {
+ return false;
+ }
+
+ // Resolve bindings.
+ TDZCheckCache* tdzCache = bce->innermostTDZCheckCache;
+ uint32_t firstFrameSlot = frameSlotStart();
+ ParserBindingIter bi(*bindings, firstFrameSlot, /* isNamedLambda = */ false);
+ for (; bi; bi++) {
+ if (!checkSlotLimits(bce, bi)) {
+ return false;
+ }
+
+ NameLocation loc = bi.nameLocation();
+ if (!putNameInCache(bce, bi.name(), loc)) {
+ return false;
+ }
+
+ if (!tdzCache->noteTDZCheck(bce, bi.name(), CheckTDZ)) {
+ return false;
+ }
+ }
+
+ updateFrameFixedSlots(bce, bi);
+
+ ScopeIndex scopeIndex;
+ if (!ScopeStencil::createForLexicalScope(
+ bce->fc, bce->compilationState, kind, bindings, firstFrameSlot,
+ enclosingScopeIndex(bce), &scopeIndex)) {
+ return false;
+ }
+ if (!internScopeStencil(bce, scopeIndex)) {
+ return false;
+ }
+
+ if (ScopeKindIsInBody(kind) && hasEnvironment()) {
+ // After interning the VM scope we can get the scope index.
+ if (!bce->emitInternedScopeOp(index(), JSOp::PushLexicalEnv)) {
+ return false;
+ }
+ }
+
+ // Lexical scopes need notes to be mapped from a pc.
+ if (!appendScopeNote(bce)) {
+ return false;
+ }
+
+ // Put frame slots in TDZ. Environment slots are poisoned during
+ // environment creation.
+ //
+ // This must be done after appendScopeNote to be considered in the extent
+ // of the scope.
+ if (!deadZoneFrameSlotRange(bce, firstFrameSlot, frameSlotEnd())) {
+ return false;
+ }
+
+ return checkEnvironmentChainLength(bce);
+}
+
+bool EmitterScope::enterClassBody(BytecodeEmitter* bce, ScopeKind kind,
+ ClassBodyScope::ParserData* bindings) {
+ MOZ_ASSERT(kind == ScopeKind::ClassBody);
+ MOZ_ASSERT(this == bce->innermostEmitterScopeNoCheck());
+
+ if (!ensureCache(bce)) {
+ return false;
+ }
+
+ // Resolve bindings.
+ TDZCheckCache* tdzCache = bce->innermostTDZCheckCache;
+ uint32_t firstFrameSlot = frameSlotStart();
+ ParserBindingIter bi(*bindings, firstFrameSlot);
+ for (; bi; bi++) {
+ if (!checkSlotLimits(bce, bi)) {
+ return false;
+ }
+
+ NameLocation loc = bi.nameLocation();
+ if (!putNameInCache(bce, bi.name(), loc)) {
+ return false;
+ }
+
+ if (!tdzCache->noteTDZCheck(bce, bi.name(), CheckTDZ)) {
+ return false;
+ }
+ }
+
+ updateFrameFixedSlots(bce, bi);
+
+ ScopeIndex scopeIndex;
+ if (!ScopeStencil::createForClassBodyScope(
+ bce->fc, bce->compilationState, kind, bindings, firstFrameSlot,
+ enclosingScopeIndex(bce), &scopeIndex)) {
+ return false;
+ }
+ if (!internScopeStencil(bce, scopeIndex)) {
+ return false;
+ }
+
+ if (ScopeKindIsInBody(kind) && hasEnvironment()) {
+ // After interning the VM scope we can get the scope index.
+ //
+ // ClassBody uses PushClassBodyEnv, however, PopLexicalEnv supports both
+ // cases and doesn't need extra specialization.
+ if (!bce->emitInternedScopeOp(index(), JSOp::PushClassBodyEnv)) {
+ return false;
+ }
+ }
+
+ // Lexical scopes need notes to be mapped from a pc.
+ if (!appendScopeNote(bce)) {
+ return false;
+ }
+
+ return checkEnvironmentChainLength(bce);
+}
+
+bool EmitterScope::enterNamedLambda(BytecodeEmitter* bce, FunctionBox* funbox) {
+ MOZ_ASSERT(this == bce->innermostEmitterScopeNoCheck());
+ MOZ_ASSERT(funbox->namedLambdaBindings());
+
+ if (!ensureCache(bce)) {
+ return false;
+ }
+
+ ParserBindingIter bi(*funbox->namedLambdaBindings(), LOCALNO_LIMIT,
+ /* isNamedLambda = */ true);
+ MOZ_ASSERT(bi.kind() == BindingKind::NamedLambdaCallee);
+
+ // The lambda name, if not closed over, is accessed via JSOp::Callee and
+ // not a frame slot. Do not update frame slot information.
+ NameLocation loc = bi.nameLocation();
+ if (!putNameInCache(bce, bi.name(), loc)) {
+ return false;
+ }
+
+ bi++;
+ MOZ_ASSERT(!bi, "There should be exactly one binding in a NamedLambda scope");
+
+ ScopeKind scopeKind =
+ funbox->strict() ? ScopeKind::StrictNamedLambda : ScopeKind::NamedLambda;
+
+ ScopeIndex scopeIndex;
+ if (!ScopeStencil::createForLexicalScope(
+ bce->fc, bce->compilationState, scopeKind,
+ funbox->namedLambdaBindings(), LOCALNO_LIMIT,
+ enclosingScopeIndex(bce), &scopeIndex)) {
+ return false;
+ }
+ if (!internScopeStencil(bce, scopeIndex)) {
+ return false;
+ }
+
+ return checkEnvironmentChainLength(bce);
+}
+
+bool EmitterScope::enterFunction(BytecodeEmitter* bce, FunctionBox* funbox) {
+ MOZ_ASSERT(this == bce->innermostEmitterScopeNoCheck());
+
+ // If there are parameter expressions, there is an extra var scope.
+ if (!funbox->functionHasExtraBodyVarScope()) {
+ bce->setVarEmitterScope(this);
+ }
+
+ if (!ensureCache(bce)) {
+ return false;
+ }
+
+ // Resolve body-level bindings, if there are any.
+ auto bindings = funbox->functionScopeBindings();
+ if (bindings) {
+ NameLocationMap& cache = *nameCache_;
+
+ ParserBindingIter bi(*bindings, funbox->hasParameterExprs);
+ for (; bi; bi++) {
+ if (!checkSlotLimits(bce, bi)) {
+ return false;
+ }
+
+ NameLocation loc = bi.nameLocation();
+ NameLocationMap::AddPtr p = cache.lookupForAdd(bi.name());
+
+ // The only duplicate bindings that occur are simple formal
+ // parameters, in which case the last position counts, so update the
+ // location.
+ if (p) {
+ MOZ_ASSERT(bi.kind() == BindingKind::FormalParameter);
+ MOZ_ASSERT(!funbox->hasDestructuringArgs);
+ MOZ_ASSERT(!funbox->hasRest());
+ p->value() = loc;
+ continue;
+ }
+
+ if (!cache.add(p, bi.name(), loc)) {
+ ReportOutOfMemory(bce->fc);
+ return false;
+ }
+ }
+
+ updateFrameFixedSlots(bce, bi);
+ } else {
+ nextFrameSlot_ = 0;
+ }
+
+ // If the function's scope may be extended at runtime due to sloppy direct
+ // eval, any names beyond the function scope must be accessed dynamically as
+ // we don't know if the name will become a 'var' binding due to direct eval.
+ if (funbox->funHasExtensibleScope()) {
+ fallbackFreeNameLocation_ = Some(NameLocation::Dynamic());
+ } else if (funbox->isStandalone) {
+ // If the function is standalone, the enclosing scope is either an empty
+ // global or non-syntactic scope, and there's no static bindings.
+ if (bce->compilationState.input.target ==
+ CompilationInput::CompilationTarget::
+ StandaloneFunctionInNonSyntacticScope) {
+ fallbackFreeNameLocation_ = Some(NameLocation::Dynamic());
+ } else {
+ fallbackFreeNameLocation_ = Some(NameLocation::Global(BindingKind::Var));
+ }
+ }
+
+ // In case of parameter expressions, the parameters are lexical
+ // bindings and have TDZ.
+ if (funbox->hasParameterExprs && nextFrameSlot_) {
+ uint32_t paramFrameSlotEnd = 0;
+ for (ParserBindingIter bi(*bindings, true); bi; bi++) {
+ if (!BindingKindIsLexical(bi.kind())) {
+ break;
+ }
+
+ NameLocation loc = bi.nameLocation();
+ if (loc.kind() == NameLocation::Kind::FrameSlot) {
+ MOZ_ASSERT(paramFrameSlotEnd <= loc.frameSlot());
+ paramFrameSlotEnd = loc.frameSlot() + 1;
+ }
+ }
+
+ if (!deadZoneFrameSlotRange(bce, 0, paramFrameSlotEnd)) {
+ return false;
+ }
+ }
+
+ ScopeIndex scopeIndex;
+ if (!ScopeStencil::createForFunctionScope(
+ bce->fc, bce->compilationState, funbox->functionScopeBindings(),
+ funbox->hasParameterExprs,
+ funbox->needsCallObjectRegardlessOfBindings(), funbox->index(),
+ funbox->isArrow(), enclosingScopeIndex(bce), &scopeIndex)) {
+ return false;
+ }
+ if (!internBodyScopeStencil(bce, scopeIndex)) {
+ return false;
+ }
+
+ return checkEnvironmentChainLength(bce);
+}
+
+bool EmitterScope::enterFunctionExtraBodyVar(BytecodeEmitter* bce,
+ FunctionBox* funbox) {
+ MOZ_ASSERT(funbox->hasParameterExprs);
+ MOZ_ASSERT(funbox->extraVarScopeBindings() ||
+ funbox->needsExtraBodyVarEnvironmentRegardlessOfBindings());
+ MOZ_ASSERT(this == bce->innermostEmitterScopeNoCheck());
+
+ // The extra var scope is never popped once it's entered. It replaces the
+ // function scope as the var emitter scope.
+ bce->setVarEmitterScope(this);
+
+ if (!ensureCache(bce)) {
+ return false;
+ }
+
+ // Resolve body-level bindings, if there are any.
+ uint32_t firstFrameSlot = frameSlotStart();
+ if (auto bindings = funbox->extraVarScopeBindings()) {
+ ParserBindingIter bi(*bindings, firstFrameSlot);
+ for (; bi; bi++) {
+ if (!checkSlotLimits(bce, bi)) {
+ return false;
+ }
+
+ NameLocation loc = bi.nameLocation();
+ MOZ_ASSERT(bi.kind() == BindingKind::Var);
+ if (!putNameInCache(bce, bi.name(), loc)) {
+ return false;
+ }
+ }
+
+ uint32_t priorEnd = bce->maxFixedSlots;
+ updateFrameFixedSlots(bce, bi);
+
+ // If any of the bound slots were previously used, reset them to undefined.
+ // This doesn't break TDZ for let/const/class bindings because there aren't
+ // any in extra body var scopes. We assert above that bi.kind() is Var.
+ uint32_t end = std::min(priorEnd, nextFrameSlot_);
+ if (firstFrameSlot < end) {
+ if (!clearFrameSlotRange(bce, JSOp::Undefined, firstFrameSlot, end)) {
+ return false;
+ }
+ }
+ } else {
+ nextFrameSlot_ = firstFrameSlot;
+ }
+
+ // If the extra var scope may be extended at runtime due to sloppy
+ // direct eval, any names beyond the var scope must be accessed
+ // dynamically as we don't know if the name will become a 'var' binding
+ // due to direct eval.
+ if (funbox->funHasExtensibleScope()) {
+ fallbackFreeNameLocation_ = Some(NameLocation::Dynamic());
+ }
+
+ // Create and intern the VM scope.
+ ScopeIndex scopeIndex;
+ if (!ScopeStencil::createForVarScope(
+ bce->fc, bce->compilationState, ScopeKind::FunctionBodyVar,
+ funbox->extraVarScopeBindings(), firstFrameSlot,
+ funbox->needsExtraBodyVarEnvironmentRegardlessOfBindings(),
+ enclosingScopeIndex(bce), &scopeIndex)) {
+ return false;
+ }
+ if (!internScopeStencil(bce, scopeIndex)) {
+ return false;
+ }
+
+ if (hasEnvironment()) {
+ if (!bce->emitInternedScopeOp(index(), JSOp::PushVarEnv)) {
+ return false;
+ }
+ }
+
+ // The extra var scope needs a note to be mapped from a pc.
+ if (!appendScopeNote(bce)) {
+ return false;
+ }
+
+ return checkEnvironmentChainLength(bce);
+}
+
+bool EmitterScope::enterGlobal(BytecodeEmitter* bce,
+ GlobalSharedContext* globalsc) {
+ MOZ_ASSERT(this == bce->innermostEmitterScopeNoCheck());
+
+ // TODO-Stencil
+ // This is another snapshot-sensitive location.
+ // The incoming atoms from the global scope object should be snapshotted.
+ // For now, converting them to ParserAtoms here individually.
+
+ bce->setVarEmitterScope(this);
+
+ if (!ensureCache(bce)) {
+ return false;
+ }
+
+ if (bce->emitterMode == BytecodeEmitter::SelfHosting) {
+ // In self-hosting, it is incorrect to consult the global scope because
+ // self-hosted scripts are cloned into their target compartments before
+ // they are run. Instead of Global, Intrinsic is used for all names.
+ //
+ // Intrinsic lookups are redirected to the special intrinsics holder
+ // in the global object, into which any missing values are cloned
+ // lazily upon first access.
+ fallbackFreeNameLocation_ = Some(NameLocation::Intrinsic());
+
+ return internEmptyGlobalScopeAsBody(bce);
+ }
+
+ ScopeIndex scopeIndex;
+ if (!ScopeStencil::createForGlobalScope(bce->fc, bce->compilationState,
+ globalsc->scopeKind(),
+ globalsc->bindings, &scopeIndex)) {
+ return false;
+ }
+ if (!internBodyScopeStencil(bce, scopeIndex)) {
+ return false;
+ }
+
+ // See: JSScript::outermostScope.
+ MOZ_ASSERT(bce->bodyScopeIndex == GCThingIndex::outermostScopeIndex(),
+ "Global scope must be index 0");
+
+ // Resolve binding names.
+ //
+ // NOTE: BytecodeEmitter::emitDeclarationInstantiation will emit the
+ // redeclaration check and initialize these bindings.
+ if (globalsc->bindings) {
+ for (ParserBindingIter bi(*globalsc->bindings); bi; bi++) {
+ NameLocation loc = bi.nameLocation();
+ if (!putNameInCache(bce, bi.name(), loc)) {
+ return false;
+ }
+ }
+ }
+
+ // Note that to save space, we don't add free names to the cache for
+ // global scopes. They are assumed to be global vars in the syntactic
+ // global scope, dynamic accesses under non-syntactic global scope.
+ if (globalsc->scopeKind() == ScopeKind::Global) {
+ fallbackFreeNameLocation_ = Some(NameLocation::Global(BindingKind::Var));
+ } else {
+ fallbackFreeNameLocation_ = Some(NameLocation::Dynamic());
+ }
+
+ return true;
+}
+
+bool EmitterScope::enterEval(BytecodeEmitter* bce, EvalSharedContext* evalsc) {
+ MOZ_ASSERT(this == bce->innermostEmitterScopeNoCheck());
+
+ bce->setVarEmitterScope(this);
+
+ if (!ensureCache(bce)) {
+ return false;
+ }
+
+ // Create the `var` scope. Note that there is also a lexical scope, created
+ // separately in emitScript().
+ ScopeKind scopeKind =
+ evalsc->strict() ? ScopeKind::StrictEval : ScopeKind::Eval;
+
+ ScopeIndex scopeIndex;
+ if (!ScopeStencil::createForEvalScope(
+ bce->fc, bce->compilationState, scopeKind, evalsc->bindings,
+ enclosingScopeIndex(bce), &scopeIndex)) {
+ return false;
+ }
+ if (!internBodyScopeStencil(bce, scopeIndex)) {
+ return false;
+ }
+
+ if (evalsc->strict()) {
+ if (evalsc->bindings) {
+ ParserBindingIter bi(*evalsc->bindings, true);
+ for (; bi; bi++) {
+ if (!checkSlotLimits(bce, bi)) {
+ return false;
+ }
+
+ NameLocation loc = bi.nameLocation();
+ if (!putNameInCache(bce, bi.name(), loc)) {
+ return false;
+ }
+ }
+
+ updateFrameFixedSlots(bce, bi);
+ }
+ } else {
+ // For simplicity, treat all free name lookups in nonstrict eval scripts as
+ // dynamic.
+ fallbackFreeNameLocation_ = Some(NameLocation::Dynamic());
+ }
+
+ if (hasEnvironment()) {
+ if (!bce->emitInternedScopeOp(index(), JSOp::PushVarEnv)) {
+ return false;
+ }
+ } else {
+ // NOTE: BytecodeEmitter::emitDeclarationInstantiation will emit the
+ // redeclaration check and initialize these bindings for sloppy
+ // eval.
+
+ // As an optimization, if the eval does not have its own var
+ // environment and is directly enclosed in a global scope, then all
+ // free name lookups are global.
+ if (scope(bce).enclosing().is<GlobalScope>()) {
+ fallbackFreeNameLocation_ = Some(NameLocation::Global(BindingKind::Var));
+ }
+ }
+
+ return checkEnvironmentChainLength(bce);
+}
+
+bool EmitterScope::enterModule(BytecodeEmitter* bce,
+ ModuleSharedContext* modulesc) {
+ MOZ_ASSERT(this == bce->innermostEmitterScopeNoCheck());
+
+ bce->setVarEmitterScope(this);
+
+ if (!ensureCache(bce)) {
+ return false;
+ }
+
+ // Resolve body-level bindings, if there are any.
+ TDZCheckCache* tdzCache = bce->innermostTDZCheckCache;
+ Maybe<uint32_t> firstLexicalFrameSlot;
+ if (ModuleScope::ParserData* bindings = modulesc->bindings) {
+ ParserBindingIter bi(*bindings);
+ for (; bi; bi++) {
+ if (!checkSlotLimits(bce, bi)) {
+ return false;
+ }
+
+ NameLocation loc = bi.nameLocation();
+ if (!putNameInCache(bce, bi.name(), loc)) {
+ return false;
+ }
+
+ if (BindingKindIsLexical(bi.kind())) {
+ if (loc.kind() == NameLocation::Kind::FrameSlot &&
+ !firstLexicalFrameSlot) {
+ firstLexicalFrameSlot = Some(loc.frameSlot());
+ }
+
+ if (!tdzCache->noteTDZCheck(bce, bi.name(), CheckTDZ)) {
+ return false;
+ }
+ }
+ }
+
+ updateFrameFixedSlots(bce, bi);
+ } else {
+ nextFrameSlot_ = 0;
+ }
+
+ // Modules are toplevel, so any free names are global.
+ fallbackFreeNameLocation_ = Some(NameLocation::Global(BindingKind::Var));
+
+ // Put lexical frame slots in TDZ. Environment slots are poisoned during
+ // environment creation.
+ if (firstLexicalFrameSlot) {
+ if (!deadZoneFrameSlotRange(bce, *firstLexicalFrameSlot, frameSlotEnd())) {
+ return false;
+ }
+ }
+
+ // Create and intern the VM scope creation data.
+ ScopeIndex scopeIndex;
+ if (!ScopeStencil::createForModuleScope(
+ bce->fc, bce->compilationState, modulesc->bindings,
+ enclosingScopeIndex(bce), &scopeIndex)) {
+ return false;
+ }
+ if (!internBodyScopeStencil(bce, scopeIndex)) {
+ return false;
+ }
+
+ return checkEnvironmentChainLength(bce);
+}
+
+bool EmitterScope::enterWith(BytecodeEmitter* bce) {
+ MOZ_ASSERT(this == bce->innermostEmitterScopeNoCheck());
+
+ if (!ensureCache(bce)) {
+ return false;
+ }
+
+ // 'with' make all accesses dynamic and unanalyzable.
+ fallbackFreeNameLocation_ = Some(NameLocation::Dynamic());
+
+ ScopeIndex scopeIndex;
+ if (!ScopeStencil::createForWithScope(bce->fc, bce->compilationState,
+ enclosingScopeIndex(bce),
+ &scopeIndex)) {
+ return false;
+ }
+
+ if (!internScopeStencil(bce, scopeIndex)) {
+ return false;
+ }
+
+ if (!bce->emitInternedScopeOp(index(), JSOp::EnterWith)) {
+ return false;
+ }
+
+ if (!appendScopeNote(bce)) {
+ return false;
+ }
+
+ return checkEnvironmentChainLength(bce);
+}
+
+bool EmitterScope::deadZoneFrameSlots(BytecodeEmitter* bce) const {
+ return deadZoneFrameSlotRange(bce, frameSlotStart(), frameSlotEnd());
+}
+
+bool EmitterScope::leave(BytecodeEmitter* bce, bool nonLocal) {
+ // If we aren't leaving the scope due to a non-local jump (e.g., break),
+ // we must be the innermost scope.
+ MOZ_ASSERT_IF(!nonLocal, this == bce->innermostEmitterScopeNoCheck());
+
+ ScopeKind kind = scope(bce).kind();
+ switch (kind) {
+ case ScopeKind::Lexical:
+ case ScopeKind::SimpleCatch:
+ case ScopeKind::Catch:
+ case ScopeKind::FunctionLexical:
+ case ScopeKind::ClassBody:
+ if (bce->sc->isFunctionBox() &&
+ bce->sc->asFunctionBox()->needsClearSlotsOnExit()) {
+ if (!deadZoneFrameSlots(bce)) {
+ return false;
+ }
+ }
+ if (!bce->emit1(hasEnvironment() ? JSOp::PopLexicalEnv
+ : JSOp::DebugLeaveLexicalEnv)) {
+ return false;
+ }
+ break;
+
+ case ScopeKind::With:
+ if (!bce->emit1(JSOp::LeaveWith)) {
+ return false;
+ }
+ break;
+
+ case ScopeKind::Function:
+ case ScopeKind::FunctionBodyVar:
+ case ScopeKind::NamedLambda:
+ case ScopeKind::StrictNamedLambda:
+ case ScopeKind::Eval:
+ case ScopeKind::StrictEval:
+ case ScopeKind::Global:
+ case ScopeKind::NonSyntactic:
+ case ScopeKind::Module:
+ break;
+
+ case ScopeKind::WasmInstance:
+ case ScopeKind::WasmFunction:
+ MOZ_CRASH("No wasm function scopes in JS");
+ }
+
+ // Finish up the scope if we are leaving it in LIFO fashion.
+ if (!nonLocal) {
+ // Popping scopes due to non-local jumps generate additional scope
+ // notes. See NonLocalExitControl::prepareForNonLocalJump.
+ if (ScopeKindIsInBody(kind)) {
+ if (kind == ScopeKind::FunctionBodyVar) {
+ // The extra function var scope is never popped once it's pushed,
+ // so its scope note extends until the end of any possible code.
+ bce->bytecodeSection().scopeNoteList().recordEndFunctionBodyVar(
+ noteIndex_);
+ } else {
+ bce->bytecodeSection().scopeNoteList().recordEnd(
+ noteIndex_, bce->bytecodeSection().offset());
+ }
+ }
+ }
+
+ return true;
+}
+
+AbstractScopePtr EmitterScope::scope(const BytecodeEmitter* bce) const {
+ return bce->perScriptData().gcThingList().getScope(index());
+}
+
+mozilla::Maybe<ScopeIndex> EmitterScope::scopeIndex(
+ const BytecodeEmitter* bce) const {
+ return bce->perScriptData().gcThingList().getScopeIndex(index());
+}
+
+NameLocation EmitterScope::lookup(BytecodeEmitter* bce,
+ TaggedParserAtomIndex name) {
+ if (Maybe<NameLocation> loc = lookupInCache(bce, name)) {
+ return *loc;
+ }
+ return searchAndCache(bce, name);
+}
+
+/* static */
+uint32_t EmitterScope::CountEnclosingCompilationEnvironments(
+ BytecodeEmitter* bce, EmitterScope* emitterScope) {
+ uint32_t environments = emitterScope->hasEnvironment() ? 1 : 0;
+ while ((emitterScope = emitterScope->enclosing(&bce))) {
+ if (emitterScope->hasEnvironment()) {
+ environments++;
+ }
+ }
+ return environments;
+}
+
+void EmitterScope::lookupPrivate(BytecodeEmitter* bce,
+ TaggedParserAtomIndex name, NameLocation& loc,
+ mozilla::Maybe<NameLocation>& brandLoc) {
+ loc = lookup(bce, name);
+
+ // Private Brand checking relies on the ability to construct a new
+ // environment coordinate for a name at a fixed offset, which will
+ // correspond to the private brand for that class.
+ //
+ // If our name lookup isn't a fixed location, we must construct a
+ // new environment coordinate, using information available from our private
+ // field cache. (See cachePrivateFieldsForEval, and Bug 1638309).
+ //
+ // This typically involves a DebugEnvironmentProxy, so we need to use a
+ // DebugEnvironmentCoordinate.
+ //
+ // See also Bug 793345 which argues that we should remove the
+ // DebugEnvironmentProxy.
+ if (loc.kind() != NameLocation::Kind::EnvironmentCoordinate &&
+ loc.kind() != NameLocation::Kind::FrameSlot) {
+ MOZ_ASSERT(loc.kind() == NameLocation::Kind::Dynamic ||
+ loc.kind() == NameLocation::Kind::Global);
+ // Private fields don't require brand checking and can be correctly
+ // code-generated with dynamic name lookup bytecode we have today. However,
+ // for that to happen we first need to figure out if we have a Private
+ // method or private field, which we cannot disambiguate based on the
+ // dynamic lookup.
+ //
+ // However, this is precisely the case that the private field eval case can
+ // help us handle. It knows the truth about these private bindings.
+ mozilla::Maybe<NameLocation> cacheEntry =
+ bce->compilationState.scopeContext.getPrivateFieldLocation(name);
+ MOZ_ASSERT(cacheEntry);
+
+ if (cacheEntry->bindingKind() == BindingKind::PrivateMethod) {
+ MOZ_ASSERT(cacheEntry->kind() ==
+ NameLocation::Kind::DebugEnvironmentCoordinate);
+ // To construct the brand check there are two hop values required:
+ //
+ // 1. Compilation Hops: The number of environment hops required to get to
+ // the compilation enclosing environment.
+ // 2. "external hops", to get from compilation enclosing debug environment
+ // to the environment that actually contains our brand. This is
+ // determined by the cacheEntry. This traversal will bypass a Debug
+ // environment proxy, which is why need to use
+ // DebugEnvironmentCoordinate.
+
+ uint32_t compilation_hops =
+ CountEnclosingCompilationEnvironments(bce, this);
+
+ uint32_t external_hops = cacheEntry->environmentCoordinate().hops();
+
+ brandLoc = Some(NameLocation::DebugEnvironmentCoordinate(
+ BindingKind::Synthetic, compilation_hops + external_hops,
+ ClassBodyLexicalEnvironmentObject::privateBrandSlot()));
+ } else {
+ brandLoc = Nothing();
+ }
+ return;
+ }
+
+ if (loc.bindingKind() == BindingKind::PrivateMethod) {
+ uint32_t hops = 0;
+ if (loc.kind() == NameLocation::Kind::EnvironmentCoordinate) {
+ hops = loc.environmentCoordinate().hops();
+ } else {
+ // If we have a FrameSlot, then our innermost emitter scope must be a
+ // class body scope, and we can generate an environment coordinate with
+ // hops=0 to find the associated brand location.
+ MOZ_ASSERT(bce->innermostScope().is<ClassBodyScope>());
+ }
+
+ brandLoc = Some(NameLocation::EnvironmentCoordinate(
+ BindingKind::Synthetic, hops,
+ ClassBodyLexicalEnvironmentObject::privateBrandSlot()));
+ } else {
+ brandLoc = Nothing();
+ }
+}
+
+Maybe<NameLocation> EmitterScope::locationBoundInScope(
+ TaggedParserAtomIndex name, EmitterScope* target) {
+ // The target scope must be an intra-frame enclosing scope of this
+ // one. Count the number of extra hops to reach it.
+ uint8_t extraHops = 0;
+ for (EmitterScope* es = this; es != target; es = es->enclosingInFrame()) {
+ if (es->hasEnvironment()) {
+ extraHops++;
+ }
+ }
+
+ // Caches are prepopulated with bound names. So if the name is bound in a
+ // particular scope, it must already be in the cache. Furthermore, don't
+ // consult the fallback location as we only care about binding names.
+ Maybe<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..8f985faffe
--- /dev/null
+++ b/js/src/frontend/EmitterScope.h
@@ -0,0 +1,210 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef frontend_EmitterScope_h
+#define frontend_EmitterScope_h
+
+#include "mozilla/Maybe.h"
+
+#include <stdint.h>
+
+#include "ds/Nestable.h"
+#include "frontend/AbstractScopePtr.h"
+#include "frontend/NameAnalysisTypes.h"
+#include "frontend/NameCollections.h"
+#include "frontend/Stencil.h"
+#include "vm/Opcodes.h" // JSOp
+#include "vm/SharedStencil.h" // GCThingIndex
+
+namespace js {
+namespace frontend {
+
+struct BytecodeEmitter;
+class EvalSharedContext;
+class FunctionBox;
+class GlobalSharedContext;
+class ModuleSharedContext;
+class TaggedParserAtomIndex;
+
+// A scope that introduces bindings.
+class EmitterScope : public Nestable<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_;
+
+ [[nodiscard]] bool ensureCache(BytecodeEmitter* bce);
+
+ [[nodiscard]] bool checkSlotLimits(BytecodeEmitter* bce,
+ const ParserBindingIter& bi);
+
+ [[nodiscard]] bool checkEnvironmentChainLength(BytecodeEmitter* bce);
+
+ void updateFrameFixedSlots(BytecodeEmitter* bce, const ParserBindingIter& bi);
+
+ [[nodiscard]] bool putNameInCache(BytecodeEmitter* bce,
+ TaggedParserAtomIndex name,
+ NameLocation loc);
+
+ mozilla::Maybe<NameLocation> lookupInCache(BytecodeEmitter* bce,
+ TaggedParserAtomIndex name);
+
+ EmitterScope* enclosing(BytecodeEmitter** bce) const;
+
+ mozilla::Maybe<ScopeIndex> enclosingScopeIndex(BytecodeEmitter* bce) const;
+
+ static bool nameCanBeFree(BytecodeEmitter* bce, TaggedParserAtomIndex name);
+
+ NameLocation searchAndCache(BytecodeEmitter* bce, TaggedParserAtomIndex name);
+
+ [[nodiscard]] bool internEmptyGlobalScopeAsBody(BytecodeEmitter* bce);
+
+ [[nodiscard]] bool internScopeStencil(BytecodeEmitter* bce, ScopeIndex index);
+
+ [[nodiscard]] bool internBodyScopeStencil(BytecodeEmitter* bce,
+ ScopeIndex index);
+ [[nodiscard]] bool appendScopeNote(BytecodeEmitter* bce);
+
+ [[nodiscard]] bool clearFrameSlotRange(BytecodeEmitter* bce, JSOp opcode,
+ uint32_t slotStart,
+ uint32_t slotEnd) const;
+
+ [[nodiscard]] bool deadZoneFrameSlotRange(BytecodeEmitter* bce,
+ uint32_t slotStart,
+ uint32_t slotEnd) const {
+ return clearFrameSlotRange(bce, JSOp::Uninitialized, slotStart, slotEnd);
+ }
+
+ public:
+ explicit EmitterScope(BytecodeEmitter* bce);
+
+ void dump(BytecodeEmitter* bce);
+
+ [[nodiscard]] bool enterLexical(BytecodeEmitter* bce, ScopeKind kind,
+ LexicalScope::ParserData* bindings);
+ [[nodiscard]] bool enterClassBody(BytecodeEmitter* bce, ScopeKind kind,
+ ClassBodyScope::ParserData* bindings);
+ [[nodiscard]] bool enterNamedLambda(BytecodeEmitter* bce,
+ FunctionBox* funbox);
+ [[nodiscard]] bool enterFunction(BytecodeEmitter* bce, FunctionBox* funbox);
+ [[nodiscard]] bool enterFunctionExtraBodyVar(BytecodeEmitter* bce,
+ FunctionBox* funbox);
+ [[nodiscard]] bool enterGlobal(BytecodeEmitter* bce,
+ GlobalSharedContext* globalsc);
+ [[nodiscard]] bool enterEval(BytecodeEmitter* bce, EvalSharedContext* evalsc);
+ [[nodiscard]] bool enterModule(BytecodeEmitter* module,
+ ModuleSharedContext* modulesc);
+ [[nodiscard]] bool enterWith(BytecodeEmitter* bce);
+ [[nodiscard]] bool deadZoneFrameSlots(BytecodeEmitter* bce) const;
+
+ [[nodiscard]] bool leave(BytecodeEmitter* bce, bool nonLocal = false);
+
+ GCThingIndex index() const {
+ MOZ_ASSERT(scopeIndex_ != ScopeNote::NoScopeIndex,
+ "Did you forget to intern a Scope?");
+ return scopeIndex_;
+ }
+
+ uint32_t noteIndex() const { return noteIndex_; }
+
+ AbstractScopePtr scope(const BytecodeEmitter* bce) const;
+ mozilla::Maybe<ScopeIndex> 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, TaggedParserAtomIndex name);
+
+ // Find both the slot associated with a private name and the location of the
+ // corresponding `.privateBrand` binding.
+ //
+ // Simply doing two separate lookups, one for `name` and another for
+ // `.privateBrand`, would give the wrong answer in this case:
+ //
+ // class Outer {
+ // #outerMethod() { reutrn "ok"; }
+ //
+ // test() {
+ // class Inner {
+ // #innerMethod() {}
+ // test(outer) {
+ // return outer.#outerMethod();
+ // }
+ // }
+ // return new Inner().test(this);
+ // }
+ // }
+ //
+ // new Outer().test(); // should return "ok"
+ //
+ // At the point in Inner.test where `#outerMethod` is called, we need to
+ // check for the private brand of `Outer`, not `Inner`; but both class bodies
+ // have `.privateBrand` bindings. In a normal `lookup`, the inner binding
+ // would shadow the outer one.
+ //
+ // This method instead sets `brandLoc` to the location of the `.privateBrand`
+ // binding in the same class body as the private name `name`, ignoring
+ // shadowing. If `name` refers to a name that is actually stamped onto the
+ // target object (anything other than a non-static private method), then
+ // `brandLoc` is set to Nothing.
+ void lookupPrivate(BytecodeEmitter* bce, TaggedParserAtomIndex name,
+ NameLocation& loc, mozilla::Maybe<NameLocation>& brandLoc);
+
+ mozilla::Maybe<NameLocation> locationBoundInScope(TaggedParserAtomIndex name,
+ EmitterScope* target);
+
+ // For a given emitter scope, return the number of enclosing environments in
+ // the current compilation (this excludes environments that could enclose the
+ // compilation, like would happen for an eval copmilation).
+ static uint32_t CountEnclosingCompilationEnvironments(
+ BytecodeEmitter* bce, EmitterScope* emitterScope);
+};
+
+} /* namespace frontend */
+} /* namespace js */
+
+#endif /* frontend_EmitterScope_h */
diff --git a/js/src/frontend/ErrorReporter.h b/js/src/frontend/ErrorReporter.h
new file mode 100644
index 0000000000..b6f14a0852
--- /dev/null
+++ b/js/src/frontend/ErrorReporter.h
@@ -0,0 +1,342 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef frontend_ErrorReporter_h
+#define frontend_ErrorReporter_h
+
+#include "mozilla/Variant.h"
+
+#include <stdarg.h> // for va_list
+#include <stddef.h> // for size_t
+#include <stdint.h> // for uint32_t
+
+#include "js/UniquePtr.h"
+#include "vm/ErrorReporting.h" // ErrorMetadata, ReportCompile{Error,Warning}
+
+namespace JS {
+class JS_PUBLIC_API ReadOnlyCompileOptions;
+}
+
+namespace js {
+namespace frontend {
+
+// An interface class to provide strictMode getter method, which is used by
+// ErrorReportMixin::strictModeError* methods.
+//
+// This class is separated to be used as a back-channel from TokenStream to the
+// strict mode flag which is available inside Parser, to avoid exposing the
+// rest of SharedContext to TokenStream.
+class StrictModeGetter {
+ public:
+ virtual bool strictMode() const = 0;
+};
+
+// This class provides error reporting methods, including warning, extra
+// warning, and strict mode error.
+//
+// A class that inherits this class must provide the following methods:
+// * options
+// * getContext
+// * computeErrorMetadata
+class ErrorReportMixin : public StrictModeGetter {
+ public:
+ // Returns a compile options (extra warning, warning as error) for current
+ // compilation.
+ virtual const JS::ReadOnlyCompileOptions& options() const = 0;
+
+ // Returns the current context.
+ virtual FrontendContext* getContext() const = 0;
+
+ // A variant class for the offset of the error or warning.
+ struct Current {};
+ struct NoOffset {};
+ using ErrorOffset = mozilla::Variant<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
+ [[nodiscard]] virtual bool computeErrorMetadata(
+ ErrorMetadata* err, const ErrorOffset& offset) const = 0;
+
+ // ==== error ====
+ //
+ // Reports an error.
+ //
+ // Methods ending with "At" are for an error at given offset.
+ // The offset is passed to computeErrorMetadata method and is transparent
+ // for this class.
+ //
+ // Methods ending with "NoOffset" are for an error that doesn't correspond
+ // to any offset. NoOffset is passed to computeErrorMetadata for them.
+ //
+ // Other methods except errorWithNotesAtVA are for an error at the current
+ // offset. Current is passed to computeErrorMetadata for them.
+ //
+ // Methods contains "WithNotes" can be used if there are error notes.
+ //
+ // errorWithNotesAtVA is the actual implementation for all of above.
+ // This can be called if the caller already has a va_list.
+
+ void error(unsigned errorNumber, ...) const {
+ va_list args;
+ va_start(args, errorNumber);
+
+ errorWithNotesAtVA(nullptr, mozilla::AsVariant(Current()), errorNumber,
+ &args);
+
+ va_end(args);
+ }
+ void errorWithNotes(UniquePtr<JSErrorNotes> notes, unsigned errorNumber,
+ ...) const {
+ va_list args;
+ va_start(args, errorNumber);
+
+ errorWithNotesAtVA(std::move(notes), mozilla::AsVariant(Current()),
+ errorNumber, &args);
+
+ va_end(args);
+ }
+ void errorAt(uint32_t offset, unsigned errorNumber, ...) const {
+ va_list args;
+ va_start(args, errorNumber);
+
+ errorWithNotesAtVA(nullptr, mozilla::AsVariant(offset), errorNumber, &args);
+
+ va_end(args);
+ }
+ void errorWithNotesAt(UniquePtr<JSErrorNotes> notes, uint32_t offset,
+ unsigned errorNumber, ...) const {
+ va_list args;
+ va_start(args, errorNumber);
+
+ errorWithNotesAtVA(std::move(notes), mozilla::AsVariant(offset),
+ errorNumber, &args);
+
+ va_end(args);
+ }
+ void errorNoOffset(unsigned errorNumber, ...) const {
+ va_list args;
+ va_start(args, errorNumber);
+
+ errorWithNotesAtVA(nullptr, mozilla::AsVariant(NoOffset()), errorNumber,
+ &args);
+
+ va_end(args);
+ }
+ void errorWithNotesNoOffset(UniquePtr<JSErrorNotes> notes,
+ unsigned errorNumber, ...) const {
+ va_list args;
+ va_start(args, errorNumber);
+
+ errorWithNotesAtVA(std::move(notes), mozilla::AsVariant(NoOffset()),
+ errorNumber, &args);
+
+ va_end(args);
+ }
+ void errorWithNotesAtVA(UniquePtr<JSErrorNotes> notes,
+ const ErrorOffset& offset, unsigned errorNumber,
+ va_list* args) const {
+ ErrorMetadata metadata;
+ if (!computeErrorMetadata(&metadata, offset)) {
+ return;
+ }
+
+ ReportCompileErrorLatin1(getContext(), std::move(metadata),
+ std::move(notes), errorNumber, args);
+ }
+
+ // ==== warning ====
+ //
+ // Reports a warning.
+ //
+ // Returns true if the warning is reported.
+ // Returns false if the warning is treated as an error, or an error occurs
+ // while reporting.
+ //
+ // See the comment on the error section for details on what the arguments
+ // and function names indicate for all these functions.
+
+ [[nodiscard]] bool warning(unsigned errorNumber, ...) const {
+ va_list args;
+ va_start(args, errorNumber);
+
+ bool result = warningWithNotesAtVA(nullptr, mozilla::AsVariant(Current()),
+ errorNumber, &args);
+
+ va_end(args);
+
+ return result;
+ }
+ [[nodiscard]] bool warningAt(uint32_t offset, unsigned errorNumber,
+ ...) const {
+ va_list args;
+ va_start(args, errorNumber);
+
+ bool result = warningWithNotesAtVA(nullptr, mozilla::AsVariant(offset),
+ errorNumber, &args);
+
+ va_end(args);
+
+ return result;
+ }
+ [[nodiscard]] bool warningNoOffset(unsigned errorNumber, ...) const {
+ va_list args;
+ va_start(args, errorNumber);
+
+ bool result = warningWithNotesAtVA(nullptr, mozilla::AsVariant(NoOffset()),
+ errorNumber, &args);
+
+ va_end(args);
+
+ return result;
+ }
+ [[nodiscard]] bool warningWithNotesAtVA(UniquePtr<JSErrorNotes> notes,
+ const ErrorOffset& offset,
+ unsigned errorNumber,
+ va_list* args) const {
+ ErrorMetadata metadata;
+ if (!computeErrorMetadata(&metadata, offset)) {
+ return false;
+ }
+
+ return compileWarning(std::move(metadata), std::move(notes), errorNumber,
+ args);
+ }
+
+ // ==== strictModeError ====
+ //
+ // Reports an error if in strict mode code, or warn if not.
+ //
+ // Returns true if not in strict mode and a warning is reported.
+ // Returns false if the error reported, or an error occurs while reporting.
+ //
+ // See the comment on the error section for details on what the arguments
+ // and function names indicate for all these functions.
+
+ [[nodiscard]] bool strictModeError(unsigned errorNumber, ...) const {
+ va_list args;
+ va_start(args, errorNumber);
+
+ bool result = strictModeErrorWithNotesAtVA(
+ nullptr, mozilla::AsVariant(Current()), errorNumber, &args);
+
+ va_end(args);
+
+ return result;
+ }
+ [[nodiscard]] bool strictModeErrorWithNotes(UniquePtr<JSErrorNotes> notes,
+ unsigned errorNumber, ...) const {
+ va_list args;
+ va_start(args, errorNumber);
+
+ bool result = strictModeErrorWithNotesAtVA(
+ std::move(notes), mozilla::AsVariant(Current()), errorNumber, &args);
+
+ va_end(args);
+
+ return result;
+ }
+ [[nodiscard]] bool strictModeErrorAt(uint32_t offset, unsigned errorNumber,
+ ...) const {
+ va_list args;
+ va_start(args, errorNumber);
+
+ bool result = strictModeErrorWithNotesAtVA(
+ nullptr, mozilla::AsVariant(offset), errorNumber, &args);
+
+ va_end(args);
+
+ return result;
+ }
+ [[nodiscard]] bool strictModeErrorWithNotesAt(UniquePtr<JSErrorNotes> notes,
+ uint32_t offset,
+ unsigned errorNumber,
+ ...) const {
+ va_list args;
+ va_start(args, errorNumber);
+
+ bool result = strictModeErrorWithNotesAtVA(
+ std::move(notes), mozilla::AsVariant(offset), errorNumber, &args);
+
+ va_end(args);
+
+ return result;
+ }
+ [[nodiscard]] bool strictModeErrorNoOffset(unsigned errorNumber, ...) const {
+ va_list args;
+ va_start(args, errorNumber);
+
+ bool result = strictModeErrorWithNotesAtVA(
+ nullptr, mozilla::AsVariant(NoOffset()), errorNumber, &args);
+
+ va_end(args);
+
+ return result;
+ }
+ [[nodiscard]] bool strictModeErrorWithNotesNoOffset(
+ UniquePtr<JSErrorNotes> notes, unsigned errorNumber, ...) const {
+ va_list args;
+ va_start(args, errorNumber);
+
+ bool result = strictModeErrorWithNotesAtVA(
+ std::move(notes), mozilla::AsVariant(NoOffset()), errorNumber, &args);
+
+ va_end(args);
+
+ return result;
+ }
+ [[nodiscard]] bool strictModeErrorWithNotesAtVA(UniquePtr<JSErrorNotes> notes,
+ const ErrorOffset& offset,
+ unsigned errorNumber,
+ va_list* args) const {
+ if (!strictMode()) {
+ return true;
+ }
+
+ ErrorMetadata metadata;
+ if (!computeErrorMetadata(&metadata, offset)) {
+ return false;
+ }
+
+ ReportCompileErrorLatin1(getContext(), std::move(metadata),
+ std::move(notes), errorNumber, args);
+ return false;
+ }
+
+ // Reports a warning, or an error if the warning is treated as an error.
+ [[nodiscard]] bool compileWarning(ErrorMetadata&& metadata,
+ UniquePtr<JSErrorNotes> notes,
+ unsigned errorNumber, va_list* args) const {
+ return ReportCompileWarning(getContext(), std::move(metadata),
+ std::move(notes), errorNumber, args);
+ }
+};
+
+// An interface class to provide miscellaneous methods used by error reporting
+// etc. They're mostly used by BytecodeCompiler, BytecodeEmitter, and helper
+// classes for emitter.
+class ErrorReporter : public ErrorReportMixin {
+ public:
+ // Sets *onThisLine to true if the given offset is inside the given line
+ // number `lineNum`, or false otherwise, and returns true.
+ //
+ // Return false if an error happens. This method itself doesn't report an
+ // error, and any failure is supposed to be reported as OOM in the caller.
+ virtual bool isOnThisLine(size_t offset, uint32_t lineNum,
+ bool* onThisLine) const = 0;
+
+ // Returns the line number for given offset.
+ virtual uint32_t lineAt(size_t offset) const = 0;
+
+ // Returns the column number for given offset.
+ virtual uint32_t columnAt(size_t offset) const = 0;
+};
+
+} // namespace frontend
+} // namespace js
+
+#endif // frontend_ErrorReporter_h
diff --git a/js/src/frontend/ExpressionStatementEmitter.cpp b/js/src/frontend/ExpressionStatementEmitter.cpp
new file mode 100644
index 0000000000..bb00c831b1
--- /dev/null
+++ b/js/src/frontend/ExpressionStatementEmitter.cpp
@@ -0,0 +1,54 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "frontend/ExpressionStatementEmitter.h"
+
+#include "frontend/BytecodeEmitter.h"
+#include "vm/Opcodes.h"
+
+using namespace js;
+using namespace js::frontend;
+
+using mozilla::Maybe;
+
+ExpressionStatementEmitter::ExpressionStatementEmitter(BytecodeEmitter* bce,
+ ValueUsage valueUsage)
+ : bce_(bce), valueUsage_(valueUsage) {}
+
+bool ExpressionStatementEmitter::prepareForExpr(uint32_t beginPos) {
+ MOZ_ASSERT(state_ == State::Start);
+
+ if (!bce_->updateSourceCoordNotes(beginPos)) {
+ return false;
+ }
+
+#ifdef DEBUG
+ depth_ = bce_->bytecodeSection().stackDepth();
+ state_ = State::Expr;
+#endif
+ return true;
+}
+
+bool ExpressionStatementEmitter::emitEnd() {
+ MOZ_ASSERT(state_ == State::Expr);
+ MOZ_ASSERT(bce_->bytecodeSection().stackDepth() == depth_ + 1);
+
+ // [stack] VAL
+
+ JSOp op = valueUsage_ == ValueUsage::WantValue ? JSOp::SetRval : JSOp::Pop;
+ if (!bce_->emit1(op)) {
+ // [stack] # if WantValue
+ // [stack] VAL
+ // [stack] # otherwise
+ // [stack]
+ return false;
+ }
+
+#ifdef DEBUG
+ state_ = State::End;
+#endif
+ return true;
+}
diff --git a/js/src/frontend/ExpressionStatementEmitter.h b/js/src/frontend/ExpressionStatementEmitter.h
new file mode 100644
index 0000000000..d143530420
--- /dev/null
+++ b/js/src/frontend/ExpressionStatementEmitter.h
@@ -0,0 +1,81 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef frontend_ExpressionStatementEmitter_h
+#define frontend_ExpressionStatementEmitter_h
+
+#include "mozilla/Attributes.h"
+
+#include <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(offset_of_expr);
+// emit(expr);
+// ese.emitEnd();
+//
+class MOZ_STACK_CLASS ExpressionStatementEmitter {
+ BytecodeEmitter* bce_;
+
+#ifdef DEBUG
+ // The stack depth before emitting expression.
+ int32_t depth_;
+#endif
+
+ // The usage of the value of the expression.
+ ValueUsage valueUsage_;
+
+#ifdef DEBUG
+ // The state of this emitter.
+ //
+ // +-------+ prepareForExpr +------+ emitEnd +-----+
+ // | Start |--------------->| Expr |-------->| End |
+ // +-------+ +------+ +-----+
+ enum class State {
+ // The initial state.
+ Start,
+
+ // After calling prepareForExpr.
+ Expr,
+
+ // After calling emitEnd.
+ End
+ };
+ State state_ = State::Start;
+#endif
+
+ public:
+ ExpressionStatementEmitter(BytecodeEmitter* bce, ValueUsage valueUsage);
+
+ // Parameters are the offset in the source code for each character below:
+ //
+ // expr;
+ // ^
+ // |
+ // beginPos
+ [[nodiscard]] bool prepareForExpr(uint32_t beginPos);
+ [[nodiscard]] bool emitEnd();
+};
+
+} // namespace frontend
+} // namespace js
+
+#endif /* frontend_ExpressionStatementEmitter_h */
diff --git a/js/src/frontend/FoldConstants.cpp b/js/src/frontend/FoldConstants.cpp
new file mode 100644
index 0000000000..829a9fb505
--- /dev/null
+++ b/js/src/frontend/FoldConstants.cpp
@@ -0,0 +1,1581 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "frontend/FoldConstants.h"
+
+#include "mozilla/FloatingPoint.h"
+#include "mozilla/Maybe.h" // mozilla::Maybe
+
+#include "jslibmath.h"
+#include "jsmath.h"
+
+#include "frontend/FullParseHandler.h"
+#include "frontend/ParseNode.h"
+#include "frontend/ParseNodeVisitor.h"
+#include "frontend/ParserAtom.h" // ParserAtomsTable, TaggedParserAtomIndex
+#include "js/Conversions.h"
+#include "js/Stack.h" // JS::NativeStackLimit
+#include "util/StringBuffer.h" // StringBuffer
+
+using namespace js;
+using namespace js::frontend;
+
+using JS::GenericNaN;
+using JS::ToInt32;
+using JS::ToUint32;
+using mozilla::IsNegative;
+using mozilla::NegativeInfinity;
+using mozilla::PositiveInfinity;
+
+struct FoldInfo {
+ FrontendContext* fc;
+ ParserAtomsTable& parserAtoms;
+ FullParseHandler* handler;
+};
+
+// Don't use ReplaceNode directly, because we want the constant folder to keep
+// the attributes isInParens and isDirectRHSAnonFunction of the old node being
+// replaced.
+[[nodiscard]] inline bool TryReplaceNode(ParseNode** pnp, ParseNode* pn) {
+ // convenience check: can call TryReplaceNode(pnp, alloc_parsenode())
+ // directly, without having to worry about alloc returning null.
+ if (!pn) {
+ return false;
+ }
+ pn->setInParens((*pnp)->isInParens());
+ pn->setDirectRHSAnonFunction((*pnp)->isDirectRHSAnonFunction());
+ ReplaceNode(pnp, pn);
+ return true;
+}
+
+static bool ContainsHoistedDeclaration(FoldInfo& info, ParseNode* node,
+ bool* result);
+
+static bool ListContainsHoistedDeclaration(FoldInfo& info, ListNode* list,
+ bool* result) {
+ for (ParseNode* node : list->contents()) {
+ if (!ContainsHoistedDeclaration(info, node, result)) {
+ return false;
+ }
+ if (*result) {
+ return true;
+ }
+ }
+
+ *result = false;
+ return true;
+}
+
+// Determines whether the given ParseNode contains any declarations whose
+// visibility will extend outside the node itself -- that is, whether the
+// ParseNode contains any var statements.
+//
+// THIS IS NOT A GENERAL-PURPOSE FUNCTION. It is only written to work in the
+// specific context of deciding that |node|, as one arm of a ParseNodeKind::If
+// controlled by a constant condition, contains a declaration that forbids
+// |node| being completely eliminated as dead.
+static bool ContainsHoistedDeclaration(FoldInfo& info, ParseNode* node,
+ bool* result) {
+ AutoCheckRecursionLimit recursion(info.fc);
+ if (!recursion.check(info.fc)) {
+ return false;
+ }
+
+restart:
+
+ // With a better-typed AST, we would have distinct parse node classes for
+ // expressions and for statements and would characterize expressions with
+ // ExpressionKind and statements with StatementKind. Perhaps someday. In
+ // the meantime we must characterize every ParseNodeKind, even the
+ // expression/sub-expression ones that, if we handle all statement kinds
+ // correctly, we'll never see.
+ switch (node->getKind()) {
+ // Base case.
+ case ParseNodeKind::VarStmt:
+ *result = true;
+ return true;
+
+ // Non-global lexical declarations are block-scoped (ergo not hoistable).
+ case ParseNodeKind::LetDecl:
+ case ParseNodeKind::ConstDecl:
+ MOZ_ASSERT(node->is<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::ImportNamespaceSpec:
+ case ParseNodeKind::ExportFromStmt:
+ case ParseNodeKind::ExportDefaultStmt:
+ case ParseNodeKind::ExportSpecList:
+ case ParseNodeKind::ExportSpec:
+ case ParseNodeKind::ExportNamespaceSpec:
+ case ParseNodeKind::ExportStmt:
+ case ParseNodeKind::ExportBatchSpecStmt:
+ case ParseNodeKind::CallImportExpr:
+ case ParseNodeKind::CallImportSpec:
+ case ParseNodeKind::ImportAssertionList:
+ case ParseNodeKind::ImportAssertion:
+ case ParseNodeKind::ImportModuleRequest:
+ *result = false;
+ return true;
+
+ // Statements possibly containing hoistable declarations only in the left
+ // half, in ParseNode terms -- the loop body in AST terms.
+ case ParseNodeKind::DoWhileStmt:
+ return ContainsHoistedDeclaration(info, node->as<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(info, node->as<BinaryNode>().right(),
+ result);
+
+ case ParseNodeKind::LabelStmt:
+ return ContainsHoistedDeclaration(
+ info, 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(info, consequent, result)) {
+ return false;
+ }
+ if (*result) {
+ return true;
+ }
+
+ if ((node = ifNode->kid3())) {
+ goto restart;
+ }
+
+ *result = false;
+ return true;
+ }
+
+ // try-statements have statements to execute, and one or both of a
+ // catch-list and a finally-block.
+ case ParseNodeKind::TryStmt: {
+ TernaryNode* tryNode = &node->as<TernaryNode>();
+
+ MOZ_ASSERT(tryNode->kid2() || tryNode->kid3(),
+ "must have either catch or finally");
+
+ ParseNode* tryBlock = tryNode->kid1();
+ if (!ContainsHoistedDeclaration(info, tryBlock, result)) {
+ return false;
+ }
+ if (*result) {
+ return true;
+ }
+
+ if (ParseNode* catchScope = tryNode->kid2()) {
+ BinaryNode* catchNode =
+ &catchScope->as<LexicalScopeNode>().scopeBody()->as<BinaryNode>();
+ MOZ_ASSERT(catchNode->isKind(ParseNodeKind::Catch));
+
+ ParseNode* catchStatements = catchNode->right();
+ if (!ContainsHoistedDeclaration(info, catchStatements, result)) {
+ return false;
+ }
+ if (*result) {
+ return true;
+ }
+ }
+
+ if (ParseNode* finallyBlock = tryNode->kid3()) {
+ return ContainsHoistedDeclaration(info, finallyBlock, result);
+ }
+
+ *result = false;
+ return true;
+ }
+
+ // A switch node's left half is an expression; only its right half (a
+ // list of cases/defaults, or a block node) could contain hoisted
+ // declarations.
+ case ParseNodeKind::SwitchStmt: {
+ SwitchStatement* switchNode = &node->as<SwitchStatement>();
+ return ContainsHoistedDeclaration(info, &switchNode->lexicalForCaseList(),
+ result);
+ }
+
+ case ParseNodeKind::Case: {
+ CaseClause* caseClause = &node->as<CaseClause>();
+ return ContainsHoistedDeclaration(info, 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(info, loopBody, result);
+ }
+
+ case ParseNodeKind::LexicalScope: {
+ LexicalScopeNode* scope = &node->as<LexicalScopeNode>();
+ ParseNode* expr = scope->scopeBody();
+
+ if (expr->isKind(ParseNodeKind::ForStmt) || expr->is<FunctionNode>()) {
+ return ContainsHoistedDeclaration(info, expr, result);
+ }
+
+ MOZ_ASSERT(expr->isKind(ParseNodeKind::StatementList));
+ return ListContainsHoistedDeclaration(
+ info, &scope->scopeBody()->as<ListNode>(), result);
+ }
+
+ // List nodes with all non-null children.
+ case ParseNodeKind::StatementList:
+ return ListContainsHoistedDeclaration(info, &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::PrivateInExpr:
+ case ParseNodeKind::LshExpr:
+ case ParseNodeKind::RshExpr:
+ case ParseNodeKind::UrshExpr:
+ case ParseNodeKind::AddExpr:
+ case ParseNodeKind::SubExpr:
+ case ParseNodeKind::MulExpr:
+ case ParseNodeKind::DivExpr:
+ case ParseNodeKind::ModExpr:
+ case ParseNodeKind::PowExpr:
+ case ParseNodeKind::InitExpr:
+ case ParseNodeKind::AssignExpr:
+ case ParseNodeKind::AddAssignExpr:
+ case ParseNodeKind::SubAssignExpr:
+ case ParseNodeKind::CoalesceAssignExpr:
+ case ParseNodeKind::OrAssignExpr:
+ case ParseNodeKind::AndAssignExpr:
+ case ParseNodeKind::BitOrAssignExpr:
+ case ParseNodeKind::BitXorAssignExpr:
+ case ParseNodeKind::BitAndAssignExpr:
+ case ParseNodeKind::LshAssignExpr:
+ case ParseNodeKind::RshAssignExpr:
+ case ParseNodeKind::UrshAssignExpr:
+ case ParseNodeKind::MulAssignExpr:
+ case ParseNodeKind::DivAssignExpr:
+ case ParseNodeKind::ModAssignExpr:
+ case ParseNodeKind::PowAssignExpr:
+ case ParseNodeKind::CommaExpr:
+ case ParseNodeKind::ArrayExpr:
+ case ParseNodeKind::ObjectExpr:
+ case ParseNodeKind::PropertyNameExpr:
+ case ParseNodeKind::DotExpr:
+ case ParseNodeKind::ElemExpr:
+ case ParseNodeKind::Arguments:
+ case ParseNodeKind::CallExpr:
+ case ParseNodeKind::PrivateMemberExpr:
+ case ParseNodeKind::OptionalChain:
+ case ParseNodeKind::OptionalDotExpr:
+ case ParseNodeKind::OptionalElemExpr:
+ case ParseNodeKind::OptionalCallExpr:
+ case ParseNodeKind::OptionalPrivateMemberExpr:
+ case ParseNodeKind::Name:
+ case ParseNodeKind::PrivateName:
+ case ParseNodeKind::TemplateStringExpr:
+ case ParseNodeKind::TemplateStringListExpr:
+ case ParseNodeKind::TaggedTemplateExpr:
+ case ParseNodeKind::CallSiteObj:
+ case ParseNodeKind::StringExpr:
+ case ParseNodeKind::RegExpExpr:
+ case ParseNodeKind::TrueExpr:
+ case ParseNodeKind::FalseExpr:
+ case ParseNodeKind::NullExpr:
+ case ParseNodeKind::RawUndefinedExpr:
+ case ParseNodeKind::ThisExpr:
+ case ParseNodeKind::Elision:
+ case ParseNodeKind::NumberExpr:
+ case ParseNodeKind::BigIntExpr:
+ case ParseNodeKind::NewExpr:
+ case ParseNodeKind::Generator:
+ case ParseNodeKind::ParamsBody:
+ case ParseNodeKind::Catch:
+ case ParseNodeKind::ForIn:
+ case ParseNodeKind::ForOf:
+ case ParseNodeKind::ForHead:
+ case ParseNodeKind::DefaultConstructor:
+ case ParseNodeKind::ClassBodyScope:
+ case ParseNodeKind::ClassMethod:
+ case ParseNodeKind::ClassField:
+ case ParseNodeKind::StaticClassBlock:
+ case ParseNodeKind::ClassMemberList:
+ case ParseNodeKind::ClassNames:
+ case ParseNodeKind::NewTargetExpr:
+ case ParseNodeKind::ImportMetaExpr:
+ case ParseNodeKind::PosHolder:
+ case ParseNodeKind::SuperCallExpr:
+ case ParseNodeKind::SuperBase:
+ case ParseNodeKind::SetThis:
+#ifdef ENABLE_DECORATORS
+ case ParseNodeKind::DecoratorList:
+#endif
+ MOZ_CRASH(
+ "ContainsHoistedDeclaration should have indicated false on "
+ "some parent node without recurring to test this node");
+ case ParseNodeKind::LastUnused:
+ case ParseNodeKind::Limit:
+ MOZ_CRASH("unexpected sentinel ParseNodeKind in node");
+
+#ifdef ENABLE_RECORD_TUPLE
+ case ParseNodeKind::RecordExpr:
+ case ParseNodeKind::TupleExpr:
+ MOZ_CRASH("Record and Tuple are not supported yet");
+#endif
+ }
+
+ MOZ_CRASH("invalid node kind");
+}
+
+/*
+ * Fold from one constant type to another.
+ * XXX handles only strings and numbers for now
+ */
+static bool FoldType(FoldInfo info, ParseNode** pnp, ParseNodeKind kind) {
+ const ParseNode* pn = *pnp;
+ if (!pn->isKind(kind)) {
+ switch (kind) {
+ case ParseNodeKind::NumberExpr:
+ if (pn->isKind(ParseNodeKind::StringExpr)) {
+ auto atom = pn->as<NameNode>().atom();
+ double d = info.parserAtoms.toNumber(atom);
+ if (!TryReplaceNode(
+ pnp, info.handler->newNumber(d, NoDecimal, pn->pn_pos))) {
+ return false;
+ }
+ }
+ break;
+
+ case ParseNodeKind::StringExpr:
+ if (pn->isKind(ParseNodeKind::NumberExpr)) {
+ TaggedParserAtomIndex atom =
+ pn->as<NumericLiteral>().toAtom(info.fc, info.parserAtoms);
+ if (!atom) {
+ return false;
+ }
+ if (!TryReplaceNode(
+ pnp, info.handler->newStringLiteral(atom, pn->pn_pos))) {
+ return false;
+ }
+ }
+ break;
+
+ default:
+ MOZ_CRASH("Invalid type in constant folding FoldType");
+ }
+ }
+ return true;
+}
+
+static bool IsEffectless(ParseNode* node) {
+ return node->isKind(ParseNodeKind::TrueExpr) ||
+ node->isKind(ParseNodeKind::FalseExpr) ||
+ node->isKind(ParseNodeKind::StringExpr) ||
+ node->isKind(ParseNodeKind::TemplateStringExpr) ||
+ node->isKind(ParseNodeKind::NumberExpr) ||
+ node->isKind(ParseNodeKind::BigIntExpr) ||
+ node->isKind(ParseNodeKind::NullExpr) ||
+ node->isKind(ParseNodeKind::RawUndefinedExpr) ||
+ node->isKind(ParseNodeKind::Function);
+}
+
+enum Truthiness { Truthy, Falsy, Unknown };
+
+static Truthiness Boolish(ParseNode* pn) {
+ switch (pn->getKind()) {
+ case ParseNodeKind::NumberExpr:
+ return (pn->as<NumericLiteral>().value() != 0 &&
+ !std::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() ==
+ TaggedParserAtomIndex::WellKnown::empty())
+ ? Falsy
+ : Truthy;
+
+ case ParseNodeKind::TrueExpr:
+ case ParseNodeKind::Function:
+ return Truthy;
+
+ case ParseNodeKind::FalseExpr:
+ case ParseNodeKind::NullExpr:
+ case ParseNodeKind::RawUndefinedExpr:
+ return Falsy;
+
+ case ParseNodeKind::VoidExpr: {
+ // |void <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.
+ TaggedParserAtomIndex result;
+ if (expr->isKind(ParseNodeKind::StringExpr) ||
+ expr->isKind(ParseNodeKind::TemplateStringExpr)) {
+ result = TaggedParserAtomIndex::WellKnown::string();
+ } else if (expr->isKind(ParseNodeKind::NumberExpr)) {
+ result = TaggedParserAtomIndex::WellKnown::number();
+ } else if (expr->isKind(ParseNodeKind::BigIntExpr)) {
+ result = TaggedParserAtomIndex::WellKnown::bigint();
+ } else if (expr->isKind(ParseNodeKind::NullExpr)) {
+ result = TaggedParserAtomIndex::WellKnown::object();
+ } else if (expr->isKind(ParseNodeKind::TrueExpr) ||
+ expr->isKind(ParseNodeKind::FalseExpr)) {
+ result = TaggedParserAtomIndex::WellKnown::boolean();
+ } else if (expr->is<FunctionNode>()) {
+ result = TaggedParserAtomIndex::WellKnown::function();
+ }
+
+ if (result) {
+ if (!TryReplaceNode(nodePtr,
+ info.handler->newStringLiteral(result, node->pn_pos))) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+static bool FoldDeleteExpr(FoldInfo info, ParseNode** nodePtr) {
+ UnaryNode* node = &(*nodePtr)->as<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, 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();
+ TaggedParserAtomIndex name;
+ if (key->isKind(ParseNodeKind::StringExpr)) {
+ auto keyIndex = key->as<NameNode>().atom();
+ uint32_t index;
+ if (info.parserAtoms.isIndex(keyIndex, &index)) {
+ // Optimization 1: We have something like expr["100"]. This is
+ // equivalent to expr[100] which is faster.
+ if (!TryReplaceNode(
+ elem->unsafeRightReference(),
+ info.handler->newNumber(index, NoDecimal, key->pn_pos))) {
+ return false;
+ }
+ key = &elem->key();
+ } else {
+ name = keyIndex;
+ }
+ } else if (key->isKind(ParseNodeKind::NumberExpr)) {
+ auto* numeric = &key->as<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.
+ name = numeric->toAtom(info.fc, info.parserAtoms);
+ if (!name) {
+ return false;
+ }
+ }
+ }
+
+ // If we don't have a name, we can't optimize to getprop.
+ if (!name) {
+ return true;
+ }
+
+ // Optimization 3: We have expr["foo"] where foo is not an index. Convert
+ // to a property access (like expr.foo) that optimizes better downstream.
+
+ NameNode* propertyNameExpr = info.handler->newPropertyName(name, key->pn_pos);
+ if (!propertyNameExpr) {
+ return false;
+ }
+ if (!TryReplaceNode(
+ nodePtr, info.handler->newPropertyAccess(expr, propertyNameExpr))) {
+ return false;
+ }
+
+ return true;
+}
+
+static bool FoldAdd(FoldInfo info, ParseNode** nodePtr) {
+ ListNode* node = &(*nodePtr)->as<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;
+ }
+
+ do {
+ // Concat all strings.
+ MOZ_ASSERT((*current)->isKind(ParseNodeKind::StringExpr));
+
+ // To avoid unnecessarily copy when there's no strings after the
+ // first item, lazily construct StringBuffer and append the first item.
+ mozilla::Maybe<StringBuffer> accum;
+ TaggedParserAtomIndex firstAtom;
+ firstAtom = (*current)->as<NameNode>().atom();
+
+ do {
+ // Try folding the next operand to a string.
+ if (!FoldType(info, next, ParseNodeKind::StringExpr)) {
+ return false;
+ }
+
+ // Stop glomming once folding doesn't produce a string.
+ if (!(*next)->isKind(ParseNodeKind::StringExpr)) {
+ break;
+ }
+
+ if (!accum) {
+ accum.emplace(info.fc);
+ if (!accum->append(info.parserAtoms, firstAtom)) {
+ return false;
+ }
+ }
+ // Append this string and remove the node.
+ if (!accum->append(info.parserAtoms, (*next)->as<NameNode>().atom())) {
+ return false;
+ }
+
+ (*current)->pn_next = (*next)->pn_next;
+ next = &(*current)->pn_next;
+
+ node->unsafeDecrementCount();
+ } while (*next);
+
+ // Replace with concatenation if we multiple nodes.
+ if (accum) {
+ auto combination = accum->finishParserAtom(info.parserAtoms, info.fc);
+ if (!combination) {
+ return false;
+ }
+
+ // Replace |current|'s string with the entire combination.
+ MOZ_ASSERT((*current)->isKind(ParseNodeKind::StringExpr));
+ (*current)->as<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;
+
+ ParserAtomsTable& parserAtoms;
+ FullParseHandler* handler;
+
+ FoldInfo info() const { return FoldInfo{fc_, parserAtoms, handler}; }
+
+ public:
+ FoldVisitor(FrontendContext* fc, ParserAtomsTable& parserAtoms,
+ FullParseHandler* handler)
+ : RewritingParseNodeVisitor(fc),
+ parserAtoms(parserAtoms),
+ handler(handler) {}
+
+ bool visitElemExpr(ParseNode*& pn) {
+ return Base::visitElemExpr(pn) && FoldElement(info(), &pn);
+ }
+
+ bool visitTypeOfExpr(ParseNode*& pn) {
+ return Base::visitTypeOfExpr(pn) && FoldTypeOfExpr(info(), &pn);
+ }
+
+ bool visitDeleteExpr(ParseNode*& pn) {
+ return Base::visitDeleteExpr(pn) && FoldDeleteExpr(info(), &pn);
+ }
+
+ bool visitDeleteElemExpr(ParseNode*& pn) {
+ return Base::visitDeleteElemExpr(pn) && FoldDeleteElement(info(), &pn);
+ }
+
+ bool visitNotExpr(ParseNode*& pn) {
+ return Base::visitNotExpr(pn) && FoldNot(info(), &pn);
+ }
+
+ bool visitBitNotExpr(ParseNode*& pn) {
+ return Base::visitBitNotExpr(pn) && FoldUnaryArithmetic(info(), &pn);
+ }
+
+ bool visitPosExpr(ParseNode*& pn) {
+ return Base::visitPosExpr(pn) && FoldUnaryArithmetic(info(), &pn);
+ }
+
+ bool visitNegExpr(ParseNode*& pn) {
+ return Base::visitNegExpr(pn) && FoldUnaryArithmetic(info(), &pn);
+ }
+
+ bool visitPowExpr(ParseNode*& pn) {
+ return Base::visitPowExpr(pn) && FoldExponentiation(info(), &pn);
+ }
+
+ bool visitMulExpr(ParseNode*& pn) {
+ return Base::visitMulExpr(pn) && FoldBinaryArithmetic(info(), &pn);
+ }
+
+ bool visitDivExpr(ParseNode*& pn) {
+ return Base::visitDivExpr(pn) && FoldBinaryArithmetic(info(), &pn);
+ }
+
+ bool visitModExpr(ParseNode*& pn) {
+ return Base::visitModExpr(pn) && FoldBinaryArithmetic(info(), &pn);
+ }
+
+ bool visitAddExpr(ParseNode*& pn) {
+ return Base::visitAddExpr(pn) && FoldAdd(info(), &pn);
+ }
+
+ bool visitSubExpr(ParseNode*& pn) {
+ return Base::visitSubExpr(pn) && FoldBinaryArithmetic(info(), &pn);
+ }
+
+ bool visitLshExpr(ParseNode*& pn) {
+ return Base::visitLshExpr(pn) && FoldBinaryArithmetic(info(), &pn);
+ }
+
+ bool visitRshExpr(ParseNode*& pn) {
+ return Base::visitRshExpr(pn) && FoldBinaryArithmetic(info(), &pn);
+ }
+
+ bool visitUrshExpr(ParseNode*& pn) {
+ return Base::visitUrshExpr(pn) && FoldBinaryArithmetic(info(), &pn);
+ }
+
+ bool visitAndExpr(ParseNode*& pn) {
+ // Note that this does result in the unfortunate fact that dead arms of this
+ // node get constant folded. The same goes for visitOr and visitCoalesce.
+ return Base::visitAndExpr(pn) && FoldAndOrCoalesce(info(), &pn);
+ }
+
+ bool visitOrExpr(ParseNode*& pn) {
+ return Base::visitOrExpr(pn) && FoldAndOrCoalesce(info(), &pn);
+ }
+
+ bool visitCoalesceExpr(ParseNode*& pn) {
+ return Base::visitCoalesceExpr(pn) && FoldAndOrCoalesce(info(), &pn);
+ }
+
+ bool visitConditionalExpr(ParseNode*& pn) {
+ // Don't call base-class visitConditional because FoldConditional processes
+ // pn's child nodes specially to save stack space.
+ return FoldConditional(info(), &pn);
+ }
+
+ private:
+ bool internalVisitCall(BinaryNode* node) {
+ MOZ_ASSERT(node->isKind(ParseNodeKind::CallExpr) ||
+ node->isKind(ParseNodeKind::OptionalCallExpr) ||
+ node->isKind(ParseNodeKind::SuperCallExpr) ||
+ node->isKind(ParseNodeKind::NewExpr) ||
+ node->isKind(ParseNodeKind::TaggedTemplateExpr));
+
+ // Don't fold a parenthesized callable component in an invocation, as this
+ // might cause a different |this| value to be used, changing semantics:
+ //
+ // var prop = "global";
+ // var obj = { prop: "obj", f: function() { return this.prop; } };
+ // assertEq((true ? obj.f : null)(), "global");
+ // assertEq(obj.f(), "obj");
+ // assertEq((true ? obj.f : null)``, "global");
+ // assertEq(obj.f``, "obj");
+ //
+ // As an exception to this, we do allow folding the function in
+ // `(function() { ... })()` (the module pattern), because that lets us
+ // constant fold code inside that function.
+ //
+ // See bug 537673 and bug 1182373.
+ ParseNode* callee = node->left();
+ if (node->isKind(ParseNodeKind::NewExpr) || !callee->isInParens() ||
+ callee->is<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(FrontendContext* fc, ParserAtomsTable& parserAtoms,
+ FullParseHandler* handler, ParseNode** pnp) {
+ FoldVisitor visitor(fc, parserAtoms, handler);
+ return visitor.visit(*pnp);
+}
+static bool Fold(FoldInfo info, ParseNode** pnp) {
+ return Fold(info.fc, info.parserAtoms, info.handler, pnp);
+}
+
+bool frontend::FoldConstants(FrontendContext* fc, ParserAtomsTable& parserAtoms,
+ ParseNode** pnp, FullParseHandler* handler) {
+ return Fold(fc, parserAtoms, handler, pnp);
+}
diff --git a/js/src/frontend/FoldConstants.h b/js/src/frontend/FoldConstants.h
new file mode 100644
index 0000000000..70252b148b
--- /dev/null
+++ b/js/src/frontend/FoldConstants.h
@@ -0,0 +1,53 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef frontend_FoldConstants_h
+#define frontend_FoldConstants_h
+
+#include "frontend/SyntaxParseHandler.h"
+
+namespace js {
+
+class FrontendContext;
+
+namespace frontend {
+
+class FullParseHandler;
+template <class ParseHandler>
+class PerHandlerParser;
+class ParserAtomsTable;
+
+// Perform constant folding on the given AST. For example, the program
+// `print(2 + 2)` would become `print(4)`.
+//
+// pnp is the address of a pointer variable that points to the root node of the
+// AST. On success, *pnp points to the root node of the new tree, which may be
+// the same node (unchanged or modified in place) or a new node.
+//
+// Usage:
+// pn = parser->statement();
+// if (!pn) {
+// return false;
+// }
+// if (!FoldConstants(fc, parserAtoms, &pn, parser)) {
+// return false;
+// }
+[[nodiscard]] extern bool FoldConstants(FrontendContext* fc,
+ ParserAtomsTable& parserAtoms,
+ ParseNode** pnp,
+ FullParseHandler* handler);
+
+[[nodiscard]] inline bool FoldConstants(FrontendContext* fc,
+ ParserAtomsTable& parserAtoms,
+ typename SyntaxParseHandler::Node* pnp,
+ SyntaxParseHandler* handler) {
+ return true;
+}
+
+} /* namespace frontend */
+} /* namespace js */
+
+#endif /* frontend_FoldConstants_h */
diff --git a/js/src/frontend/ForInEmitter.cpp b/js/src/frontend/ForInEmitter.cpp
new file mode 100644
index 0000000000..4d9f9ad540
--- /dev/null
+++ b/js/src/frontend/ForInEmitter.cpp
@@ -0,0 +1,154 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "frontend/ForInEmitter.h"
+
+#include "frontend/BytecodeEmitter.h"
+#include "frontend/EmitterScope.h"
+#include "vm/Opcodes.h"
+#include "vm/StencilEnums.h" // TryNoteKind
+
+using namespace js;
+using namespace js::frontend;
+
+using mozilla::Nothing;
+
+ForInEmitter::ForInEmitter(BytecodeEmitter* bce,
+ const EmitterScope* headLexicalEmitterScope)
+ : bce_(bce), headLexicalEmitterScope_(headLexicalEmitterScope) {}
+
+bool ForInEmitter::emitIterated() {
+ MOZ_ASSERT(state_ == State::Start);
+ tdzCacheForIteratedValue_.emplace(bce_);
+
+#ifdef DEBUG
+ state_ = State::Iterated;
+#endif
+ return true;
+}
+
+bool ForInEmitter::emitInitialize() {
+ MOZ_ASSERT(state_ == State::Iterated);
+ tdzCacheForIteratedValue_.reset();
+
+ if (!bce_->emit1(JSOp::Iter)) {
+ // [stack] ITER
+ return false;
+ }
+
+ loopInfo_.emplace(bce_, StatementKind::ForInLoop);
+
+ if (!loopInfo_->emitLoopHead(bce_, Nothing())) {
+ // [stack] ITER
+ return false;
+ }
+
+ if (!bce_->emit1(JSOp::MoreIter)) {
+ // [stack] ITER NEXTITERVAL?
+ return false;
+ }
+ if (!bce_->emit1(JSOp::IsNoIter)) {
+ // [stack] ITER NEXTITERVAL? ISNOITER
+ return false;
+ }
+ if (!bce_->emitJump(JSOp::JumpIfTrue, &loopInfo_->breaks)) {
+ // [stack] ITER NEXTITERVAL?
+ return false;
+ }
+
+ // If the loop had an escaping lexical declaration, reset the declaration's
+ // bindings to uninitialized to implement TDZ semantics.
+ if (headLexicalEmitterScope_) {
+ // The environment chain only includes an environment for the
+ // for-in loop head *if* a scope binding is captured, thereby
+ // requiring recreation each iteration. If a lexical scope exists
+ // for the head, it must be the innermost one. If that scope has
+ // closed-over bindings inducing an environment, recreate the
+ // current environment.
+ MOZ_ASSERT(headLexicalEmitterScope_ == bce_->innermostEmitterScope());
+ MOZ_ASSERT(headLexicalEmitterScope_->scope(bce_).kind() ==
+ ScopeKind::Lexical);
+
+ if (headLexicalEmitterScope_->hasEnvironment()) {
+ if (!bce_->emitInternedScopeOp(headLexicalEmitterScope_->index(),
+ JSOp::RecreateLexicalEnv)) {
+ // [stack] ITER ITERVAL
+ return false;
+ }
+ }
+
+ // For uncaptured bindings, put them back in TDZ.
+ if (!headLexicalEmitterScope_->deadZoneFrameSlots(bce_)) {
+ return false;
+ }
+ }
+
+#ifdef DEBUG
+ loopDepth_ = bce_->bytecodeSection().stackDepth();
+#endif
+ MOZ_ASSERT(loopDepth_ >= 2);
+
+#ifdef DEBUG
+ state_ = State::Initialize;
+#endif
+ return true;
+}
+
+bool ForInEmitter::emitBody() {
+ MOZ_ASSERT(state_ == State::Initialize);
+
+ MOZ_ASSERT(bce_->bytecodeSection().stackDepth() == loopDepth_,
+ "iterator and iterval must be left on the stack");
+
+#ifdef DEBUG
+ state_ = State::Body;
+#endif
+ return true;
+}
+
+bool ForInEmitter::emitEnd(uint32_t forPos) {
+ MOZ_ASSERT(state_ == State::Body);
+
+ // Make sure this code is attributed to the "for".
+ if (!bce_->updateSourceCoordNotes(forPos)) {
+ return false;
+ }
+
+ if (!loopInfo_->emitContinueTarget(bce_)) {
+ // [stack] ITER ITERVAL
+ return false;
+ }
+
+ if (!bce_->emit1(JSOp::Pop)) {
+ // [stack] ITER
+ return false;
+ }
+ if (!loopInfo_->emitLoopEnd(bce_, JSOp::Goto, TryNoteKind::ForIn)) {
+ // [stack] ITER
+ return false;
+ }
+
+ // When we leave the loop body and jump to this point, the iteration value is
+ // still on the stack. Account for that by updating the stack depth manually.
+ int32_t stackDepth = bce_->bytecodeSection().stackDepth() + 1;
+ MOZ_ASSERT(stackDepth == loopDepth_);
+ bce_->bytecodeSection().setStackDepth(stackDepth);
+
+ // [stack] ITER ITERVAL
+
+ // Pop the value and iterator and close the iterator.
+ if (!bce_->emit1(JSOp::EndIter)) {
+ // [stack]
+ return false;
+ }
+
+ loopInfo_.reset();
+
+#ifdef DEBUG
+ state_ = State::End;
+#endif
+ return true;
+}
diff --git a/js/src/frontend/ForInEmitter.h b/js/src/frontend/ForInEmitter.h
new file mode 100644
index 0000000000..ed5aa312c5
--- /dev/null
+++ b/js/src/frontend/ForInEmitter.h
@@ -0,0 +1,117 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef frontend_ForInEmitter_h
+#define frontend_ForInEmitter_h
+
+#include "mozilla/Attributes.h"
+#include "mozilla/Maybe.h"
+
+#include <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(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
+ [[nodiscard]] bool emitIterated();
+ [[nodiscard]] bool emitInitialize();
+ [[nodiscard]] bool emitBody();
+ [[nodiscard]] bool emitEnd(uint32_t forPos);
+};
+
+} /* namespace frontend */
+} /* namespace js */
+
+#endif /* frontend_ForInEmitter_h */
diff --git a/js/src/frontend/ForOfEmitter.cpp b/js/src/frontend/ForOfEmitter.cpp
new file mode 100644
index 0000000000..2bef507a99
--- /dev/null
+++ b/js/src/frontend/ForOfEmitter.cpp
@@ -0,0 +1,223 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "frontend/ForOfEmitter.h"
+
+#include "frontend/BytecodeEmitter.h"
+#include "frontend/EmitterScope.h"
+#include "frontend/ParserAtom.h" // TaggedParserAtomIndex
+#include "vm/Opcodes.h"
+#include "vm/StencilEnums.h" // TryNoteKind
+
+using namespace js;
+using namespace js::frontend;
+
+using mozilla::Nothing;
+
+ForOfEmitter::ForOfEmitter(BytecodeEmitter* bce,
+ const EmitterScope* headLexicalEmitterScope,
+ SelfHostedIter selfHostedIter, IteratorKind iterKind)
+ : bce_(bce),
+ selfHostedIter_(selfHostedIter),
+ iterKind_(iterKind),
+ headLexicalEmitterScope_(headLexicalEmitterScope) {}
+
+bool ForOfEmitter::emitIterated() {
+ MOZ_ASSERT(state_ == State::Start);
+
+ // Evaluate the expression being iterated. The forHeadExpr should use a
+ // distinct TDZCheckCache to evaluate since (abstractly) it runs in its
+ // own LexicalEnvironment.
+ tdzCacheForIteratedValue_.emplace(bce_);
+
+#ifdef DEBUG
+ state_ = State::Iterated;
+#endif
+ return true;
+}
+
+bool ForOfEmitter::emitInitialize(uint32_t forPos,
+ bool isIteratorMethodOnStack) {
+ MOZ_ASSERT(state_ == State::Iterated);
+
+ tdzCacheForIteratedValue_.reset();
+
+ // [stack] # if isIteratorMethodOnStack
+ // [stack] ITERABLE ITERFN SYNC_ITERFN?
+ // [stack] # else isIteratorMethodOnStack
+ // [stack] ITERABLE
+
+ if (iterKind_ == IteratorKind::Async) {
+ if (!bce_->emitAsyncIterator(selfHostedIter_, isIteratorMethodOnStack)) {
+ // [stack] NEXT ITER
+ return false;
+ }
+ } else {
+ if (!bce_->emitIterator(selfHostedIter_, isIteratorMethodOnStack)) {
+ // [stack] NEXT ITER
+ return false;
+ }
+ }
+
+ // For-of loops have the iterator next method and the iterator itself on the
+ // stack.
+
+ int32_t iterDepth = bce_->bytecodeSection().stackDepth();
+ loopInfo_.emplace(bce_, iterDepth, selfHostedIter_, iterKind_);
+
+ if (!loopInfo_->emitLoopHead(bce_, Nothing())) {
+ // [stack] NEXT ITER
+ return false;
+ }
+
+ // If the loop had an escaping lexical declaration, replace the current
+ // environment with an dead zoned one to implement TDZ semantics.
+ if (headLexicalEmitterScope_) {
+ // The environment chain only includes an environment for the for-of
+ // loop head *if* a scope binding is captured, thereby requiring
+ // recreation each iteration. If a lexical scope exists for the head,
+ // it must be the innermost one. If that scope has closed-over
+ // bindings inducing an environment, recreate the current environment.
+ MOZ_ASSERT(headLexicalEmitterScope_ == bce_->innermostEmitterScope());
+ MOZ_ASSERT(headLexicalEmitterScope_->scope(bce_).kind() ==
+ ScopeKind::Lexical);
+
+ if (headLexicalEmitterScope_->hasEnvironment()) {
+ if (!bce_->emitInternedScopeOp(headLexicalEmitterScope_->index(),
+ JSOp::RecreateLexicalEnv)) {
+ // [stack] NEXT ITER
+ return false;
+ }
+ }
+
+ // For uncaptured bindings, put them back in TDZ.
+ if (!headLexicalEmitterScope_->deadZoneFrameSlots(bce_)) {
+ return false;
+ }
+ }
+
+#ifdef DEBUG
+ loopDepth_ = bce_->bytecodeSection().stackDepth();
+#endif
+
+ // Make sure this code is attributed to the "for".
+ if (!bce_->updateSourceCoordNotes(forPos)) {
+ return false;
+ }
+
+ if (!bce_->emit1(JSOp::Dup2)) {
+ // [stack] NEXT ITER NEXT ITER
+ return false;
+ }
+
+ if (!bce_->emitIteratorNext(mozilla::Some(forPos), iterKind_,
+ selfHostedIter_)) {
+ // [stack] NEXT ITER RESULT
+ return false;
+ }
+
+ if (!bce_->emit1(JSOp::Dup)) {
+ // [stack] NEXT ITER RESULT RESULT
+ return false;
+ }
+ if (!bce_->emitAtomOp(JSOp::GetProp,
+ TaggedParserAtomIndex::WellKnown::done())) {
+ // [stack] NEXT ITER RESULT DONE
+ return false;
+ }
+
+ // if (done) break;
+ MOZ_ASSERT(bce_->innermostNestableControl == loopInfo_.ptr(),
+ "must be at the top-level of the loop");
+ if (!bce_->emitJump(JSOp::JumpIfTrue, &loopInfo_->breaks)) {
+ // [stack] NEXT ITER RESULT
+ return false;
+ }
+
+ // Emit code to assign result.value to the iteration variable.
+ //
+ // Note that ES 13.7.5.13, step 5.c says getting result.value does not
+ // call IteratorClose, so start TryNoteKind::ForOfIterClose after the GetProp.
+ if (!bce_->emitAtomOp(JSOp::GetProp,
+ TaggedParserAtomIndex::WellKnown::value())) {
+ // [stack] NEXT ITER VALUE
+ return false;
+ }
+
+ if (!loopInfo_->emitBeginCodeNeedingIteratorClose(bce_)) {
+ return false;
+ }
+
+#ifdef DEBUG
+ state_ = State::Initialize;
+#endif
+ return true;
+}
+
+bool ForOfEmitter::emitBody() {
+ MOZ_ASSERT(state_ == State::Initialize);
+
+ MOZ_ASSERT(bce_->bytecodeSection().stackDepth() == loopDepth_ + 1,
+ "the stack must be balanced around the initializing "
+ "operation");
+
+#ifdef DEBUG
+ state_ = State::Body;
+#endif
+ return true;
+}
+
+bool ForOfEmitter::emitEnd(uint32_t iteratedPos) {
+ MOZ_ASSERT(state_ == State::Body);
+
+ MOZ_ASSERT(bce_->bytecodeSection().stackDepth() == loopDepth_ + 1,
+ "the stack must be balanced around the for-of body");
+
+ if (!loopInfo_->emitEndCodeNeedingIteratorClose(bce_)) {
+ // [stack] NEXT ITER VALUE
+ return false;
+ }
+
+ if (!loopInfo_->emitContinueTarget(bce_)) {
+ // [stack] NEXT ITER VALUE
+ return false;
+ }
+
+ // We use the iterated value's position to attribute the backedge,
+ // which corresponds to the iteration protocol.
+ // This is a bit misleading for 2nd and later iterations and might need
+ // some fix (bug 1482003).
+ if (!bce_->updateSourceCoordNotes(iteratedPos)) {
+ return false;
+ }
+
+ if (!bce_->emit1(JSOp::Pop)) {
+ // [stack] NEXT ITER
+ return false;
+ }
+
+ if (!loopInfo_->emitLoopEnd(bce_, JSOp::Goto, TryNoteKind::ForOf)) {
+ // [stack] NEXT ITER
+ return false;
+ }
+
+ // All jumps/breaks to this point still have an extra value on the stack.
+ MOZ_ASSERT(bce_->bytecodeSection().stackDepth() == loopDepth_);
+ bce_->bytecodeSection().setStackDepth(bce_->bytecodeSection().stackDepth() +
+ 1);
+
+ if (!bce_->emitPopN(3)) {
+ // [stack]
+ return false;
+ }
+
+ loopInfo_.reset();
+
+#ifdef DEBUG
+ state_ = State::End;
+#endif
+ return true;
+}
diff --git a/js/src/frontend/ForOfEmitter.h b/js/src/frontend/ForOfEmitter.h
new file mode 100644
index 0000000000..22e67f3f94
--- /dev/null
+++ b/js/src/frontend/ForOfEmitter.h
@@ -0,0 +1,116 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef frontend_ForOfEmitter_h
+#define frontend_ForOfEmitter_h
+
+#include "mozilla/Attributes.h" // MOZ_STACK_CLASS
+#include "mozilla/Maybe.h" // mozilla::Maybe
+
+#include <stdint.h> // int32_t
+
+#include "frontend/ForOfLoopControl.h" // ForOfLoopControl
+#include "frontend/IteratorKind.h" // IteratorKind
+#include "frontend/SelfHostedIter.h" // SelfHostedIter
+#include "frontend/TDZCheckCache.h" // TDZCheckCache
+
+namespace js {
+namespace frontend {
+
+struct BytecodeEmitter;
+class EmitterScope;
+
+// Class for emitting bytecode for for-of loop.
+//
+// Usage: (check for the return value is omitted for simplicity)
+//
+// `for (init of iterated) body`
+// // headLexicalEmitterScope: lexical scope for init
+// ForOfEmitter forOf(this, headLexicalEmitterScope);
+// forOf.emitIterated();
+// emit(iterated);
+// forOf.emitInitialize(offset_of_for);
+// emit(init);
+// forOf.emitBody();
+// emit(body);
+// forOf.emitEnd(offset_of_iterated);
+//
+class MOZ_STACK_CLASS ForOfEmitter {
+ BytecodeEmitter* bce_;
+
+#ifdef DEBUG
+ // The stack depth before emitting IteratorNext code inside loop.
+ int32_t loopDepth_ = 0;
+#endif
+
+ SelfHostedIter selfHostedIter_;
+ IteratorKind iterKind_;
+
+ mozilla::Maybe<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,
+ SelfHostedIter selfHostedIter, IteratorKind iterKind);
+
+ // The offset in the source code for each character below:
+ //
+ // for ( var x of obj ) { ... }
+ // ^ ^
+ // | |
+ // | iteratedPos
+ // |
+ // forPos
+ [[nodiscard]] bool emitIterated();
+ [[nodiscard]] bool emitInitialize(uint32_t forPos,
+ bool isIteratorMethodOnStack);
+ [[nodiscard]] bool emitBody();
+ [[nodiscard]] bool emitEnd(uint32_t iteratedPos);
+};
+
+} /* namespace frontend */
+} /* namespace js */
+
+#endif /* frontend_ForOfEmitter_h */
diff --git a/js/src/frontend/ForOfLoopControl.cpp b/js/src/frontend/ForOfLoopControl.cpp
new file mode 100644
index 0000000000..084f8d80eb
--- /dev/null
+++ b/js/src/frontend/ForOfLoopControl.cpp
@@ -0,0 +1,196 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "frontend/ForOfLoopControl.h"
+
+#include "frontend/BytecodeEmitter.h" // BytecodeEmitter
+#include "frontend/IfEmitter.h" // InternalIfEmitter
+#include "vm/CompletionKind.h" // CompletionKind
+#include "vm/Opcodes.h" // JSOp
+
+using namespace js;
+using namespace js::frontend;
+
+ForOfLoopControl::ForOfLoopControl(BytecodeEmitter* bce, int32_t iterDepth,
+ SelfHostedIter selfHostedIter,
+ IteratorKind iterKind)
+ : LoopControl(bce, StatementKind::ForOfLoop),
+ iterDepth_(iterDepth),
+ numYieldsAtBeginCodeNeedingIterClose_(UINT32_MAX),
+ selfHostedIter_(selfHostedIter),
+ iterKind_(iterKind) {}
+
+bool ForOfLoopControl::emitBeginCodeNeedingIteratorClose(BytecodeEmitter* bce) {
+ tryCatch_.emplace(bce, TryEmitter::Kind::TryCatch,
+ TryEmitter::ControlKind::NonSyntactic);
+
+ if (!tryCatch_->emitTry()) {
+ return false;
+ }
+
+ MOZ_ASSERT(numYieldsAtBeginCodeNeedingIterClose_ == UINT32_MAX);
+ numYieldsAtBeginCodeNeedingIterClose_ = bce->bytecodeSection().numYields();
+
+ return true;
+}
+
+bool ForOfLoopControl::emitEndCodeNeedingIteratorClose(BytecodeEmitter* bce) {
+ if (!tryCatch_->emitCatch()) {
+ // [stack] ITER ... EXCEPTION
+ return false;
+ }
+
+ unsigned slotFromTop = bce->bytecodeSection().stackDepth() - iterDepth_;
+ if (!bce->emitDupAt(slotFromTop)) {
+ // [stack] ITER ... EXCEPTION ITER
+ return false;
+ }
+ if (!emitIteratorCloseInInnermostScopeWithTryNote(bce,
+ CompletionKind::Throw)) {
+ return false; // ITER ... EXCEPTION
+ }
+
+ if (!bce->emit1(JSOp::Throw)) {
+ // [stack] ITER ...
+ return false;
+ }
+
+ // If any yields were emitted, then this for-of loop is inside a star
+ // generator and must handle the case of Generator.return. Like in
+ // yield*, it is handled with a finally block. If the generator is
+ // closing, then the exception/resumeindex value (second value on
+ // the stack) will be a magic JS_GENERATOR_CLOSING value.
+ // TODO: Refactor this to eliminate the swaps.
+ uint32_t numYieldsEmitted = bce->bytecodeSection().numYields();
+ if (numYieldsEmitted > numYieldsAtBeginCodeNeedingIterClose_) {
+ if (!tryCatch_->emitFinally()) {
+ return false;
+ }
+ // [stack] ITER ... FVALUE FTYPE
+ InternalIfEmitter ifGeneratorClosing(bce);
+ if (!bce->emit1(JSOp::Swap)) {
+ // [stack] ITER ... FTYPE FVALUE
+ return false;
+ }
+ if (!bce->emit1(JSOp::IsGenClosing)) {
+ // [stack] ITER ... FTYPE FVALUE CLOSING
+ return false;
+ }
+ if (!ifGeneratorClosing.emitThen()) {
+ // [stack] ITER ... FTYPE FVALUE
+ return false;
+ }
+ if (!bce->emitDupAt(slotFromTop + 1)) {
+ // [stack] ITER ... FTYPE FVALUE ITER
+ return false;
+ }
+ if (!emitIteratorCloseInInnermostScopeWithTryNote(bce,
+ CompletionKind::Normal)) {
+ // [stack] ITER ... FTYPE FVALUE
+ return false;
+ }
+ if (!ifGeneratorClosing.emitEnd()) {
+ // [stack] ITER ... FTYPE FVALUE
+ return false;
+ }
+ if (!bce->emit1(JSOp::Swap)) {
+ // [stack] ITER ... FVALUE FTYPE
+ return false;
+ }
+ }
+
+ if (!tryCatch_->emitEnd()) {
+ return false;
+ }
+
+ tryCatch_.reset();
+ numYieldsAtBeginCodeNeedingIterClose_ = UINT32_MAX;
+
+ return true;
+}
+
+bool ForOfLoopControl::emitIteratorCloseInInnermostScopeWithTryNote(
+ BytecodeEmitter* bce,
+ CompletionKind completionKind /* = CompletionKind::Normal */) {
+ BytecodeOffset start = bce->bytecodeSection().offset();
+ if (!emitIteratorCloseInScope(bce, *bce->innermostEmitterScope(),
+ completionKind)) {
+ return false;
+ }
+ BytecodeOffset end = bce->bytecodeSection().offset();
+ return bce->addTryNote(TryNoteKind::ForOfIterClose, 0, start, end);
+}
+
+bool ForOfLoopControl::emitIteratorCloseInScope(
+ BytecodeEmitter* bce, EmitterScope& currentScope,
+ CompletionKind completionKind /* = CompletionKind::Normal */) {
+ return bce->emitIteratorCloseInScope(currentScope, iterKind_, completionKind,
+ selfHostedIter_);
+}
+
+// Since we're in the middle of emitting code that will leave
+// |bce->innermostEmitterScope()|, passing the innermost emitter scope to
+// emitIteratorCloseInScope and looking up .generator there would be very,
+// very wrong. We'd find .generator in the function environment, and we'd
+// compute a NameLocation with the correct slot, but we'd compute a
+// hop-count to the function environment that was too big. At runtime we'd
+// either crash, or we'd find a user-controllable value in that slot, and
+// Very Bad Things would ensue as we reinterpreted that value as an
+// iterator.
+bool ForOfLoopControl::emitPrepareForNonLocalJumpFromScope(
+ BytecodeEmitter* bce, EmitterScope& currentScope, bool isTarget,
+ BytecodeOffset* tryNoteStart) {
+ // Pop unnecessary value from the stack. Effectively this means
+ // leaving try-catch block. However, the performing IteratorClose can
+ // reach the depth for try-catch, and effectively re-enter the
+ // try-catch block.
+ if (!bce->emit1(JSOp::Pop)) {
+ // [stack] NEXT ITER
+ return false;
+ }
+
+ // Pop the iterator's next method.
+ if (!bce->emit1(JSOp::Swap)) {
+ // [stack] ITER NEXT
+ return false;
+ }
+ if (!bce->emit1(JSOp::Pop)) {
+ // [stack] ITER
+ return false;
+ }
+
+ if (!bce->emit1(JSOp::Dup)) {
+ // [stack] ITER ITER
+ return false;
+ }
+
+ *tryNoteStart = bce->bytecodeSection().offset();
+ if (!emitIteratorCloseInScope(bce, currentScope, CompletionKind::Normal)) {
+ // [stack] ITER
+ return false;
+ }
+
+ if (isTarget) {
+ // At the level of the target block, there's bytecode after the
+ // loop that will pop the next method, the iterator, and the
+ // value, so push two undefineds to balance the stack.
+ if (!bce->emit1(JSOp::Undefined)) {
+ // [stack] ITER UNDEF
+ return false;
+ }
+ if (!bce->emit1(JSOp::Undefined)) {
+ // [stack] ITER UNDEF UNDEF
+ return false;
+ }
+ } else {
+ if (!bce->emit1(JSOp::Pop)) {
+ // [stack]
+ return false;
+ }
+ }
+
+ return true;
+}
diff --git a/js/src/frontend/ForOfLoopControl.h b/js/src/frontend/ForOfLoopControl.h
new file mode 100644
index 0000000000..5ffda9a8ba
--- /dev/null
+++ b/js/src/frontend/ForOfLoopControl.h
@@ -0,0 +1,97 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef frontend_ForOfLoopControl_h
+#define frontend_ForOfLoopControl_h
+
+#include "mozilla/Maybe.h" // mozilla::Maybe
+
+#include <stdint.h> // int32_t, uint32_t
+
+#include "frontend/BytecodeControlStructures.h" // NestableControl, LoopControl
+#include "frontend/IteratorKind.h" // IteratorKind
+#include "frontend/SelfHostedIter.h" // SelfHostedIter
+#include "frontend/TryEmitter.h" // TryEmitter
+#include "vm/CompletionKind.h" // CompletionKind
+
+namespace js {
+namespace frontend {
+
+struct BytecodeEmitter;
+class BytecodeOffset;
+class EmitterScope;
+
+class ForOfLoopControl : public LoopControl {
+ // The stack depth of the iterator.
+ int32_t iterDepth_;
+
+ // For-of loops, when throwing from non-iterator code (i.e. from the body
+ // or from evaluating the LHS of the loop condition), need to call
+ // IteratorClose. This is done by enclosing the body of the loop with
+ // try-catch and calling IteratorClose in the `catch` block.
+ //
+ // If IteratorClose itself throws, we must not re-call
+ // IteratorClose. Since non-local jumps like break and return call
+ // IteratorClose, whenever a non-local jump is emitted, we must
+ // prevent the catch block from catching any exception thrown from
+ // IteratorClose. We do this by wrapping the non-local jump in a
+ // ForOfIterClose try-note.
+ //
+ // for (x of y) {
+ // // Operations for iterator (IteratorNext etc) are outside of
+ // // try-block.
+ // try {
+ // ...
+ // if (...) {
+ // // Before non-local jump, close iterator.
+ // CloseIter(iter, CompletionKind::Return); // Covered by
+ // return; // trynote
+ // }
+ // ...
+ // } catch (e) {
+ // // When propagating an exception, we swallow any exceptions
+ // // thrown while closing the iterator.
+ // CloseIter(iter, CompletionKind::Throw);
+ // throw e;
+ // }
+ // }
+ mozilla::Maybe<TryEmitter> tryCatch_;
+
+ // Used to track if any yields were emitted between calls to to
+ // emitBeginCodeNeedingIteratorClose and emitEndCodeNeedingIteratorClose.
+ uint32_t numYieldsAtBeginCodeNeedingIterClose_;
+
+ SelfHostedIter selfHostedIter_;
+
+ IteratorKind iterKind_;
+
+ public:
+ ForOfLoopControl(BytecodeEmitter* bce, int32_t iterDepth,
+ SelfHostedIter selfHostedIter, IteratorKind iterKind);
+
+ [[nodiscard]] bool emitBeginCodeNeedingIteratorClose(BytecodeEmitter* bce);
+ [[nodiscard]] bool emitEndCodeNeedingIteratorClose(BytecodeEmitter* bce);
+
+ [[nodiscard]] bool emitIteratorCloseInInnermostScopeWithTryNote(
+ BytecodeEmitter* bce,
+ CompletionKind completionKind = CompletionKind::Normal);
+ [[nodiscard]] bool emitIteratorCloseInScope(
+ BytecodeEmitter* bce, EmitterScope& currentScope,
+ CompletionKind completionKind = CompletionKind::Normal);
+
+ [[nodiscard]] bool emitPrepareForNonLocalJumpFromScope(
+ BytecodeEmitter* bce, EmitterScope& currentScope, bool isTarget,
+ BytecodeOffset* tryNoteStart);
+};
+template <>
+inline bool NestableControl::is<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..0b4f2d638c
--- /dev/null
+++ b/js/src/frontend/Frontend2.cpp
@@ -0,0 +1,701 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "frontend/Frontend2.h"
+
+#include "mozilla/Maybe.h" // mozilla::Maybe
+#include "mozilla/OperatorNewExtensions.h" // mozilla::KnownNotNull
+#include "mozilla/Range.h" // mozilla::Range
+#include "mozilla/Span.h" // mozilla::Span
+#include "mozilla/Variant.h" // mozilla::AsVariant
+
+#include <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/CompilationStencil.h" // CompilationState, CompilationStencil
+#include "frontend/FrontendContext.h" // AutoReportFrontendContext
+#include "frontend/Parser.h" // NewEmptyLexicalScopeData, NewEmptyGlobalScopeData, NewEmptyVarScopeData, NewEmptyFunctionScopeData
+#include "frontend/ParserAtom.h" // ParserAtomsTable, TaggedParserAtomIndex
+#include "frontend/ScriptIndex.h" // ScriptIndex
+#include "frontend/smoosh_generated.h" // CVec, Smoosh*, smoosh_*
+#include "frontend/SourceNotes.h" // SrcNote
+#include "frontend/Stencil.h" // ScopeStencil, RegExpIndex
+#include "frontend/TokenStream.h" // TokenStreamAnyChars
+#include "irregexp/RegExpAPI.h" // irregexp::CheckPatternSyntax
+#include "js/CharacterEncoding.h" // JS::UTF8Chars, UTF8CharsToNewTwoByteCharsZ
+#include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_*
+#include "js/GCAPI.h" // JS::AutoCheckCannotGC
+#include "js/HeapAPI.h" // JS::GCCellPtr
+#include "js/RegExpFlags.h" // JS::RegExpFlag, JS::RegExpFlags
+#include "js/RootingAPI.h" // JS::MutableHandle
+#include "js/UniquePtr.h" // js::UniquePtr
+#include "js/Utility.h" // JS::UniqueTwoByteChars, StringBufferArena
+#include "vm/JSScript.h" // JSScript
+#include "vm/Scope.h" // GetScopeDataTrailingNames
+#include "vm/ScopeKind.h" // ScopeKind
+#include "vm/SharedStencil.h" // ImmutableScriptData, ScopeNote, TryNote, GCThingIndex
+
+using mozilla::Utf8Unit;
+
+using namespace js::gc;
+using namespace js::frontend;
+using namespace js;
+
+namespace js {
+
+namespace frontend {
+
+// Given the result of SmooshMonkey's parser, Convert the list of atoms into
+// the list of ParserAtoms.
+bool ConvertAtoms(JSContext* cx, FrontendContext* fc,
+ const SmooshResult& result,
+ CompilationState& compilationState,
+ Vector<TaggedParserAtomIndex>& 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);
+ auto atom = compilationState.parserAtoms.internUtf8(fc, s, len);
+ if (!atom) {
+ return false;
+ }
+ // We don't collect atomization information in smoosh yet.
+ // Assume it needs to be atomized.
+ compilationState.parserAtoms.markUsedByStencil(atom,
+ ParserAtom::Atomize::Yes);
+ allAtoms.infallibleAppend(atom);
+ }
+
+ return true;
+}
+
+void CopyBindingNames(JSContext* cx, CVec<SmooshBindingName>& from,
+ Vector<TaggedParserAtomIndex>& allAtoms,
+ ParserBindingName* to) {
+ // We're setting trailing array's content before setting its length.
+ JS::AutoCheckCannotGC nogc(cx);
+
+ size_t numBindings = from.len;
+ for (size_t i = 0; i < numBindings; i++) {
+ SmooshBindingName& name = from.data[i];
+ new (mozilla::KnownNotNull, &to[i]) ParserBindingName(
+ allAtoms[name.name], name.is_closed_over, name.is_top_level_function);
+ }
+}
+
+void CopyBindingNames(JSContext* cx, CVec<COption<SmooshBindingName>>& from,
+ Vector<TaggedParserAtomIndex>& 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], name.is_closed_over, name.is_top_level_function);
+ } else {
+ new (mozilla::KnownNotNull, &to[i])
+ ParserBindingName(TaggedParserAtomIndex::null(), false, false);
+ }
+ }
+}
+
+// Given the result of SmooshMonkey's parser, convert a list of scope data
+// into a list of ScopeStencil.
+bool ConvertScopeStencil(JSContext* cx, FrontendContext* fc,
+ const SmooshResult& result,
+ Vector<TaggedParserAtomIndex>& allAtoms,
+ CompilationState& compilationState) {
+ LifoAlloc& alloc = compilationState.alloc;
+
+ if (result.scopes.len > TaggedScriptThingIndex::IndexLimit) {
+ ReportAllocationOverflow(fc);
+ return false;
+ }
+
+ for (size_t i = 0; i < result.scopes.len; i++) {
+ SmooshScopeData& scopeData = result.scopes.data[i];
+ ScopeIndex index;
+
+ switch (scopeData.tag) {
+ case SmooshScopeData::Tag::Global: {
+ auto& global = scopeData.AsGlobal();
+
+ size_t numBindings = global.bindings.len;
+ GlobalScope::ParserData* data =
+ NewEmptyGlobalScopeData(fc, alloc, numBindings);
+ if (!data) {
+ return false;
+ }
+
+ CopyBindingNames(cx, global.bindings, allAtoms,
+ GetScopeDataTrailingNamesPointer(data));
+
+ data->slotInfo.letStart = global.let_start;
+ data->slotInfo.constStart = global.const_start;
+ data->length = numBindings;
+
+ if (!ScopeStencil::createForGlobalScope(
+ fc, compilationState, ScopeKind::Global, data, &index)) {
+ return false;
+ }
+ break;
+ }
+ case SmooshScopeData::Tag::Var: {
+ auto& var = scopeData.AsVar();
+
+ size_t numBindings = var.bindings.len;
+
+ VarScope::ParserData* data =
+ NewEmptyVarScopeData(fc, alloc, numBindings);
+ if (!data) {
+ return false;
+ }
+
+ CopyBindingNames(cx, var.bindings, allAtoms,
+ GetScopeDataTrailingNamesPointer(data));
+
+ // NOTE: data->slotInfo.nextFrameSlot is set in
+ // ScopeStencil::createForVarScope.
+
+ data->length = numBindings;
+
+ uint32_t firstFrameSlot = var.first_frame_slot;
+ ScopeIndex enclosingIndex(var.enclosing);
+ if (!ScopeStencil::createForVarScope(
+ fc, compilationState, ScopeKind::FunctionBodyVar, data,
+ firstFrameSlot, var.function_has_extensible_scope,
+ mozilla::Some(enclosingIndex), &index)) {
+ return false;
+ }
+ break;
+ }
+ case SmooshScopeData::Tag::Lexical: {
+ auto& lexical = scopeData.AsLexical();
+
+ size_t numBindings = lexical.bindings.len;
+ LexicalScope::ParserData* data =
+ NewEmptyLexicalScopeData(fc, alloc, numBindings);
+ if (!data) {
+ return false;
+ }
+
+ CopyBindingNames(cx, lexical.bindings, allAtoms,
+ GetScopeDataTrailingNamesPointer(data));
+
+ // NOTE: data->slotInfo.nextFrameSlot is set in
+ // ScopeStencil::createForLexicalScope.
+
+ data->slotInfo.constStart = lexical.const_start;
+ data->length = numBindings;
+
+ uint32_t firstFrameSlot = lexical.first_frame_slot;
+ ScopeIndex enclosingIndex(lexical.enclosing);
+ if (!ScopeStencil::createForLexicalScope(
+ fc, compilationState, ScopeKind::Lexical, data, firstFrameSlot,
+ mozilla::Some(enclosingIndex), &index)) {
+ return false;
+ }
+ break;
+ }
+ case SmooshScopeData::Tag::Function: {
+ auto& function = scopeData.AsFunction();
+
+ size_t numBindings = function.bindings.len;
+ FunctionScope::ParserData* data =
+ NewEmptyFunctionScopeData(fc, alloc, numBindings);
+ if (!data) {
+ return false;
+ }
+
+ CopyBindingNames(cx, function.bindings, allAtoms,
+ GetScopeDataTrailingNamesPointer(data));
+
+ // NOTE: data->slotInfo.nextFrameSlot is set in
+ // ScopeStencil::createForFunctionScope.
+
+ if (function.has_parameter_exprs) {
+ data->slotInfo.setHasParameterExprs();
+ }
+ data->slotInfo.nonPositionalFormalStart =
+ function.non_positional_formal_start;
+ data->slotInfo.varStart = function.var_start;
+ data->length = numBindings;
+
+ bool hasParameterExprs = function.has_parameter_exprs;
+ bool needsEnvironment = function.non_positional_formal_start;
+ ScriptIndex functionIndex = ScriptIndex(function.function_index);
+ bool isArrow = function.is_arrow;
+
+ ScopeIndex enclosingIndex(function.enclosing);
+ if (!ScopeStencil::createForFunctionScope(
+ fc, compilationState, data, hasParameterExprs, needsEnvironment,
+ functionIndex, isArrow, mozilla::Some(enclosingIndex),
+ &index)) {
+ return false;
+ }
+ break;
+ }
+ }
+
+ // `ConvertGCThings` depends on this condition.
+ MOZ_ASSERT(index == i);
+ }
+
+ return true;
+}
+
+// Given the result of SmooshMonkey's parser, convert a list of RegExp data
+// into a list of RegExpStencil.
+bool ConvertRegExpData(JSContext* cx, FrontendContext* fc,
+ const SmooshResult& result,
+ CompilationState& compilationState) {
+ auto len = result.regexps.len;
+ if (len == 0) {
+ return true;
+ }
+
+ if (len > TaggedScriptThingIndex::IndexLimit) {
+ ReportAllocationOverflow(fc);
+ return false;
+ }
+
+ if (!compilationState.regExpData.reserve(len)) {
+ js::ReportOutOfMemory(fc);
+ return false;
+ }
+
+ for (size_t i = 0; i < len; i++) {
+ SmooshRegExpItem& item = result.regexps.data[i];
+ auto s = smoosh_get_slice_at(result, item.pattern);
+ auto len = smoosh_get_slice_len_at(result, item.pattern);
+
+ JS::RegExpFlags::Flag flags = JS::RegExpFlag::NoFlags;
+ if (item.global) {
+ flags |= JS::RegExpFlag::Global;
+ }
+ if (item.ignore_case) {
+ flags |= JS::RegExpFlag::IgnoreCase;
+ }
+ if (item.multi_line) {
+ flags |= JS::RegExpFlag::Multiline;
+ }
+ if (item.dot_all) {
+ flags |= JS::RegExpFlag::DotAll;
+ }
+ if (item.sticky) {
+ flags |= JS::RegExpFlag::Sticky;
+ }
+ if (item.unicode) {
+ flags |= JS::RegExpFlag::Unicode;
+ }
+
+ // FIXME: This check should be done at parse time.
+ size_t length;
+ JS::UniqueTwoByteChars pattern(
+ UTF8CharsToNewTwoByteCharsZ(cx, JS::UTF8Chars(s, len), &length,
+ StringBufferArena)
+ .get());
+ if (!pattern) {
+ return false;
+ }
+
+ mozilla::Range<const char16_t> range(pattern.get(), length);
+
+ TokenStreamAnyChars ts(fc, compilationState.input.options,
+ /* smg = */ nullptr);
+
+ // See Parser<FullParseHandler, Unit>::newRegExp.
+
+ if (!irregexp::CheckPatternSyntax(cx->tempLifoAlloc(), fc->stackLimit(), ts,
+ range, flags)) {
+ return false;
+ }
+
+ const mozilla::Utf8Unit* sUtf8 =
+ reinterpret_cast<const mozilla::Utf8Unit*>(s);
+ auto atom = compilationState.parserAtoms.internUtf8(fc, sUtf8, len);
+ if (!atom) {
+ return false;
+ }
+
+ // RegExp patterm must be atomized.
+ compilationState.parserAtoms.markUsedByStencil(atom,
+ ParserAtom::Atomize::Yes);
+ compilationState.regExpData.infallibleEmplaceBack(atom,
+ JS::RegExpFlags(flags));
+ }
+
+ return true;
+}
+
+// Convert SmooshImmutableScriptData into ImmutableScriptData.
+UniquePtr<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;
+ }
+
+ AutoReportFrontendContext fc(cx);
+ return ImmutableScriptData::new_(
+ &fc, smooshScriptData.main_offset, smooshScriptData.nfixed,
+ smooshScriptData.nslots, GCThingIndex(smooshScriptData.body_scope_index),
+ smooshScriptData.num_ic_entries, isFunction, smooshScriptData.fun_length,
+ 0,
+ mozilla::Span(smooshScriptData.bytecode.data,
+ smooshScriptData.bytecode.len),
+ mozilla::Span<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, FrontendContext* fc,
+ const SmooshResult& result,
+ const SmooshScriptStencil& smooshScript,
+ CompilationState& compilationState,
+ Vector<TaggedParserAtomIndex>& allAtoms,
+ ScriptIndex scriptIndex) {
+ size_t ngcthings = smooshScript.gcthings.len;
+
+ // If there are no things, avoid the allocation altogether.
+ if (ngcthings == 0) {
+ return true;
+ }
+
+ TaggedScriptThingIndex* cursor = nullptr;
+ if (!compilationState.allocateGCThingsUninitialized(fc, scriptIndex,
+ ngcthings, &cursor)) {
+ return false;
+ }
+
+ for (size_t i = 0; i < ngcthings; i++) {
+ SmooshGCThing& item = smooshScript.gcthings.data[i];
+
+ // Pointer to the uninitialized element.
+ void* raw = &cursor[i];
+
+ switch (item.tag) {
+ case SmooshGCThing::Tag::Null: {
+ new (raw) TaggedScriptThingIndex();
+ break;
+ }
+ case SmooshGCThing::Tag::Atom: {
+ new (raw) TaggedScriptThingIndex(allAtoms[item.AsAtom()]);
+ break;
+ }
+ case SmooshGCThing::Tag::Function: {
+ new (raw) TaggedScriptThingIndex(ScriptIndex(item.AsFunction()));
+ break;
+ }
+ case SmooshGCThing::Tag::Scope: {
+ new (raw) TaggedScriptThingIndex(ScopeIndex(item.AsScope()));
+ break;
+ }
+ case SmooshGCThing::Tag::RegExp: {
+ new (raw) TaggedScriptThingIndex(RegExpIndex(item.AsRegExp()));
+ break;
+ }
+ }
+ }
+
+ return true;
+}
+
+// Given the result of SmooshMonkey's parser, convert a specific script
+// or function to a StencilScript, given a fixed set of source atoms.
+//
+// The StencilScript would then be in charge of handling the lifetime and
+// (until GC things gets removed from stencil) tracing API of the GC.
+bool ConvertScriptStencil(JSContext* cx, FrontendContext* fc,
+ const SmooshResult& result,
+ const SmooshScriptStencil& smooshScript,
+ Vector<TaggedParserAtomIndex>& allAtoms,
+ CompilationState& compilationState,
+ ScriptIndex scriptIndex) {
+ using ImmutableFlags = js::ImmutableScriptFlagsEnum;
+
+ const JS::ReadOnlyCompileOptions& options = compilationState.input.options;
+
+ ScriptStencil& script = compilationState.scriptData[scriptIndex];
+ ScriptStencilExtra& scriptExtra = compilationState.scriptExtra[scriptIndex];
+
+ scriptExtra.immutableFlags =
+ ImmutableScriptFlags(smooshScript.immutable_flags);
+
+ // FIXME: The following flags should be set in jsparagus.
+ scriptExtra.immutableFlags.setFlag(ImmutableFlags::SelfHosted,
+ options.selfHostingMode);
+ scriptExtra.immutableFlags.setFlag(ImmutableFlags::ForceStrict,
+ options.forceStrictMode());
+ scriptExtra.immutableFlags.setFlag(ImmutableFlags::HasNonSyntacticScope,
+ options.nonSyntacticScope);
+
+ if (&smooshScript == &result.scripts.data[0]) {
+ scriptExtra.immutableFlags.setFlag(ImmutableFlags::TreatAsRunOnce,
+ options.isRunOnce);
+ scriptExtra.immutableFlags.setFlag(ImmutableFlags::NoScriptRval,
+ options.noScriptRval);
+ }
+
+ bool isFunction =
+ scriptExtra.immutableFlags.hasFlag(ImmutableFlags::IsFunction);
+
+ if (smooshScript.immutable_script_data.IsSome()) {
+ auto index = smooshScript.immutable_script_data.AsSome();
+ auto immutableScriptData = ConvertImmutableScriptData(
+ cx, result.script_data_list.data[index], isFunction);
+ if (!immutableScriptData) {
+ return false;
+ }
+
+ auto sharedData = SharedImmutableScriptData::createWith(
+ fc, std::move(immutableScriptData));
+ if (!sharedData) {
+ return false;
+ }
+
+ if (!compilationState.sharedData.addAndShare(fc, scriptIndex, sharedData)) {
+ return false;
+ }
+
+ script.setHasSharedData();
+ }
+
+ scriptExtra.extent.sourceStart = smooshScript.extent.source_start;
+ scriptExtra.extent.sourceEnd = smooshScript.extent.source_end;
+ scriptExtra.extent.toStringStart = smooshScript.extent.to_string_start;
+ scriptExtra.extent.toStringEnd = smooshScript.extent.to_string_end;
+ scriptExtra.extent.lineno = smooshScript.extent.lineno;
+ scriptExtra.extent.column = smooshScript.extent.column;
+
+ if (isFunction) {
+ if (smooshScript.fun_name.IsSome()) {
+ script.functionAtom = allAtoms[smooshScript.fun_name.AsSome()];
+ }
+ script.functionFlags = FunctionFlags(smooshScript.fun_flags);
+ scriptExtra.nargs = smooshScript.fun_nargs;
+ if (smooshScript.lazy_function_enclosing_scope_index.IsSome()) {
+ script.setLazyFunctionEnclosingScopeIndex(ScopeIndex(
+ smooshScript.lazy_function_enclosing_scope_index.AsSome()));
+ }
+ if (smooshScript.was_function_emitted) {
+ script.setWasEmittedByEnclosingScript();
+ }
+ }
+
+ if (!ConvertGCThings(cx, fc, result, smooshScript, compilationState, allAtoms,
+ scriptIndex)) {
+ return false;
+ }
+
+ return true;
+}
+
+// Free given SmooshResult on leaving scope.
+class AutoFreeSmooshResult {
+ SmooshResult* result_;
+
+ public:
+ AutoFreeSmooshResult() = delete;
+
+ explicit AutoFreeSmooshResult(SmooshResult* result) : result_(result) {}
+ ~AutoFreeSmooshResult() {
+ if (result_) {
+ smoosh_free(*result_);
+ }
+ }
+};
+
+// Free given SmooshParseResult on leaving scope.
+class AutoFreeSmooshParseResult {
+ SmooshParseResult* result_;
+
+ public:
+ AutoFreeSmooshParseResult() = delete;
+
+ explicit AutoFreeSmooshParseResult(SmooshParseResult* result)
+ : result_(result) {}
+ ~AutoFreeSmooshParseResult() {
+ if (result_) {
+ smoosh_free_parse_result(*result_);
+ }
+ }
+};
+
+void InitSmoosh() { smoosh_init(); }
+
+void ReportSmooshCompileError(JSContext* cx, FrontendContext* fc,
+ ErrorMetadata&& metadata, int errorNumber, ...) {
+ va_list args;
+ va_start(args, errorNumber);
+ ReportCompileErrorUTF8(fc, std::move(metadata), /* notes = */ nullptr,
+ errorNumber, &args);
+ va_end(args);
+}
+
+/* static */
+bool Smoosh::tryCompileGlobalScriptToExtensibleStencil(
+ JSContext* cx, FrontendContext* fc, CompilationInput& input,
+ JS::SourceText<mozilla::Utf8Unit>& srcBuf,
+ UniquePtr<ExtensibleCompilationStencil>& stencilOut) {
+ // 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();
+
+ SmooshCompileOptions compileOptions;
+ compileOptions.no_script_rval = input.options.noScriptRval;
+
+ SmooshResult result = smoosh_run(bytes, length, &compileOptions);
+ AutoFreeSmooshResult afsr(&result);
+
+ if (result.error.data) {
+ ErrorMetadata metadata;
+ metadata.filename = "<unknown>";
+ metadata.lineNumber = 1;
+ metadata.columnNumber = 0;
+ metadata.isMuted = false;
+ ReportSmooshCompileError(cx, fc, std::move(metadata),
+ JSMSG_SMOOSH_COMPILE_ERROR,
+ reinterpret_cast<const char*>(result.error.data));
+ return false;
+ }
+
+ if (result.unimplemented) {
+ MOZ_ASSERT(!stencilOut);
+ return true;
+ }
+
+ if (!input.initForGlobal(fc)) {
+ return false;
+ }
+
+ LifoAllocScope parserAllocScope(&cx->tempLifoAlloc());
+
+ Vector<TaggedParserAtomIndex> allAtoms(fc);
+ CompilationState compilationState(fc, parserAllocScope, input);
+ if (!ConvertAtoms(cx, fc, result, compilationState, allAtoms)) {
+ return false;
+ }
+
+ if (!ConvertScopeStencil(cx, fc, result, allAtoms, compilationState)) {
+ return false;
+ }
+
+ if (!ConvertRegExpData(cx, fc, result, compilationState)) {
+ return false;
+ }
+
+ auto len = result.scripts.len;
+ if (len == 0) {
+ // FIXME: What does it mean to have no scripts?
+ MOZ_ASSERT(!stencilOut);
+ return true;
+ }
+
+ if (len > TaggedScriptThingIndex::IndexLimit) {
+ ReportAllocationOverflow(fc);
+ return false;
+ }
+
+ if (!compilationState.scriptData.resize(len)) {
+ js::ReportOutOfMemory(fc);
+ return false;
+ }
+
+ if (!compilationState.scriptExtra.resize(len)) {
+ js::ReportOutOfMemory(fc);
+ return false;
+ }
+
+ // NOTE: Currently we don't support delazification or standalone function.
+ // Once we support, fix the following loop to include 0-th item
+ // and check if it's function.
+ MOZ_ASSERT_IF(result.scripts.len > 0, result.scripts.data[0].fun_flags == 0);
+ for (size_t i = 1; i < result.scripts.len; i++) {
+ auto& script = result.scripts.data[i];
+ if (script.immutable_script_data.IsSome()) {
+ compilationState.nonLazyFunctionCount++;
+ }
+ }
+
+ if (!compilationState.prepareSharedDataStorage(fc)) {
+ return false;
+ }
+
+ for (size_t i = 0; i < len; i++) {
+ if (!ConvertScriptStencil(cx, fc, result, result.scripts.data[i], allAtoms,
+ compilationState, ScriptIndex(i))) {
+ return false;
+ }
+ }
+
+ auto stencil =
+ fc->getAllocator()->make_unique<frontend::ExtensibleCompilationStencil>(
+ std::move(compilationState));
+ if (!stencil) {
+ return false;
+ }
+
+ stencilOut = std::move(stencil);
+ return true;
+}
+
+bool SmooshParseScript(JSContext* cx, const uint8_t* bytes, size_t length) {
+ SmooshParseResult result = smoosh_test_parse_script(bytes, length);
+ AutoFreeSmooshParseResult afspr(&result);
+ if (result.error.data) {
+ JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
+ result.unimplemented ? JSMSG_SMOOSH_UNIMPLEMENTED
+ : JSMSG_SMOOSH_COMPILE_ERROR,
+ reinterpret_cast<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..6d14de999c
--- /dev/null
+++ b/js/src/frontend/Frontend2.h
@@ -0,0 +1,61 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef frontend_Frontend2_h
+#define frontend_Frontend2_h
+
+#include "mozilla/Utf8.h" // mozilla::Utf8Unit
+
+#include <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;
+class FrontendContext;
+
+namespace frontend {
+
+struct CompilationInput;
+struct ExtensibleCompilationStencil;
+struct CompilationGCOutput;
+struct CompilationState;
+
+// This is declarated as a class mostly to solve dependency around `friend`
+// declarations in the simple way.
+class Smoosh {
+ public:
+ [[nodiscard]] static bool tryCompileGlobalScriptToExtensibleStencil(
+ JSContext* cx, FrontendContext* fc, CompilationInput& input,
+ JS::SourceText<mozilla::Utf8Unit>& srcBuf,
+ UniquePtr<ExtensibleCompilationStencil>& stencilOut);
+};
+
+// Initialize SmooshMonkey globals, such as the logging system.
+void InitSmoosh();
+
+// Use the SmooshMonkey frontend to parse and free the generated AST. Returns
+// true if no error were detected while parsing.
+[[nodiscard]] bool SmooshParseScript(JSContext* cx, const uint8_t* bytes,
+ size_t length);
+[[nodiscard]] bool SmooshParseModule(JSContext* cx, const uint8_t* bytes,
+ size_t length);
+
+} // namespace frontend
+
+} // namespace js
+
+#endif /* frontend_Frontend2_h */
diff --git a/js/src/frontend/FrontendContext.cpp b/js/src/frontend/FrontendContext.cpp
new file mode 100644
index 0000000000..c9f1bf75b4
--- /dev/null
+++ b/js/src/frontend/FrontendContext.cpp
@@ -0,0 +1,237 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "frontend/FrontendContext.h"
+
+#include "gc/GC.h"
+#include "js/AllocPolicy.h" // js::ReportOutOfMemory
+#include "js/friend/StackLimits.h" // js::ReportOverRecursed
+#include "js/Modules.h"
+#include "util/DifferentialTesting.h"
+#include "util/NativeStack.h" // GetNativeStackBase
+#include "vm/JSContext.h"
+
+using namespace js;
+
+void FrontendAllocator::reportAllocationOverflow() {
+ fc_->onAllocationOverflow();
+}
+
+void* FrontendAllocator::onOutOfMemory(AllocFunction allocFunc,
+ arena_id_t arena, size_t nbytes,
+ void* reallocPtr) {
+ return fc_->onOutOfMemory(allocFunc, arena, nbytes, reallocPtr);
+}
+
+FrontendContext::~FrontendContext() {
+ if (ownNameCollectionPool_) {
+ MOZ_ASSERT(nameCollectionPool_);
+ js_delete(nameCollectionPool_);
+ }
+}
+
+bool FrontendContext::setSupportedImportAssertions(
+ const JS::ImportAssertionVector& supportedImportAssertions) {
+ MOZ_ASSERT(supportedImportAssertions_.empty());
+ if (!supportedImportAssertions_.appendAll(supportedImportAssertions)) {
+ ReportOutOfMemory();
+ return false;
+ }
+ return true;
+}
+
+void FrontendContext::setStackQuota(JS::NativeStackSize stackSize) {
+#ifdef __wasi__
+ stackLimit_ = JS::WASINativeStackLimit;
+#else // __wasi__
+ if (stackSize == 0) {
+ stackLimit_ = JS::NativeStackLimitMax;
+ } else {
+ stackLimit_ = JS::GetNativeStackLimit(GetNativeStackBase(), stackSize - 1);
+ }
+#endif // !__wasi__
+}
+
+bool FrontendContext::allocateOwnedPool() {
+ MOZ_ASSERT(!nameCollectionPool_);
+
+ nameCollectionPool_ = js_new<frontend::NameCollectionPool>();
+ if (!nameCollectionPool_) {
+ return false;
+ }
+ ownNameCollectionPool_ = true;
+ return true;
+}
+
+bool FrontendContext::hadErrors() const {
+ if (maybeCx_) {
+ if (maybeCx_->isExceptionPending()) {
+ return true;
+ }
+ }
+
+ return errors_.hadErrors();
+}
+
+void* FrontendContext::onOutOfMemory(AllocFunction allocFunc, arena_id_t arena,
+ size_t nbytes, void* reallocPtr) {
+ addPendingOutOfMemory();
+ return nullptr;
+}
+
+void FrontendContext::onAllocationOverflow() {
+ errors_.allocationOverflow = true;
+}
+
+void FrontendContext::onOutOfMemory() { addPendingOutOfMemory(); }
+
+void FrontendContext::onOverRecursed() { errors_.overRecursed = true; }
+
+void FrontendContext::recoverFromOutOfMemory() {
+ // TODO: Remove this branch once error report directly against JSContext is
+ // removed from the frontend code.
+ if (maybeCx_) {
+ maybeCx_->recoverFromOutOfMemory();
+ }
+
+ errors_.outOfMemory = false;
+}
+
+const JSErrorFormatString* FrontendContext::gcSafeCallback(
+ JSErrorCallback callback, void* userRef, const unsigned errorNumber) {
+ mozilla::Maybe<gc::AutoSuppressGC> suppressGC;
+ if (maybeCx_) {
+ suppressGC.emplace(maybeCx_);
+ }
+
+ return callback(userRef, errorNumber);
+}
+
+void FrontendContext::reportError(CompileError&& err) {
+ if (errors_.error) {
+ errors_.error.reset();
+ }
+
+ // When compiling off thread, save the error so that the thread finishing the
+ // parse can report it later.
+ errors_.error.emplace(std::move(err));
+}
+
+bool FrontendContext::reportWarning(CompileError&& err) {
+ if (!errors_.warnings.append(std::move(err))) {
+ ReportOutOfMemory();
+ return false;
+ }
+
+ return true;
+}
+
+void FrontendContext::ReportOutOfMemory() {
+ /*
+ * OOMs are non-deterministic, especially across different execution modes
+ * (e.g. interpreter vs JIT). When doing differential testing, print to
+ * stderr so that the fuzzers can detect this.
+ */
+ if (SupportDifferentialTesting()) {
+ fprintf(stderr, "ReportOutOfMemory called\n");
+ }
+
+ addPendingOutOfMemory();
+}
+
+void FrontendContext::addPendingOutOfMemory() { errors_.outOfMemory = true; }
+
+void FrontendContext::setCurrentJSContext(JSContext* cx) {
+ MOZ_ASSERT(!nameCollectionPool_);
+
+ maybeCx_ = cx;
+ nameCollectionPool_ = &cx->frontendCollectionPool();
+ scriptDataTableHolder_ = &cx->runtime()->scriptDataTableHolder();
+ stackLimit_ = cx->stackLimitForCurrentPrincipal();
+}
+
+bool FrontendContext::convertToRuntimeError(
+ JSContext* cx, Warning warning /* = Warning::Report */) {
+ // Report out of memory errors eagerly, or errors could be malformed.
+ if (hadOutOfMemory()) {
+ js::ReportOutOfMemory(cx);
+ return false;
+ }
+
+ if (maybeError()) {
+ if (!maybeError()->throwError(cx)) {
+ return false;
+ }
+ }
+ if (warning == Warning::Report) {
+ for (CompileError& error : warnings()) {
+ if (!error.throwError(cx)) {
+ return false;
+ }
+ }
+ }
+ if (hadOverRecursed()) {
+ js::ReportOverRecursed(cx);
+ }
+ if (hadAllocationOverflow()) {
+ js::ReportAllocationOverflow(cx);
+ }
+ return true;
+}
+
+void FrontendContext::linkWithJSContext(JSContext* cx) {
+ if (cx) {
+ cx->setFrontendErrors(&errors_);
+ }
+}
+
+#ifdef __wasi__
+void FrontendContext::incWasiRecursionDepth() {
+ if (maybeCx_) {
+ IncWasiRecursionDepth(maybeCx_);
+ }
+}
+
+void FrontendContext::decWasiRecursionDepth() {
+ if (maybeCx_) {
+ DecWasiRecursionDepth(maybeCx_);
+ }
+}
+
+bool FrontendContext::checkWasiRecursionLimit() {
+ if (maybeCx_) {
+ return CheckWasiRecursionLimit(maybeCx_);
+ }
+ return true;
+}
+
+JS_PUBLIC_API void js::IncWasiRecursionDepth(FrontendContext* fc) {
+ fc->incWasiRecursionDepth();
+}
+
+JS_PUBLIC_API void js::DecWasiRecursionDepth(FrontendContext* fc) {
+ fc->decWasiRecursionDepth();
+}
+
+JS_PUBLIC_API bool js::CheckWasiRecursionLimit(FrontendContext* fc) {
+ return fc->checkWasiRecursionLimit();
+}
+#endif // __wasi__
+
+FrontendContext* js::NewFrontendContext() {
+ UniquePtr<FrontendContext> fc = MakeUnique<FrontendContext>();
+ if (!fc) {
+ return nullptr;
+ }
+
+ if (!fc->allocateOwnedPool()) {
+ return nullptr;
+ }
+
+ return fc.release();
+}
+
+void js::DestroyFrontendContext(FrontendContext* fc) { js_delete_poison(fc); }
diff --git a/js/src/frontend/FrontendContext.h b/js/src/frontend/FrontendContext.h
new file mode 100644
index 0000000000..72c9e15154
--- /dev/null
+++ b/js/src/frontend/FrontendContext.h
@@ -0,0 +1,260 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef frontend_FrontendContext_h
+#define frontend_FrontendContext_h
+
+#include "mozilla/Assertions.h" // MOZ_ASSERT
+#include "mozilla/Attributes.h" // MOZ_STACK_CLASS
+#include "mozilla/Maybe.h" // mozilla::Maybe
+
+#include <stddef.h> // size_t
+
+#include "js/AllocPolicy.h" // SystemAllocPolicy, AllocFunction
+#include "js/ErrorReport.h" // JSErrorCallback, JSErrorFormatString
+#include "js/Modules.h" // JS::ImportAssertionVector
+#include "js/Stack.h" // JS::NativeStackSize, JS::NativeStackLimit, JS::NativeStackLimitMax
+#include "js/Vector.h" // Vector
+#include "vm/ErrorReporting.h" // CompileError
+#include "vm/MallocProvider.h" // MallocProvider
+#include "vm/SharedScriptDataTableHolder.h" // js::SharedScriptDataTableHolder, js::globalSharedScriptDataTableHolder
+
+struct JSContext;
+
+namespace js {
+
+class FrontendContext;
+
+namespace frontend {
+class NameCollectionPool;
+} // namespace frontend
+
+struct FrontendErrors {
+ FrontendErrors() = default;
+ // Any errors or warnings produced during compilation. These are reported
+ // when finishing the script.
+ mozilla::Maybe<CompileError> error;
+ Vector<CompileError, 0, SystemAllocPolicy> warnings;
+ bool overRecursed = false;
+ bool outOfMemory = false;
+ bool allocationOverflow = false;
+
+ bool hadErrors() const {
+ return outOfMemory || overRecursed || allocationOverflow || error;
+ }
+};
+
+class FrontendAllocator : public MallocProvider<FrontendAllocator> {
+ private:
+ FrontendContext* const fc_;
+
+ public:
+ explicit FrontendAllocator(FrontendContext* fc) : fc_(fc) {}
+
+ void* onOutOfMemory(js::AllocFunction allocFunc, arena_id_t arena,
+ size_t nbytes, void* reallocPtr = nullptr);
+ void reportAllocationOverflow();
+};
+
+class FrontendContext {
+ private:
+ FrontendAllocator alloc_;
+ js::FrontendErrors errors_;
+
+ // NameCollectionPool can be either:
+ // * owned by this FrontendContext, or
+ // * borrowed from JSContext
+ frontend::NameCollectionPool* nameCollectionPool_;
+ bool ownNameCollectionPool_;
+
+ js::SharedScriptDataTableHolder* scriptDataTableHolder_;
+
+ JS::ImportAssertionVector supportedImportAssertions_;
+
+ JS::NativeStackLimit stackLimit_ = JS::NativeStackLimitMax;
+
+ protected:
+ // (optional) Current JSContext to support main-thread-specific
+ // handling for error reporting, GC, and memory allocation.
+ //
+ // Set by setCurrentJSContext.
+ JSContext* maybeCx_ = nullptr;
+
+ public:
+ FrontendContext()
+ : alloc_(this),
+ nameCollectionPool_(nullptr),
+ ownNameCollectionPool_(false),
+ scriptDataTableHolder_(&js::globalSharedScriptDataTableHolder),
+ supportedImportAssertions_() {}
+ ~FrontendContext();
+
+ void setStackQuota(JS::NativeStackSize stackSize);
+ JS::NativeStackLimit stackLimit() const { return stackLimit_; }
+
+ bool allocateOwnedPool();
+
+ frontend::NameCollectionPool& nameCollectionPool() {
+ MOZ_ASSERT(
+ nameCollectionPool_,
+ "Either allocateOwnedPool or setCurrentJSContext must be called");
+ return *nameCollectionPool_;
+ }
+
+ js::SharedScriptDataTableHolder* scriptDataTableHolder() {
+ MOZ_ASSERT(scriptDataTableHolder_);
+ return scriptDataTableHolder_;
+ }
+
+ FrontendAllocator* getAllocator() { return &alloc_; }
+
+ // Use the given JSContext's for:
+ // * js::frontend::NameCollectionPool for reusing allocation
+ // * js::SharedScriptDataTableHolder for de-duplicating bytecode
+ // within given runtime
+ // * Copy the native stack limit from the JSContext
+ //
+ // And also this JSContext can be retrieved by maybeCurrentJSContext below.
+ void setCurrentJSContext(JSContext* cx);
+
+ // Returns JSContext if any.
+ //
+ // This can be used only for:
+ // * Main-thread-specific operation, such as operating on JSAtom
+ // * Optional operation, such as providing better error message
+ JSContext* maybeCurrentJSContext() { return maybeCx_; }
+
+ const JS::ImportAssertionVector& getSupportedImportAssertions() const {
+ return supportedImportAssertions_;
+ }
+ bool setSupportedImportAssertions(
+ const JS::ImportAssertionVector& supportedImportAssertions);
+
+ enum class Warning { Suppress, Report };
+
+ // Returns false if the error cannot be converted (such as due to OOM). An
+ // error might still be reported to the given JSContext. Returns true
+ // otherwise.
+ bool convertToRuntimeError(JSContext* cx, Warning warning = Warning::Report);
+
+ void linkWithJSContext(JSContext* cx);
+
+ mozilla::Maybe<CompileError>& maybeError() { return errors_.error; }
+ Vector<CompileError, 0, SystemAllocPolicy>& warnings() {
+ return errors_.warnings;
+ }
+
+ // Report CompileErrors
+ void reportError(js::CompileError&& err);
+ bool reportWarning(js::CompileError&& err);
+
+ // Report FrontendAllocator errors
+ void* onOutOfMemory(js::AllocFunction allocFunc, arena_id_t arena,
+ size_t nbytes, void* reallocPtr = nullptr);
+ void onAllocationOverflow();
+
+ void onOutOfMemory();
+ void onOverRecursed();
+
+ void recoverFromOutOfMemory();
+
+ const JSErrorFormatString* gcSafeCallback(JSErrorCallback callback,
+ void* userRef,
+ const unsigned errorNumber);
+
+ // Status of errors reported to this FrontendContext
+ bool hadOutOfMemory() const { return errors_.outOfMemory; }
+ bool hadOverRecursed() const { return errors_.overRecursed; }
+ bool hadAllocationOverflow() const { return errors_.allocationOverflow; }
+ bool hadErrors() const;
+
+#ifdef __wasi__
+ void incWasiRecursionDepth();
+ void decWasiRecursionDepth();
+ bool checkWasiRecursionLimit();
+#endif // __wasi__
+
+ private:
+ void ReportOutOfMemory();
+ void addPendingOutOfMemory();
+};
+
+// Automatically report any pending exception when leaving the scope.
+class MOZ_STACK_CLASS AutoReportFrontendContext : public FrontendContext {
+ // The target JSContext to report the errors to.
+ JSContext* cx_;
+
+ Warning warning_;
+
+ public:
+ explicit AutoReportFrontendContext(JSContext* cx,
+ Warning warning = Warning::Report)
+ : FrontendContext(), cx_(cx), warning_(warning) {
+ setCurrentJSContext(cx_);
+ MOZ_ASSERT(cx_ == maybeCx_);
+ }
+
+ ~AutoReportFrontendContext() {
+ if (cx_) {
+ convertToRuntimeErrorAndClear();
+ }
+ }
+
+ void clearAutoReport() { cx_ = nullptr; }
+
+ bool convertToRuntimeErrorAndClear() {
+ bool result = convertToRuntimeError(cx_, warning_);
+ cx_ = nullptr;
+ return result;
+ }
+};
+
+/*
+ * Explicitly report any pending exception before leaving the scope.
+ *
+ * Before an instance of this class leaves the scope, you must call either
+ * failure() (if there are exceptions to report) or ok() (if there are no
+ * exceptions to report).
+ */
+class ManualReportFrontendContext : public FrontendContext {
+ JSContext* cx_;
+#ifdef DEBUG
+ bool handled_ = false;
+#endif
+
+ public:
+ explicit ManualReportFrontendContext(JSContext* cx)
+ : FrontendContext(), cx_(cx) {
+ setCurrentJSContext(cx_);
+ }
+
+ ~ManualReportFrontendContext() { MOZ_ASSERT(handled_); }
+
+ void ok() {
+#ifdef DEBUG
+ handled_ = true;
+#endif
+ }
+
+ void failure() {
+#ifdef DEBUG
+ handled_ = true;
+#endif
+ convertToRuntimeError(cx_);
+ }
+};
+
+// Create function for FrontendContext, which is manually allocated and
+// exclusively owned.
+extern FrontendContext* NewFrontendContext();
+
+// Destroy function for FrontendContext, which was allocated with
+// NewFrontendContext.
+extern void DestroyFrontendContext(FrontendContext* fc);
+
+} // namespace js
+
+#endif /* frontend_FrontendContext_h */
diff --git a/js/src/frontend/FullParseHandler.h b/js/src/frontend/FullParseHandler.h
new file mode 100644
index 0000000000..d5dc32026b
--- /dev/null
+++ b/js/src/frontend/FullParseHandler.h
@@ -0,0 +1,1195 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef frontend_FullParseHandler_h
+#define frontend_FullParseHandler_h
+
+#include "mozilla/Maybe.h" // mozilla::Maybe
+
+#include <cstddef> // std::nullptr_t
+#include <string.h>
+
+#include "jstypes.h"
+
+#include "frontend/CompilationStencil.h" // CompilationState
+#include "frontend/FunctionSyntaxKind.h" // FunctionSyntaxKind
+#include "frontend/NameAnalysisTypes.h" // PrivateNameKind
+#include "frontend/ParseNode.h"
+#include "frontend/ParserAtom.h" // TaggedParserAtomIndex
+#include "frontend/SharedContext.h"
+#include "frontend/Stencil.h"
+
+namespace js {
+namespace frontend {
+
+class TokenStreamAnyChars;
+
+// Parse handler used when generating a full parse tree for all code which the
+// parser encounters.
+class FullParseHandler {
+ ParseNodeAllocator allocator;
+
+ ParseNode* allocParseNode(size_t size) {
+ return static_cast<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:
+ //
+ // - reuseGCThings if ture it means that the following fields are valid.
+ // - gcThingsData holds an incomplete stencil-like copy of inner functions as
+ // well as atoms.
+ // - scriptData and scriptExtra_ hold information necessary to locate inner
+ // functions to skip over each.
+ // - lazyInnerFunctionIndex is used as we skip over inner functions
+ // (see skipLazyInnerFunction),
+ // - lazyClosedOverBindingIndex is used to synchronize binding computation
+ // with the scope traversal.
+ // (see propagateFreeNamesAndMarkClosedOverBindings),
+ const CompilationSyntaxParseCache& previousParseCache_;
+
+ size_t lazyInnerFunctionIndex;
+ size_t lazyClosedOverBindingIndex;
+
+ bool reuseGCThings;
+
+ public:
+ /* new_ methods for creating parse nodes. These report OOM on context. */
+ JS_DECLARE_NEW_METHODS(new_, allocParseNode, inline)
+
+ // FIXME: Use ListNode instead of ListNodeType as an alias (bug 1489008).
+ using Node = ParseNode*;
+
+#define DECLARE_TYPE(typeName, longTypeName, asMethodName) \
+ using longTypeName = typeName*;
+ FOR_EACH_PARSENODE_SUBCLASS(DECLARE_TYPE)
+#undef DECLARE_TYPE
+
+ using NullNode = std::nullptr_t;
+
+ bool isPropertyOrPrivateMemberAccess(Node node) {
+ return node->isKind(ParseNodeKind::DotExpr) ||
+ node->isKind(ParseNodeKind::ElemExpr) ||
+ node->isKind(ParseNodeKind::PrivateMemberExpr);
+ }
+
+ bool isOptionalPropertyOrPrivateMemberAccess(Node node) {
+ return node->isKind(ParseNodeKind::OptionalDotExpr) ||
+ node->isKind(ParseNodeKind::OptionalElemExpr) ||
+ node->isKind(ParseNodeKind::PrivateMemberExpr);
+ }
+
+ bool isFunctionCall(Node node) {
+ // Note: super() is a special form, *not* a function call.
+ return node->isKind(ParseNodeKind::CallExpr);
+ }
+
+ static bool isUnparenthesizedDestructuringPattern(Node node) {
+ return !node->isInParens() && (node->isKind(ParseNodeKind::ObjectExpr) ||
+ node->isKind(ParseNodeKind::ArrayExpr));
+ }
+
+ static bool isParenthesizedDestructuringPattern(Node node) {
+ // Technically this isn't a destructuring pattern at all -- the grammar
+ // doesn't treat it as such. But we need to know when this happens to
+ // consider it a SyntaxError rather than an invalid-left-hand-side
+ // ReferenceError.
+ return node->isInParens() && (node->isKind(ParseNodeKind::ObjectExpr) ||
+ node->isKind(ParseNodeKind::ArrayExpr));
+ }
+
+ FullParseHandler(FrontendContext* fc, CompilationState& compilationState)
+ : allocator(fc, compilationState.parserAllocScope.alloc()),
+ previousParseCache_(compilationState.previousParseCache),
+ lazyInnerFunctionIndex(0),
+ lazyClosedOverBindingIndex(0),
+ reuseGCThings(compilationState.input.isDelazifying()) {}
+
+ static NullNode null() { return NullNode(); }
+
+#define DECLARE_AS(typeName, longTypeName, asMethodName) \
+ static longTypeName asMethodName(Node node) { return &node->as<typeName>(); }
+ FOR_EACH_PARSENODE_SUBCLASS(DECLARE_AS)
+#undef DECLARE_AS
+
+ NameNodeType newName(TaggedParserAtomIndex name, const TokenPos& pos) {
+ 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(TaggedParserAtomIndex atom,
+ const TokenPos& pos) {
+ return new_<NameNode>(ParseNodeKind::ObjectPropertyName, atom, pos);
+ }
+
+ NameNodeType newPrivateName(TaggedParserAtomIndex 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, bool isZero,
+ const TokenPos& pos) {
+ return new_<BigIntLiteral>(index, isZero, pos);
+ }
+
+ BooleanLiteralType newBooleanLiteral(bool cond, const TokenPos& pos) {
+ return new_<BooleanLiteral>(cond, pos);
+ }
+
+ NameNodeType newStringLiteral(TaggedParserAtomIndex atom,
+ const TokenPos& pos) {
+ return new_<NameNode>(ParseNodeKind::StringExpr, atom, pos);
+ }
+
+ NameNodeType newTemplateStringLiteral(TaggedParserAtomIndex 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));
+ }
+
+ [[nodiscard]] 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;
+ }
+
+ [[nodiscard]] bool addSpreadElement(ListNodeType literal, uint32_t begin,
+ Node inner) {
+ MOZ_ASSERT(
+ literal->isKind(ParseNodeKind::ArrayExpr) ||
+ IF_RECORD_TUPLE(literal->isKind(ParseNodeKind::TupleExpr), false));
+
+ UnaryNodeType spread = newSpread(begin, inner);
+ if (!spread) {
+ return false;
+ }
+ addList(/* list = */ literal, /* kid = */ spread);
+ literal->setHasNonConstInitializer();
+ return true;
+ }
+
+ void addArrayElement(ListNodeType literal, Node element) {
+ MOZ_ASSERT(
+ literal->isKind(ParseNodeKind::ArrayExpr) ||
+ literal->isKind(ParseNodeKind::CallSiteObj) ||
+ IF_RECORD_TUPLE(literal->isKind(ParseNodeKind::TupleExpr), false));
+ if (!element->isConstant()) {
+ literal->setHasNonConstInitializer();
+ }
+ addList(/* list = */ literal, /* kid = */ element);
+ }
+
+ CallNodeType newCall(Node callee, Node args, JSOp callOp) {
+ return new_<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));
+ }
+
+#ifdef ENABLE_RECORD_TUPLE
+ ListNodeType newRecordLiteral(uint32_t begin) {
+ return new_<ListNode>(ParseNodeKind::RecordExpr,
+ TokenPos(begin, begin + 1));
+ }
+
+ ListNodeType newTupleLiteral(uint32_t begin) {
+ return new_<ListNode>(ParseNodeKind::TupleExpr, TokenPos(begin, begin + 1));
+ }
+#endif
+
+ ClassNodeType newClass(Node name, Node heritage,
+ LexicalScopeNodeType memberBlock,
+#ifdef ENABLE_DECORATORS
+ ListNodeType decorators,
+#endif
+ const TokenPos& pos) {
+ return new_<ClassNode>(name, heritage, memberBlock,
+#ifdef ENABLE_DECORATORS
+ decorators,
+#endif
+ 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);
+ }
+ NewTargetNodeType newNewTarget(NullaryNodeType newHolder,
+ NullaryNodeType targetHolder,
+ NameNodeType newTargetName) {
+ return new_<NewTargetNode>(newHolder, targetHolder, newTargetName);
+ }
+ 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);
+ }
+ [[nodiscard]] bool addPrototypeMutation(ListNodeType literal, uint32_t begin,
+ Node expr) {
+ MOZ_ASSERT(literal->isKind(ParseNodeKind::ObjectExpr));
+
+ // Object literals with mutated [[Prototype]] are non-constant so that
+ // singleton objects will have Object.prototype as their [[Prototype]].
+ literal->setHasNonConstInitializer();
+
+ UnaryNode* mutation = newUnary(ParseNodeKind::MutateProto, begin, expr);
+ if (!mutation) {
+ return false;
+ }
+ addList(/* list = */ literal, /* kid = */ mutation);
+ return true;
+ }
+
+ BinaryNodeType newPropertyDefinition(Node key, Node val) {
+ MOZ_ASSERT(isUsableAsObjectPropertyName(key));
+ checkAndSetIsDirectRHSAnonFunction(val);
+ return new_<PropertyDefinition>(key, val, AccessorType::None);
+ }
+
+ void addPropertyDefinition(ListNodeType literal, BinaryNodeType propdef) {
+ MOZ_ASSERT(
+ literal->isKind(ParseNodeKind::ObjectExpr) ||
+ IF_RECORD_TUPLE(literal->isKind(ParseNodeKind::RecordExpr), false));
+ MOZ_ASSERT(propdef->isKind(ParseNodeKind::PropertyDefinition));
+
+ if (!propdef->right()->isConstant()) {
+ literal->setHasNonConstInitializer();
+ }
+
+ addList(/* list = */ literal, /* kid = */ propdef);
+ }
+
+ [[nodiscard]] bool addPropertyDefinition(ListNodeType literal, Node key,
+ Node val) {
+ BinaryNode* propdef = newPropertyDefinition(key, val);
+ if (!propdef) {
+ return false;
+ }
+ addPropertyDefinition(literal, propdef);
+ return true;
+ }
+
+ [[nodiscard]] bool addShorthand(ListNodeType literal, NameNodeType name,
+ NameNodeType expr) {
+ MOZ_ASSERT(
+ literal->isKind(ParseNodeKind::ObjectExpr) ||
+ IF_RECORD_TUPLE(literal->isKind(ParseNodeKind::RecordExpr), false));
+ MOZ_ASSERT(name->isKind(ParseNodeKind::ObjectPropertyName));
+ MOZ_ASSERT(expr->isKind(ParseNodeKind::Name));
+ MOZ_ASSERT(name->atom() == expr->atom());
+
+ literal->setHasNonConstInitializer();
+ BinaryNode* propdef = newBinary(ParseNodeKind::Shorthand, name, expr);
+ if (!propdef) {
+ return false;
+ }
+ addList(/* list = */ literal, /* kid = */ propdef);
+ return true;
+ }
+
+ [[nodiscard]] bool addSpreadProperty(ListNodeType literal, uint32_t begin,
+ Node inner) {
+ MOZ_ASSERT(
+ literal->isKind(ParseNodeKind::ObjectExpr) ||
+ IF_RECORD_TUPLE(literal->isKind(ParseNodeKind::RecordExpr), false));
+
+ literal->setHasNonConstInitializer();
+ ParseNode* spread = newSpread(begin, inner);
+ if (!spread) {
+ return false;
+ }
+ addList(/* list = */ literal, /* kid = */ spread);
+ return true;
+ }
+
+ [[nodiscard]] bool addObjectMethodDefinition(ListNodeType literal, Node key,
+ FunctionNodeType funNode,
+ AccessorType atype) {
+ literal->setHasNonConstInitializer();
+
+ checkAndSetIsDirectRHSAnonFunction(funNode);
+
+ ParseNode* propdef =
+ newObjectMethodOrPropertyDefinition(key, funNode, atype);
+ if (!propdef) {
+ return false;
+ }
+
+ addList(/* list = */ literal, /* kid = */ propdef);
+ return true;
+ }
+
+ [[nodiscard]] ClassMethod* newDefaultClassConstructor(
+ Node key, FunctionNodeType funNode) {
+ MOZ_ASSERT(isUsableAsObjectPropertyName(key));
+
+ checkAndSetIsDirectRHSAnonFunction(funNode);
+
+ return new_<ClassMethod>(
+ ParseNodeKind::DefaultConstructor, key, funNode, AccessorType::None,
+ /* isStatic = */ false, /* initializeIfPrivate = */ nullptr
+#ifdef ENABLE_DECORATORS
+ ,
+ /* decorators = */ nullptr
+#endif
+ );
+ }
+
+ [[nodiscard]] ClassMethod* newClassMethodDefinition(
+ Node key, FunctionNodeType funNode, AccessorType atype, bool isStatic,
+ mozilla::Maybe<FunctionNodeType> initializerIfPrivate
+#ifdef ENABLE_DECORATORS
+ ,
+ ListNodeType decorators
+#endif
+ ) {
+ MOZ_ASSERT(isUsableAsObjectPropertyName(key));
+
+ checkAndSetIsDirectRHSAnonFunction(funNode);
+
+ if (initializerIfPrivate.isSome()) {
+ return new_<ClassMethod>(ParseNodeKind::ClassMethod, key, funNode, atype,
+ isStatic, initializerIfPrivate.value()
+#ifdef ENABLE_DECORATORS
+ ,
+ decorators
+#endif
+ );
+ }
+ return new_<ClassMethod>(ParseNodeKind::ClassMethod, key, funNode, atype,
+ isStatic, /* initializeIfPrivate = */ nullptr
+#ifdef ENABLE_DECORATORS
+ ,
+ decorators
+#endif
+ );
+ }
+
+ [[nodiscard]] ClassField* newClassFieldDefinition(
+ Node name, FunctionNodeType initializer, bool isStatic
+#ifdef ENABLE_DECORATORS
+ ,
+ ListNodeType decorators, bool hasAccessor
+#endif
+ ) {
+ MOZ_ASSERT(isUsableAsObjectPropertyName(name));
+
+ return new_<ClassField>(name, initializer, isStatic
+#if ENABLE_DECORATORS
+ ,
+ decorators, hasAccessor
+#endif
+ );
+ }
+
+ [[nodiscard]] StaticClassBlock* newStaticClassBlock(FunctionNodeType block) {
+ return new_<StaticClassBlock>(block);
+ }
+
+ [[nodiscard]] bool addClassMemberDefinition(ListNodeType memberList,
+ Node member) {
+ MOZ_ASSERT(memberList->isKind(ParseNodeKind::ClassMemberList));
+ // Constructors can be surrounded by LexicalScopes.
+ MOZ_ASSERT(member->isKind(ParseNodeKind::DefaultConstructor) ||
+ member->isKind(ParseNodeKind::ClassMethod) ||
+ member->isKind(ParseNodeKind::ClassField) ||
+ member->isKind(ParseNodeKind::StaticClassBlock) ||
+ (member->isKind(ParseNodeKind::LexicalScope) &&
+ member->as<LexicalScopeNode>().scopeBody()->is<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);
+ }
+
+ [[nodiscard]] 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();
+ }
+ }
+
+ [[nodiscard]] bool prependInitialYield(ListNodeType stmtList, Node genName) {
+ MOZ_ASSERT(stmtList->isKind(ParseNodeKind::StatementList));
+
+ TokenPos yieldPos(stmtList->pn_pos.begin, stmtList->pn_pos.begin + 1);
+ NullaryNode* makeGen =
+ new_<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 newImportAssertion(Node keyNode, Node valueNode) {
+ return newBinary(ParseNodeKind::ImportAssertion, keyNode, valueNode);
+ }
+
+ BinaryNodeType newModuleRequest(Node moduleSpec, Node importAssertionList,
+ const TokenPos& pos) {
+ return new_<BinaryNode>(ParseNodeKind::ImportModuleRequest, pos, moduleSpec,
+ importAssertionList);
+ }
+
+ BinaryNodeType newImportDeclaration(Node importSpecSet, Node moduleRequest,
+ const TokenPos& pos) {
+ return new_<BinaryNode>(ParseNodeKind::ImportDecl, pos, importSpecSet,
+ moduleRequest);
+ }
+
+ BinaryNodeType newImportSpec(Node importNameNode, Node bindingName) {
+ return newBinary(ParseNodeKind::ImportSpec, importNameNode, bindingName);
+ }
+
+ UnaryNodeType newImportNamespaceSpec(uint32_t begin, Node bindingName) {
+ return newUnary(ParseNodeKind::ImportNamespaceSpec, begin, bindingName);
+ }
+
+ UnaryNodeType newExportDeclaration(Node kid, const TokenPos& pos) {
+ return new_<UnaryNode>(ParseNodeKind::ExportStmt, pos, kid);
+ }
+
+ BinaryNodeType newExportFromDeclaration(uint32_t begin, Node exportSpecSet,
+ Node moduleRequest) {
+ BinaryNode* decl = new_<BinaryNode>(ParseNodeKind::ExportFromStmt,
+ exportSpecSet, moduleRequest);
+ if (!decl) {
+ return nullptr;
+ }
+ decl->pn_pos.begin = begin;
+ return decl;
+ }
+
+ BinaryNodeType newExportDefaultDeclaration(Node kid, Node maybeBinding,
+ const TokenPos& pos) {
+ if (maybeBinding) {
+ MOZ_ASSERT(maybeBinding->isKind(ParseNodeKind::Name));
+ MOZ_ASSERT(!maybeBinding->isInParens());
+
+ checkAndSetIsDirectRHSAnonFunction(kid);
+ }
+
+ return new_<BinaryNode>(ParseNodeKind::ExportDefaultStmt, pos, kid,
+ maybeBinding);
+ }
+
+ BinaryNodeType newExportSpec(Node bindingName, Node exportName) {
+ return newBinary(ParseNodeKind::ExportSpec, bindingName, exportName);
+ }
+
+ UnaryNodeType newExportNamespaceSpec(uint32_t begin, Node exportName) {
+ return newUnary(ParseNodeKind::ExportNamespaceSpec, begin, exportName);
+ }
+
+ NullaryNodeType newExportBatchSpec(const TokenPos& pos) {
+ return new_<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);
+ }
+
+ BinaryNodeType newCallImportSpec(Node specifierArg, Node optionalArg) {
+ return new_<BinaryNode>(ParseNodeKind::CallImportSpec, specifierArg,
+ optionalArg);
+ }
+
+ UnaryNodeType newExprStatement(Node expr, uint32_t end) {
+ MOZ_ASSERT(expr->pn_pos.end <= end);
+ return new_<UnaryNode>(ParseNodeKind::ExpressionStmt,
+ TokenPos(expr->pn_pos.begin, end), expr);
+ }
+
+ 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(TaggedParserAtomIndex label,
+ const TokenPos& pos) {
+ return new_<ContinueStatement>(label, pos);
+ }
+
+ BreakStatementType newBreakStatement(TaggedParserAtomIndex label,
+ const TokenPos& pos) {
+ return new_<BreakStatement>(label, pos);
+ }
+
+ UnaryNodeType newReturnStatement(Node expr, const TokenPos& pos) {
+ MOZ_ASSERT_IF(expr, 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(TaggedParserAtomIndex label,
+ Node stmt, uint32_t begin) {
+ return new_<LabeledStatement>(label, stmt, begin);
+ }
+
+ UnaryNodeType newThrowStatement(Node expr, const TokenPos& pos) {
+ MOZ_ASSERT(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(TaggedParserAtomIndex 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);
+ }
+
+ PrivateMemberAccessType newPrivateMemberAccess(Node lhs,
+ NameNodeType privateName,
+ uint32_t end) {
+ return new_<PrivateMemberAccess>(lhs, privateName, lhs->pn_pos.begin, end);
+ }
+
+ OptionalPrivateMemberAccessType newOptionalPrivateMemberAccess(
+ Node lhs, NameNodeType privateName, uint32_t end) {
+ return new_<OptionalPrivateMemberAccess>(lhs, privateName,
+ 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;
+ }
+
+ [[nodiscard]] inline bool setLastFunctionFormalParameterDefault(
+ FunctionNodeType funNode, Node defaultValue);
+
+ void checkAndSetIsDirectRHSAnonFunction(Node pn) {
+ if (IsAnonymousFunctionDefinition(pn)) {
+ pn->setDirectRHSAnonFunction(true);
+ }
+ }
+
+ ParamsBodyNodeType newParamsBody(const TokenPos& pos) {
+ return new_<ParamsBodyNode>(pos);
+ }
+
+ 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);
+ }
+
+ void setFunctionFormalParametersAndBody(FunctionNodeType funNode,
+ ParamsBodyNodeType paramsBody) {
+ funNode->setBody(paramsBody);
+ }
+ void setFunctionBox(FunctionNodeType funNode, FunctionBox* funbox) {
+ funNode->setFunbox(funbox);
+ funbox->functionNode = funNode;
+ }
+ void addFunctionFormalParameter(FunctionNodeType funNode, Node argpn) {
+ addList(/* list = */ funNode->body(), /* kid = */ argpn);
+ }
+ void setFunctionBody(FunctionNodeType funNode, LexicalScopeNodeType body) {
+ addList(/* list = */ funNode->body(), /* kid = */ body);
+ }
+
+ ModuleNodeType newModule(const TokenPos& pos) {
+ return new_<ModuleNode>(pos);
+ }
+
+ LexicalScopeNodeType newLexicalScope(LexicalScope::ParserData* bindings,
+ Node body,
+ ScopeKind kind = ScopeKind::Lexical) {
+ return new_<LexicalScopeNode>(bindings, body, kind);
+ }
+
+ ClassBodyScopeNodeType newClassBodyScope(ClassBodyScope::ParserData* bindings,
+ ListNodeType body) {
+ return new_<ClassBodyScopeNode>(bindings, body);
+ }
+
+ 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) &&
+ lhs->isKind(ParseNodeKind::Name) && !lhs->isInParens()) {
+ checkAndSetIsDirectRHSAnonFunction(rhs);
+ }
+
+ return new_<AssignmentNode>(kind, lhs, rhs);
+ }
+
+ BinaryNodeType newInitExpr(Node lhs, Node rhs) {
+ TokenPos pos(lhs->pn_pos.begin, rhs->pn_pos.end);
+ return new_<BinaryNode>(ParseNodeKind::InitExpr, pos, lhs, rhs);
+ }
+
+ bool isUnparenthesizedAssignment(Node node) {
+ if ((node->isKind(ParseNodeKind::AssignExpr)) && !node->isInParens()) {
+ return true;
+ }
+
+ return false;
+ }
+
+ bool isUnparenthesizedUnaryExpression(Node node) {
+ if (!node->isInParens()) {
+ ParseNodeKind kind = node->getKind();
+ return kind == ParseNodeKind::VoidExpr ||
+ kind == ParseNodeKind::NotExpr ||
+ kind == ParseNodeKind::BitNotExpr ||
+ kind == ParseNodeKind::PosExpr || kind == ParseNodeKind::NegExpr ||
+ kind == ParseNodeKind::AwaitExpr || IsTypeofKind(kind) ||
+ IsDeleteKind(kind);
+ }
+ return false;
+ }
+
+ bool isReturnStatement(Node node) {
+ return node->isKind(ParseNodeKind::ReturnStmt);
+ }
+
+ bool isStatementPermittedAfterReturnStatement(Node node) {
+ ParseNodeKind kind = node->getKind();
+ return kind == ParseNodeKind::Function || kind == ParseNodeKind::VarStmt ||
+ kind == ParseNodeKind::BreakStmt ||
+ kind == ParseNodeKind::ThrowStmt || kind == ParseNodeKind::EmptyStmt;
+ }
+
+ bool isSuperBase(Node node) { return node->isKind(ParseNodeKind::SuperBase); }
+
+ bool isUsableAsObjectPropertyName(Node node) {
+ return node->isKind(ParseNodeKind::NumberExpr) ||
+ node->isKind(ParseNodeKind::BigIntExpr) ||
+ node->isKind(ParseNodeKind::ObjectPropertyName) ||
+ node->isKind(ParseNodeKind::StringExpr) ||
+ node->isKind(ParseNodeKind::ComputedName) ||
+ node->isKind(ParseNodeKind::PrivateName);
+ }
+
+ AssignmentNodeType finishInitializerAssignment(NameNodeType nameNode,
+ Node init) {
+ MOZ_ASSERT(nameNode->isKind(ParseNodeKind::Name));
+ MOZ_ASSERT(!nameNode->isInParens());
+
+ checkAndSetIsDirectRHSAnonFunction(init);
+
+ return newAssignment(ParseNodeKind::AssignExpr, nameNode, init);
+ }
+
+ void setBeginPosition(Node pn, Node oth) {
+ setBeginPosition(pn, oth->pn_pos.begin);
+ }
+ void setBeginPosition(Node pn, uint32_t begin) {
+ pn->pn_pos.begin = begin;
+ MOZ_ASSERT(pn->pn_pos.begin <= pn->pn_pos.end);
+ }
+
+ void setEndPosition(Node pn, Node oth) {
+ setEndPosition(pn, oth->pn_pos.end);
+ }
+ void setEndPosition(Node pn, uint32_t end) {
+ pn->pn_pos.end = end;
+ MOZ_ASSERT(pn->pn_pos.begin <= pn->pn_pos.end);
+ }
+
+ uint32_t getFunctionNameOffset(Node func, TokenStreamAnyChars& ts) {
+ return func->pn_pos.begin;
+ }
+
+ ListNodeType newList(ParseNodeKind kind, const TokenPos& pos) {
+ auto* list = new_<ListNode>(kind, pos);
+ MOZ_ASSERT_IF(list, !list->is<DeclarationListNode>());
+ MOZ_ASSERT_IF(list, !list->is<ParamsBodyNode>());
+ return list;
+ }
+
+ ListNodeType newList(ParseNodeKind kind, Node kid) {
+ auto* list = new_<ListNode>(kind, kid);
+ MOZ_ASSERT_IF(list, !list->is<DeclarationListNode>());
+ MOZ_ASSERT_IF(list, !list->is<ParamsBodyNode>());
+ return list;
+ }
+
+ DeclarationListNodeType newDeclarationList(ParseNodeKind kind,
+ const TokenPos& pos) {
+ return new_<DeclarationListNode>(kind, pos);
+ }
+
+ ListNodeType newCommaExpressionList(Node kid) {
+ return new_<ListNode>(ParseNodeKind::CommaExpr, kid);
+ }
+
+ void addList(ListNodeType list, Node kid) { list->append(kid); }
+
+ void setListHasNonConstInitializer(ListNodeType literal) {
+ literal->setHasNonConstInitializer();
+ }
+ template <typename NodeType>
+ [[nodiscard]] NodeType parenthesize(NodeType node) {
+ node->setInParens(true);
+ return node;
+ }
+ template <typename NodeType>
+ [[nodiscard]] NodeType setLikelyIIFE(NodeType node) {
+ return parenthesize(node);
+ }
+
+ bool isName(Node node) { return node->isKind(ParseNodeKind::Name); }
+
+ bool isArgumentsName(Node node) {
+ return node->isKind(ParseNodeKind::Name) &&
+ node->as<NameNode>().atom() ==
+ TaggedParserAtomIndex::WellKnown::arguments();
+ }
+
+ bool isEvalName(Node node) {
+ return node->isKind(ParseNodeKind::Name) &&
+ node->as<NameNode>().atom() ==
+ TaggedParserAtomIndex::WellKnown::eval();
+ }
+
+ bool isAsyncKeyword(Node node) {
+ return node->isKind(ParseNodeKind::Name) &&
+ node->pn_pos.begin + strlen("async") == node->pn_pos.end &&
+ node->as<NameNode>().atom() ==
+ TaggedParserAtomIndex::WellKnown::async();
+ }
+
+ bool isPrivateName(Node node) {
+ return node->isKind(ParseNodeKind::PrivateName);
+ }
+
+ bool isPrivateMemberAccess(Node node) {
+ if (node->isKind(ParseNodeKind::OptionalChain)) {
+ return isPrivateMemberAccess(node->as<UnaryNode>().kid());
+ }
+ return node->is<PrivateMemberAccessBase>();
+ }
+
+ TaggedParserAtomIndex maybeDottedProperty(Node pn) {
+ return pn->is<PropertyAccessBase>() ? pn->as<PropertyAccessBase>().name()
+ : TaggedParserAtomIndex::null();
+ }
+ TaggedParserAtomIndex isStringExprStatement(Node pn, TokenPos* pos) {
+ if (pn->is<UnaryNode>()) {
+ UnaryNode* unary = &pn->as<UnaryNode>();
+ if (auto atom = unary->isStringExprStatement()) {
+ *pos = unary->kid()->pn_pos;
+ return atom;
+ }
+ }
+ return TaggedParserAtomIndex::null();
+ }
+
+ bool reuseLazyInnerFunctions() { return reuseGCThings; }
+ bool reuseClosedOverBindings() { return reuseGCThings; }
+ bool reuseRegexpSyntaxParse() { return reuseGCThings; }
+ void nextLazyInnerFunction() { lazyInnerFunctionIndex++; }
+ TaggedParserAtomIndex nextLazyClosedOverBinding() {
+ // Trailing nullptrs were elided in PerHandlerParser::finishFunction().
+ auto closedOverBindings = previousParseCache_.closedOverBindings();
+ if (lazyClosedOverBindingIndex >= closedOverBindings.Length()) {
+ return TaggedParserAtomIndex::null();
+ }
+
+ return closedOverBindings[lazyClosedOverBindingIndex++];
+ }
+ const ScriptStencil& cachedScriptData() const {
+ // lazyInnerFunctionIndex is incremented with nextLazyInnferFunction before
+ // reading the content, thus we need -1 to access the element that we just
+ // skipped.
+ return previousParseCache_.scriptData(lazyInnerFunctionIndex - 1);
+ }
+ const ScriptStencilExtra& cachedScriptExtra() const {
+ // lazyInnerFunctionIndex is incremented with nextLazyInnferFunction before
+ // reading the content, thus we need -1 to access the element that we just
+ // skipped.
+ return previousParseCache_.scriptExtra(lazyInnerFunctionIndex - 1);
+ }
+
+ void setPrivateNameKind(Node node, PrivateNameKind kind) {
+ MOZ_ASSERT(node->is<NameNode>());
+ node->as<NameNode>().setPrivateNameKind(kind);
+ }
+};
+
+inline bool FullParseHandler::setLastFunctionFormalParameterDefault(
+ FunctionNodeType funNode, Node defaultValue) {
+ ParamsBodyNode* body = funNode->body();
+ ParseNode* arg = body->last();
+ ParseNode* pn = newAssignment(ParseNodeKind::AssignExpr, arg, defaultValue);
+ if (!pn) {
+ return false;
+ }
+
+ body->replaceLast(pn);
+ return true;
+}
+
+} // namespace frontend
+} // namespace js
+
+#endif /* frontend_FullParseHandler_h */
diff --git a/js/src/frontend/FunctionEmitter.cpp b/js/src/frontend/FunctionEmitter.cpp
new file mode 100644
index 0000000000..fbff6cf1c5
--- /dev/null
+++ b/js/src/frontend/FunctionEmitter.cpp
@@ -0,0 +1,1017 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "frontend/FunctionEmitter.h"
+
+#include "mozilla/Assertions.h" // MOZ_ASSERT
+
+#include "frontend/AsyncEmitter.h" // AsyncEmitter
+#include "frontend/BytecodeEmitter.h" // BytecodeEmitter
+#include "frontend/FunctionSyntaxKind.h" // FunctionSyntaxKind
+#include "frontend/ModuleSharedContext.h" // ModuleSharedContext
+#include "frontend/NameAnalysisTypes.h" // NameLocation
+#include "frontend/NameOpEmitter.h" // NameOpEmitter
+#include "frontend/SharedContext.h" // SharedContext
+#include "vm/ModuleBuilder.h" // ModuleBuilder
+#include "vm/Opcodes.h" // JSOp
+#include "vm/Scope.h" // BindingKind
+
+using namespace js;
+using namespace js::frontend;
+
+using mozilla::Maybe;
+using mozilla::Some;
+
+FunctionEmitter::FunctionEmitter(BytecodeEmitter* bce, FunctionBox* funbox,
+ FunctionSyntaxKind syntaxKind,
+ IsHoisted isHoisted)
+ : bce_(bce),
+ funbox_(funbox),
+ name_(funbox_->explicitName()),
+ syntaxKind_(syntaxKind),
+ isHoisted_(isHoisted) {}
+
+bool FunctionEmitter::prepareForNonLazy() {
+ MOZ_ASSERT(state_ == State::Start);
+
+ MOZ_ASSERT(funbox_->isInterpreted());
+ MOZ_ASSERT(funbox_->emitBytecode);
+ MOZ_ASSERT(!funbox_->wasEmittedByEnclosingScript());
+
+ // [stack]
+
+ funbox_->setWasEmittedByEnclosingScript(true);
+
+#ifdef DEBUG
+ state_ = State::NonLazy;
+#endif
+ return true;
+}
+
+bool FunctionEmitter::emitNonLazyEnd() {
+ MOZ_ASSERT(state_ == State::NonLazy);
+
+ // [stack]
+
+ if (!emitFunction()) {
+ // [stack] FUN?
+ return false;
+ }
+
+#ifdef DEBUG
+ state_ = State::End;
+#endif
+ return true;
+}
+
+bool FunctionEmitter::emitLazy() {
+ MOZ_ASSERT(state_ == State::Start);
+
+ MOZ_ASSERT(funbox_->isInterpreted());
+ MOZ_ASSERT(!funbox_->emitBytecode);
+ MOZ_ASSERT(!funbox_->wasEmittedByEnclosingScript());
+
+ // [stack]
+
+ funbox_->setWasEmittedByEnclosingScript(true);
+
+ // Prepare to update the inner lazy script now that it's parent is fully
+ // compiled. These updates will be applied in UpdateEmittedInnerFunctions().
+ funbox_->setEnclosingScopeForInnerLazyFunction(bce_->innermostScopeIndex());
+
+ if (!emitFunction()) {
+ // [stack] FUN?
+ return false;
+ }
+
+#ifdef DEBUG
+ state_ = State::End;
+#endif
+ return true;
+}
+
+bool FunctionEmitter::emitAgain() {
+ MOZ_ASSERT(state_ == State::Start);
+ MOZ_ASSERT(funbox_->wasEmittedByEnclosingScript());
+
+ // [stack]
+
+ // Annex B block-scoped functions are hoisted like any other assignment
+ // that assigns the function to the outer 'var' binding.
+ if (!funbox_->isAnnexB) {
+#ifdef DEBUG
+ state_ = State::End;
+#endif
+ return true;
+ }
+
+ // Get the location of the 'var' binding in the body scope. The
+ // name must be found, else there is a bug in the Annex B handling
+ // in Parser.
+ //
+ // In sloppy eval contexts, this location is dynamic.
+ Maybe<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_->wasEmittedByEnclosingScript());
+ MOZ_ASSERT(funbox_->isAsmJSModule());
+
+ // [stack]
+
+ funbox_->setWasEmittedByEnclosingScript(true);
+
+ if (!emitFunction()) {
+ // [stack]
+ return false;
+ }
+
+#ifdef DEBUG
+ state_ = State::End;
+#endif
+ return true;
+}
+
+bool FunctionEmitter::emitFunction() {
+ // Make the function object a literal in the outer script's pool.
+ GCThingIndex index;
+ if (!bce_->perScriptData().gcThingList().append(funbox_, &index)) {
+ return false;
+ }
+
+ // [stack]
+
+ if (isHoisted_ == IsHoisted::No) {
+ return emitNonHoisted(index);
+ // [stack] FUN?
+ }
+
+ bool topLevelFunction;
+ if (bce_->sc->isFunctionBox() ||
+ (bce_->sc->isEvalContext() && bce_->sc->strict())) {
+ // No nested functions inside other functions are top-level.
+ topLevelFunction = false;
+ } else {
+ // In sloppy eval scripts, top-level functions are accessed dynamically.
+ // In global and module scripts, top-level functions are those bound in
+ // the var scope.
+ NameLocation loc = bce_->lookupName(name_);
+ topLevelFunction = loc.kind() == NameLocation::Kind::Dynamic ||
+ loc.bindingKind() == BindingKind::Var;
+ }
+
+ if (topLevelFunction) {
+ return emitTopLevelFunction(index);
+ // [stack]
+ }
+
+ return emitHoisted(index);
+ // [stack]
+}
+
+bool FunctionEmitter::emitNonHoisted(GCThingIndex index) {
+ // Non-hoisted functions simply emit their respective op.
+
+ // [stack]
+
+ if (syntaxKind_ == FunctionSyntaxKind::DerivedClassConstructor) {
+ // [stack] PROTO
+ if (!bce_->emitGCIndexOp(JSOp::FunWithProto, index)) {
+ // [stack] FUN
+ return false;
+ }
+ return true;
+ }
+
+ // This is a FunctionExpression, ArrowFunctionExpression, or class
+ // constructor. Emit the single instruction (without location info).
+ if (!bce_->emitGCIndexOp(JSOp::Lambda, index)) {
+ // [stack] FUN
+ return false;
+ }
+
+ return true;
+}
+
+bool FunctionEmitter::emitHoisted(GCThingIndex index) {
+ MOZ_ASSERT(syntaxKind_ == FunctionSyntaxKind::Statement);
+
+ // [stack]
+
+ // For functions nested within functions and blocks, make a lambda and
+ // initialize the binding name of the function in the current scope.
+
+ NameOpEmitter noe(bce_, name_, NameOpEmitter::Kind::Initialize);
+ if (!noe.prepareForRhs()) {
+ // [stack]
+ return false;
+ }
+
+ if (!bce_->emitGCIndexOp(JSOp::Lambda, index)) {
+ // [stack] FUN
+ return false;
+ }
+
+ if (!noe.emitAssignment()) {
+ // [stack] FUN
+ return false;
+ }
+
+ if (!bce_->emit1(JSOp::Pop)) {
+ // [stack]
+ return false;
+ }
+
+ return true;
+}
+
+bool FunctionEmitter::emitTopLevelFunction(GCThingIndex index) {
+ // [stack]
+
+ if (bce_->sc->isModuleContext()) {
+ // For modules, we record the function and instantiate the binding
+ // during ModuleInstantiate(), before the script is run.
+ return bce_->sc->asModuleContext()->builder.noteFunctionDeclaration(
+ bce_->fc, index);
+ }
+
+ MOZ_ASSERT(bce_->sc->isGlobalContext() || bce_->sc->isEvalContext());
+ MOZ_ASSERT(syntaxKind_ == FunctionSyntaxKind::Statement);
+ MOZ_ASSERT(bce_->inPrologue());
+
+ // NOTE: The `index` is not directly stored as an opcode, but we collect the
+ // range of indices in `BytecodeEmitter::emitDeclarationInstantiation` instead
+ // of discrete indices.
+ (void)index;
+
+ return true;
+}
+
+bool FunctionScriptEmitter::prepareForParameters() {
+ MOZ_ASSERT(state_ == State::Start);
+ MOZ_ASSERT(bce_->inPrologue());
+
+ // [stack]
+
+ if (paramStart_) {
+ bce_->setScriptStartOffsetIfUnset(*paramStart_);
+ }
+
+ // The ordering of these EmitterScopes is important. The named lambda
+ // scope needs to enclose the function scope needs to enclose the extra
+ // var scope.
+
+ if (funbox_->namedLambdaBindings()) {
+ namedLambdaEmitterScope_.emplace(bce_);
+ if (!namedLambdaEmitterScope_->enterNamedLambda(bce_, funbox_)) {
+ return false;
+ }
+ }
+
+ if (funbox_->needsPromiseResult()) {
+ asyncEmitter_.emplace(bce_);
+ }
+
+ if (bodyEnd_) {
+ bce_->setFunctionBodyEndPos(*bodyEnd_);
+ }
+
+ if (paramStart_) {
+ if (!bce_->updateLineNumberNotes(*paramStart_)) {
+ return false;
+ }
+ }
+
+ tdzCache_.emplace(bce_);
+ functionEmitterScope_.emplace(bce_);
+
+ if (!functionEmitterScope_->enterFunction(bce_, funbox_)) {
+ return false;
+ }
+
+ if (!emitInitializeClosedOverArgumentBindings()) {
+ // [stack]
+ return false;
+ }
+
+ if (funbox_->hasParameterExprs) {
+ // There's parameter exprs, emit them in the main section.
+ //
+ // One caveat is that Debugger considers ops in the prologue to be
+ // unreachable (i.e. cannot set a breakpoint on it). If there are no
+ // parameter exprs, any unobservable environment ops (like pushing the
+ // call object, setting '.this', etc) need to go in the prologue, else it
+ // messes up breakpoint tests.
+ bce_->switchToMain();
+ }
+
+ if (!bce_->emitInitializeFunctionSpecialNames()) {
+ // [stack]
+ return false;
+ }
+
+ if (!funbox_->hasParameterExprs) {
+ bce_->switchToMain();
+ }
+
+ if (funbox_->needsPromiseResult()) {
+ if (funbox_->hasParameterExprs || funbox_->hasDestructuringArgs) {
+ if (!asyncEmitter_->prepareForParamsWithExpressionOrDestructuring()) {
+ return false;
+ }
+ } else {
+ if (!asyncEmitter_->prepareForParamsWithoutExpressionOrDestructuring()) {
+ return false;
+ }
+ }
+ }
+
+ if (funbox_->isClassConstructor()) {
+ if (!funbox_->isDerivedClassConstructor()) {
+ if (!bce_->emitInitializeInstanceMembers(false)) {
+ // [stack]
+ return false;
+ }
+ }
+ }
+
+#ifdef DEBUG
+ state_ = State::Parameters;
+#endif
+ return true;
+}
+
+bool FunctionScriptEmitter::emitInitializeClosedOverArgumentBindings() {
+ // Initialize CallObject slots for closed-over arguments. If the function has
+ // parameter expressions, these are lexical bindings and we initialize the
+ // slots to the magic TDZ value. If the function doesn't have parameter
+ // expressions, we copy the frame's arguments.
+
+ MOZ_ASSERT(bce_->inPrologue());
+
+ auto* bindings = funbox_->functionScopeBindings();
+ if (!bindings) {
+ return true;
+ }
+
+ const bool hasParameterExprs = funbox_->hasParameterExprs;
+
+ bool pushedUninitialized = false;
+ for (ParserPositionalFormalParameterIter fi(*bindings, hasParameterExprs); fi;
+ fi++) {
+ if (!fi.closedOver()) {
+ continue;
+ }
+
+ if (hasParameterExprs) {
+ NameLocation nameLoc = bce_->lookupName(fi.name());
+ if (!pushedUninitialized) {
+ if (!bce_->emit1(JSOp::Uninitialized)) {
+ // [stack] UNINITIALIZED
+ return false;
+ }
+ pushedUninitialized = true;
+ }
+ if (!bce_->emitEnvCoordOp(JSOp::InitAliasedLexical,
+ nameLoc.environmentCoordinate())) {
+ // [stack] UNINITIALIZED
+ return false;
+ }
+ } else {
+ NameOpEmitter noe(bce_, fi.name(), NameOpEmitter::Kind::Initialize);
+ if (!noe.prepareForRhs()) {
+ // [stack]
+ return false;
+ }
+
+ if (!bce_->emitArgOp(JSOp::GetFrameArg, fi.argumentSlot())) {
+ // [stack] VAL
+ return false;
+ }
+
+ if (!noe.emitAssignment()) {
+ // [stack] VAL
+ return false;
+ }
+
+ if (!bce_->emit1(JSOp::Pop)) {
+ // [stack]
+ return false;
+ }
+ }
+ }
+
+ if (pushedUninitialized) {
+ MOZ_ASSERT(hasParameterExprs);
+ if (!bce_->emit1(JSOp::Pop)) {
+ // [stack]
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool FunctionScriptEmitter::prepareForBody() {
+ MOZ_ASSERT(state_ == State::Parameters);
+
+ // [stack]
+
+ if (funbox_->needsPromiseResult()) {
+ if (!asyncEmitter_->emitParamsEpilogue()) {
+ return false;
+ }
+ }
+
+ if (!emitExtraBodyVarScope()) {
+ // [stack]
+ return false;
+ }
+
+ if (funbox_->needsPromiseResult()) {
+ if (!asyncEmitter_->prepareForBody()) {
+ return false;
+ }
+ }
+
+#ifdef DEBUG
+ state_ = State::Body;
+#endif
+ return true;
+}
+
+bool FunctionScriptEmitter::emitExtraBodyVarScope() {
+ // [stack]
+
+ if (!funbox_->functionHasExtraBodyVarScope()) {
+ return true;
+ }
+
+ extraBodyVarEmitterScope_.emplace(bce_);
+ if (!extraBodyVarEmitterScope_->enterFunctionExtraBodyVar(bce_, funbox_)) {
+ return false;
+ }
+
+ if (!funbox_->extraVarScopeBindings() || !funbox_->functionScopeBindings()) {
+ return true;
+ }
+
+ // After emitting expressions for all parameters, copy over any formal
+ // parameters which have been redeclared as vars. For example, in the
+ // following, the var y in the body scope is 42:
+ //
+ // function f(x, y = 42) { var y; }
+ //
+ for (ParserBindingIter bi(*funbox_->functionScopeBindings(), true); bi;
+ bi++) {
+ auto name = bi.name();
+
+ // There may not be a var binding of the same name.
+ if (!bce_->locationOfNameBoundInScope(name,
+ extraBodyVarEmitterScope_.ptr())) {
+ continue;
+ }
+
+ // The '.this', '.newTarget', and '.generator' function special binding
+ // should never appear in the extra var scope. 'arguments', however, may.
+ MOZ_ASSERT(name != TaggedParserAtomIndex::WellKnown::dotThis() &&
+ name != TaggedParserAtomIndex::WellKnown::dotNewTarget() &&
+ name != TaggedParserAtomIndex::WellKnown::dotGenerator());
+
+ NameOpEmitter noe(bce_, name, NameOpEmitter::Kind::Initialize);
+ if (!noe.prepareForRhs()) {
+ // [stack]
+ return false;
+ }
+
+ NameLocation paramLoc =
+ *bce_->locationOfNameBoundInScope(name, functionEmitterScope_.ptr());
+ if (!bce_->emitGetNameAtLocation(name, paramLoc)) {
+ // [stack] VAL
+ return false;
+ }
+
+ if (!noe.emitAssignment()) {
+ // [stack] VAL
+ return false;
+ }
+
+ if (!bce_->emit1(JSOp::Pop)) {
+ // [stack]
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool FunctionScriptEmitter::emitEndBody() {
+ MOZ_ASSERT(state_ == State::Body);
+ // [stack]
+
+ if (bodyEnd_) {
+ if (!bce_->updateSourceCoordNotes(*bodyEnd_)) {
+ return false;
+ }
+ }
+
+ if (funbox_->needsFinalYield()) {
+ // If we fall off the end of a generator or async function, we
+ // do a final yield with an |undefined| payload. We put all
+ // the code to do this in one place, both to reduce bytecode
+ // size and to prevent any OOM or debugger exception that occurs
+ // at this point from being caught inside the function.
+ if (!bce_->emit1(JSOp::Undefined)) {
+ // [stack] UNDEF
+ return false;
+ }
+ if (!bce_->emit1(JSOp::SetRval)) {
+ // [stack]
+ return false;
+ }
+
+ // Return statements in the body of the function will jump here
+ // with the return payload in rval.
+ if (!bce_->emitJumpTargetAndPatch(bce_->finalYields)) {
+ // [stack]
+ return false;
+ }
+
+ if (funbox_->needsIteratorResult()) {
+ MOZ_ASSERT(!funbox_->needsPromiseResult());
+ // Emit final yield bytecode for generators, for example:
+ // function gen * () { ... }
+ if (!bce_->emitPrepareIteratorResult()) {
+ // [stack] RESULT
+ return false;
+ }
+
+ if (!bce_->emit1(JSOp::GetRval)) {
+ // [stack] RESULT RVAL
+ return false;
+ }
+
+ if (!bce_->emitFinishIteratorResult(true)) {
+ // [stack] RESULT
+ return false;
+ }
+
+ if (!bce_->emit1(JSOp::SetRval)) {
+ // [stack]
+ return false;
+ }
+
+ } else if (funbox_->needsPromiseResult()) {
+ // Emit final yield bytecode for async functions, for example:
+ // async function deferred() { ... }
+ if (!bce_->emit1(JSOp::GetRval)) {
+ // [stack] RVAL
+ return false;
+ }
+
+ if (!bce_->emitGetDotGeneratorInInnermostScope()) {
+ // [stack] RVAL GEN
+ return false;
+ }
+
+ if (!bce_->emit2(JSOp::AsyncResolve,
+ uint8_t(AsyncFunctionResolveKind::Fulfill))) {
+ // [stack] PROMISE
+ return false;
+ }
+
+ if (!bce_->emit1(JSOp::SetRval)) {
+ // [stack]
+ return false;
+ }
+ }
+
+ // Emit the final yield.
+ if (!bce_->emitGetDotGeneratorInInnermostScope()) {
+ // [stack] GEN
+ return false;
+ }
+
+ if (!bce_->emitYieldOp(JSOp::FinalYieldRval)) {
+ return false;
+ }
+
+ if (funbox_->needsPromiseResult()) {
+ // Emit the reject catch block.
+ if (!asyncEmitter_->emitEndFunction()) {
+ return false;
+ }
+ }
+
+ } else {
+ // Non-generator functions just return |undefined|. The
+ // JSOp::RetRval emitted below will do that, except if the
+ // script has a finally block: there can be a non-undefined
+ // value in the return value slot. Make sure the return value
+ // is |undefined|.
+ if (bce_->hasTryFinally) {
+ if (!bce_->emit1(JSOp::Undefined)) {
+ // [stack] UNDEF
+ return false;
+ }
+ if (!bce_->emit1(JSOp::SetRval)) {
+ // [stack]
+ return false;
+ }
+ }
+ }
+
+ // Execute |CheckReturn| right before exiting the class constructor.
+ if (funbox_->isDerivedClassConstructor()) {
+ if (!bce_->emitJumpTargetAndPatch(bce_->endOfDerivedClassConstructorBody)) {
+ return false;
+ }
+
+ if (!bce_->emitCheckDerivedClassConstructorReturn()) {
+ // [stack]
+ return false;
+ }
+ }
+
+ if (extraBodyVarEmitterScope_) {
+ if (!extraBodyVarEmitterScope_->leave(bce_)) {
+ return false;
+ }
+
+ extraBodyVarEmitterScope_.reset();
+ }
+
+ if (!functionEmitterScope_->leave(bce_)) {
+ return false;
+ }
+ functionEmitterScope_.reset();
+ tdzCache_.reset();
+
+ // We only want to mark the end of a function as a breakable position if
+ // there is token there that the user can easily associate with the function
+ // as a whole. Since arrow function single-expression bodies have no closing
+ // curly bracket, we do not place a breakpoint at their end position.
+ if (!funbox_->hasExprBody()) {
+ if (!bce_->markSimpleBreakpoint()) {
+ return false;
+ }
+ }
+
+ // Emit JSOp::RetRval except for sync arrow function with expression body
+ // which always ends with JSOp::Return. Other parts of the codebase depend
+ // on these opcodes being the last opcode.
+ // See JSScript::lastPC and BaselineCompiler::emitBody.
+ if (!funbox_->hasExprBody() || funbox_->isAsync()) {
+ if (!bce_->emitReturnRval()) {
+ // [stack]
+ return false;
+ }
+ }
+
+ if (namedLambdaEmitterScope_) {
+ if (!namedLambdaEmitterScope_->leave(bce_)) {
+ return false;
+ }
+ namedLambdaEmitterScope_.reset();
+ }
+
+#ifdef DEBUG
+ state_ = State::EndBody;
+#endif
+ return true;
+}
+
+bool FunctionScriptEmitter::intoStencil() {
+ MOZ_ASSERT(state_ == State::EndBody);
+
+ if (!bce_->intoScriptStencil(funbox_->index())) {
+ return false;
+ }
+
+#ifdef DEBUG
+ state_ = State::End;
+#endif
+
+ return true;
+}
+
+FunctionParamsEmitter::FunctionParamsEmitter(BytecodeEmitter* bce,
+ FunctionBox* funbox)
+ : bce_(bce),
+ funbox_(funbox),
+ functionEmitterScope_(bce_->innermostEmitterScope()) {}
+
+bool FunctionParamsEmitter::emitSimple(TaggedParserAtomIndex paramName) {
+ MOZ_ASSERT(state_ == State::Start);
+
+ // [stack]
+
+ if (funbox_->hasParameterExprs) {
+ if (!bce_->emitArgOp(JSOp::GetArg, argSlot_)) {
+ // [stack] ARG
+ return false;
+ }
+
+ if (!emitAssignment(paramName)) {
+ // [stack]
+ return false;
+ }
+ }
+
+ argSlot_++;
+ return true;
+}
+
+bool FunctionParamsEmitter::prepareForDefault() {
+ MOZ_ASSERT(state_ == State::Start);
+
+ // [stack]
+
+ if (!prepareForInitializer()) {
+ // [stack]
+ return false;
+ }
+
+#ifdef DEBUG
+ state_ = State::Default;
+#endif
+ return true;
+}
+
+bool FunctionParamsEmitter::emitDefaultEnd(TaggedParserAtomIndex paramName) {
+ MOZ_ASSERT(state_ == State::Default);
+
+ // [stack] DEFAULT
+
+ if (!emitInitializerEnd()) {
+ // [stack] ARG/DEFAULT
+ return false;
+ }
+ if (!emitAssignment(paramName)) {
+ // [stack]
+ return false;
+ }
+
+ argSlot_++;
+
+#ifdef DEBUG
+ state_ = State::Start;
+#endif
+ return true;
+}
+
+bool FunctionParamsEmitter::prepareForDestructuring() {
+ MOZ_ASSERT(state_ == State::Start);
+
+ // [stack]
+
+ if (!bce_->emitArgOp(JSOp::GetArg, argSlot_)) {
+ // [stack] ARG
+ return false;
+ }
+
+#ifdef DEBUG
+ state_ = State::Destructuring;
+#endif
+ return true;
+}
+
+bool FunctionParamsEmitter::emitDestructuringEnd() {
+ MOZ_ASSERT(state_ == State::Destructuring);
+
+ // [stack] ARG
+
+ if (!bce_->emit1(JSOp::Pop)) {
+ // [stack]
+ return false;
+ }
+
+ argSlot_++;
+
+#ifdef DEBUG
+ state_ = State::Start;
+#endif
+ return true;
+}
+
+bool FunctionParamsEmitter::prepareForDestructuringDefaultInitializer() {
+ MOZ_ASSERT(state_ == State::Start);
+
+ // [stack]
+
+ if (!prepareForInitializer()) {
+ // [stack]
+ return false;
+ }
+
+#ifdef DEBUG
+ state_ = State::DestructuringDefaultInitializer;
+#endif
+ return true;
+}
+
+bool FunctionParamsEmitter::prepareForDestructuringDefault() {
+ MOZ_ASSERT(state_ == State::DestructuringDefaultInitializer);
+
+ // [stack] DEFAULT
+
+ if (!emitInitializerEnd()) {
+ // [stack] ARG/DEFAULT
+ return false;
+ }
+
+#ifdef DEBUG
+ state_ = State::DestructuringDefault;
+#endif
+ return true;
+}
+
+bool FunctionParamsEmitter::emitDestructuringDefaultEnd() {
+ MOZ_ASSERT(state_ == State::DestructuringDefault);
+
+ // [stack] ARG/DEFAULT
+
+ if (!bce_->emit1(JSOp::Pop)) {
+ // [stack]
+ return false;
+ }
+
+ argSlot_++;
+
+#ifdef DEBUG
+ state_ = State::Start;
+#endif
+ return true;
+}
+
+bool FunctionParamsEmitter::emitRest(TaggedParserAtomIndex paramName) {
+ MOZ_ASSERT(state_ == State::Start);
+
+ // [stack]
+
+ if (!emitRestArray()) {
+ // [stack] REST
+ return false;
+ }
+ if (!emitAssignment(paramName)) {
+ // [stack]
+ return false;
+ }
+
+#ifdef DEBUG
+ state_ = State::End;
+#endif
+ return true;
+}
+
+bool FunctionParamsEmitter::prepareForDestructuringRest() {
+ MOZ_ASSERT(state_ == State::Start);
+
+ // [stack]
+
+ if (!emitRestArray()) {
+ // [stack] REST
+ return false;
+ }
+
+#ifdef DEBUG
+ state_ = State::DestructuringRest;
+#endif
+ return true;
+}
+
+bool FunctionParamsEmitter::emitDestructuringRestEnd() {
+ MOZ_ASSERT(state_ == State::DestructuringRest);
+
+ // [stack] REST
+
+ if (!bce_->emit1(JSOp::Pop)) {
+ // [stack]
+ return false;
+ }
+
+#ifdef DEBUG
+ state_ = State::End;
+#endif
+ return true;
+}
+
+bool FunctionParamsEmitter::prepareForInitializer() {
+ // [stack]
+
+ // If we have an initializer, emit the initializer and assign it
+ // to the argument slot. TDZ is taken care of afterwards.
+ MOZ_ASSERT(funbox_->hasParameterExprs);
+ if (!bce_->emitArgOp(JSOp::GetArg, argSlot_)) {
+ // [stack] ARG
+ return false;
+ }
+ default_.emplace(bce_);
+ if (!default_->prepareForDefault()) {
+ // [stack]
+ return false;
+ }
+ return true;
+}
+
+bool FunctionParamsEmitter::emitInitializerEnd() {
+ // [stack] DEFAULT
+
+ if (!default_->emitEnd()) {
+ // [stack] ARG/DEFAULT
+ return false;
+ }
+ default_.reset();
+ return true;
+}
+
+bool FunctionParamsEmitter::emitRestArray() {
+ // [stack]
+
+ if (!bce_->emit1(JSOp::Rest)) {
+ // [stack] REST
+ return false;
+ }
+ return true;
+}
+
+bool FunctionParamsEmitter::emitAssignment(TaggedParserAtomIndex paramName) {
+ // [stack] ARG
+
+ NameLocation paramLoc =
+ *bce_->locationOfNameBoundInScope(paramName, functionEmitterScope_);
+
+ // RHS is already pushed in the caller side.
+ // Make sure prepareForRhs doesn't touch stack.
+ MOZ_ASSERT(paramLoc.kind() == NameLocation::Kind::ArgumentSlot ||
+ paramLoc.kind() == NameLocation::Kind::FrameSlot ||
+ paramLoc.kind() == NameLocation::Kind::EnvironmentCoordinate);
+
+ NameOpEmitter noe(bce_, paramName, paramLoc, NameOpEmitter::Kind::Initialize);
+ if (!noe.prepareForRhs()) {
+ // [stack] ARG
+ return false;
+ }
+
+ if (!noe.emitAssignment()) {
+ // [stack] ARG
+ return false;
+ }
+
+ if (!bce_->emit1(JSOp::Pop)) {
+ // [stack]
+ return false;
+ }
+
+ return true;
+}
diff --git a/js/src/frontend/FunctionEmitter.h b/js/src/frontend/FunctionEmitter.h
new file mode 100644
index 0000000000..b36f223664
--- /dev/null
+++ b/js/src/frontend/FunctionEmitter.h
@@ -0,0 +1,436 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef frontend_FunctionEmitter_h
+#define frontend_FunctionEmitter_h
+
+#include "mozilla/Attributes.h" // MOZ_STACK_CLASS
+
+#include <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/ParserAtom.h" // TaggedParserAtomIndex
+#include "frontend/TDZCheckCache.h" // TDZCheckCache
+
+namespace js {
+
+class GCThingIndex;
+
+namespace frontend {
+
+struct BytecodeEmitter;
+class FunctionBox;
+
+// Class for emitting function declaration, expression, or method etc.
+//
+// This class handles the enclosing script's part (function object creation,
+// declaration, etc). The content of the function script is handled by
+// FunctionScriptEmitter and FunctionParamsEmitter.
+//
+// Usage: (check for the return value is omitted for simplicity)
+//
+// `function f() {}`, non lazy script
+// FunctionEmitter fe(this, funbox_for_f, FunctionSyntaxKind::Statement,
+// FunctionEmitter::IsHoisted::No);
+// fe.prepareForNonLazy();
+//
+// // Emit script with FunctionScriptEmitter here.
+// ...
+//
+// fe.emitNonLazyEnd();
+//
+// `function f() {}`, lazy script
+// FunctionEmitter fe(this, funbox_for_f, FunctionSyntaxKind::Statement,
+// FunctionEmitter::IsHoisted::No);
+// fe.emitLazy();
+//
+// `function f() {}`, emitting hoisted function again
+// // See emitAgain comment for more details
+// FunctionEmitter fe(this, funbox_for_f, FunctionSyntaxKind::Statement,
+// FunctionEmitter::IsHoisted::Yes);
+// fe.emitAgain();
+//
+// `function f() { "use asm"; }`
+// FunctionEmitter fe(this, funbox_for_f, FunctionSyntaxKind::Statement,
+// FunctionEmitter::IsHoisted::No);
+// fe.emitAsmJSModule();
+//
+class MOZ_STACK_CLASS FunctionEmitter {
+ public:
+ enum class IsHoisted { No, Yes };
+
+ private:
+ BytecodeEmitter* bce_;
+
+ FunctionBox* funbox_;
+
+ // Function's explicit name.
+ TaggedParserAtomIndex name_;
+
+ FunctionSyntaxKind syntaxKind_;
+ IsHoisted isHoisted_;
+
+#ifdef DEBUG
+ // The state of this emitter.
+ //
+ // +-------+
+ // | Start |-+
+ // +-------+ |
+ // |
+ // +-------+
+ // |
+ // | [non-lazy function]
+ // | prepareForNonLazy +---------+ emitNonLazyEnd +-----+
+ // +--------------------->| NonLazy |---------------->+->| End |
+ // | +---------+ ^ +-----+
+ // | |
+ // | [lazy function] |
+ // | emitLazy |
+ // +------------------------------------------------->+
+ // | ^
+ // | [emitting hoisted function again] |
+ // | emitAgain |
+ // +------------------------------------------------->+
+ // | ^
+ // | [asm.js module] |
+ // | emitAsmJSModule |
+ // +--------------------------------------------------+
+ //
+ enum class State {
+ // The initial state.
+ Start,
+
+ // After calling prepareForNonLazy.
+ NonLazy,
+
+ // After calling emitNonLazyEnd, emitLazy, emitAgain, or emitAsmJSModule.
+ End
+ };
+ State state_ = State::Start;
+#endif
+
+ public:
+ FunctionEmitter(BytecodeEmitter* bce, FunctionBox* funbox,
+ FunctionSyntaxKind syntaxKind, IsHoisted isHoisted);
+
+ [[nodiscard]] bool prepareForNonLazy();
+ [[nodiscard]] bool emitNonLazyEnd();
+
+ [[nodiscard]] bool emitLazy();
+
+ [[nodiscard]] bool emitAgain();
+
+ [[nodiscard]] bool emitAsmJSModule();
+
+ private:
+ // Emit the function declaration, expression, method etc.
+ // This leaves function object on the stack for expression etc,
+ // and doesn't for declaration.
+ [[nodiscard]] bool emitFunction();
+
+ // Helper methods used by emitFunction for each case.
+ // `index` is the object index of the function.
+ [[nodiscard]] bool emitNonHoisted(GCThingIndex index);
+ [[nodiscard]] bool emitHoisted(GCThingIndex index);
+ [[nodiscard]] bool emitTopLevelFunction(GCThingIndex index);
+};
+
+// Class for emitting function script.
+// Parameters are handled by FunctionParamsEmitter.
+//
+// Usage: (check for the return value is omitted for simplicity)
+//
+// `function f(a) { expr }`
+// FunctionScriptEmitter fse(this, funbox_for_f,
+// Some(offset_of_opening_paren),
+// Some(offset_of_closing_brace));
+// fse.prepareForParameters();
+//
+// // Emit parameters with FunctionParamsEmitter here.
+// ...
+//
+// fse.prepareForBody();
+// emit(expr);
+// fse.emitEnd();
+//
+// // Do NameFunctions operation here if needed.
+//
+// fse.intoStencil();
+//
+class MOZ_STACK_CLASS FunctionScriptEmitter {
+ private:
+ BytecodeEmitter* bce_;
+
+ FunctionBox* funbox_;
+
+ // Scope for the function name for a named lambda.
+ // None for anonymous function.
+ mozilla::Maybe<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) {}
+
+ [[nodiscard]] bool prepareForParameters();
+ [[nodiscard]] bool prepareForBody();
+ [[nodiscard]] bool emitEndBody();
+
+ // Generate the ScriptStencil using the bytecode emitter data.
+ [[nodiscard]] bool intoStencil();
+
+ private:
+ [[nodiscard]] bool emitExtraBodyVarScope();
+ [[nodiscard]] bool emitInitializeClosedOverArgumentBindings();
+};
+
+// Class for emitting function parameters.
+//
+// Usage: (check for the return value is omitted for simplicity)
+//
+// `function f(a, b=10, ...c) {}`
+// FunctionParamsEmitter fpe(this, funbox_for_f);
+//
+// fpe.emitSimple(atom_of_a);
+//
+// fpe.prepareForDefault();
+// emit(10);
+// fpe.emitDefaultEnd(atom_of_b);
+//
+// fpe.emitRest(atom_of_c);
+//
+// `function f([a], [b]=[1], ...[c]) {}`
+// FunctionParamsEmitter fpe(this, funbox_for_f);
+//
+// fpe.prepareForDestructuring();
+// emit(destructuring_for_[a]);
+// fpe.emitDestructuringEnd();
+//
+// fpe.prepareForDestructuringDefaultInitializer();
+// emit([1]);
+// fpe.prepareForDestructuringDefault();
+// emit(destructuring_for_[b]);
+// fpe.emitDestructuringDefaultEnd();
+//
+// fpe.prepareForDestructuringRest();
+// emit(destructuring_for_[c]);
+// fpe.emitDestructuringRestEnd();
+//
+class MOZ_STACK_CLASS FunctionParamsEmitter {
+ private:
+ BytecodeEmitter* bce_;
+
+ FunctionBox* funbox_;
+
+ // The pointer to `FunctionScriptEmitter::functionEmitterScope_`,
+ // passed via `BytecodeEmitter::innermostEmitterScope()`.
+ EmitterScope* functionEmitterScope_;
+
+ // The slot for the current parameter.
+ // NOTE: after emitting rest parameter, this isn't incremented.
+ uint16_t argSlot_ = 0;
+
+ // DefaultEmitter for default parameter.
+ mozilla::Maybe<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).
+ [[nodiscard]] bool emitSimple(TaggedParserAtomIndex paramName);
+
+ [[nodiscard]] bool prepareForDefault();
+ [[nodiscard]] bool emitDefaultEnd(TaggedParserAtomIndex paramName);
+
+ [[nodiscard]] bool prepareForDestructuring();
+ [[nodiscard]] bool emitDestructuringEnd();
+
+ [[nodiscard]] bool prepareForDestructuringDefaultInitializer();
+ [[nodiscard]] bool prepareForDestructuringDefault();
+ [[nodiscard]] bool emitDestructuringDefaultEnd();
+
+ [[nodiscard]] bool emitRest(TaggedParserAtomIndex paramName);
+
+ [[nodiscard]] bool prepareForDestructuringRest();
+ [[nodiscard]] bool emitDestructuringRestEnd();
+
+ private:
+ [[nodiscard]] bool prepareForInitializer();
+ [[nodiscard]] bool emitInitializerEnd();
+
+ [[nodiscard]] bool emitRestArray();
+
+ [[nodiscard]] bool emitAssignment(TaggedParserAtomIndex paramName);
+};
+
+} /* namespace frontend */
+} /* namespace js */
+
+#endif /* frontend_FunctionEmitter_h */
diff --git a/js/src/frontend/FunctionSyntaxKind.h b/js/src/frontend/FunctionSyntaxKind.h
new file mode 100644
index 0000000000..2f3d59ab08
--- /dev/null
+++ b/js/src/frontend/FunctionSyntaxKind.h
@@ -0,0 +1,41 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef frontend_FunctionSyntaxKind_h
+#define frontend_FunctionSyntaxKind_h
+
+#include <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,
+
+ // Mostly static class blocks act similar to field initializers, however,
+ // there is some difference in static semantics.
+ StaticClassBlock,
+
+ ClassConstructor,
+ DerivedClassConstructor,
+ Getter,
+ Setter,
+};
+
+} /* namespace frontend */
+} /* namespace js */
+
+#endif /* frontend_FunctionSyntaxKind_h */
diff --git a/js/src/frontend/GenerateReservedWords.py b/js/src/frontend/GenerateReservedWords.py
new file mode 100644
index 0000000000..078f8bf833
--- /dev/null
+++ b/js/src/frontend/GenerateReservedWords.py
@@ -0,0 +1,232 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+import re
+import sys
+
+
+def read_reserved_word_list(filename, enable_decorators):
+ macro_pat = re.compile(r"MACRO\(([^,]+), *[^,]+, *[^\)]+\)\s*\\?")
+
+ reserved_word_list = []
+ index = 0
+ with open(filename, "r") as f:
+ for line in f:
+ m = macro_pat.search(line)
+ if m:
+ reserved_word = m.group(1)
+ if reserved_word == "accessor" and not enable_decorators:
+ continue
+ reserved_word_list.append((index, reserved_word))
+ index += 1
+
+ assert len(reserved_word_list) != 0
+
+ return reserved_word_list
+
+
+def line(opt, s):
+ opt["output"].write("{}{}\n".format(" " * opt["indent_level"], s))
+
+
+def indent(opt):
+ opt["indent_level"] += 1
+
+
+def dedent(opt):
+ opt["indent_level"] -= 1
+
+
+def span_and_count_at(reserved_word_list, column):
+ assert len(reserved_word_list) != 0
+
+ chars_dict = {}
+ for index, word in reserved_word_list:
+ chars_dict[ord(word[column])] = True
+
+ chars = sorted(chars_dict.keys())
+ return chars[-1] - chars[0] + 1, len(chars)
+
+
+def optimal_switch_column(opt, reserved_word_list, columns, unprocessed_columns):
+ assert len(reserved_word_list) != 0
+ assert unprocessed_columns != 0
+
+ min_count = 0
+ min_span = 0
+ min_count_index = 0
+ min_span_index = 0
+
+ for index in range(0, unprocessed_columns):
+ span, count = span_and_count_at(reserved_word_list, columns[index])
+ assert span != 0
+
+ if span == 1:
+ assert count == 1
+ return 1, True
+
+ assert count != 1
+ if index == 0 or min_span > span:
+ min_span = span
+ min_span_index = index
+
+ if index == 0 or min_count > count:
+ min_count = count
+ min_count_index = index
+
+ if min_count <= opt["use_if_threshold"]:
+ return min_count_index, True
+
+ return min_span_index, False
+
+
+def split_list_per_column(reserved_word_list, column):
+ assert len(reserved_word_list) != 0
+
+ column_dict = {}
+ for item in reserved_word_list:
+ index, word = item
+ per_column = column_dict.setdefault(word[column], [])
+ per_column.append(item)
+
+ return sorted(column_dict.items())
+
+
+def generate_letter_switch(opt, unprocessed_columns, reserved_word_list, columns=None):
+ assert len(reserved_word_list) != 0
+
+ if not columns:
+ columns = range(0, unprocessed_columns)
+
+ if len(reserved_word_list) == 1:
+ index, word = reserved_word_list[0]
+
+ if unprocessed_columns == 0:
+ line(opt, "JSRW_GOT_MATCH({}) /* {} */".format(index, word))
+ return
+
+ if unprocessed_columns > opt["char_tail_test_threshold"]:
+ line(opt, "JSRW_TEST_GUESS({}) /* {} */".format(index, word))
+ return
+
+ conds = []
+ for column in columns[0:unprocessed_columns]:
+ quoted = repr(word[column])
+ conds.append("JSRW_AT({})=={}".format(column, quoted))
+
+ line(opt, "if ({}) {{".format(" && ".join(conds)))
+
+ indent(opt)
+ line(opt, "JSRW_GOT_MATCH({}) /* {} */".format(index, word))
+ dedent(opt)
+
+ line(opt, "}")
+ line(opt, "JSRW_NO_MATCH()")
+ return
+
+ assert unprocessed_columns != 0
+
+ optimal_column_index, use_if = optimal_switch_column(
+ opt, reserved_word_list, columns, unprocessed_columns
+ )
+ optimal_column = columns[optimal_column_index]
+
+ # Make a copy to avoid breaking passed list.
+ columns = list(columns)
+ columns[optimal_column_index] = columns[unprocessed_columns - 1]
+
+ list_per_column = split_list_per_column(reserved_word_list, optimal_column)
+
+ if not use_if:
+ line(opt, "switch (JSRW_AT({})) {{".format(optimal_column))
+
+ for char, reserved_word_list_per_column in list_per_column:
+ quoted = repr(char)
+ if use_if:
+ line(opt, "if (JSRW_AT({}) == {}) {{".format(optimal_column, quoted))
+ else:
+ line(opt, " case {}:".format(quoted))
+
+ indent(opt)
+ generate_letter_switch(
+ opt, unprocessed_columns - 1, reserved_word_list_per_column, columns
+ )
+ dedent(opt)
+
+ if use_if:
+ line(opt, "}")
+
+ if not use_if:
+ line(opt, "}")
+
+ line(opt, "JSRW_NO_MATCH()")
+
+
+def split_list_per_length(reserved_word_list):
+ assert len(reserved_word_list) != 0
+
+ length_dict = {}
+ for item in reserved_word_list:
+ index, word = item
+ per_length = length_dict.setdefault(len(word), [])
+ per_length.append(item)
+
+ return sorted(length_dict.items())
+
+
+def generate_switch(opt, reserved_word_list):
+ assert len(reserved_word_list) != 0
+
+ line(opt, "/*")
+ line(
+ opt,
+ " * Generating switch for the list of {} entries:".format(
+ len(reserved_word_list)
+ ),
+ )
+ for index, word in reserved_word_list:
+ line(opt, " * {}".format(word))
+ line(opt, " */")
+
+ list_per_length = split_list_per_length(reserved_word_list)
+
+ use_if = False
+ if len(list_per_length) < opt["use_if_threshold"]:
+ use_if = True
+
+ if not use_if:
+ line(opt, "switch (JSRW_LENGTH()) {")
+
+ for length, reserved_word_list_per_length in list_per_length:
+ if use_if:
+ line(opt, "if (JSRW_LENGTH() == {}) {{".format(length))
+ else:
+ line(opt, " case {}:".format(length))
+
+ indent(opt)
+ generate_letter_switch(opt, length, reserved_word_list_per_length)
+ dedent(opt)
+
+ if use_if:
+ line(opt, "}")
+
+ if not use_if:
+ line(opt, "}")
+ line(opt, "JSRW_NO_MATCH()")
+
+
+def main(output, reserved_words_h, enable_decorators=False):
+ reserved_word_list = read_reserved_word_list(reserved_words_h, enable_decorators)
+
+ opt = {
+ "indent_level": 1,
+ "use_if_threshold": 3,
+ "char_tail_test_threshold": 4,
+ "output": output,
+ }
+ generate_switch(opt, reserved_word_list)
+
+
+if __name__ == "__main__":
+ main(sys.stdout, *sys.argv[1:])
diff --git a/js/src/frontend/IfEmitter.cpp b/js/src/frontend/IfEmitter.cpp
new file mode 100644
index 0000000000..ced510e0b7
--- /dev/null
+++ b/js/src/frontend/IfEmitter.cpp
@@ -0,0 +1,286 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "frontend/IfEmitter.h"
+
+#include "frontend/BytecodeEmitter.h"
+#include "vm/Opcodes.h"
+
+using namespace js;
+using namespace js::frontend;
+
+using mozilla::Maybe;
+
+BranchEmitterBase::BranchEmitterBase(BytecodeEmitter* bce,
+ LexicalKind lexicalKind)
+ : bce_(bce), lexicalKind_(lexicalKind) {}
+
+IfEmitter::IfEmitter(BytecodeEmitter* bce, LexicalKind lexicalKind)
+ : BranchEmitterBase(bce, lexicalKind) {}
+
+IfEmitter::IfEmitter(BytecodeEmitter* bce)
+ : IfEmitter(bce, LexicalKind::MayContainLexicalAccessInBranch) {}
+
+bool BranchEmitterBase::emitThenInternal(ConditionKind conditionKind) {
+ // The end of TDZCheckCache for cond for else-if.
+ if (lexicalKind_ == LexicalKind::MayContainLexicalAccessInBranch) {
+ tdzCache_.reset();
+ }
+
+ // Emit a jump around the then part.
+ JSOp op = conditionKind == ConditionKind::Positive ? JSOp::JumpIfFalse
+ : JSOp::JumpIfTrue;
+ if (!bce_->emitJump(op, &jumpAroundThen_)) {
+ return false;
+ }
+
+ // To restore stack depth in else part (if present), save depth of the then
+ // part.
+ thenDepth_ = bce_->bytecodeSection().stackDepth();
+
+ // Enclose then-branch with TDZCheckCache.
+ if (lexicalKind_ == LexicalKind::MayContainLexicalAccessInBranch) {
+ tdzCache_.emplace(bce_);
+ }
+
+ return true;
+}
+
+void BranchEmitterBase::calculateOrCheckPushed() {
+#ifdef DEBUG
+ if (!calculatedPushed_) {
+ pushed_ = bce_->bytecodeSection().stackDepth() - thenDepth_;
+ calculatedPushed_ = true;
+ } else {
+ MOZ_ASSERT(pushed_ == bce_->bytecodeSection().stackDepth() - thenDepth_);
+ }
+#endif
+}
+
+bool BranchEmitterBase::emitElseInternal() {
+ calculateOrCheckPushed();
+
+ // The end of TDZCheckCache for then-clause.
+ if (lexicalKind_ == LexicalKind::MayContainLexicalAccessInBranch) {
+ MOZ_ASSERT(tdzCache_.isSome());
+ tdzCache_.reset();
+ }
+
+ // Emit a jump from the end of our then part around the else part. The
+ // patchJumpsToTarget call at the bottom of this function will fix up
+ // the offset with jumpsAroundElse value.
+ if (!bce_->emitJump(JSOp::Goto, &jumpsAroundElse_)) {
+ return false;
+ }
+
+ // Ensure the branch-if-false comes here, then emit the else.
+ if (!bce_->emitJumpTargetAndPatch(jumpAroundThen_)) {
+ return false;
+ }
+
+ // Clear jumpAroundThen_ offset, to tell emitEnd there was an else part.
+ jumpAroundThen_ = JumpList();
+
+ // Restore stack depth of the then part.
+ bce_->bytecodeSection().setStackDepth(thenDepth_);
+
+ // Enclose else-branch with TDZCheckCache.
+ if (lexicalKind_ == LexicalKind::MayContainLexicalAccessInBranch) {
+ tdzCache_.emplace(bce_);
+ }
+
+ return true;
+}
+
+bool BranchEmitterBase::emitEndInternal() {
+ // The end of TDZCheckCache for then or else-clause.
+ if (lexicalKind_ == LexicalKind::MayContainLexicalAccessInBranch) {
+ MOZ_ASSERT(tdzCache_.isSome());
+ tdzCache_.reset();
+ }
+
+ calculateOrCheckPushed();
+
+ if (jumpAroundThen_.offset.valid()) {
+ // No else part for the last branch, fixup the branch-if-false to
+ // come here.
+ if (!bce_->emitJumpTargetAndPatch(jumpAroundThen_)) {
+ return false;
+ }
+ }
+
+ // Patch all the jumps around else parts.
+ if (!bce_->emitJumpTargetAndPatch(jumpsAroundElse_)) {
+ return false;
+ }
+
+ return true;
+}
+
+bool IfEmitter::emitIf(const Maybe<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..eaf4e2d8f4
--- /dev/null
+++ b/js/src/frontend/IfEmitter.h
@@ -0,0 +1,311 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef frontend_IfEmitter_h
+#define frontend_IfEmitter_h
+
+#include "mozilla/Attributes.h"
+#include "mozilla/Maybe.h"
+
+#include <stdint.h>
+
+#include "frontend/JumpList.h"
+#include "frontend/TDZCheckCache.h"
+
+namespace js {
+namespace frontend {
+
+struct BytecodeEmitter;
+
+class MOZ_STACK_CLASS BranchEmitterBase {
+ protected:
+ BytecodeEmitter* bce_;
+
+ // Jump around the then clause, to the beginning of the else clause.
+ JumpList jumpAroundThen_;
+
+ // Jump around the else clause, to the end of the entire branch.
+ JumpList jumpsAroundElse_;
+
+ // The stack depth before emitting the then block.
+ // Used for restoring stack depth before emitting the else block.
+ // Also used for assertion to make sure then and else blocks pushed the
+ // same number of values.
+ int32_t thenDepth_ = 0;
+
+ enum class ConditionKind { Positive, Negative };
+
+ // Whether the then-clause, the else-clause, or else-if condition may
+ // contain declaration or access to lexical variables, which means they
+ // should have their own TDZCheckCache. Basically TDZCheckCache should be
+ // created for each basic block, which then-clause, else-clause, and
+ // else-if condition are, but for internally used branches which are
+ // known not to touch lexical variables we can skip creating TDZCheckCache
+ // for them.
+ //
+ // See the comment for TDZCheckCache class for more details.
+ enum class LexicalKind {
+ // For syntactic branches (if, if-else, and conditional expression),
+ // which basically may contain declaration or accesses to lexical
+ // variables inside then-clause, else-clause, and else-if condition.
+ MayContainLexicalAccessInBranch,
+
+ // For internally used branches which don't touch lexical variables
+ // inside then-clause, else-clause, nor else-if condition.
+ NoLexicalAccessInBranch
+ };
+ LexicalKind lexicalKind_;
+
+ mozilla::Maybe<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);
+
+ [[nodiscard]] bool emitThenInternal(ConditionKind conditionKind);
+ void calculateOrCheckPushed();
+ [[nodiscard]] bool emitElseInternal();
+ [[nodiscard]] bool emitEndInternal();
+
+ public:
+#ifdef DEBUG
+ // Returns the number of values pushed onto the value stack inside
+ // `then_block` and `else_block`.
+ // Can be used in assertion after emitting if-then-else.
+ int32_t pushed() const { return pushed_; }
+
+ // Returns the number of values popped onto the value stack inside
+ // `then_block` and `else_block`.
+ // Can be used in assertion after emitting if-then-else.
+ int32_t popped() const { return -pushed_; }
+#endif
+};
+
+// Class for emitting bytecode for blocks like if-then-else.
+//
+// This class can be used to emit single if-then-else block, or cascading
+// else-if blocks.
+//
+// Usage: (check for the return value is omitted for simplicity)
+//
+// `if (cond) then_block`
+// IfEmitter ifThen(this);
+// ifThen.emitIf(Some(offset_of_if));
+// emit(cond);
+// ifThen.emitThen();
+// emit(then_block);
+// ifThen.emitEnd();
+//
+// `if (!cond) then_block`
+// IfEmitter ifThen(this);
+// ifThen.emitIf(Some(offset_of_if));
+// emit(cond);
+// ifThen.emitThen(IfEmitter::ConditionKind::Negative);
+// emit(then_block);
+// ifThen.emitEnd();
+//
+// `if (cond) then_block else else_block`
+// IfEmitter ifThenElse(this);
+// ifThen.emitIf(Some(offset_of_if));
+// emit(cond);
+// ifThenElse.emitThenElse();
+// emit(then_block);
+// ifThenElse.emitElse();
+// emit(else_block);
+// ifThenElse.emitEnd();
+//
+// `if (c1) b1 else if (c2) b2 else if (c3) b3 else b4`
+// IfEmitter ifThenElse(this);
+// ifThen.emitIf(Some(offset_of_if));
+// emit(c1);
+// ifThenElse.emitThenElse();
+// emit(b1);
+// ifThenElse.emitElseIf(Some(offset_of_if));
+// emit(c2);
+// ifThenElse.emitThenElse();
+// emit(b2);
+// ifThenElse.emitElseIf(Some(offset_of_if));
+// emit(c3);
+// ifThenElse.emitThenElse();
+// emit(b3);
+// ifThenElse.emitElse();
+// emit(b4);
+// ifThenElse.emitEnd();
+//
+class MOZ_STACK_CLASS IfEmitter : public BranchEmitterBase {
+ public:
+ using ConditionKind = BranchEmitterBase::ConditionKind;
+
+ protected:
+#ifdef DEBUG
+ // The state of this emitter.
+ //
+ // +-------+ emitIf +----+
+ // | Start |------->| If |-+
+ // +-------+ +----+ |
+ // |
+ // +--------------------+
+ // |
+ // v emitThen +------+ emitEnd +-----+
+ // +->+--------->| Then |---------------------------->+-------->| End |
+ // ^ | +------+ ^ +-----+
+ // | | |
+ // | | |
+ // | | |
+ // | | emitThenElse +----------+ emitElse +------+ |
+ // | +------------->| ThenElse |-+--------->| Else |-+
+ // | +----------+ | +------+
+ // | |
+ // | | emitElseIf +--------+
+ // | +----------->| ElseIf |-+
+ // | +--------+ |
+ // | |
+ // +------------------------------------------------------+
+ enum class State {
+ // The initial state.
+ Start,
+
+ // After calling emitIf.
+ If,
+
+ // After calling emitThen.
+ Then,
+
+ // After calling emitThenElse.
+ ThenElse,
+
+ // After calling emitElse.
+ Else,
+
+ // After calling emitElseIf.
+ ElseIf,
+
+ // After calling emitEnd.
+ End
+ };
+ State state_ = State::Start;
+#endif
+
+ protected:
+ // For InternalIfEmitter.
+ IfEmitter(BytecodeEmitter* bce, LexicalKind lexicalKind);
+
+ public:
+ explicit IfEmitter(BytecodeEmitter* bce);
+
+ // `ifPos` is the offset in the source code for the character below:
+ //
+ // if ( cond ) { ... } else if ( cond2 ) { ... }
+ // ^ ^
+ // | |
+ // | ifPos for emitElseIf
+ // |
+ // ifPos for emitIf
+ //
+ // Can be Nothing() if not available.
+ [[nodiscard]] bool emitIf(const mozilla::Maybe<uint32_t>& ifPos);
+
+ [[nodiscard]] bool emitThen(
+ ConditionKind conditionKind = ConditionKind::Positive);
+ [[nodiscard]] bool emitThenElse(
+ ConditionKind conditionKind = ConditionKind::Positive);
+
+ [[nodiscard]] bool emitElseIf(const mozilla::Maybe<uint32_t>& ifPos);
+ [[nodiscard]] bool emitElse();
+
+ [[nodiscard]] bool emitEnd();
+};
+
+// Class for emitting bytecode for blocks like if-then-else which doesn't touch
+// lexical variables.
+//
+// See the comments above NoLexicalAccessInBranch for more details when to use
+// this instead of IfEmitter.
+// Compared to IfEmitter, this class doesn't have emitIf method, given that
+// it doesn't have syntactic `if`, and also the `cond` value can be already
+// on the stack.
+//
+// Usage: (check for the return value is omitted for simplicity)
+//
+// `if (cond) then_block else else_block` (effectively)
+// emit(cond);
+// InternalIfEmitter ifThenElse(this);
+// ifThenElse.emitThenElse();
+// emit(then_block);
+// ifThenElse.emitElse();
+// emit(else_block);
+// ifThenElse.emitEnd();
+//
+class MOZ_STACK_CLASS InternalIfEmitter : public IfEmitter {
+ public:
+ explicit InternalIfEmitter(BytecodeEmitter* bce);
+};
+
+// Class for emitting bytecode for conditional expression.
+//
+// Usage: (check for the return value is omitted for simplicity)
+//
+// `cond ? then_expr : else_expr`
+// CondEmitter condElse(this);
+// condElse.emitCond();
+// emit(cond);
+// condElse.emitThenElse();
+// emit(then_expr);
+// condElse.emitElse();
+// emit(else_expr);
+// condElse.emitEnd();
+//
+class MOZ_STACK_CLASS CondEmitter : public BranchEmitterBase {
+#ifdef DEBUG
+ // The state of this emitter.
+ //
+ // +-------+ emitCond +------+ emitThenElse +----------+
+ // | Start |--------->| Cond |------------->| ThenElse |-+
+ // +-------+ +------+ +----------+ |
+ // |
+ // +-----------------+
+ // |
+ // | emitElse +------+ emitEnd +-----+
+ // +--------->| Else |-------->| End |
+ // +------+ +-----+
+ enum class State {
+ // The initial state.
+ Start,
+
+ // After calling emitCond.
+ Cond,
+
+ // After calling emitThenElse.
+ ThenElse,
+
+ // After calling emitElse.
+ Else,
+
+ // After calling emitEnd.
+ End
+ };
+ State state_ = State::Start;
+#endif
+
+ public:
+ explicit CondEmitter(BytecodeEmitter* bce);
+
+ [[nodiscard]] bool emitCond();
+ [[nodiscard]] bool emitThenElse(
+ ConditionKind conditionKind = ConditionKind::Positive);
+ [[nodiscard]] bool emitElse();
+ [[nodiscard]] bool emitEnd();
+};
+
+} /* namespace frontend */
+} /* namespace js */
+
+#endif /* frontend_IfEmitter_h */
diff --git a/js/src/frontend/IteratorKind.h b/js/src/frontend/IteratorKind.h
new file mode 100644
index 0000000000..7e8f0316b7
--- /dev/null
+++ b/js/src/frontend/IteratorKind.h
@@ -0,0 +1,16 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef frontend_IteratorKind_h
+#define frontend_IteratorKind_h
+
+namespace js::frontend {
+
+enum class IteratorKind { Sync, Async };
+
+} /* namespace js::frontend */
+
+#endif /* frontend_IteratorKind_h */
diff --git a/js/src/frontend/JumpList.cpp b/js/src/frontend/JumpList.cpp
new file mode 100644
index 0000000000..ec2628ad07
--- /dev/null
+++ b/js/src/frontend/JumpList.cpp
@@ -0,0 +1,46 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "frontend/JumpList.h"
+
+#include "mozilla/Assertions.h" // MOZ_ASSERT
+
+#include "vm/BytecodeUtil.h" // GET_JUMP_OFFSET, SET_JUMP_OFFSET, IsJumpOpcode
+
+using namespace js;
+using namespace js::frontend;
+
+void JumpList::push(jsbytecode* code, BytecodeOffset jumpOffset) {
+ if (!offset.valid()) {
+ SET_JUMP_OFFSET(&code[jumpOffset.value()], END_OF_LIST_DELTA);
+ } else {
+ SET_JUMP_OFFSET(&code[jumpOffset.value()], (offset - jumpOffset).value());
+ }
+ offset = jumpOffset;
+}
+
+void JumpList::patchAll(jsbytecode* code, JumpTarget target) {
+ if (!offset.valid()) {
+ // This list is not used. Nothing to do.
+ return;
+ }
+
+ BytecodeOffsetDiff delta;
+ BytecodeOffset jumpOffset = offset;
+ while (true) {
+ jsbytecode* pc = &code[jumpOffset.value()];
+ MOZ_ASSERT(IsJumpOpcode(JSOp(*pc)));
+ delta = BytecodeOffsetDiff(GET_JUMP_OFFSET(pc));
+ MOZ_ASSERT(delta.value() == END_OF_LIST_DELTA || delta.value() < 0);
+ BytecodeOffsetDiff span = target.offset - jumpOffset;
+ SET_JUMP_OFFSET(pc, span.value());
+
+ if (delta.value() == END_OF_LIST_DELTA) {
+ break;
+ }
+ jumpOffset += delta;
+ }
+}
diff --git a/js/src/frontend/JumpList.h b/js/src/frontend/JumpList.h
new file mode 100644
index 0000000000..97846ce3de
--- /dev/null
+++ b/js/src/frontend/JumpList.h
@@ -0,0 +1,84 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef frontend_JumpList_h
+#define frontend_JumpList_h
+
+#include <stddef.h> // ptrdiff_t
+
+#include "frontend/BytecodeOffset.h" // BytecodeOffset
+#include "js/TypeDecls.h" // jsbytecode
+
+namespace js {
+namespace frontend {
+
+// Linked list of jump instructions that need to be patched. The linked list is
+// stored in the bytes of the incomplete bytecode that will be patched, so no
+// extra memory is needed, and patching the instructions destroys the list.
+//
+// Example:
+//
+// JumpList brList;
+// if (!emitJump(JSOp::JumpIfFalse, &brList)) {
+// return false;
+// }
+// ...
+// JumpTarget label;
+// if (!emitJumpTarget(&label)) {
+// return false;
+// }
+// ...
+// if (!emitJump(JSOp::Goto, &brList)) {
+// return false;
+// }
+// ...
+// patchJumpsToTarget(brList, label);
+//
+// +-> (the delta is END_OF_LIST_DELTA (=0) for the last
+// | item)
+// |
+// |
+// JumpIfFalse .. <+ + +-+ JumpIfFalse ..
+// .. | | ..
+// label: | +-> label:
+// JumpTarget | | JumpTarget
+// .. | | ..
+// Goto .. <+ +----+ +-+ Goto .. <+
+// | |
+// | |
+// + +
+// brList brList
+//
+// | ^
+// +------- patchJumpsToTarget -------+
+//
+
+// Offset of a jump target instruction, used for patching jump instructions.
+struct JumpTarget {
+ BytecodeOffset offset = BytecodeOffset::invalidOffset();
+};
+
+struct JumpList {
+ // Delta value for pre-patchJumpsToTarget that marks the end of the link.
+ static const ptrdiff_t END_OF_LIST_DELTA = 0;
+
+ // -1 is used to mark the end of jump lists.
+ JumpList() : offset(BytecodeOffset::invalidOffset()) {}
+
+ BytecodeOffset offset;
+
+ // Add a jump instruction to the list.
+ void push(jsbytecode* code, BytecodeOffset jumpOffset);
+
+ // Patch all jump instructions in this list to jump to `target`. This
+ // clobbers the list.
+ void patchAll(jsbytecode* code, JumpTarget target);
+};
+
+} /* namespace frontend */
+} /* namespace js */
+
+#endif /* frontend_JumpList_h */
diff --git a/js/src/frontend/LabelEmitter.cpp b/js/src/frontend/LabelEmitter.cpp
new file mode 100644
index 0000000000..8b9b6233e6
--- /dev/null
+++ b/js/src/frontend/LabelEmitter.cpp
@@ -0,0 +1,40 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "frontend/LabelEmitter.h"
+
+#include "mozilla/Assertions.h" // MOZ_ASSERT
+
+#include "frontend/BytecodeEmitter.h" // BytecodeEmitter
+
+using namespace js;
+using namespace js::frontend;
+
+void LabelEmitter::emitLabel(TaggedParserAtomIndex name) {
+ MOZ_ASSERT(state_ == State::Start);
+
+ controlInfo_.emplace(bce_, name, bce_->bytecodeSection().offset());
+
+#ifdef DEBUG
+ state_ = State::Label;
+#endif
+}
+
+bool LabelEmitter::emitEnd() {
+ MOZ_ASSERT(state_ == State::Label);
+
+ // Patch the break/continue to this label.
+ if (!controlInfo_->patchBreaks(bce_)) {
+ return false;
+ }
+
+ controlInfo_.reset();
+
+#ifdef DEBUG
+ state_ = State::End;
+#endif
+ return true;
+}
diff --git a/js/src/frontend/LabelEmitter.h b/js/src/frontend/LabelEmitter.h
new file mode 100644
index 0000000000..f3d9ec67c0
--- /dev/null
+++ b/js/src/frontend/LabelEmitter.h
@@ -0,0 +1,65 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef frontend_LabelEmitter_h
+#define frontend_LabelEmitter_h
+
+#include "mozilla/Attributes.h" // MOZ_STACK_CLASS
+#include "mozilla/Maybe.h" // Maybe
+
+#include "frontend/BytecodeControlStructures.h" // LabelControl
+
+namespace js {
+namespace frontend {
+
+struct BytecodeEmitter;
+class TaggedParserAtomIndex;
+
+// Class for emitting labeled statement.
+//
+// Usage: (check for the return value is omitted for simplicity)
+//
+// `label: expr;`
+// LabelEmitter le(this);
+// le.emitLabel(name_of_label);
+// emit(expr);
+// le.emitEnd();
+//
+class MOZ_STACK_CLASS LabelEmitter {
+ BytecodeEmitter* bce_;
+
+ mozilla::Maybe<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(TaggedParserAtomIndex name);
+ [[nodiscard]] bool emitEnd();
+};
+
+} /* namespace frontend */
+} /* namespace js */
+
+#endif /* frontend_LabelEmitter_h */
diff --git a/js/src/frontend/LexicalScopeEmitter.cpp b/js/src/frontend/LexicalScopeEmitter.cpp
new file mode 100644
index 0000000000..aeda8df9de
--- /dev/null
+++ b/js/src/frontend/LexicalScopeEmitter.cpp
@@ -0,0 +1,57 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "frontend/LexicalScopeEmitter.h"
+
+using namespace js;
+using namespace js::frontend;
+
+LexicalScopeEmitter::LexicalScopeEmitter(BytecodeEmitter* bce) : bce_(bce) {}
+
+bool LexicalScopeEmitter::emitScope(ScopeKind kind,
+ LexicalScope::ParserData* bindings) {
+ MOZ_ASSERT(state_ == State::Start);
+ MOZ_ASSERT(bindings);
+
+ tdzCache_.emplace(bce_);
+ emitterScope_.emplace(bce_);
+ if (!emitterScope_->enterLexical(bce_, kind, bindings)) {
+ return false;
+ }
+
+#ifdef DEBUG
+ state_ = State::Scope;
+#endif
+ return true;
+}
+
+bool LexicalScopeEmitter::emitEmptyScope() {
+ MOZ_ASSERT(state_ == State::Start);
+
+ tdzCache_.emplace(bce_);
+
+#ifdef DEBUG
+ state_ = State::Scope;
+#endif
+ return true;
+}
+
+bool LexicalScopeEmitter::emitEnd() {
+ MOZ_ASSERT(state_ == State::Scope);
+
+ if (emitterScope_) {
+ if (!emitterScope_->leave(bce_)) {
+ return false;
+ }
+ emitterScope_.reset();
+ }
+ tdzCache_.reset();
+
+#ifdef DEBUG
+ state_ = State::End;
+#endif
+ return true;
+}
diff --git a/js/src/frontend/LexicalScopeEmitter.h b/js/src/frontend/LexicalScopeEmitter.h
new file mode 100644
index 0000000000..379ecc0a89
--- /dev/null
+++ b/js/src/frontend/LexicalScopeEmitter.h
@@ -0,0 +1,94 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef frontend_LexicalScopeEmitter_h
+#define frontend_LexicalScopeEmitter_h
+
+#include "mozilla/Attributes.h" // MOZ_STACK_CLASS
+#include "mozilla/Maybe.h" // Maybe
+
+#include "frontend/EmitterScope.h" // EmitterScope
+#include "frontend/TDZCheckCache.h" // TDZCheckCache
+#include "vm/Scope.h" // ScopeKind, LexicalScope
+
+namespace js {
+namespace frontend {
+
+struct BytecodeEmitter;
+
+// Class for emitting bytecode for lexical scope.
+//
+// In addition to emitting code for entering and leaving a scope, this RAII
+// guard affects the code emitted for `break` and other non-structured
+// control flow. See NonLocalExitControl::prepareForNonLocalJump().
+//
+// Usage: (check for the return value is omitted for simplicity)
+//
+// `{ ... }` -- lexical scope with no bindings
+// LexicalScopeEmitter lse(this);
+// lse.emitEmptyScope();
+// emit(scopeBody);
+// lse.emitEnd();
+//
+// `{ let a; body }`
+// LexicalScopeEmitter lse(this);
+// lse.emitScope(ScopeKind::Lexical, scopeBinding);
+// emit(let_and_body);
+// lse.emitEnd();
+//
+// `catch (e) { body }`
+// LexicalScopeEmitter lse(this);
+// lse.emitScope(ScopeKind::SimpleCatch, scopeBinding);
+// emit(body);
+// lse.emitEnd();
+//
+// `catch ([a, b]) { body }`
+// LexicalScopeEmitter lse(this);
+// lse.emitScope(ScopeKind::Catch, scopeBinding);
+// emit(body);
+// lse.emitEnd();
+class MOZ_STACK_CLASS LexicalScopeEmitter {
+ BytecodeEmitter* bce_;
+
+ mozilla::Maybe<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_; }
+
+ [[nodiscard]] bool emitScope(ScopeKind kind,
+ LexicalScope::ParserData* bindings);
+ [[nodiscard]] bool emitEmptyScope();
+
+ [[nodiscard]] bool emitEnd();
+};
+
+} /* namespace frontend */
+} /* namespace js */
+
+#endif /* frontend_LexicalScopeEmitter_h */
diff --git a/js/src/frontend/ModuleSharedContext.h b/js/src/frontend/ModuleSharedContext.h
new file mode 100644
index 0000000000..fad729bd30
--- /dev/null
+++ b/js/src/frontend/ModuleSharedContext.h
@@ -0,0 +1,47 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef frontend_ModuleSharedContext_h
+#define frontend_ModuleSharedContext_h
+
+#include "mozilla/Assertions.h" // MOZ_ASSERT
+#include "mozilla/Attributes.h" // MOZ_STACK_CLASS
+
+#include "jstypes.h"
+
+#include "frontend/SharedContext.h" // js::frontend::SharedContext
+#include "vm/Scope.h" // js::ModuleScope
+
+namespace JS {
+class JS_PUBLIC_API ReadOnlyCompileOptions;
+}
+
+namespace js {
+
+class ModuleBuilder;
+struct SourceExtent;
+
+namespace frontend {
+
+class MOZ_STACK_CLASS ModuleSharedContext : public SuspendableContext {
+ public:
+ ModuleScope::ParserData* bindings;
+ ModuleBuilder& builder;
+
+ ModuleSharedContext(FrontendContext* fc,
+ const JS::ReadOnlyCompileOptions& options,
+ ModuleBuilder& builder, SourceExtent extent);
+};
+
+inline ModuleSharedContext* SharedContext::asModuleContext() {
+ MOZ_ASSERT(isModuleContext());
+ return static_cast<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..d2f44c170c
--- /dev/null
+++ b/js/src/frontend/NameAnalysisTypes.h
@@ -0,0 +1,390 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef frontend_NameAnalysisTypes_h
+#define frontend_NameAnalysisTypes_h
+
+#include "mozilla/Assertions.h" // MOZ_ASSERT, MOZ_CRASH
+#include "mozilla/Casting.h" // mozilla::AssertedCast
+
+#include <stdint.h> // uint8_t, uint16_t, uint32_t
+
+#include "vm/BindingKind.h" // BindingKind, BindingLocation
+#include "vm/BytecodeFormatFlags.h" // JOF_ENVCOORD
+#include "vm/BytecodeUtil.h" // ENVCOORD_HOPS_BITS, ENVCOORD_SLOT_BITS, GET_ENVCOORD_HOPS, GET_ENVCOORD_SLOT, ENVCOORD_HOPS_LEN, JOF_OPTYPE, JSOp, LOCALNO_LIMIT
+
+namespace js {
+
+// An "environment coordinate" describes how to get from head of the
+// environment chain to a given lexically-enclosing variable. An environment
+// coordinate has two dimensions:
+// - hops: the number of environment objects on the scope chain to skip
+// - slot: the slot on the environment object holding the variable's value
+class EnvironmentCoordinate {
+ uint32_t hops_;
+ uint32_t slot_;
+
+ // Technically, hops_/slot_ are ENVCOORD_(HOPS|SLOT)_BITS wide. Since
+ // EnvironmentCoordinate is a temporary value, don't bother with a bitfield as
+ // this only adds overhead.
+ static_assert(ENVCOORD_HOPS_BITS <= 32, "We have enough bits below");
+ static_assert(ENVCOORD_SLOT_BITS <= 32, "We have enough bits below");
+
+ public:
+ explicit inline EnvironmentCoordinate(jsbytecode* pc)
+ : hops_(GET_ENVCOORD_HOPS(pc)),
+ slot_(GET_ENVCOORD_SLOT(pc + ENVCOORD_HOPS_LEN)) {
+ MOZ_ASSERT(JOF_OPTYPE(JSOp(*pc)) == JOF_ENVCOORD ||
+ JOF_OPTYPE(JSOp(*pc)) == JOF_DEBUGCOORD);
+ }
+
+ EnvironmentCoordinate() = default;
+
+ void setHops(uint32_t hops) {
+ MOZ_ASSERT(hops < ENVCOORD_HOPS_LIMIT);
+ hops_ = hops;
+ }
+
+ void setSlot(uint32_t slot) {
+ MOZ_ASSERT(slot < ENVCOORD_SLOT_LIMIT);
+ slot_ = slot;
+ }
+
+ uint32_t hops() const {
+ MOZ_ASSERT(hops_ < ENVCOORD_HOPS_LIMIT);
+ return hops_;
+ }
+
+ uint32_t slot() const {
+ MOZ_ASSERT(slot_ < ENVCOORD_SLOT_LIMIT);
+ return slot_;
+ }
+
+ bool operator==(const EnvironmentCoordinate& rhs) const {
+ return hops() == rhs.hops() && slot() == rhs.slot();
+ }
+};
+
+namespace frontend {
+
+enum class ParseGoal : uint8_t { Script, Module };
+
+// A detailed kind used for tracking declarations in the Parser. Used for
+// specific early error semantics and better error messages.
+enum class DeclarationKind : uint8_t {
+ PositionalFormalParameter,
+ FormalParameter,
+ CoverArrowParameter,
+ Var,
+ Let,
+ Const,
+ Class, // Handled as same as `let` after parsing.
+ Import,
+ BodyLevelFunction,
+ ModuleBodyLevelFunction,
+ LexicalFunction,
+ SloppyLexicalFunction,
+ VarForAnnexBLexicalFunction,
+ SimpleCatchParameter,
+ CatchParameter,
+ PrivateName,
+ Synthetic,
+ PrivateMethod, // slot to store nonstatic private method
+};
+
+// Class field kind.
+enum class FieldPlacement : uint8_t { Unspecified, Instance, Static };
+
+static inline BindingKind DeclarationKindToBindingKind(DeclarationKind kind) {
+ switch (kind) {
+ case DeclarationKind::PositionalFormalParameter:
+ case DeclarationKind::FormalParameter:
+ case DeclarationKind::CoverArrowParameter:
+ return BindingKind::FormalParameter;
+
+ case DeclarationKind::Var:
+ case DeclarationKind::BodyLevelFunction:
+ case DeclarationKind::ModuleBodyLevelFunction:
+ case DeclarationKind::VarForAnnexBLexicalFunction:
+ return BindingKind::Var;
+
+ case DeclarationKind::Let:
+ case DeclarationKind::Class:
+ case DeclarationKind::LexicalFunction:
+ case DeclarationKind::SloppyLexicalFunction:
+ case DeclarationKind::SimpleCatchParameter:
+ case DeclarationKind::CatchParameter:
+ return BindingKind::Let;
+
+ case DeclarationKind::Const:
+ return BindingKind::Const;
+
+ case DeclarationKind::Import:
+ return BindingKind::Import;
+
+ case DeclarationKind::Synthetic:
+ case DeclarationKind::PrivateName:
+ return BindingKind::Synthetic;
+
+ case DeclarationKind::PrivateMethod:
+ return BindingKind::PrivateMethod;
+ }
+
+ MOZ_CRASH("Bad DeclarationKind");
+}
+
+static inline bool DeclarationKindIsLexical(DeclarationKind kind) {
+ return BindingKindIsLexical(DeclarationKindToBindingKind(kind));
+}
+
+// Used in Parser and BytecodeEmitter to track the kind of a private name.
+enum class PrivateNameKind : uint8_t {
+ None,
+ Field,
+ Method,
+ Getter,
+ Setter,
+ GetterSetter,
+};
+
+enum class ClosedOver : bool { No = false, Yes = true };
+
+// Used in Parser to track declared names.
+class DeclaredNameInfo {
+ uint32_t pos_;
+ DeclarationKind kind_;
+
+ // If the declared name is a binding, whether the binding is closed
+ // over. Its value is meaningless if the declared name is not a binding
+ // (i.e., a 'var' declared name in a non-var scope).
+ bool closedOver_;
+
+ PrivateNameKind privateNameKind_;
+
+ // Only updated for private names (see noteDeclaredPrivateName),
+ // tracks if declaration was instance or static to allow issuing
+ // early errors in the case where we mismatch instance and static
+ // private getter/setters.
+ FieldPlacement placement_;
+
+ public:
+ explicit DeclaredNameInfo(DeclarationKind kind, uint32_t pos,
+ ClosedOver closedOver = ClosedOver::No)
+ : pos_(pos),
+ kind_(kind),
+ closedOver_(bool(closedOver)),
+ privateNameKind_(PrivateNameKind::None),
+ placement_(FieldPlacement::Unspecified) {}
+
+ // Needed for InlineMap.
+ DeclaredNameInfo() = default;
+
+ DeclarationKind kind() const { return kind_; }
+
+ static const uint32_t npos = uint32_t(-1);
+
+ uint32_t pos() const { return pos_; }
+
+ void alterKind(DeclarationKind kind) { kind_ = kind; }
+
+ void setClosedOver() { closedOver_ = true; }
+
+ bool closedOver() const { return closedOver_; }
+
+ void setPrivateNameKind(PrivateNameKind privateNameKind) {
+ privateNameKind_ = privateNameKind;
+ }
+
+ void setFieldPlacement(FieldPlacement placement) {
+ MOZ_ASSERT(placement != FieldPlacement::Unspecified);
+ placement_ = placement;
+ }
+
+ PrivateNameKind privateNameKind() const { return privateNameKind_; }
+
+ FieldPlacement placement() const { return placement_; }
+};
+
+// Used in BytecodeEmitter to map names to locations.
+class NameLocation {
+ public:
+ enum class Kind : uint8_t {
+ // Cannot statically determine where the name lives. Needs to walk the
+ // environment chain to search for the name.
+ Dynamic,
+
+ // The name lives on the global or is a global lexical binding. Search
+ // for the name on the global scope.
+ Global,
+
+ // Special mode used only when emitting self-hosted scripts. See
+ // BytecodeEmitter::lookupName.
+ Intrinsic,
+
+ // In a named lambda, the name is the callee itself.
+ NamedLambdaCallee,
+
+ // The name is a positional formal parameter name and can be retrieved
+ // directly from the stack using slot_.
+ ArgumentSlot,
+
+ // The name is not closed over and lives on the frame in slot_.
+ FrameSlot,
+
+ // The name is closed over and lives on an environment hops_ away in slot_.
+ EnvironmentCoordinate,
+
+ // The name is closed over and lives on an environment hops_ away in slot_,
+ // where one or more of the environments may be a DebugEnvironmentProxy
+ DebugEnvironmentCoordinate,
+
+ // An imported name in a module.
+ Import,
+
+ // Cannot statically determine where the synthesized var for Annex
+ // B.3.3 lives.
+ DynamicAnnexBVar
+ };
+
+ private:
+ // Where the name lives.
+ Kind kind_;
+
+ // If the name is not Dynamic or DynamicAnnexBVar, the kind of the
+ // binding.
+ BindingKind bindingKind_;
+
+ // If the name is closed over and accessed via EnvironmentCoordinate, the
+ // number of dynamic environments to skip.
+ //
+ // Otherwise UINT8_MAX.
+ uint8_t hops_;
+
+ // If the name lives on the frame, the slot frame.
+ //
+ // If the name is closed over and accessed via EnvironmentCoordinate, the
+ // slot on the environment.
+ //
+ // Otherwise 0.
+ uint32_t slot_ : ENVCOORD_SLOT_BITS;
+
+ static_assert(LOCALNO_BITS == ENVCOORD_SLOT_BITS,
+ "Frame and environment slots must be same sized.");
+
+ NameLocation(Kind kind, BindingKind bindingKind, uint8_t hops = UINT8_MAX,
+ uint32_t slot = 0)
+ : kind_(kind), bindingKind_(bindingKind), hops_(hops), slot_(slot) {}
+
+ public:
+ // Default constructor for InlineMap.
+ NameLocation() = default;
+
+ static NameLocation Dynamic() {
+ return NameLocation(Kind::Dynamic, BindingKind::Import);
+ }
+
+ static NameLocation Global(BindingKind bindKind) {
+ MOZ_ASSERT(bindKind != BindingKind::FormalParameter);
+ return NameLocation(Kind::Global, bindKind);
+ }
+
+ static NameLocation Intrinsic() {
+ return NameLocation(Kind::Intrinsic, BindingKind::Var);
+ }
+
+ static NameLocation NamedLambdaCallee() {
+ return NameLocation(Kind::NamedLambdaCallee,
+ BindingKind::NamedLambdaCallee);
+ }
+
+ static NameLocation ArgumentSlot(uint16_t slot) {
+ return NameLocation(Kind::ArgumentSlot, BindingKind::FormalParameter, 0,
+ slot);
+ }
+
+ static NameLocation FrameSlot(BindingKind bindKind, uint32_t slot) {
+ MOZ_ASSERT(slot < LOCALNO_LIMIT);
+ return NameLocation(Kind::FrameSlot, bindKind, 0, slot);
+ }
+
+ static NameLocation EnvironmentCoordinate(BindingKind bindKind, uint8_t hops,
+ uint32_t slot) {
+ MOZ_ASSERT(slot < ENVCOORD_SLOT_LIMIT);
+ return NameLocation(Kind::EnvironmentCoordinate, bindKind, hops, slot);
+ }
+ static NameLocation DebugEnvironmentCoordinate(BindingKind bindKind,
+ uint8_t hops, uint32_t slot) {
+ MOZ_ASSERT(slot < ENVCOORD_SLOT_LIMIT);
+ return NameLocation(Kind::DebugEnvironmentCoordinate, bindKind, hops, slot);
+ }
+
+ static NameLocation Import() {
+ return NameLocation(Kind::Import, BindingKind::Import);
+ }
+
+ static NameLocation DynamicAnnexBVar() {
+ return NameLocation(Kind::DynamicAnnexBVar, BindingKind::Var);
+ }
+
+ bool operator==(const NameLocation& other) const {
+ return kind_ == other.kind_ && bindingKind_ == other.bindingKind_ &&
+ hops_ == other.hops_ && slot_ == other.slot_;
+ }
+
+ bool operator!=(const NameLocation& other) const { return !(*this == other); }
+
+ Kind kind() const { return kind_; }
+
+ uint16_t argumentSlot() const {
+ MOZ_ASSERT(kind_ == Kind::ArgumentSlot);
+ return mozilla::AssertedCast<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 ||
+ kind_ == Kind::DebugEnvironmentCoordinate);
+ class EnvironmentCoordinate coord;
+ coord.setHops(hops_);
+ coord.setSlot(slot_);
+ return coord;
+ }
+
+ BindingKind bindingKind() const {
+ MOZ_ASSERT(kind_ != Kind::Dynamic);
+ return bindingKind_;
+ }
+
+ bool isLexical() const { return BindingKindIsLexical(bindingKind()); }
+
+ bool isConst() const { return bindingKind() == BindingKind::Const; }
+
+ bool isSynthetic() const { return bindingKind() == BindingKind::Synthetic; }
+
+ bool isPrivateMethod() const {
+ return bindingKind() == BindingKind::PrivateMethod;
+ }
+
+ bool hasKnownSlot() const {
+ return kind_ == Kind::ArgumentSlot || kind_ == Kind::FrameSlot ||
+ kind_ == Kind::EnvironmentCoordinate;
+ }
+};
+
+} // namespace frontend
+} // namespace js
+
+#endif // frontend_NameAnalysisTypes_h
diff --git a/js/src/frontend/NameCollections.h b/js/src/frontend/NameCollections.h
new file mode 100644
index 0000000000..57806a6984
--- /dev/null
+++ b/js/src/frontend/NameCollections.h
@@ -0,0 +1,455 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef frontend_NameCollections_h
+#define frontend_NameCollections_h
+
+#include "mozilla/Assertions.h" // MOZ_ASSERT
+#include "mozilla/Attributes.h" // MOZ_IMPLICIT
+
+#include <stddef.h> // size_t
+#include <stdint.h> // uint32_t, uint64_t
+#include <type_traits> // std::{true_type, false_type, is_trivial_v, is_trivially_copyable_v, is_trivially_destructible_v}
+#include <utility> // std::forward
+
+#include "ds/InlineTable.h" // InlineMap, DefaultKeyPolicy
+#include "frontend/NameAnalysisTypes.h" // AtomVector, FunctionBoxVector
+#include "frontend/ParserAtom.h" // TaggedParserAtomIndex, TrivialTaggedParserAtomIndex
+#include "frontend/TaggedParserAtomIndexHasher.h" // TrivialTaggedParserAtomIndexHasher
+#include "js/AllocPolicy.h" // SystemAllocPolicy, ReportOutOfMemory
+#include "js/Utility.h" // js_new, js_delete
+#include "js/Vector.h" // Vector
+
+namespace js {
+
+namespace detail {
+
+// For InlineMap<TrivialTaggedParserAtomIndex>.
+// See DefaultKeyPolicy definition in InlineTable.h for more details.
+template <>
+class DefaultKeyPolicy<frontend::TrivialTaggedParserAtomIndex> {
+ public:
+ DefaultKeyPolicy() = delete;
+ DefaultKeyPolicy(const frontend::TrivialTaggedParserAtomIndex&) = delete;
+
+ static bool isTombstone(const frontend::TrivialTaggedParserAtomIndex& atom) {
+ return atom.isNull();
+ }
+ static void setToTombstone(frontend::TrivialTaggedParserAtomIndex& atom) {
+ atom = frontend::TrivialTaggedParserAtomIndex::null();
+ }
+};
+
+} // namespace detail
+
+namespace frontend {
+
+class FunctionBox;
+
+// A pool of recyclable containers for use in the frontend. The Parser and
+// BytecodeEmitter create many maps for name analysis that are short-lived
+// (i.e., for the duration of parsing or emitting a lexical scope). Making
+// them recyclable cuts down significantly on allocator churn.
+template <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(FrontendContext* fc) {
+ ConcreteCollectionPool::template assertInvariants<Collection>();
+
+ RepresentativeCollection* collection;
+ if (recyclable_.empty()) {
+ collection = allocate();
+ if (!collection) {
+ ReportOutOfMemory(fc);
+ }
+ } 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; }
+};
+
+template <typename MapValue>
+using RecyclableNameMapBase =
+ InlineMap<TrivialTaggedParserAtomIndex,
+ RecyclableAtomMapValueWrapper<MapValue>, 24,
+ TrivialTaggedParserAtomIndexHasher, SystemAllocPolicy>;
+
+// Define wrapper methods to accept TaggedParserAtomIndex.
+template <typename MapValue>
+class RecyclableNameMap : public RecyclableNameMapBase<MapValue> {
+ using Base = RecyclableNameMapBase<MapValue>;
+
+ public:
+ template <typename... Args>
+ [[nodiscard]] MOZ_ALWAYS_INLINE bool add(typename Base::AddPtr& p,
+ const TaggedParserAtomIndex& key,
+ Args&&... args) {
+ return Base::add(p, TrivialTaggedParserAtomIndex::from(key),
+ std::forward<Args>(args)...);
+ }
+
+ MOZ_ALWAYS_INLINE
+ typename Base::Ptr lookup(const TaggedParserAtomIndex& l) {
+ return Base::lookup(TrivialTaggedParserAtomIndex::from(l));
+ }
+
+ MOZ_ALWAYS_INLINE
+ typename Base::AddPtr lookupForAdd(const TaggedParserAtomIndex& l) {
+ return Base::lookupForAdd(TrivialTaggedParserAtomIndex::from(l));
+ }
+};
+
+using DeclaredNameMap = RecyclableNameMap<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.");
+ }
+};
+
+using AtomVector = Vector<TrivialTaggedParserAtomIndex, 24, SystemAllocPolicy>;
+
+using FunctionBoxVector = Vector<FunctionBox*, 24, SystemAllocPolicy>;
+
+class NameCollectionPool {
+ InlineTablePool<AtomIndexMap> mapPool_;
+ VectorPool<AtomVector> atomVectorPool_;
+ VectorPool<FunctionBoxVector> functionBoxVectorPool_;
+ 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(FrontendContext* fc) {
+ MOZ_ASSERT(hasActiveCompilation());
+ return mapPool_.acquire<Map>(fc);
+ }
+
+ template <typename Map>
+ void releaseMap(Map** map) {
+ MOZ_ASSERT(hasActiveCompilation());
+ MOZ_ASSERT(map);
+ if (*map) {
+ mapPool_.release(map);
+ }
+ }
+
+ template <typename Vector>
+ inline Vector* acquireVector(FrontendContext* fc);
+
+ template <typename Vector>
+ inline void releaseVector(Vector** vec);
+
+ void purge() {
+ if (!hasActiveCompilation()) {
+ mapPool_.purgeAll();
+ atomVectorPool_.purgeAll();
+ functionBoxVectorPool_.purgeAll();
+ }
+ }
+};
+
+template <>
+inline AtomVector* NameCollectionPool::acquireVector<AtomVector>(
+ FrontendContext* fc) {
+ MOZ_ASSERT(hasActiveCompilation());
+ return atomVectorPool_.acquire<AtomVector>(fc);
+}
+
+template <>
+inline void NameCollectionPool::releaseVector<AtomVector>(AtomVector** vec) {
+ MOZ_ASSERT(hasActiveCompilation());
+ MOZ_ASSERT(vec);
+ if (*vec) {
+ atomVectorPool_.release(vec);
+ }
+}
+
+template <>
+inline FunctionBoxVector* NameCollectionPool::acquireVector<FunctionBoxVector>(
+ FrontendContext* fc) {
+ MOZ_ASSERT(hasActiveCompilation());
+ return functionBoxVectorPool_.acquire<FunctionBoxVector>(fc);
+}
+
+template <>
+inline void NameCollectionPool::releaseVector<FunctionBoxVector>(
+ FunctionBoxVector** vec) {
+ MOZ_ASSERT(hasActiveCompilation());
+ MOZ_ASSERT(vec);
+ if (*vec) {
+ functionBoxVectorPool_.release(vec);
+ }
+}
+
+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(FrontendContext* fc) {
+ MOZ_ASSERT(!collection_);
+ collection_ = Impl<T>::acquireCollection(fc, pool_);
+ return !!collection_;
+ }
+
+ explicit operator bool() const { return !!collection_; }
+
+ T* operator->() { return &collection(); }
+
+ const T* operator->() const { return &collection(); }
+
+ T& operator*() { return collection(); }
+
+ const T& operator*() const { return collection(); }
+};
+
+template <typename Map>
+class PooledMapPtr : public PooledCollectionPtr<Map, PooledMapPtr> {
+ friend class PooledCollectionPtr<Map, PooledMapPtr>;
+
+ static Map* acquireCollection(FrontendContext* fc, NameCollectionPool& pool) {
+ return pool.acquireMap<Map>(fc);
+ }
+
+ 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(FrontendContext* fc,
+ NameCollectionPool& pool) {
+ return pool.acquireVector<Vector>(fc);
+ }
+
+ 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..0ad8e55758
--- /dev/null
+++ b/js/src/frontend/NameFunctions.cpp
@@ -0,0 +1,531 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "frontend/NameFunctions.h"
+
+#include "mozilla/ScopeExit.h"
+#include "mozilla/Sprintf.h"
+
+#include "frontend/ParseNode.h"
+#include "frontend/ParseNodeVisitor.h"
+#include "frontend/ParserAtom.h" // ParserAtomsTable
+#include "frontend/SharedContext.h"
+#include "util/Poison.h"
+#include "util/StringBuffer.h"
+
+using namespace js;
+using namespace js::frontend;
+
+namespace {
+
+class NameResolver : public ParseNodeVisitor<NameResolver> {
+ using Base = ParseNodeVisitor;
+
+ static const size_t MaxParents = 100;
+
+ FrontendContext* fc_;
+ ParserAtomsTable& parserAtoms_;
+ TaggedParserAtomIndex prefix_;
+
+ // Number of nodes in the parents array.
+ size_t nparents_;
+
+ // Stack of ParseNodes from the root to the current node.
+ // Only elements 0..nparents_ are initialized.
+ MOZ_INIT_OUTSIDE_CTOR
+ ParseNode* parents_[MaxParents];
+
+ // When naming a function, the buffer where the name is built.
+ // When we are not under resolveFun, buf_ is empty.
+ StringBuffer buf_;
+
+ /* Test whether a ParseNode represents a function invocation */
+ bool isCall(ParseNode* pn) {
+ return pn && pn->isKind(ParseNodeKind::CallExpr);
+ }
+
+ /*
+ * Append a reference to a property named |name| to |buf_|. If |name| is
+ * a proper identifier name, then we append '.name'; otherwise, we
+ * append '["name"]'.
+ *
+ * Note that we need the IsIdentifier check for atoms from both
+ * ParseNodeKind::Name nodes and ParseNodeKind::String nodes:
+ * given code like a["b c"], the front end will produce a ParseNodeKind::Dot
+ * with a ParseNodeKind::Name child whose name contains spaces.
+ */
+ bool appendPropertyReference(TaggedParserAtomIndex name) {
+ if (parserAtoms_.isIdentifier(name)) {
+ return buf_.append('.') && buf_.append(parserAtoms_, name);
+ }
+
+ /* Quote the string as needed. */
+ UniqueChars source = parserAtoms_.toQuotedString(name);
+ if (!source) {
+ ReportOutOfMemory(fc_);
+ return false;
+ }
+ return buf_.append('[') &&
+ buf_.append(source.get(), strlen(source.get())) && buf_.append(']');
+ }
+
+ /* Append a number to buf_. */
+ bool appendNumber(double n) {
+ char number[30];
+ int digits = SprintfLiteral(number, "%g", n);
+ return buf_.append(number, digits);
+ }
+
+ // Append "[<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 represents where the function is being assigned to.
+ *
+ * |*foundName| is set to true if a name is found for the expression.
+ */
+ bool nameExpression(ParseNode* n, bool* foundName) {
+ switch (n->getKind()) {
+ case ParseNodeKind::DotExpr: {
+ PropertyAccess* prop = &n->as<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(parserAtoms_, 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.
+ */
+ [[nodiscard]] bool resolveFun(FunctionNode* funNode,
+ TaggedParserAtomIndex* retId) {
+ MOZ_ASSERT(funNode != nullptr);
+
+ FunctionBox* funbox = funNode->funbox();
+
+ MOZ_ASSERT(buf_.empty());
+ auto resetBuf = mozilla::MakeScopeExit([&] { buf_.clear(); });
+
+ *retId = TaggedParserAtomIndex::null();
+
+ // If the function already has a name, use that.
+ if (funbox->displayAtom()) {
+ if (!prefix_) {
+ *retId = funbox->displayAtom();
+ return true;
+ }
+ if (!buf_.append(parserAtoms_, prefix_) || !buf_.append('/') ||
+ !buf_.append(parserAtoms_, funbox->displayAtom())) {
+ return false;
+ }
+ *retId = buf_.finishParserAtom(parserAtoms_, fc_);
+ return !!*retId;
+ }
+
+ // If a prefix is specified, then it is a form of namespace.
+ if (prefix_) {
+ if (!buf_.append(parserAtoms_, prefix_) || !buf_.append('/')) {
+ return false;
+ }
+ }
+
+ // Gather all nodes relevant to naming.
+ ParseNode* toName[MaxParents];
+ size_t size;
+ ParseNode* assignment = gatherNameable(toName, &size);
+
+ // If the function is assigned to something, then that is very relevant.
+ if (assignment) {
+ // e.g, foo = function() {}
+ if (assignment->is<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)) {
+ // Here we handle two cases:
+ // 1) ObjectPropertyName category, e.g `foo: function() {}`
+ // 2) StringExpr category, e.g `"foo": function() {}`
+ if (!appendPropertyReference(left->as<NameNode>().atom())) {
+ return false;
+ }
+ } else if (left->isKind(ParseNodeKind::NumberExpr)) {
+ // This case handles Number expression Anonymous Functions
+ // for example: `{ 10: function() {} }`.
+ if (!appendNumericPropertyReference(
+ left->as<NumericLiteral>().value())) {
+ return false;
+ }
+ } else if (left->isKind(ParseNodeKind::ComputedName) &&
+ (left->as<UnaryNode>().kid()->isKind(
+ ParseNodeKind::StringExpr) ||
+ left->as<UnaryNode>().kid()->isKind(
+ ParseNodeKind::NumberExpr)) &&
+ node->as<PropertyDefinition>().accessorType() ==
+ AccessorType::None) {
+ // In this branch we handle computed property with string
+ // or numeric literal:
+ // e.g, `{ ["foo"]: function(){} }`, and `{ [10]: function() {} }`.
+ //
+ // Note we only handle the names that are known at compile time,
+ // so if we have `var x = "foo"; ({ [x]: function(){} })`, we don't
+ // handle that here, it's handled at runtime by JSOp::SetFunName.
+ // The accessor type of the property must be AccessorType::None,
+ // given getters and setters need prefix and we cannot handle it here.
+ ParseNode* kid = left->as<UnaryNode>().kid();
+ if (kid->isKind(ParseNodeKind::StringExpr)) {
+ if (!appendPropertyReference(kid->as<NameNode>().atom())) {
+ return false;
+ }
+ } else {
+ MOZ_ASSERT(kid->isKind(ParseNodeKind::NumberExpr));
+ if (!appendNumericPropertyReference(
+ kid->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_, fc_);
+ if (!*retId) {
+ return false;
+ }
+
+ // Skip assigning the guessed name if the function has a (dynamically)
+ // computed inferred name.
+ if (!funNode->isDirectRHSAnonFunction()) {
+ funbox->setGuessedAtom(*retId);
+ }
+ return true;
+ }
+
+ /*
+ * Tests whether parents_[pos] is a function call whose callee is cur.
+ * This is the case for functions which do things like simply create a scope
+ * for new variables and then return an anonymous function using this scope.
+ */
+ bool isDirectCall(int pos, ParseNode* cur) {
+ return pos >= 0 && isCall(parents_[pos]) &&
+ parents_[pos]->as<BinaryNode>().left() == cur;
+ }
+
+ public:
+ [[nodiscard]] bool visitFunction(FunctionNode* pn) {
+ TaggedParserAtomIndex savedPrefix = prefix_;
+ TaggedParserAtomIndex newPrefix;
+ if (!resolveFun(pn, &newPrefix)) {
+ return false;
+ }
+
+ // If a function looks like (function(){})() where the parent node
+ // of the definition of the function is a call, then it shouldn't
+ // contribute anything to the namespace, so don't bother updating
+ // the prefix to whatever was returned.
+ if (!isDirectCall(nparents_ - 2, pn)) {
+ prefix_ = newPrefix;
+ }
+
+ bool ok = Base::visitFunction(pn);
+
+ prefix_ = savedPrefix;
+ return ok;
+ }
+
+ // Skip this type of node. It never contains functions.
+ [[nodiscard]] bool visitCallSiteObj(CallSiteNode* callSite) {
+ // This node only contains internal strings or undefined and an array -- no
+ // user-controlled expressions.
+ return true;
+ }
+
+ // Skip walking the list of string parts, which never contains functions.
+ [[nodiscard]] bool visitTaggedTemplateExpr(BinaryNode* taggedTemplate) {
+ ParseNode* tag = taggedTemplate->left();
+
+ // The leading expression, e.g. |tag| in |tag`foo`|,
+ // that might contain functions.
+ if (!visit(tag)) {
+ return false;
+ }
+
+ // The callsite object node is first. This node only contains
+ // internal strings or undefined and an array -- no user-controlled
+ // expressions.
+ CallSiteNode* element =
+ &taggedTemplate->right()->as<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.
+ [[nodiscard]] bool internalVisitSpecList(ListNode* pn) {
+ // Import/export spec lists contain import/export specs containing only
+ // pairs of names or strings. Alternatively, an export spec list may
+ // contain a single export batch specifier.
+#ifdef DEBUG
+ bool isImport = pn->isKind(ParseNodeKind::ImportSpecList);
+ ParseNode* item = pn->head();
+ if (!isImport && item && item->isKind(ParseNodeKind::ExportBatchSpecStmt)) {
+ MOZ_ASSERT(item->is<NullaryNode>());
+ } else {
+ for (ParseNode* item : pn->contents()) {
+ if (item->is<UnaryNode>()) {
+ auto* spec = &item->as<UnaryNode>();
+ MOZ_ASSERT(spec->isKind(isImport
+ ? ParseNodeKind::ImportNamespaceSpec
+ : ParseNodeKind::ExportNamespaceSpec));
+ MOZ_ASSERT(spec->kid()->isKind(ParseNodeKind::Name) ||
+ spec->kid()->isKind(ParseNodeKind::StringExpr));
+ } else {
+ auto* spec = &item->as<BinaryNode>();
+ MOZ_ASSERT(spec->isKind(isImport ? ParseNodeKind::ImportSpec
+ : ParseNodeKind::ExportSpec));
+ MOZ_ASSERT(spec->left()->isKind(ParseNodeKind::Name) ||
+ spec->left()->isKind(ParseNodeKind::StringExpr));
+ MOZ_ASSERT(spec->right()->isKind(ParseNodeKind::Name) ||
+ spec->right()->isKind(ParseNodeKind::StringExpr));
+ }
+ }
+ }
+#endif
+ return true;
+ }
+
+ public:
+ [[nodiscard]] bool visitImportSpecList(ListNode* pn) {
+ return internalVisitSpecList(pn);
+ }
+
+ [[nodiscard]] bool visitExportSpecList(ListNode* pn) {
+ return internalVisitSpecList(pn);
+ }
+
+ NameResolver(FrontendContext* fc, ParserAtomsTable& parserAtoms)
+ : ParseNodeVisitor(fc),
+ fc_(fc),
+ parserAtoms_(parserAtoms),
+ nparents_(0),
+ buf_(fc) {}
+
+ /*
+ * Resolve names for all anonymous functions in the given ParseNode tree.
+ */
+ [[nodiscard]] bool visit(ParseNode* pn) {
+ // Push pn to the parse node stack.
+ if (nparents_ >= MaxParents) {
+ // Silently skip very deeply nested functions.
+ return true;
+ }
+ auto initialParents = nparents_;
+ parents_[initialParents] = pn;
+ nparents_++;
+
+ bool ok = Base::visit(pn);
+
+ // Pop pn from the parse node stack.
+ nparents_--;
+ MOZ_ASSERT(initialParents == nparents_, "nparents imbalance detected");
+ MOZ_ASSERT(parents_[initialParents] == pn,
+ "pushed child shouldn't change underneath us");
+ AlwaysPoison(&parents_[initialParents], JS_OOB_PARSE_NODE_PATTERN,
+ sizeof(parents_[initialParents]), MemCheckKind::MakeUndefined);
+
+ return ok;
+ }
+};
+
+} /* anonymous namespace */
+
+bool frontend::NameFunctions(FrontendContext* fc, ParserAtomsTable& parserAtoms,
+ ParseNode* pn) {
+ NameResolver nr(fc, parserAtoms);
+ return nr.visit(pn);
+}
diff --git a/js/src/frontend/NameFunctions.h b/js/src/frontend/NameFunctions.h
new file mode 100644
index 0000000000..aec65bdc5e
--- /dev/null
+++ b/js/src/frontend/NameFunctions.h
@@ -0,0 +1,27 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef frontend_NameFunctions_h
+#define frontend_NameFunctions_h
+
+#include "js/TypeDecls.h"
+
+namespace js {
+
+class FrontendContext;
+
+namespace frontend {
+
+class ParseNode;
+class ParserAtomsTable;
+
+[[nodiscard]] bool NameFunctions(FrontendContext* fc,
+ ParserAtomsTable& parserAtoms, ParseNode* pn);
+
+} /* namespace frontend */
+} /* namespace js */
+
+#endif /* frontend_NameFunctions_h */
diff --git a/js/src/frontend/NameOpEmitter.cpp b/js/src/frontend/NameOpEmitter.cpp
new file mode 100644
index 0000000000..f36c7a2028
--- /dev/null
+++ b/js/src/frontend/NameOpEmitter.cpp
@@ -0,0 +1,481 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "frontend/NameOpEmitter.h"
+
+#include "frontend/AbstractScopePtr.h"
+#include "frontend/BytecodeEmitter.h"
+#include "frontend/ParserAtom.h" // ParserAtom
+#include "frontend/SharedContext.h"
+#include "frontend/TDZCheckCache.h"
+#include "frontend/ValueUsage.h"
+#include "js/Value.h"
+#include "vm/Opcodes.h"
+
+using namespace js;
+using namespace js::frontend;
+
+NameOpEmitter::NameOpEmitter(BytecodeEmitter* bce, TaggedParserAtomIndex name,
+ Kind kind)
+ : bce_(bce), kind_(kind), name_(name), loc_(bce_->lookupName(name_)) {}
+
+NameOpEmitter::NameOpEmitter(BytecodeEmitter* bce, TaggedParserAtomIndex name,
+ const NameLocation& loc, Kind kind)
+ : bce_(bce), kind_(kind), name_(name), loc_(loc) {}
+
+bool NameOpEmitter::emitGet() {
+ MOZ_ASSERT(state_ == State::Start);
+
+ switch (loc_.kind()) {
+ case NameLocation::Kind::Dynamic:
+ if (!bce_->emitAtomOp(JSOp::GetName, name_)) {
+ // [stack] VAL
+ return false;
+ }
+ break;
+ case NameLocation::Kind::Global: {
+ MOZ_ASSERT(bce_->outermostScope().hasNonSyntacticScopeOnChain() ==
+ bce_->sc->hasNonSyntacticScope());
+ if (bce_->sc->hasNonSyntacticScope()) {
+ if (!bce_->emitAtomOp(JSOp::GetName, name_)) {
+ // [stack] VAL
+ return false;
+ }
+ } else {
+ // Some names on the global are not configurable and have fixed values
+ // which we can emit instead.
+ if (name_ == TaggedParserAtomIndex::WellKnown::undefined()) {
+ if (!bce_->emit1(JSOp::Undefined)) {
+ return false;
+ }
+ } else if (name_ == TaggedParserAtomIndex::WellKnown::NaN()) {
+ if (!bce_->emitDouble(JS::GenericNaN())) {
+ return false;
+ }
+ } else if (name_ == TaggedParserAtomIndex::WellKnown::Infinity()) {
+ if (!bce_->emitDouble(JS::Infinity())) {
+ return false;
+ }
+ } else {
+ if (!bce_->emitAtomOp(JSOp::GetGName, name_)) {
+ // [stack] VAL
+ return false;
+ }
+ }
+ }
+ break;
+ }
+ case NameLocation::Kind::Intrinsic:
+ if (name_ == TaggedParserAtomIndex::WellKnown::undefined()) {
+ if (!bce_->emit1(JSOp::Undefined)) {
+ // [stack] Undefined
+ return false;
+ }
+ } else {
+ if (!bce_->emitAtomOp(JSOp::GetIntrinsic, name_)) {
+ // [stack] VAL
+ return false;
+ }
+ }
+ break;
+ case NameLocation::Kind::NamedLambdaCallee:
+ if (!bce_->emit1(JSOp::Callee)) {
+ // [stack] VAL
+ return false;
+ }
+ break;
+ case NameLocation::Kind::Import:
+ if (!bce_->emitAtomOp(JSOp::GetImport, name_)) {
+ // [stack] VAL
+ return false;
+ }
+ break;
+ case NameLocation::Kind::ArgumentSlot:
+ if (!bce_->emitArgOp(JSOp::GetArg, loc_.argumentSlot())) {
+ // [stack] VAL
+ return false;
+ }
+ break;
+ case NameLocation::Kind::FrameSlot:
+ if (!bce_->emitLocalOp(JSOp::GetLocal, loc_.frameSlot())) {
+ // [stack] VAL
+ return false;
+ }
+ if (loc_.isLexical()) {
+ if (!bce_->emitTDZCheckIfNeeded(name_, loc_, ValueIsOnStack::Yes)) {
+ // [stack] VAL
+ return false;
+ }
+ }
+ break;
+ case NameLocation::Kind::EnvironmentCoordinate:
+ case NameLocation::Kind::DebugEnvironmentCoordinate:
+ if (!bce_->emitEnvCoordOp(
+ loc_.kind() == NameLocation::Kind::EnvironmentCoordinate
+ ? JSOp::GetAliasedVar
+ : JSOp::GetAliasedDebugVar,
+ loc_.environmentCoordinate())) {
+ // [stack] VAL
+ return false;
+ }
+ if (loc_.isLexical()) {
+ if (!bce_->emitTDZCheckIfNeeded(name_, loc_, ValueIsOnStack::Yes)) {
+ // [stack] VAL
+ return false;
+ }
+ }
+ break;
+ case NameLocation::Kind::DynamicAnnexBVar:
+ MOZ_CRASH(
+ "Synthesized vars for Annex B.3.3 should only be used in "
+ "initialization");
+ }
+
+ if (isCall()) {
+ MOZ_ASSERT(bce_->outermostScope().hasNonSyntacticScopeOnChain() ==
+ bce_->sc->hasNonSyntacticScope());
+ switch (loc_.kind()) {
+ case NameLocation::Kind::Dynamic:
+ case NameLocation::Kind::Global:
+ MOZ_ASSERT(bce_->emitterMode != BytecodeEmitter::SelfHosting);
+ if (bce_->needsImplicitThis() || bce_->sc->hasNonSyntacticScope()) {
+ MOZ_ASSERT_IF(bce_->needsImplicitThis(),
+ loc_.kind() == NameLocation::Kind::Dynamic);
+ if (!bce_->emitAtomOp(JSOp::ImplicitThis, name_)) {
+ // [stack] CALLEE THIS
+ return false;
+ }
+ } else {
+ if (!bce_->emit1(JSOp::Undefined)) {
+ // [stack] CALLEE UNDEF
+ return false;
+ }
+ }
+ break;
+ case NameLocation::Kind::Intrinsic:
+ case NameLocation::Kind::NamedLambdaCallee:
+ case NameLocation::Kind::Import:
+ case NameLocation::Kind::ArgumentSlot:
+ case NameLocation::Kind::FrameSlot:
+ case NameLocation::Kind::EnvironmentCoordinate:
+ if (bce_->emitterMode == BytecodeEmitter::SelfHosting) {
+ if (!bce_->emitDebugCheckSelfHosted()) {
+ // [stack] CALLEE
+ return false;
+ }
+ }
+ if (!bce_->emit1(JSOp::Undefined)) {
+ // [stack] CALLEE UNDEF
+ return false;
+ }
+ break;
+ case NameLocation::Kind::DebugEnvironmentCoordinate:
+ MOZ_CRASH(
+ "DebugEnvironmentCoordinate should only be used to get the private "
+ "brand, and so should never call.");
+ break;
+ case NameLocation::Kind::DynamicAnnexBVar:
+ MOZ_CRASH(
+ "Synthesized vars for Annex B.3.3 should only be used in "
+ "initialization");
+ }
+ }
+
+#ifdef DEBUG
+ state_ = State::Get;
+#endif
+ return true;
+}
+
+bool NameOpEmitter::prepareForRhs() {
+ MOZ_ASSERT(state_ == State::Start);
+
+ switch (loc_.kind()) {
+ case NameLocation::Kind::Dynamic:
+ case NameLocation::Kind::Import:
+ case NameLocation::Kind::DynamicAnnexBVar:
+ if (!bce_->makeAtomIndex(name_, ParserAtom::Atomize::Yes, &atomIndex_)) {
+ return false;
+ }
+ if (loc_.kind() == NameLocation::Kind::DynamicAnnexBVar) {
+ // Annex B vars always go on the nearest variable environment,
+ // even if lexical environments in between contain same-named
+ // bindings.
+ if (!bce_->emit1(JSOp::BindVar)) {
+ // [stack] ENV
+ return false;
+ }
+ } else {
+ if (!bce_->emitAtomOp(JSOp::BindName, atomIndex_)) {
+ // [stack] ENV
+ return false;
+ }
+ }
+ emittedBindOp_ = true;
+ break;
+ case NameLocation::Kind::Global:
+ if (!bce_->makeAtomIndex(name_, ParserAtom::Atomize::Yes, &atomIndex_)) {
+ return false;
+ }
+ MOZ_ASSERT(bce_->outermostScope().hasNonSyntacticScopeOnChain() ==
+ bce_->sc->hasNonSyntacticScope());
+
+ if (loc_.isLexical() && isInitialize()) {
+ // InitGLexical always gets the global lexical scope. It doesn't
+ // need a BindName/BindGName.
+ MOZ_ASSERT(bce_->innermostScope().is<GlobalScope>());
+ } else if (bce_->sc->hasNonSyntacticScope()) {
+ if (!bce_->emitAtomOp(JSOp::BindName, atomIndex_)) {
+ // [stack] ENV
+ return false;
+ }
+ emittedBindOp_ = true;
+ } else {
+ if (!bce_->emitAtomOp(JSOp::BindGName, atomIndex_)) {
+ // [stack] ENV
+ return false;
+ }
+ emittedBindOp_ = true;
+ }
+ break;
+ case NameLocation::Kind::Intrinsic:
+ break;
+ case NameLocation::Kind::NamedLambdaCallee:
+ break;
+ case NameLocation::Kind::ArgumentSlot:
+ break;
+ case NameLocation::Kind::FrameSlot:
+ break;
+ case NameLocation::Kind::DebugEnvironmentCoordinate:
+ break;
+ case NameLocation::Kind::EnvironmentCoordinate:
+ break;
+ }
+
+ // For compound assignments, first get the LHS value, then emit
+ // the RHS and the op.
+ if (isCompoundAssignment() || isIncDec()) {
+ if (loc_.kind() == NameLocation::Kind::Dynamic) {
+ // For dynamic accesses we need to emit GetBoundName instead of
+ // GetName for correctness: looking up @@unscopables on the
+ // environment chain (due to 'with' environments) must only happen
+ // once.
+ //
+ // GetBoundName uses the environment already pushed on the stack
+ // from the earlier BindName.
+ if (!bce_->emit1(JSOp::Dup)) {
+ // [stack] ENV ENV
+ return false;
+ }
+ if (!bce_->emitAtomOp(JSOp::GetBoundName, name_)) {
+ // [stack] ENV V
+ return false;
+ }
+ } else {
+ if (!emitGet()) {
+ // [stack] ENV? V
+ return false;
+ }
+ }
+ }
+
+#ifdef DEBUG
+ state_ = State::Rhs;
+#endif
+ return true;
+}
+
+#if defined(__clang__) && defined(XP_WIN) && \
+ (defined(_M_X64) || defined(__x86_64__))
+// Work around a CPU bug. See bug 1524257.
+__attribute__((__aligned__(32)))
+#endif
+bool NameOpEmitter::emitAssignment() {
+ MOZ_ASSERT(state_ == State::Rhs);
+
+ switch (loc_.kind()) {
+ case NameLocation::Kind::Dynamic:
+ case NameLocation::Kind::Import:
+ case NameLocation::Kind::DynamicAnnexBVar:
+ if (!bce_->emitAtomOp(bce_->strictifySetNameOp(JSOp::SetName),
+ atomIndex_)) {
+ return false;
+ }
+ break;
+ case NameLocation::Kind::Global: {
+ JSOp op;
+ if (emittedBindOp_) {
+ MOZ_ASSERT(bce_->outermostScope().hasNonSyntacticScopeOnChain() ==
+ bce_->sc->hasNonSyntacticScope());
+ if (bce_->sc->hasNonSyntacticScope()) {
+ op = bce_->strictifySetNameOp(JSOp::SetName);
+ } else {
+ op = bce_->strictifySetNameOp(JSOp::SetGName);
+ }
+ } else {
+ op = JSOp::InitGLexical;
+ }
+ if (!bce_->emitAtomOp(op, atomIndex_)) {
+ return false;
+ }
+ break;
+ }
+ case NameLocation::Kind::Intrinsic:
+ if (!bce_->emitAtomOp(JSOp::SetIntrinsic, name_)) {
+ return false;
+ }
+ break;
+ case NameLocation::Kind::NamedLambdaCallee:
+ // Assigning to the named lambda is a no-op in sloppy mode but
+ // throws in strict mode.
+ if (bce_->sc->strict()) {
+ if (!bce_->emitAtomOp(JSOp::ThrowSetConst, name_)) {
+ return false;
+ }
+ }
+ break;
+ case NameLocation::Kind::ArgumentSlot:
+ if (!bce_->emitArgOp(JSOp::SetArg, loc_.argumentSlot())) {
+ return false;
+ }
+ break;
+ case NameLocation::Kind::FrameSlot: {
+ JSOp op = JSOp::SetLocal;
+ // Lexicals, Synthetics and Private Methods have very similar handling
+ // around a variety of areas, including initialization.
+ if (loc_.isLexical() || loc_.isPrivateMethod() || loc_.isSynthetic()) {
+ if (isInitialize()) {
+ op = JSOp::InitLexical;
+ } else {
+ if (loc_.isConst()) {
+ op = JSOp::ThrowSetConst;
+ }
+ if (!bce_->emitTDZCheckIfNeeded(name_, loc_, ValueIsOnStack::No)) {
+ return false;
+ }
+ }
+ }
+ if (op == JSOp::ThrowSetConst) {
+ if (!bce_->emitAtomOp(op, name_)) {
+ return false;
+ }
+ } else {
+ if (!bce_->emitLocalOp(op, loc_.frameSlot())) {
+ return false;
+ }
+ }
+ if (op == JSOp::InitLexical) {
+ if (!bce_->innermostTDZCheckCache->noteTDZCheck(bce_, name_,
+ DontCheckTDZ)) {
+ return false;
+ }
+ }
+ break;
+ }
+ case NameLocation::Kind::EnvironmentCoordinate: {
+ JSOp op = JSOp::SetAliasedVar;
+ // Lexicals, Synthetics and Private Methods have very similar handling
+ // around a variety of areas, including initialization.
+ if (loc_.isLexical() || loc_.isPrivateMethod() || loc_.isSynthetic()) {
+ if (isInitialize()) {
+ op = JSOp::InitAliasedLexical;
+ } else {
+ if (loc_.isConst()) {
+ op = JSOp::ThrowSetConst;
+ }
+ if (!bce_->emitTDZCheckIfNeeded(name_, loc_, ValueIsOnStack::No)) {
+ return false;
+ }
+ }
+ }
+ if (loc_.bindingKind() == BindingKind::NamedLambdaCallee) {
+ // Assigning to the named lambda is a no-op in sloppy mode and throws
+ // in strict mode.
+ op = JSOp::ThrowSetConst;
+ if (bce_->sc->strict()) {
+ if (!bce_->emitAtomOp(op, name_)) {
+ return false;
+ }
+ }
+ } else {
+ if (op == JSOp::ThrowSetConst) {
+ if (!bce_->emitAtomOp(op, name_)) {
+ return false;
+ }
+ } else {
+ if (!bce_->emitEnvCoordOp(op, loc_.environmentCoordinate())) {
+ return false;
+ }
+ }
+ }
+ if (op == JSOp::InitAliasedLexical) {
+ if (!bce_->innermostTDZCheckCache->noteTDZCheck(bce_, name_,
+ DontCheckTDZ)) {
+ return false;
+ }
+ }
+ break;
+ }
+ case NameLocation::Kind::DebugEnvironmentCoordinate:
+ MOZ_CRASH("Shouldn't be assigning to a private brand");
+ break;
+ }
+
+#ifdef DEBUG
+ state_ = State::Assignment;
+#endif
+ return true;
+}
+
+bool NameOpEmitter::emitIncDec(ValueUsage valueUsage) {
+ MOZ_ASSERT(state_ == State::Start);
+
+ JSOp incOp = isInc() ? JSOp::Inc : JSOp::Dec;
+ if (!prepareForRhs()) {
+ // [stack] ENV? V
+ return false;
+ }
+ if (!bce_->emit1(JSOp::ToNumeric)) {
+ // [stack] ENV? N
+ return false;
+ }
+ if (isPostIncDec() && valueUsage == ValueUsage::WantValue) {
+ if (!bce_->emit1(JSOp::Dup)) {
+ // [stack] ENV? N N
+ return false;
+ }
+ }
+ if (!bce_->emit1(incOp)) {
+ // [stack] ENV? N? N+1
+ return false;
+ }
+ if (isPostIncDec() && emittedBindOp() &&
+ valueUsage == ValueUsage::WantValue) {
+ if (!bce_->emit2(JSOp::Pick, 2)) {
+ // [stack] N N+1 ENV
+ return false;
+ }
+ if (!bce_->emit1(JSOp::Swap)) {
+ // [stack] N ENV N+1
+ return false;
+ }
+ }
+ if (!emitAssignment()) {
+ // [stack] N? N+1
+ return false;
+ }
+ if (isPostIncDec() && valueUsage == ValueUsage::WantValue) {
+ if (!bce_->emit1(JSOp::Pop)) {
+ // [stack] N
+ return false;
+ }
+ }
+
+#ifdef DEBUG
+ state_ = State::IncDec;
+#endif
+ return true;
+}
diff --git a/js/src/frontend/NameOpEmitter.h b/js/src/frontend/NameOpEmitter.h
new file mode 100644
index 0000000000..62d632a321
--- /dev/null
+++ b/js/src/frontend/NameOpEmitter.h
@@ -0,0 +1,182 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef frontend_NameOpEmitter_h
+#define frontend_NameOpEmitter_h
+
+#include "mozilla/Attributes.h"
+
+#include "frontend/NameAnalysisTypes.h"
+#include "frontend/ParserAtom.h" // TaggedParserAtomIndex
+#include "vm/SharedStencil.h" // GCThingIndex
+
+namespace js {
+namespace frontend {
+
+struct BytecodeEmitter;
+enum class ValueUsage;
+
+// Class for emitting bytecode for name operation.
+//
+// Usage: (check for the return value is omitted for simplicity)
+//
+// `name;`
+// NameOpEmitter noe(this, atom_of_name
+// NameOpEmitter::Kind::Get);
+// noe.emitGet();
+//
+// `name();`
+// this is handled in CallOrNewEmitter
+//
+// `name++;`
+// NameOpEmitter noe(this, atom_of_name
+// NameOpEmitter::Kind::PostIncrement);
+// noe.emitIncDec();
+//
+// `name = 10;`
+// NameOpEmitter noe(this, atom_of_name
+// NameOpEmitter::Kind::SimpleAssignment);
+// noe.prepareForRhs();
+// emit(10);
+// noe.emitAssignment();
+//
+// `name += 10;`
+// NameOpEmitter noe(this, atom_of_name
+// NameOpEmitter::Kind::CompoundAssignment);
+// noe.prepareForRhs();
+// emit(10);
+// emit_add_op_here();
+// noe.emitAssignment();
+//
+// `name = 10;` part of `let name = 10;`
+// NameOpEmitter noe(this, atom_of_name
+// NameOpEmitter::Kind::Initialize);
+// noe.prepareForRhs();
+// emit(10);
+// noe.emitAssignment();
+//
+class MOZ_STACK_CLASS NameOpEmitter {
+ public:
+ enum class Kind {
+ Get,
+ Call,
+ PostIncrement,
+ PreIncrement,
+ PostDecrement,
+ PreDecrement,
+ SimpleAssignment,
+ CompoundAssignment,
+ Initialize
+ };
+
+ private:
+ BytecodeEmitter* bce_;
+
+ Kind kind_;
+
+ bool emittedBindOp_ = false;
+
+ TaggedParserAtomIndex name_;
+
+ GCThingIndex atomIndex_;
+
+ NameLocation loc_;
+
+#ifdef DEBUG
+ // The state of this emitter.
+ //
+ // [Get]
+ // [Call]
+ // +-------+ emitGet +-----+
+ // | Start |-+-+--------->| Get |
+ // +-------+ | +-----+
+ // |
+ // | [PostIncrement]
+ // | [PreIncrement]
+ // | [PostDecrement]
+ // | [PreDecrement]
+ // | emitIncDec +--------+
+ // +------------->| IncDec |
+ // | +--------+
+ // |
+ // | [SimpleAssignment]
+ // | prepareForRhs +-----+
+ // +--------------------->+-------------->| Rhs |-+
+ // | ^ +-----+ |
+ // | | |
+ // | | +------------------+
+ // | [CompoundAssignment] | |
+ // | emitGet +-----+ | | emitAssignment +------------+
+ // +---------->| Get |----+ + -------------->| Assignment |
+ // +-----+ +------------+
+ enum class State {
+ // The initial state.
+ Start,
+
+ // After calling emitGet.
+ Get,
+
+ // After calling emitIncDec.
+ IncDec,
+
+ // After calling prepareForRhs.
+ Rhs,
+
+ // After calling emitAssignment.
+ Assignment,
+ };
+ State state_ = State::Start;
+#endif
+
+ public:
+ NameOpEmitter(BytecodeEmitter* bce, TaggedParserAtomIndex name, Kind kind);
+ NameOpEmitter(BytecodeEmitter* bce, TaggedParserAtomIndex name,
+ const NameLocation& loc, Kind kind);
+
+ private:
+ [[nodiscard]] bool isCall() const { return kind_ == Kind::Call; }
+
+ [[nodiscard]] bool isSimpleAssignment() const {
+ return kind_ == Kind::SimpleAssignment;
+ }
+
+ [[nodiscard]] bool isCompoundAssignment() const {
+ return kind_ == Kind::CompoundAssignment;
+ }
+
+ [[nodiscard]] bool isIncDec() const {
+ return isPostIncDec() || isPreIncDec();
+ }
+
+ [[nodiscard]] bool isPostIncDec() const {
+ return kind_ == Kind::PostIncrement || kind_ == Kind::PostDecrement;
+ }
+
+ [[nodiscard]] bool isPreIncDec() const {
+ return kind_ == Kind::PreIncrement || kind_ == Kind::PreDecrement;
+ }
+
+ [[nodiscard]] bool isInc() const {
+ return kind_ == Kind::PostIncrement || kind_ == Kind::PreIncrement;
+ }
+
+ [[nodiscard]] bool isInitialize() const { return kind_ == Kind::Initialize; }
+
+ public:
+ [[nodiscard]] bool emittedBindOp() const { return emittedBindOp_; }
+
+ [[nodiscard]] const NameLocation& loc() const { return loc_; }
+
+ [[nodiscard]] bool emitGet();
+ [[nodiscard]] bool prepareForRhs();
+ [[nodiscard]] bool emitAssignment();
+ [[nodiscard]] bool emitIncDec(ValueUsage valueUsage);
+};
+
+} /* namespace frontend */
+} /* namespace js */
+
+#endif /* frontend_NameOpEmitter_h */
diff --git a/js/src/frontend/ObjLiteral.cpp b/js/src/frontend/ObjLiteral.cpp
new file mode 100644
index 0000000000..0c2aa98417
--- /dev/null
+++ b/js/src/frontend/ObjLiteral.cpp
@@ -0,0 +1,537 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sw=2 et tw=0 ft=c:
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "frontend/ObjLiteral.h"
+
+#include "mozilla/DebugOnly.h" // mozilla::DebugOnly
+#include "mozilla/HashTable.h" // mozilla::HashSet
+
+#include "NamespaceImports.h" // ValueVector
+
+#include "builtin/Array.h" // NewDenseCopiedArray
+#include "frontend/CompilationStencil.h" // frontend::{CompilationStencil, CompilationAtomCache}
+#include "frontend/ParserAtom.h" // frontend::ParserAtomTable
+#include "frontend/TaggedParserAtomIndexHasher.h" // TaggedParserAtomIndexHasher
+#include "gc/AllocKind.h" // gc::AllocKind
+#include "js/Id.h" // INT_TO_JSID
+#include "js/Printer.h" // js::Fprinter
+#include "js/RootingAPI.h" // Rooted
+#include "js/TypeDecls.h" // RootedId, RootedValue
+#include "vm/JSObject.h" // TenuredObject
+#include "vm/JSONPrinter.h" // js::JSONPrinter
+#include "vm/NativeObject.h" // NativeDefineDataProperty
+#include "vm/PlainObject.h" // PlainObject
+
+#include "gc/ObjectKind-inl.h" // gc::GetGCObjectKind
+#include "vm/JSAtom-inl.h" // AtomToId
+#include "vm/JSObject-inl.h" // NewBuiltinClassInstance
+#include "vm/NativeObject-inl.h" // AddDataPropertyNonDelegate
+
+namespace js {
+
+bool ObjLiteralWriter::checkForDuplicatedNames(FrontendContext* fc) {
+ if (!mightContainDuplicatePropertyNames_) {
+ return true;
+ }
+
+ // If possible duplicate property names are detected by bloom-filter,
+ // check again with hash-set.
+
+ mozilla::HashSet<frontend::TaggedParserAtomIndex,
+ frontend::TaggedParserAtomIndexHasher>
+ propNameSet;
+
+ if (!propNameSet.reserve(propertyCount_)) {
+ js::ReportOutOfMemory(fc);
+ return false;
+ }
+
+ ObjLiteralReader reader(getCode());
+
+ while (true) {
+ ObjLiteralInsn insn;
+ if (!reader.readInsn(&insn)) {
+ break;
+ }
+
+ if (insn.getKey().isArrayIndex()) {
+ continue;
+ }
+
+ auto propName = insn.getKey().getAtomIndex();
+
+ auto p = propNameSet.lookupForAdd(propName);
+ if (p) {
+ flags_.setFlag(ObjLiteralFlag::HasIndexOrDuplicatePropName);
+ break;
+ }
+
+ // Already reserved above and doesn't fail.
+ MOZ_ALWAYS_TRUE(propNameSet.add(p, propName));
+ }
+
+ return true;
+}
+
+static void InterpretObjLiteralValue(
+ JSContext* cx, const frontend::CompilationAtomCache& atomCache,
+ const ObjLiteralInsn& insn, MutableHandleValue valOut) {
+ switch (insn.getOp()) {
+ case ObjLiteralOpcode::ConstValue:
+ valOut.set(insn.getConstValue());
+ return;
+ case ObjLiteralOpcode::ConstString: {
+ frontend::TaggedParserAtomIndex index = insn.getAtomIndex();
+ JSString* str = atomCache.getExistingStringAt(cx, index);
+ MOZ_ASSERT(str);
+ valOut.setString(str);
+ return;
+ }
+ case ObjLiteralOpcode::Null:
+ valOut.setNull();
+ return;
+ case ObjLiteralOpcode::Undefined:
+ valOut.setUndefined();
+ return;
+ case ObjLiteralOpcode::True:
+ valOut.setBoolean(true);
+ return;
+ case ObjLiteralOpcode::False:
+ valOut.setBoolean(false);
+ return;
+ default:
+ MOZ_CRASH("Unexpected object-literal instruction opcode");
+ }
+}
+
+enum class PropertySetKind {
+ UniqueNames,
+ Normal,
+};
+
+template <PropertySetKind kind>
+bool InterpretObjLiteralObj(JSContext* cx, Handle<PlainObject*> obj,
+ const frontend::CompilationAtomCache& atomCache,
+ const mozilla::Span<const uint8_t> literalInsns) {
+ ObjLiteralReader reader(literalInsns);
+
+ RootedId propId(cx);
+ RootedValue propVal(cx);
+ while (true) {
+ // Make sure `insn` doesn't live across GC.
+ ObjLiteralInsn insn;
+ if (!reader.readInsn(&insn)) {
+ break;
+ }
+ MOZ_ASSERT(insn.isValid());
+ MOZ_ASSERT_IF(kind == PropertySetKind::UniqueNames,
+ !insn.getKey().isArrayIndex());
+
+ if (kind == PropertySetKind::Normal && insn.getKey().isArrayIndex()) {
+ propId = PropertyKey::Int(insn.getKey().getArrayIndex());
+ } else {
+ JSAtom* jsatom =
+ atomCache.getExistingAtomAt(cx, insn.getKey().getAtomIndex());
+ MOZ_ASSERT(jsatom);
+ propId = AtomToId(jsatom);
+ }
+
+ InterpretObjLiteralValue(cx, atomCache, insn, &propVal);
+
+ if constexpr (kind == PropertySetKind::UniqueNames) {
+ if (!AddDataPropertyToPlainObject(cx, obj, propId, propVal)) {
+ return false;
+ }
+ } else {
+ if (!NativeDefineDataProperty(cx, obj, propId, propVal,
+ JSPROP_ENUMERATE)) {
+ return false;
+ }
+ }
+ }
+ return true;
+}
+
+static gc::AllocKind AllocKindForObjectLiteral(uint32_t propCount) {
+ // Use NewObjectGCKind for empty object literals to reserve some fixed slots
+ // for new properties. This improves performance for common patterns such as
+ // |Object.assign({}, ...)|.
+ return (propCount == 0) ? NewObjectGCKind() : gc::GetGCObjectKind(propCount);
+}
+
+static JSObject* InterpretObjLiteralObj(
+ JSContext* cx, const frontend::CompilationAtomCache& atomCache,
+ const mozilla::Span<const uint8_t> literalInsns, ObjLiteralFlags flags,
+ uint32_t propertyCount) {
+ gc::AllocKind allocKind = AllocKindForObjectLiteral(propertyCount);
+
+ Rooted<PlainObject*> obj(
+ cx, NewPlainObjectWithAllocKind(cx, allocKind, TenuredObject));
+ if (!obj) {
+ return nullptr;
+ }
+
+ if (!flags.hasFlag(ObjLiteralFlag::HasIndexOrDuplicatePropName)) {
+ if (!InterpretObjLiteralObj<PropertySetKind::UniqueNames>(
+ cx, obj, atomCache, literalInsns)) {
+ return nullptr;
+ }
+ } else {
+ if (!InterpretObjLiteralObj<PropertySetKind::Normal>(cx, obj, atomCache,
+ literalInsns)) {
+ return nullptr;
+ }
+ }
+ return obj;
+}
+
+static JSObject* InterpretObjLiteralArray(
+ JSContext* cx, const frontend::CompilationAtomCache& atomCache,
+ const mozilla::Span<const uint8_t> literalInsns, uint32_t propertyCount) {
+ ObjLiteralReader reader(literalInsns);
+ ObjLiteralInsn insn;
+
+ Rooted<ValueVector> elements(cx, ValueVector(cx));
+ if (!elements.reserve(propertyCount)) {
+ return nullptr;
+ }
+
+ RootedValue propVal(cx);
+ while (reader.readInsn(&insn)) {
+ MOZ_ASSERT(insn.isValid());
+
+ InterpretObjLiteralValue(cx, atomCache, insn, &propVal);
+ elements.infallibleAppend(propVal);
+ }
+
+ return NewDenseCopiedArray(cx, elements.length(), elements.begin(),
+ NewObjectKind::TenuredObject);
+}
+
+// ES2023 draft rev ee74c9cb74dbfa23e62b486f5226102c345c678e
+//
+// GetTemplateObject ( templateLiteral )
+// https://tc39.es/ecma262/#sec-gettemplateobject
+//
+// Steps 8-16.
+static JSObject* InterpretObjLiteralCallSiteObj(
+ JSContext* cx, const frontend::CompilationAtomCache& atomCache,
+ const mozilla::Span<const uint8_t> literalInsns, uint32_t propertyCount) {
+ ObjLiteralReader reader(literalInsns);
+ ObjLiteralInsn insn;
+
+ // We have to read elements for two arrays. The 'cooked' values are followed
+ // by the 'raw' values. Both arrays have the same length.
+ MOZ_ASSERT((propertyCount % 2) == 0);
+ uint32_t count = propertyCount / 2;
+
+ Rooted<ValueVector> elements(cx, ValueVector(cx));
+ if (!elements.reserve(count)) {
+ return nullptr;
+ }
+
+ RootedValue propVal(cx);
+ auto readElements = [&](uint32_t count) {
+ MOZ_ASSERT(elements.empty());
+
+ for (size_t i = 0; i < count; i++) {
+ MOZ_ALWAYS_TRUE(reader.readInsn(&insn));
+ MOZ_ASSERT(insn.isValid());
+
+ InterpretObjLiteralValue(cx, atomCache, insn, &propVal);
+ MOZ_ASSERT(propVal.isString() || propVal.isUndefined());
+ elements.infallibleAppend(propVal);
+ }
+ };
+
+ // Create cooked array.
+ readElements(count);
+ Rooted<ArrayObject*> cso(
+ cx, NewDenseCopiedArray(cx, elements.length(), elements.begin(),
+ NewObjectKind::TenuredObject));
+ if (!cso) {
+ return nullptr;
+ }
+ elements.clear();
+
+ // Create raw array.
+ readElements(count);
+ Rooted<ArrayObject*> raw(
+ cx, NewDenseCopiedArray(cx, elements.length(), elements.begin(),
+ NewObjectKind::TenuredObject));
+ if (!raw) {
+ return nullptr;
+ }
+
+ // Define .raw property and freeze both arrays.
+ RootedValue rawValue(cx, ObjectValue(*raw));
+ if (!DefineDataProperty(cx, cso, cx->names().raw, rawValue, 0)) {
+ return nullptr;
+ }
+ if (!FreezeObject(cx, raw)) {
+ return nullptr;
+ }
+ if (!FreezeObject(cx, cso)) {
+ return nullptr;
+ }
+
+ return cso;
+}
+
+template <PropertySetKind kind>
+Shape* InterpretObjLiteralShape(JSContext* cx,
+ const frontend::CompilationAtomCache& atomCache,
+ const mozilla::Span<const uint8_t> literalInsns,
+ uint32_t numFixedSlots) {
+ ObjLiteralReader reader(literalInsns);
+
+ Rooted<SharedPropMap*> map(cx);
+ uint32_t mapLength = 0;
+ ObjectFlags objectFlags;
+
+ uint32_t slot = 0;
+ RootedId propId(cx);
+ while (true) {
+ // Make sure `insn` doesn't live across GC.
+ ObjLiteralInsn insn;
+ if (!reader.readInsn(&insn)) {
+ break;
+ }
+ MOZ_ASSERT(insn.isValid());
+ MOZ_ASSERT(!insn.getKey().isArrayIndex());
+ MOZ_ASSERT(insn.getOp() == ObjLiteralOpcode::Undefined);
+
+ JSAtom* jsatom =
+ atomCache.getExistingAtomAt(cx, insn.getKey().getAtomIndex());
+ MOZ_ASSERT(jsatom);
+ propId = AtomToId(jsatom);
+
+ // Assert or check property names are unique.
+ if constexpr (kind == PropertySetKind::UniqueNames) {
+ mozilla::DebugOnly<uint32_t> index;
+ MOZ_ASSERT_IF(map, !map->lookupPure(mapLength, propId, &index));
+ } else {
+ uint32_t index;
+ if (map && map->lookupPure(mapLength, propId, &index)) {
+ continue;
+ }
+ }
+
+ constexpr PropertyFlags propFlags = PropertyFlags::defaultDataPropFlags;
+
+ if (!SharedPropMap::addPropertyWithKnownSlot(cx, &PlainObject::class_, &map,
+ &mapLength, propId, propFlags,
+ slot, &objectFlags)) {
+ return nullptr;
+ }
+
+ slot++;
+ }
+
+ JSObject* proto = &cx->global()->getObjectPrototype();
+ return SharedShape::getInitialOrPropMapShape(
+ cx, &PlainObject::class_, cx->realm(), TaggedProto(proto), numFixedSlots,
+ map, mapLength, objectFlags);
+}
+
+static Shape* InterpretObjLiteralShape(
+ JSContext* cx, const frontend::CompilationAtomCache& atomCache,
+ const mozilla::Span<const uint8_t> literalInsns, ObjLiteralFlags flags,
+ uint32_t propertyCount) {
+ gc::AllocKind allocKind = AllocKindForObjectLiteral(propertyCount);
+ uint32_t numFixedSlots = GetGCKindSlots(allocKind);
+
+ if (!flags.hasFlag(ObjLiteralFlag::HasIndexOrDuplicatePropName)) {
+ return InterpretObjLiteralShape<PropertySetKind::UniqueNames>(
+ cx, atomCache, literalInsns, numFixedSlots);
+ }
+ return InterpretObjLiteralShape<PropertySetKind::Normal>(
+ cx, atomCache, literalInsns, numFixedSlots);
+}
+
+JS::GCCellPtr ObjLiteralStencil::create(
+ JSContext* cx, const frontend::CompilationAtomCache& atomCache) const {
+ switch (kind()) {
+ case ObjLiteralKind::Array: {
+ JSObject* obj =
+ InterpretObjLiteralArray(cx, atomCache, code_, propertyCount_);
+ if (!obj) {
+ return JS::GCCellPtr();
+ }
+ return JS::GCCellPtr(obj);
+ }
+ case ObjLiteralKind::CallSiteObj: {
+ JSObject* obj =
+ InterpretObjLiteralCallSiteObj(cx, atomCache, code_, propertyCount_);
+ if (!obj) {
+ return JS::GCCellPtr();
+ }
+ return JS::GCCellPtr(obj);
+ }
+ case ObjLiteralKind::Object: {
+ JSObject* obj =
+ InterpretObjLiteralObj(cx, atomCache, code_, flags(), propertyCount_);
+ if (!obj) {
+ return JS::GCCellPtr();
+ }
+ return JS::GCCellPtr(obj);
+ }
+ case ObjLiteralKind::Shape: {
+ Shape* shape = InterpretObjLiteralShape(cx, atomCache, code_, flags(),
+ propertyCount_);
+ if (!shape) {
+ return JS::GCCellPtr();
+ }
+ return JS::GCCellPtr(shape);
+ }
+ case ObjLiteralKind::Invalid:
+ break;
+ }
+ MOZ_CRASH("Invalid kind");
+}
+
+#ifdef DEBUG
+bool ObjLiteralStencil::isContainedIn(const LifoAlloc& alloc) const {
+ return alloc.contains(code_.data());
+}
+#endif
+
+#if defined(DEBUG) || defined(JS_JITSPEW)
+
+static void DumpObjLiteralFlagsItems(js::JSONPrinter& json,
+ ObjLiteralFlags flags) {
+ if (flags.hasFlag(ObjLiteralFlag::HasIndexOrDuplicatePropName)) {
+ json.value("HasIndexOrDuplicatePropName");
+ flags.clearFlag(ObjLiteralFlag::HasIndexOrDuplicatePropName);
+ }
+
+ if (!flags.isEmpty()) {
+ json.value("Unknown(%x)", flags.toRaw());
+ }
+}
+
+static const char* ObjLiteralKindToString(ObjLiteralKind kind) {
+ switch (kind) {
+ case ObjLiteralKind::Object:
+ return "Object";
+ case ObjLiteralKind::Array:
+ return "Array";
+ case ObjLiteralKind::CallSiteObj:
+ return "CallSiteObj";
+ case ObjLiteralKind::Shape:
+ return "Shape";
+ case ObjLiteralKind::Invalid:
+ break;
+ }
+ MOZ_CRASH("Invalid kind");
+}
+
+static void DumpObjLiteral(js::JSONPrinter& json,
+ const frontend::CompilationStencil* stencil,
+ mozilla::Span<const uint8_t> code,
+ ObjLiteralKind kind, const ObjLiteralFlags& flags,
+ uint32_t propertyCount) {
+ json.property("kind", ObjLiteralKindToString(kind));
+
+ json.beginListProperty("flags");
+ DumpObjLiteralFlagsItems(json, flags);
+ json.endList();
+
+ json.beginListProperty("code");
+ ObjLiteralReader reader(code);
+ ObjLiteralInsn insn;
+ while (reader.readInsn(&insn)) {
+ json.beginObject();
+
+ if (insn.getKey().isNone()) {
+ json.nullProperty("key");
+ } else if (insn.getKey().isAtomIndex()) {
+ frontend::TaggedParserAtomIndex index = insn.getKey().getAtomIndex();
+ json.beginObjectProperty("key");
+ DumpTaggedParserAtomIndex(json, index, stencil);
+ json.endObject();
+ } else if (insn.getKey().isArrayIndex()) {
+ uint32_t index = insn.getKey().getArrayIndex();
+ json.formatProperty("key", "ArrayIndex(%u)", index);
+ }
+
+ switch (insn.getOp()) {
+ case ObjLiteralOpcode::ConstValue: {
+ const Value& v = insn.getConstValue();
+ json.formatProperty("op", "ConstValue(%f)", v.toNumber());
+ break;
+ }
+ case ObjLiteralOpcode::ConstString: {
+ frontend::TaggedParserAtomIndex index = insn.getAtomIndex();
+ json.beginObjectProperty("op");
+ DumpTaggedParserAtomIndex(json, index, stencil);
+ json.endObject();
+ break;
+ }
+ case ObjLiteralOpcode::Null:
+ json.property("op", "Null");
+ break;
+ case ObjLiteralOpcode::Undefined:
+ json.property("op", "Undefined");
+ break;
+ case ObjLiteralOpcode::True:
+ json.property("op", "True");
+ break;
+ case ObjLiteralOpcode::False:
+ json.property("op", "False");
+ break;
+ default:
+ json.formatProperty("op", "Invalid(%x)", uint8_t(insn.getOp()));
+ break;
+ }
+
+ json.endObject();
+ }
+ json.endList();
+
+ json.property("propertyCount", propertyCount);
+}
+
+void ObjLiteralWriter::dump() const {
+ js::Fprinter out(stderr);
+ js::JSONPrinter json(out);
+ dump(json, nullptr);
+}
+
+void ObjLiteralWriter::dump(js::JSONPrinter& json,
+ const frontend::CompilationStencil* stencil) const {
+ json.beginObject();
+ dumpFields(json, stencil);
+ json.endObject();
+}
+
+void ObjLiteralWriter::dumpFields(
+ js::JSONPrinter& json, const frontend::CompilationStencil* stencil) const {
+ DumpObjLiteral(json, stencil, getCode(), kind_, flags_, propertyCount_);
+}
+
+void ObjLiteralStencil::dump() const {
+ js::Fprinter out(stderr);
+ js::JSONPrinter json(out);
+ dump(json, nullptr);
+}
+
+void ObjLiteralStencil::dump(
+ js::JSONPrinter& json, const frontend::CompilationStencil* stencil) const {
+ json.beginObject();
+ dumpFields(json, stencil);
+ json.endObject();
+}
+
+void ObjLiteralStencil::dumpFields(
+ js::JSONPrinter& json, const frontend::CompilationStencil* stencil) const {
+ DumpObjLiteral(json, stencil, code_, kind(), flags(), propertyCount_);
+}
+
+#endif // defined(DEBUG) || defined(JS_JITSPEW)
+
+} // namespace js
diff --git a/js/src/frontend/ObjLiteral.h b/js/src/frontend/ObjLiteral.h
new file mode 100644
index 0000000000..e39a920e65
--- /dev/null
+++ b/js/src/frontend/ObjLiteral.h
@@ -0,0 +1,772 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sw=2 et tw=0 ft=c:
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef frontend_ObjLiteral_h
+#define frontend_ObjLiteral_h
+
+#include "mozilla/BloomFilter.h" // mozilla::BitBloomFilter
+#include "mozilla/Span.h"
+
+#include "frontend/ParserAtom.h" // ParserAtomsTable, TaggedParserAtomIndex, ParserAtom
+#include "js/AllocPolicy.h"
+#include "js/Value.h"
+#include "js/Vector.h"
+#include "util/EnumFlags.h"
+#include "vm/BytecodeUtil.h"
+#include "vm/Opcodes.h"
+
+/*
+ * [SMDOC] ObjLiteral (Object Literal) Handling
+ * ============================================
+ *
+ * The `ObjLiteral*` family of classes defines an infastructure to handle
+ * object literals as they are encountered at parse time and translate them
+ * into objects or shapes that are attached to the bytecode.
+ *
+ * The object-literal "instructions", whose opcodes are defined in
+ * `ObjLiteralOpcode` below, each specify one key (atom property name, or
+ * numeric index) and one value. An `ObjLiteralWriter` buffers a linear
+ * sequence of such instructions, along with a side-table of atom references.
+ * The writer stores a compact binary format that is then interpreted by the
+ * `ObjLiteralReader` to construct an object or shape according to the
+ * instructions.
+ *
+ * This may seem like an odd dance: create an intermediate data structure that
+ * specifies key/value pairs, then later build the object/shape. Why not just do
+ * so directly, as we parse? In fact, we used to do this. However, for several
+ * good reasons, we want to avoid allocating or touching GC things at all
+ * *during* the parse. We thus use a sequence of ObjLiteral instructions as an
+ * intermediate data structure to carry object literal contents from parse to
+ * the time at which we *can* allocate GC things.
+ *
+ * (The original intent was to allow for ObjLiteral instructions to actually be
+ * invoked by a new JS opcode, JSOp::ObjLiteral, thus replacing the more
+ * general opcode sequences sometimes generated to fill in objects and removing
+ * the need to attach actual objects to JSOp::Object or JSOp::NewObject.
+ * However, this was far too invasive and led to performance regressions, so
+ * currently ObjLiteral only carries literals as far as the end of the parse
+ * pipeline, when all GC things are allocated.)
+ *
+ * ObjLiteral data structures are used to represent object literals whenever
+ * they are "compatible". See
+ * BytecodeEmitter::isPropertyListObjLiteralCompatible for the precise
+ * conditions; in brief, we can represent object literals with "primitive"
+ * (numeric, boolean, string, null/undefined) values, and "normal"
+ * (non-computed) object names. We can also represent arrays with the same
+ * value restrictions. We cannot represent nested objects. We use ObjLiteral in
+ * two different ways:
+ *
+ * - To build a template shape, when we can support the property keys but not
+ * the property values.
+ * - To build the actual result object, when we support the property keys and
+ * the values and this is a JSOp::Object case (see below).
+ *
+ * Design and Performance Considerations
+ * -------------------------------------
+ *
+ * As a brief overview, there are a number of opcodes that allocate objects:
+ *
+ * - JSOp::NewInit allocates a new empty `{}` object.
+ *
+ * - JSOp::NewObject, with a shape as an argument (held by the script data
+ * side-tables), allocates a new object with the given `shape` (property keys)
+ * and `undefined` property values.
+ *
+ * - JSOp::Object, with an object as argument, instructs the runtime to
+ * literally return the object argument as the result. This is thus only an
+ * "allocation" in the sense that the object was originally allocated when
+ * the script data / bytecode was created. It is only used when we know for
+ * sure that the script, and this program point within the script, will run
+ * *once*. (See the `treatAsRunOnce` flag on JSScript.)
+ *
+ * An operation occurs in a "singleton context", according to the parser, if it
+ * will only ever execute once. In particular, this happens when (i) the script
+ * is a "run-once" script, which is usually the case for e.g. top-level scripts
+ * of web-pages (they run on page load, but no function or handle wraps or
+ * refers to the script so it can't be invoked again), and (ii) the operation
+ * itself is not within a loop or function in that run-once script.
+ *
+ * When we encounter an object literal, we decide which opcode to use, and we
+ * construct the ObjLiteral and the bytecode using its result appropriately:
+ *
+ * - If in a singleton context, and if we support the values, we use
+ * JSOp::Object and we build the ObjLiteral instructions with values.
+ * - Otherwise, if we support the keys but not the values, or if we are not
+ * in a singleton context, we use JSOp::NewObject. In this case, the initial
+ * opcode only creates an object with empty values, so BytecodeEmitter then
+ * generates bytecode to set the values appropriately.
+ * - Otherwise, we generate JSOp::NewInit and bytecode to add properties one at
+ * a time. This will always work, but is the slowest and least
+ * memory-efficient option.
+ */
+
+namespace js {
+
+class FrontendContext;
+class JSONPrinter;
+class LifoAlloc;
+
+namespace frontend {
+struct CompilationAtomCache;
+struct CompilationStencil;
+class StencilXDR;
+} // namespace frontend
+
+// Object-literal instruction opcodes. An object literal is constructed by a
+// straight-line sequence of these ops, each adding one property to the
+// object.
+enum class ObjLiteralOpcode : uint8_t {
+ INVALID = 0,
+
+ ConstValue = 1, // numeric types only.
+ ConstString = 2,
+ Null = 3,
+ Undefined = 4,
+ True = 5,
+ False = 6,
+
+ MAX = False,
+};
+
+// The kind of GC thing constructed by the ObjLiteral framework and stored in
+// the script data.
+enum class ObjLiteralKind : uint8_t {
+ // Construct an ArrayObject from a list of dense elements.
+ Array,
+
+ // Construct an ArrayObject (the call site object) for a tagged template call
+ // from a list of dense elements for the cooked array followed by the dense
+ // elements for the `.raw` array.
+ CallSiteObj,
+
+ // Construct a PlainObject from a list of property keys/values.
+ Object,
+
+ // Construct a PlainObject Shape from a list of property keys.
+ Shape,
+
+ // Invalid sentinel value. Must be the last enum value.
+ Invalid
+};
+
+// Flags that are associated with a sequence of object-literal instructions.
+// (These become bitflags by wrapping with EnumSet below.)
+enum class ObjLiteralFlag : uint8_t {
+ // If set, this object contains index property, or duplicate non-index
+ // property.
+ // This flag is valid only if the ObjLiteralKind is not Array.
+ HasIndexOrDuplicatePropName = 1 << 0,
+
+ // Note: at most 6 flags are currently supported. See ObjLiteralKindAndFlags.
+};
+
+using ObjLiteralFlags = EnumFlags<ObjLiteralFlag>;
+
+// Helper class to encode ObjLiteralKind and ObjLiteralFlags in a single byte.
+class ObjLiteralKindAndFlags {
+ uint8_t bits_ = 0;
+
+ static constexpr size_t KindBits = 3;
+ static constexpr size_t KindMask = BitMask(KindBits);
+
+ static_assert(size_t(ObjLiteralKind::Invalid) <= KindMask,
+ "ObjLiteralKind needs more bits");
+
+ public:
+ ObjLiteralKindAndFlags() = default;
+
+ ObjLiteralKindAndFlags(ObjLiteralKind kind, ObjLiteralFlags flags)
+ : bits_(size_t(kind) | (flags.toRaw() << KindBits)) {
+ MOZ_ASSERT(this->kind() == kind);
+ MOZ_ASSERT(this->flags() == flags);
+ }
+
+ ObjLiteralKind kind() const { return ObjLiteralKind(bits_ & KindMask); }
+ ObjLiteralFlags flags() const {
+ ObjLiteralFlags res;
+ res.setRaw(bits_ >> KindBits);
+ return res;
+ }
+
+ uint8_t toRaw() const { return bits_; }
+ void setRaw(uint8_t bits) { bits_ = bits; }
+};
+
+inline bool ObjLiteralOpcodeHasValueArg(ObjLiteralOpcode op) {
+ return op == ObjLiteralOpcode::ConstValue;
+}
+
+inline bool ObjLiteralOpcodeHasAtomArg(ObjLiteralOpcode op) {
+ return op == ObjLiteralOpcode::ConstString;
+}
+
+struct ObjLiteralReaderBase;
+
+// Property name (as TaggedParserAtomIndex) or an integer index. Only used for
+// object-type literals; array literals do not require the index (the sequence
+// is always dense, with no holes, so the index is implicit). For the latter
+// case, we have a `None` placeholder.
+struct ObjLiteralKey {
+ private:
+ uint32_t value_;
+
+ enum ObjLiteralKeyType {
+ None,
+ AtomIndex,
+ ArrayIndex,
+ };
+
+ ObjLiteralKeyType type_;
+
+ ObjLiteralKey(uint32_t value, ObjLiteralKeyType ty)
+ : value_(value), type_(ty) {}
+
+ public:
+ ObjLiteralKey() : ObjLiteralKey(0, None) {}
+ ObjLiteralKey(uint32_t value, bool isArrayIndex)
+ : ObjLiteralKey(value, isArrayIndex ? ArrayIndex : AtomIndex) {}
+ ObjLiteralKey(const ObjLiteralKey& other) = default;
+
+ static ObjLiteralKey fromPropName(frontend::TaggedParserAtomIndex atomIndex) {
+ return ObjLiteralKey(atomIndex.rawData(), false);
+ }
+ static ObjLiteralKey fromArrayIndex(uint32_t index) {
+ return ObjLiteralKey(index, true);
+ }
+ static ObjLiteralKey none() { return ObjLiteralKey(); }
+
+ bool isNone() const { return type_ == None; }
+ bool isAtomIndex() const { return type_ == AtomIndex; }
+ bool isArrayIndex() const { return type_ == ArrayIndex; }
+
+ frontend::TaggedParserAtomIndex getAtomIndex() const {
+ MOZ_ASSERT(isAtomIndex());
+ return frontend::TaggedParserAtomIndex::fromRaw(value_);
+ }
+ uint32_t getArrayIndex() const {
+ MOZ_ASSERT(isArrayIndex());
+ return value_;
+ }
+
+ uint32_t rawIndex() const { return value_; }
+};
+
+struct ObjLiteralWriterBase {
+ protected:
+ friend struct ObjLiteralReaderBase; // for access to mask and shift.
+ static const uint32_t ATOM_INDEX_MASK = 0x7fffffff;
+ // If set, the atom index field is an array index, not an atom index.
+ static const uint32_t INDEXED_PROP = 0x80000000;
+
+ public:
+ using CodeVector = Vector<uint8_t, 64, js::SystemAllocPolicy>;
+
+ protected:
+ CodeVector code_;
+
+ public:
+ ObjLiteralWriterBase() = default;
+
+ uint32_t curOffset() const { return code_.length(); }
+
+ private:
+ [[nodiscard]] bool pushByte(FrontendContext* fc, uint8_t data) {
+ if (!code_.append(data)) {
+ js::ReportOutOfMemory(fc);
+ return false;
+ }
+ return true;
+ }
+
+ [[nodiscard]] bool prepareBytes(FrontendContext* fc, size_t len,
+ uint8_t** p) {
+ size_t offset = code_.length();
+ if (!code_.growByUninitialized(len)) {
+ js::ReportOutOfMemory(fc);
+ return false;
+ }
+ *p = &code_[offset];
+ return true;
+ }
+
+ template <typename T>
+ [[nodiscard]] bool pushRawData(FrontendContext* fc, T data) {
+ uint8_t* p = nullptr;
+ if (!prepareBytes(fc, sizeof(T), &p)) {
+ return false;
+ }
+ memcpy(p, &data, sizeof(T));
+ return true;
+ }
+
+ protected:
+ [[nodiscard]] bool pushOpAndName(FrontendContext* fc, ObjLiteralOpcode op,
+ ObjLiteralKey key) {
+ uint8_t opdata = static_cast<uint8_t>(op);
+ uint32_t data = key.rawIndex() | (key.isArrayIndex() ? INDEXED_PROP : 0);
+ return pushByte(fc, opdata) && pushRawData(fc, data);
+ }
+
+ [[nodiscard]] bool pushValueArg(FrontendContext* fc, const JS::Value& value) {
+ MOZ_ASSERT(value.isNumber() || value.isNullOrUndefined() ||
+ value.isBoolean());
+ uint64_t data = value.asRawBits();
+ return pushRawData(fc, data);
+ }
+
+ [[nodiscard]] bool pushAtomArg(FrontendContext* fc,
+ frontend::TaggedParserAtomIndex atomIndex) {
+ return pushRawData(fc, atomIndex.rawData());
+ }
+};
+
+// An object-literal instruction writer. This class, held by the bytecode
+// emitter, keeps a sequence of object-literal instructions emitted as object
+// literal expressions are parsed. It allows the user to 'begin' and 'end'
+// straight-line sequences, returning the offsets for this range of instructions
+// within the writer.
+struct ObjLiteralWriter : private ObjLiteralWriterBase {
+ public:
+ ObjLiteralWriter() = default;
+
+ void clear() { code_.clear(); }
+
+ using CodeVector = typename ObjLiteralWriterBase::CodeVector;
+
+ bool checkForDuplicatedNames(FrontendContext* fc);
+ mozilla::Span<const uint8_t> getCode() const { return code_; }
+ ObjLiteralKind getKind() const { return kind_; }
+ ObjLiteralFlags getFlags() const { return flags_; }
+ uint32_t getPropertyCount() const { return propertyCount_; }
+
+ void beginArray(JSOp op) {
+ MOZ_ASSERT(JOF_OPTYPE(op) == JOF_OBJECT);
+ MOZ_ASSERT(op == JSOp::Object);
+ kind_ = ObjLiteralKind::Array;
+ }
+ void beginCallSiteObj(JSOp op) {
+ MOZ_ASSERT(JOF_OPTYPE(op) == JOF_OBJECT);
+ MOZ_ASSERT(op == JSOp::CallSiteObj);
+ kind_ = ObjLiteralKind::CallSiteObj;
+ }
+ void beginObject(JSOp op) {
+ MOZ_ASSERT(JOF_OPTYPE(op) == JOF_OBJECT);
+ MOZ_ASSERT(op == JSOp::Object);
+ kind_ = ObjLiteralKind::Object;
+ }
+ void beginShape(JSOp op) {
+ MOZ_ASSERT(JOF_OPTYPE(op) == JOF_SHAPE);
+ MOZ_ASSERT(op == JSOp::NewObject);
+ kind_ = ObjLiteralKind::Shape;
+ }
+
+ bool setPropName(frontend::ParserAtomsTable& parserAtoms,
+ const frontend::TaggedParserAtomIndex propName) {
+ // Only valid in object-mode.
+ setPropNameNoDuplicateCheck(parserAtoms, propName);
+
+ if (flags_.hasFlag(ObjLiteralFlag::HasIndexOrDuplicatePropName)) {
+ return true;
+ }
+
+ // OK to early return if we've already discovered a potential duplicate.
+ if (mightContainDuplicatePropertyNames_) {
+ return true;
+ }
+
+ // Check bloom filter for duplicate, and add if not already represented.
+ if (propNamesFilter_.mightContain(propName.rawData())) {
+ mightContainDuplicatePropertyNames_ = true;
+ } else {
+ propNamesFilter_.add(propName.rawData());
+ }
+ return true;
+ }
+ void setPropNameNoDuplicateCheck(
+ frontend::ParserAtomsTable& parserAtoms,
+ const frontend::TaggedParserAtomIndex propName) {
+ MOZ_ASSERT(kind_ == ObjLiteralKind::Object ||
+ kind_ == ObjLiteralKind::Shape);
+ parserAtoms.markUsedByStencil(propName, frontend::ParserAtom::Atomize::Yes);
+ nextKey_ = ObjLiteralKey::fromPropName(propName);
+ }
+ void setPropIndex(uint32_t propIndex) {
+ MOZ_ASSERT(kind_ == ObjLiteralKind::Object);
+ MOZ_ASSERT(propIndex <= ATOM_INDEX_MASK);
+ nextKey_ = ObjLiteralKey::fromArrayIndex(propIndex);
+ flags_.setFlag(ObjLiteralFlag::HasIndexOrDuplicatePropName);
+ }
+ void beginDenseArrayElements() {
+ MOZ_ASSERT(kind_ == ObjLiteralKind::Array ||
+ kind_ == ObjLiteralKind::CallSiteObj);
+ // Dense array element sequences do not use the keys; the indices are
+ // implicit.
+ nextKey_ = ObjLiteralKey::none();
+ }
+
+ [[nodiscard]] bool propWithConstNumericValue(FrontendContext* fc,
+ const JS::Value& value) {
+ MOZ_ASSERT(kind_ != ObjLiteralKind::Shape);
+ propertyCount_++;
+ MOZ_ASSERT(value.isNumber());
+ return pushOpAndName(fc, ObjLiteralOpcode::ConstValue, nextKey_) &&
+ pushValueArg(fc, value);
+ }
+ [[nodiscard]] bool propWithAtomValue(
+ FrontendContext* fc, frontend::ParserAtomsTable& parserAtoms,
+ const frontend::TaggedParserAtomIndex value) {
+ MOZ_ASSERT(kind_ != ObjLiteralKind::Shape);
+ propertyCount_++;
+ parserAtoms.markUsedByStencil(value, frontend::ParserAtom::Atomize::No);
+ return pushOpAndName(fc, ObjLiteralOpcode::ConstString, nextKey_) &&
+ pushAtomArg(fc, value);
+ }
+ [[nodiscard]] bool propWithNullValue(FrontendContext* fc) {
+ MOZ_ASSERT(kind_ != ObjLiteralKind::Shape);
+ propertyCount_++;
+ return pushOpAndName(fc, ObjLiteralOpcode::Null, nextKey_);
+ }
+ [[nodiscard]] bool propWithUndefinedValue(FrontendContext* fc) {
+ propertyCount_++;
+ return pushOpAndName(fc, ObjLiteralOpcode::Undefined, nextKey_);
+ }
+ [[nodiscard]] bool propWithTrueValue(FrontendContext* fc) {
+ MOZ_ASSERT(kind_ != ObjLiteralKind::Shape);
+ propertyCount_++;
+ return pushOpAndName(fc, ObjLiteralOpcode::True, nextKey_);
+ }
+ [[nodiscard]] bool propWithFalseValue(FrontendContext* fc) {
+ MOZ_ASSERT(kind_ != ObjLiteralKind::Shape);
+ propertyCount_++;
+ return pushOpAndName(fc, ObjLiteralOpcode::False, nextKey_);
+ }
+
+ static bool arrayIndexInRange(int32_t i) {
+ return i >= 0 && static_cast<uint32_t>(i) <= ATOM_INDEX_MASK;
+ }
+
+#if defined(DEBUG) || defined(JS_JITSPEW)
+ void dump() const;
+ void dump(JSONPrinter& json,
+ const frontend::CompilationStencil* stencil) const;
+ void dumpFields(JSONPrinter& json,
+ const frontend::CompilationStencil* stencil) const;
+#endif
+
+ private:
+ // Set to true if we've found possible duplicate names while building.
+ // This field is placed next to `flags_` field, to reduce padding.
+ bool mightContainDuplicatePropertyNames_ = false;
+
+ ObjLiteralKind kind_ = ObjLiteralKind::Invalid;
+ ObjLiteralFlags flags_;
+ ObjLiteralKey nextKey_;
+ uint32_t propertyCount_ = 0;
+
+ // Duplicate property names detection is performed in the following way:
+ // * while emitting code, add each property names with
+ // `propNamesFilter_`
+ // * if possible duplicate property name is detected, set
+ // `mightContainDuplicatePropertyNames_` to true
+ // * in `checkForDuplicatedNames` method,
+ // if `mightContainDuplicatePropertyNames_` is true,
+ // check the duplicate property names with `HashSet`, and if it exists,
+ // set HasIndexOrDuplicatePropName flag.
+ mozilla::BitBloomFilter<12, frontend::TaggedParserAtomIndex> propNamesFilter_;
+};
+
+struct ObjLiteralReaderBase {
+ private:
+ mozilla::Span<const uint8_t> data_;
+ size_t cursor_;
+
+ [[nodiscard]] bool readByte(uint8_t* b) {
+ if (cursor_ + 1 > data_.Length()) {
+ return false;
+ }
+ *b = *data_.From(cursor_).data();
+ cursor_ += 1;
+ return true;
+ }
+
+ [[nodiscard]] bool readBytes(size_t size, const uint8_t** p) {
+ if (cursor_ + size > data_.Length()) {
+ return false;
+ }
+ *p = data_.From(cursor_).data();
+ cursor_ += size;
+ return true;
+ }
+
+ template <typename T>
+ [[nodiscard]] bool readRawData(T* data) {
+ const uint8_t* p = nullptr;
+ if (!readBytes(sizeof(T), &p)) {
+ return false;
+ }
+ memcpy(data, p, sizeof(T));
+ return true;
+ }
+
+ public:
+ explicit ObjLiteralReaderBase(mozilla::Span<const uint8_t> data)
+ : data_(data), cursor_(0) {}
+
+ [[nodiscard]] bool readOpAndKey(ObjLiteralOpcode* op, ObjLiteralKey* key) {
+ uint8_t opbyte;
+ if (!readByte(&opbyte)) {
+ return false;
+ }
+ if (MOZ_UNLIKELY(opbyte > static_cast<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;
+ }
+
+ [[nodiscard]] bool readValueArg(JS::Value* value) {
+ uint64_t data;
+ if (!readRawData(&data)) {
+ return false;
+ }
+ *value = JS::Value::fromRawBits(data);
+ return true;
+ }
+
+ [[nodiscard]] bool readAtomArg(frontend::TaggedParserAtomIndex* atomIndex) {
+ return readRawData(atomIndex->rawDataRef());
+ }
+
+ size_t cursor() const { return cursor_; }
+};
+
+// A single object-literal instruction, creating one property on an object.
+struct ObjLiteralInsn {
+ private:
+ ObjLiteralOpcode op_;
+ ObjLiteralKey key_;
+ union Arg {
+ explicit Arg(uint64_t raw_) : raw(raw_) {}
+
+ JS::Value constValue;
+ frontend::TaggedParserAtomIndex atomIndex;
+ uint64_t raw;
+ } arg_;
+
+ public:
+ ObjLiteralInsn() : op_(ObjLiteralOpcode::INVALID), arg_(0) {}
+ ObjLiteralInsn(ObjLiteralOpcode op, ObjLiteralKey key)
+ : op_(op), key_(key), arg_(0) {
+ MOZ_ASSERT(!hasConstValue());
+ MOZ_ASSERT(!hasAtomIndex());
+ }
+ ObjLiteralInsn(ObjLiteralOpcode op, ObjLiteralKey key, const JS::Value& value)
+ : op_(op), key_(key), arg_(0) {
+ MOZ_ASSERT(hasConstValue());
+ MOZ_ASSERT(!hasAtomIndex());
+ arg_.constValue = value;
+ }
+ ObjLiteralInsn(ObjLiteralOpcode op, ObjLiteralKey key,
+ frontend::TaggedParserAtomIndex atomIndex)
+ : op_(op), key_(key), arg_(0) {
+ MOZ_ASSERT(!hasConstValue());
+ MOZ_ASSERT(hasAtomIndex());
+ arg_.atomIndex = atomIndex;
+ }
+ ObjLiteralInsn(const ObjLiteralInsn& other) : ObjLiteralInsn() {
+ *this = other;
+ }
+ ObjLiteralInsn& operator=(const ObjLiteralInsn& other) {
+ op_ = other.op_;
+ key_ = other.key_;
+ arg_.raw = other.arg_.raw;
+ return *this;
+ }
+
+ bool isValid() const {
+ return op_ > ObjLiteralOpcode::INVALID && op_ <= ObjLiteralOpcode::MAX;
+ }
+
+ ObjLiteralOpcode getOp() const {
+ MOZ_ASSERT(isValid());
+ return op_;
+ }
+ const ObjLiteralKey& getKey() const {
+ MOZ_ASSERT(isValid());
+ return key_;
+ }
+
+ bool hasConstValue() const {
+ MOZ_ASSERT(isValid());
+ return ObjLiteralOpcodeHasValueArg(op_);
+ }
+ bool hasAtomIndex() const {
+ MOZ_ASSERT(isValid());
+ return ObjLiteralOpcodeHasAtomArg(op_);
+ }
+
+ JS::Value getConstValue() const {
+ MOZ_ASSERT(isValid());
+ MOZ_ASSERT(hasConstValue());
+ return arg_.constValue;
+ }
+ frontend::TaggedParserAtomIndex getAtomIndex() const {
+ MOZ_ASSERT(isValid());
+ MOZ_ASSERT(hasAtomIndex());
+ return arg_.atomIndex;
+ };
+};
+
+// A reader that parses a sequence of object-literal instructions out of the
+// encoded form.
+struct ObjLiteralReader : private ObjLiteralReaderBase {
+ public:
+ explicit ObjLiteralReader(mozilla::Span<const uint8_t> data)
+ : ObjLiteralReaderBase(data) {}
+
+ [[nodiscard]] bool readInsn(ObjLiteralInsn* insn) {
+ ObjLiteralOpcode op;
+ ObjLiteralKey key;
+ if (!readOpAndKey(&op, &key)) {
+ return false;
+ }
+ if (ObjLiteralOpcodeHasValueArg(op)) {
+ JS::Value value;
+ if (!readValueArg(&value)) {
+ return false;
+ }
+ *insn = ObjLiteralInsn(op, key, value);
+ return true;
+ }
+ if (ObjLiteralOpcodeHasAtomArg(op)) {
+ frontend::TaggedParserAtomIndex atomIndex;
+ if (!readAtomArg(&atomIndex)) {
+ return false;
+ }
+ *insn = ObjLiteralInsn(op, key, atomIndex);
+ return true;
+ }
+ *insn = ObjLiteralInsn(op, key);
+ return true;
+ }
+};
+
+// A class to modify the code, while keeping the structure.
+struct ObjLiteralModifier : private ObjLiteralReaderBase {
+ mozilla::Span<uint8_t> mutableData_;
+
+ public:
+ explicit ObjLiteralModifier(mozilla::Span<uint8_t> data)
+ : ObjLiteralReaderBase(data), mutableData_(data) {}
+
+ private:
+ // Map `atom` with `map`, and write to `atomCursor` of `mutableData_`.
+ template <typename MapT>
+ void mapOneAtom(MapT map, frontend::TaggedParserAtomIndex atom,
+ size_t atomCursor) {
+ auto atomIndex = map(atom);
+ memcpy(mutableData_.data() + atomCursor, atomIndex.rawDataRef(),
+ sizeof(frontend::TaggedParserAtomIndex));
+ }
+
+ // Map atoms in single instruction.
+ // Return true if it successfully maps.
+ // Return false if there's no more instruction.
+ template <typename MapT>
+ bool mapInsnAtom(MapT map) {
+ ObjLiteralOpcode op;
+ ObjLiteralKey key;
+
+ size_t opCursor = cursor();
+ if (!readOpAndKey(&op, &key)) {
+ return false;
+ }
+ if (key.isAtomIndex()) {
+ static constexpr size_t OpLength = 1;
+ size_t atomCursor = opCursor + OpLength;
+ mapOneAtom(map, key.getAtomIndex(), atomCursor);
+ }
+
+ if (ObjLiteralOpcodeHasValueArg(op)) {
+ JS::Value value;
+ if (!readValueArg(&value)) {
+ return false;
+ }
+ } else if (ObjLiteralOpcodeHasAtomArg(op)) {
+ size_t atomCursor = cursor();
+
+ frontend::TaggedParserAtomIndex atomIndex;
+ if (!readAtomArg(&atomIndex)) {
+ return false;
+ }
+
+ mapOneAtom(map, atomIndex, atomCursor);
+ }
+
+ return true;
+ }
+
+ public:
+ // Map TaggedParserAtomIndex inside the code in place, with given function.
+ template <typename MapT>
+ void mapAtom(MapT map) {
+ while (mapInsnAtom(map)) {
+ }
+ }
+};
+
+class ObjLiteralStencil {
+ friend class frontend::StencilXDR;
+
+ // CompilationStencil::clone has to update the code pointer.
+ friend struct frontend::CompilationStencil;
+
+ mozilla::Span<uint8_t> code_;
+ ObjLiteralKindAndFlags kindAndFlags_;
+ uint32_t propertyCount_ = 0;
+
+ public:
+ ObjLiteralStencil() = default;
+
+ ObjLiteralStencil(uint8_t* code, size_t length, ObjLiteralKind kind,
+ const ObjLiteralFlags& flags, uint32_t propertyCount)
+ : code_(mozilla::Span(code, length)),
+ kindAndFlags_(kind, flags),
+ propertyCount_(propertyCount) {}
+
+ JS::GCCellPtr create(JSContext* cx,
+ const frontend::CompilationAtomCache& atomCache) const;
+
+ mozilla::Span<const uint8_t> code() const { return code_; }
+ ObjLiteralKind kind() const { return kindAndFlags_.kind(); }
+ ObjLiteralFlags flags() const { return kindAndFlags_.flags(); }
+ uint32_t propertyCount() const { return propertyCount_; }
+
+#ifdef DEBUG
+ bool isContainedIn(const LifoAlloc& alloc) const;
+#endif
+
+#if defined(DEBUG) || defined(JS_JITSPEW)
+ void dump() const;
+ void dump(JSONPrinter& json,
+ const frontend::CompilationStencil* stencil) const;
+ void dumpFields(JSONPrinter& json,
+ const frontend::CompilationStencil* stencil) const;
+
+#endif
+};
+
+} // namespace js
+#endif // frontend_ObjLiteral_h
diff --git a/js/src/frontend/ObjectEmitter.cpp b/js/src/frontend/ObjectEmitter.cpp
new file mode 100644
index 0000000000..6a6c36ee08
--- /dev/null
+++ b/js/src/frontend/ObjectEmitter.cpp
@@ -0,0 +1,897 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "frontend/ObjectEmitter.h"
+
+#include "mozilla/Assertions.h" // MOZ_ASSERT
+
+#include "frontend/BytecodeEmitter.h" // BytecodeEmitter
+#include "frontend/IfEmitter.h" // IfEmitter
+#include "frontend/ParseNode.h" // AccessorType
+#include "frontend/SharedContext.h" // SharedContext
+#include "vm/FunctionPrefixKind.h" // FunctionPrefixKind
+#include "vm/Opcodes.h" // JSOp
+
+using namespace js;
+using namespace js::frontend;
+
+using mozilla::Maybe;
+
+PropertyEmitter::PropertyEmitter(BytecodeEmitter* bce) : bce_(bce) {}
+
+bool PropertyEmitter::prepareForProtoValue(uint32_t keyPos) {
+ MOZ_ASSERT(propertyState_ == PropertyState::Start ||
+ propertyState_ == PropertyState::Init);
+
+ // [stack] CTOR? OBJ CTOR?
+
+ if (!bce_->updateSourceCoordNotes(keyPos)) {
+ return false;
+ }
+
+#ifdef DEBUG
+ propertyState_ = PropertyState::ProtoValue;
+#endif
+ return true;
+}
+
+bool PropertyEmitter::emitMutateProto() {
+ MOZ_ASSERT(propertyState_ == PropertyState::ProtoValue);
+
+ // [stack] OBJ PROTO
+
+ if (!bce_->emit1(JSOp::MutateProto)) {
+ // [stack] OBJ
+ return false;
+ }
+
+#ifdef DEBUG
+ propertyState_ = PropertyState::Init;
+#endif
+ return true;
+}
+
+bool PropertyEmitter::prepareForSpreadOperand(uint32_t spreadPos) {
+ MOZ_ASSERT(propertyState_ == PropertyState::Start ||
+ propertyState_ == PropertyState::Init);
+
+ // [stack] OBJ
+
+ if (!bce_->updateSourceCoordNotes(spreadPos)) {
+ return false;
+ }
+ if (!bce_->emit1(JSOp::Dup)) {
+ // [stack] OBJ OBJ
+ return false;
+ }
+
+#ifdef DEBUG
+ propertyState_ = PropertyState::SpreadOperand;
+#endif
+ return true;
+}
+
+bool PropertyEmitter::emitSpread() {
+ MOZ_ASSERT(propertyState_ == PropertyState::SpreadOperand);
+
+ // [stack] OBJ OBJ VAL
+
+ if (!bce_->emitCopyDataProperties(BytecodeEmitter::CopyOption::Unfiltered)) {
+ // [stack] OBJ
+ return false;
+ }
+
+#ifdef DEBUG
+ propertyState_ = PropertyState::Init;
+#endif
+ return true;
+}
+
+MOZ_ALWAYS_INLINE bool PropertyEmitter::prepareForProp(uint32_t keyPos,
+ bool isStatic,
+ bool isIndexOrComputed) {
+ isStatic_ = isStatic;
+ isIndexOrComputed_ = isIndexOrComputed;
+
+ // [stack] CTOR? OBJ
+
+ if (!bce_->updateSourceCoordNotes(keyPos)) {
+ return false;
+ }
+
+ if (isStatic_) {
+ if (!bce_->emit1(JSOp::Dup2)) {
+ // [stack] CTOR HOMEOBJ CTOR HOMEOBJ
+ return false;
+ }
+ if (!bce_->emit1(JSOp::Pop)) {
+ // [stack] CTOR HOMEOBJ CTOR
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool PropertyEmitter::prepareForPrivateMethod() {
+ MOZ_ASSERT(propertyState_ == PropertyState::Start ||
+ propertyState_ == PropertyState::Init);
+ MOZ_ASSERT(isClass_);
+
+ isStatic_ = false;
+ isIndexOrComputed_ = false;
+
+#ifdef DEBUG
+ propertyState_ = PropertyState::PrivateMethodValue;
+#endif
+ return true;
+}
+
+bool PropertyEmitter::prepareForPrivateStaticMethod(uint32_t keyPos) {
+ MOZ_ASSERT(propertyState_ == PropertyState::Start ||
+ propertyState_ == PropertyState::Init);
+ MOZ_ASSERT(isClass_);
+
+ // [stack] CTOR OBJ
+
+ if (!prepareForProp(keyPos,
+ /* isStatic_ = */ true,
+ /* isIndexOrComputed = */ true)) {
+ // [stack] CTOR OBJ CTOR
+ return false;
+ }
+
+#ifdef DEBUG
+ propertyState_ = PropertyState::PrivateStaticMethod;
+#endif
+ return true;
+}
+
+bool PropertyEmitter::prepareForPropValue(uint32_t keyPos, Kind kind) {
+ MOZ_ASSERT(propertyState_ == PropertyState::Start ||
+ propertyState_ == PropertyState::Init);
+
+ // [stack] CTOR? OBJ
+
+ if (!prepareForProp(keyPos,
+ /* isStatic_ = */ kind == Kind::Static,
+ /* isIndexOrComputed = */ false)) {
+ // [stack] CTOR? OBJ CTOR?
+ return false;
+ }
+
+#ifdef DEBUG
+ propertyState_ = PropertyState::PropValue;
+#endif
+ return true;
+}
+
+bool PropertyEmitter::prepareForIndexPropKey(uint32_t keyPos, Kind kind) {
+ MOZ_ASSERT(propertyState_ == PropertyState::Start ||
+ propertyState_ == PropertyState::Init);
+
+ // [stack] CTOR? OBJ
+
+ if (!prepareForProp(keyPos,
+ /* isStatic_ = */ kind == Kind::Static,
+ /* isIndexOrComputed = */ true)) {
+ // [stack] CTOR? OBJ CTOR?
+ return false;
+ }
+
+#ifdef DEBUG
+ propertyState_ = PropertyState::IndexKey;
+#endif
+ return true;
+}
+
+bool PropertyEmitter::prepareForIndexPropValue() {
+ MOZ_ASSERT(propertyState_ == PropertyState::IndexKey);
+
+ // [stack] CTOR? OBJ CTOR? KEY
+
+#ifdef DEBUG
+ propertyState_ = PropertyState::IndexValue;
+#endif
+ return true;
+}
+
+bool PropertyEmitter::prepareForComputedPropKey(uint32_t keyPos, Kind kind) {
+ MOZ_ASSERT(propertyState_ == PropertyState::Start ||
+ propertyState_ == PropertyState::Init);
+
+ // [stack] CTOR? OBJ
+
+ if (!prepareForProp(keyPos,
+ /* isStatic_ = */ kind == Kind::Static,
+ /* isIndexOrComputed = */ true)) {
+ // [stack] CTOR? OBJ CTOR?
+ return false;
+ }
+
+#ifdef DEBUG
+ propertyState_ = PropertyState::ComputedKey;
+#endif
+ return true;
+}
+
+bool PropertyEmitter::prepareForComputedPropValue() {
+ MOZ_ASSERT(propertyState_ == PropertyState::ComputedKey);
+
+ // [stack] CTOR? OBJ CTOR? KEY
+
+ if (!bce_->emit1(JSOp::ToPropertyKey)) {
+ // [stack] CTOR? OBJ CTOR? KEY
+ return false;
+ }
+
+#ifdef DEBUG
+ propertyState_ = PropertyState::ComputedValue;
+#endif
+ return true;
+}
+
+bool PropertyEmitter::emitInitHomeObject() {
+ MOZ_ASSERT(propertyState_ == PropertyState::PropValue ||
+ propertyState_ == PropertyState::PrivateMethodValue ||
+ propertyState_ == PropertyState::PrivateStaticMethod ||
+ propertyState_ == PropertyState::IndexValue ||
+ propertyState_ == PropertyState::ComputedValue);
+
+ // [stack] CTOR? HOMEOBJ CTOR? KEY? FUN
+
+ // There are the following values on the stack conditionally, between
+ // HOMEOBJ and FUN:
+ // * the 2nd CTOR if isStatic_
+ // * KEY if isIndexOrComputed_
+ //
+ // JSOp::InitHomeObject uses one of the following:
+ // * HOMEOBJ if !isStatic_
+ // (`super.foo` points the super prototype property)
+ // * the 2nd CTOR if isStatic_
+ // (`super.foo` points the super constructor property)
+ if (!bce_->emitDupAt(1 + isIndexOrComputed_)) {
+ // [stack] # non-static method
+ // [stack] CTOR? HOMEOBJ CTOR KEY? FUN CTOR
+ // [stack] # static method
+ // [stack] CTOR? HOMEOBJ KEY? FUN HOMEOBJ
+ return false;
+ }
+ if (!bce_->emit1(JSOp::InitHomeObject)) {
+ // [stack] CTOR? HOMEOBJ CTOR? KEY? FUN
+ return false;
+ }
+
+#ifdef DEBUG
+ if (propertyState_ == PropertyState::PropValue) {
+ propertyState_ = PropertyState::InitHomeObj;
+ } else if (propertyState_ == PropertyState::PrivateMethodValue) {
+ propertyState_ = PropertyState::InitHomeObjForPrivateMethod;
+ } else if (propertyState_ == PropertyState::PrivateStaticMethod) {
+ propertyState_ = PropertyState::InitHomeObjForPrivateStaticMethod;
+ } else if (propertyState_ == PropertyState::IndexValue) {
+ propertyState_ = PropertyState::InitHomeObjForIndex;
+ } else {
+ propertyState_ = PropertyState::InitHomeObjForComputed;
+ }
+#endif
+ return true;
+}
+
+bool PropertyEmitter::emitInit(AccessorType accessorType,
+ TaggedParserAtomIndex key) {
+ switch (accessorType) {
+ case AccessorType::None:
+ return emitInit(isClass_ ? JSOp::InitHiddenProp : JSOp::InitProp, key);
+ case AccessorType::Getter:
+ return emitInit(
+ isClass_ ? JSOp::InitHiddenPropGetter : JSOp::InitPropGetter, key);
+ case AccessorType::Setter:
+ return emitInit(
+ isClass_ ? JSOp::InitHiddenPropSetter : JSOp::InitPropSetter, key);
+ }
+ MOZ_CRASH("Invalid op");
+}
+
+bool PropertyEmitter::emitInitIndexOrComputed(AccessorType accessorType) {
+ switch (accessorType) {
+ case AccessorType::None:
+ return emitInitIndexOrComputed(isClass_ ? JSOp::InitHiddenElem
+ : JSOp::InitElem);
+ case AccessorType::Getter:
+ return emitInitIndexOrComputed(isClass_ ? JSOp::InitHiddenElemGetter
+ : JSOp::InitElemGetter);
+ case AccessorType::Setter:
+ return emitInitIndexOrComputed(isClass_ ? JSOp::InitHiddenElemSetter
+ : JSOp::InitElemSetter);
+ }
+ MOZ_CRASH("Invalid op");
+}
+
+bool PropertyEmitter::emitPrivateStaticMethod(AccessorType accessorType) {
+ MOZ_ASSERT(isClass_);
+
+ switch (accessorType) {
+ case AccessorType::None:
+ return emitInitIndexOrComputed(JSOp::InitLockedElem);
+ case AccessorType::Getter:
+ return emitInitIndexOrComputed(JSOp::InitHiddenElemGetter);
+ case AccessorType::Setter:
+ return emitInitIndexOrComputed(JSOp::InitHiddenElemSetter);
+ }
+ MOZ_CRASH("Invalid op");
+}
+
+bool PropertyEmitter::emitInit(JSOp op, TaggedParserAtomIndex key) {
+ MOZ_ASSERT(propertyState_ == PropertyState::PropValue ||
+ propertyState_ == PropertyState::InitHomeObj);
+
+ MOZ_ASSERT(op == JSOp::InitProp || op == JSOp::InitHiddenProp ||
+ op == JSOp::InitPropGetter || op == JSOp::InitHiddenPropGetter ||
+ op == JSOp::InitPropSetter || op == JSOp::InitHiddenPropSetter);
+
+ // [stack] CTOR? OBJ CTOR? VAL
+
+ if (!bce_->emitAtomOp(op, key)) {
+ // [stack] CTOR? OBJ CTOR?
+ return false;
+ }
+
+ if (!emitPopClassConstructor()) {
+ return false;
+ }
+
+#ifdef DEBUG
+ propertyState_ = PropertyState::Init;
+#endif
+ return true;
+}
+
+bool PropertyEmitter::skipInit() {
+ MOZ_ASSERT(propertyState_ == PropertyState::PrivateMethodValue ||
+ propertyState_ == PropertyState::InitHomeObjForPrivateMethod);
+#ifdef DEBUG
+ propertyState_ = PropertyState::Init;
+#endif
+ return true;
+}
+
+bool PropertyEmitter::emitInitIndexOrComputed(JSOp op) {
+ MOZ_ASSERT(propertyState_ == PropertyState::IndexValue ||
+ propertyState_ == PropertyState::InitHomeObjForIndex ||
+ propertyState_ == PropertyState::ComputedValue ||
+ propertyState_ == PropertyState::InitHomeObjForComputed ||
+ propertyState_ == PropertyState::PrivateStaticMethod ||
+ propertyState_ ==
+ PropertyState::InitHomeObjForPrivateStaticMethod);
+
+ MOZ_ASSERT(op == JSOp::InitElem || op == JSOp::InitHiddenElem ||
+ op == JSOp::InitLockedElem || op == JSOp::InitElemGetter ||
+ op == JSOp::InitHiddenElemGetter || op == JSOp::InitElemSetter ||
+ op == JSOp::InitHiddenElemSetter);
+
+ // [stack] CTOR? OBJ CTOR? KEY VAL
+
+ if (!bce_->emit1(op)) {
+ // [stack] CTOR? OBJ CTOR?
+ return false;
+ }
+
+ if (!emitPopClassConstructor()) {
+ return false;
+ }
+
+#ifdef DEBUG
+ propertyState_ = PropertyState::Init;
+#endif
+ return true;
+}
+
+bool PropertyEmitter::emitPopClassConstructor() {
+ if (isStatic_) {
+ // [stack] CTOR HOMEOBJ CTOR
+
+ if (!bce_->emit1(JSOp::Pop)) {
+ // [stack] CTOR HOMEOBJ
+ return false;
+ }
+ }
+
+ return true;
+}
+
+ObjectEmitter::ObjectEmitter(BytecodeEmitter* bce) : PropertyEmitter(bce) {}
+
+bool ObjectEmitter::emitObject(size_t propertyCount) {
+ MOZ_ASSERT(propertyState_ == PropertyState::Start);
+ MOZ_ASSERT(objectState_ == ObjectState::Start);
+
+ // [stack]
+
+ // Emit code for {p:a, '%q':b, 2:c} that is equivalent to constructing
+ // a new object and defining (in source order) each property on the object
+ // (or mutating the object's [[Prototype]], in the case of __proto__).
+ if (!bce_->emit1(JSOp::NewInit)) {
+ // [stack] OBJ
+ return false;
+ }
+
+#ifdef DEBUG
+ objectState_ = ObjectState::Object;
+#endif
+ return true;
+}
+
+bool ObjectEmitter::emitObjectWithTemplateOnStack() {
+ MOZ_ASSERT(propertyState_ == PropertyState::Start);
+ MOZ_ASSERT(objectState_ == ObjectState::Start);
+
+#ifdef DEBUG
+ objectState_ = ObjectState::Object;
+#endif
+ return true;
+}
+
+bool ObjectEmitter::emitEnd() {
+ MOZ_ASSERT(propertyState_ == PropertyState::Start ||
+ propertyState_ == PropertyState::Init);
+ MOZ_ASSERT(objectState_ == ObjectState::Object);
+
+ // [stack] OBJ
+
+#ifdef DEBUG
+ objectState_ = ObjectState::End;
+#endif
+ return true;
+}
+
+AutoSaveLocalStrictMode::AutoSaveLocalStrictMode(SharedContext* sc) : sc_(sc) {
+ savedStrictness_ = sc_->setLocalStrictMode(true);
+}
+
+AutoSaveLocalStrictMode::~AutoSaveLocalStrictMode() {
+ if (sc_) {
+ restore();
+ }
+}
+
+void AutoSaveLocalStrictMode::restore() {
+ MOZ_ALWAYS_TRUE(sc_->setLocalStrictMode(savedStrictness_));
+ sc_ = nullptr;
+}
+
+ClassEmitter::ClassEmitter(BytecodeEmitter* bce)
+ : PropertyEmitter(bce), strictMode_(bce->sc) {
+ isClass_ = true;
+}
+
+bool ClassEmitter::emitScope(LexicalScope::ParserData* scopeBindings) {
+ MOZ_ASSERT(propertyState_ == PropertyState::Start);
+ MOZ_ASSERT(classState_ == ClassState::Start);
+
+ tdzCache_.emplace(bce_);
+
+ innerScope_.emplace(bce_);
+ if (!innerScope_->enterLexical(bce_, ScopeKind::Lexical, scopeBindings)) {
+ return false;
+ }
+
+#ifdef DEBUG
+ classState_ = ClassState::Scope;
+#endif
+
+ return true;
+}
+
+bool ClassEmitter::emitBodyScope(ClassBodyScope::ParserData* scopeBindings) {
+ MOZ_ASSERT(propertyState_ == PropertyState::Start);
+ MOZ_ASSERT(classState_ == ClassState::Start ||
+ classState_ == ClassState::Scope);
+
+ bodyTdzCache_.emplace(bce_);
+
+ bodyScope_.emplace(bce_);
+ if (!bodyScope_->enterClassBody(bce_, ScopeKind::ClassBody, scopeBindings)) {
+ return false;
+ }
+
+#ifdef DEBUG
+ classState_ = ClassState::BodyScope;
+#endif
+
+ return true;
+}
+
+bool ClassEmitter::emitClass(TaggedParserAtomIndex name,
+ TaggedParserAtomIndex nameForAnonymousClass,
+ bool hasNameOnStack) {
+ MOZ_ASSERT(propertyState_ == PropertyState::Start);
+ MOZ_ASSERT(classState_ == ClassState::Start ||
+ classState_ == ClassState::Scope ||
+ classState_ == ClassState::BodyScope);
+ MOZ_ASSERT_IF(nameForAnonymousClass || hasNameOnStack, !name);
+ MOZ_ASSERT(!(nameForAnonymousClass && hasNameOnStack));
+
+ // [stack]
+
+ name_ = name;
+ nameForAnonymousClass_ = nameForAnonymousClass;
+ hasNameOnStack_ = hasNameOnStack;
+ isDerived_ = false;
+
+ if (!bce_->emit1(JSOp::NewInit)) {
+ // [stack] HOMEOBJ
+ return false;
+ }
+
+#ifdef DEBUG
+ classState_ = ClassState::Class;
+#endif
+ return true;
+}
+
+bool ClassEmitter::emitDerivedClass(TaggedParserAtomIndex name,
+ TaggedParserAtomIndex nameForAnonymousClass,
+ bool hasNameOnStack) {
+ MOZ_ASSERT(propertyState_ == PropertyState::Start);
+ MOZ_ASSERT(classState_ == ClassState::Start ||
+ classState_ == ClassState::Scope ||
+ classState_ == ClassState::BodyScope);
+ MOZ_ASSERT_IF(nameForAnonymousClass || hasNameOnStack, !name);
+ MOZ_ASSERT(!nameForAnonymousClass || !hasNameOnStack);
+
+ // [stack] HERITAGE
+
+ name_ = name;
+ nameForAnonymousClass_ = nameForAnonymousClass;
+ hasNameOnStack_ = hasNameOnStack;
+ isDerived_ = true;
+
+ InternalIfEmitter ifThenElse(bce_);
+
+ // Heritage must be null or a non-generator constructor
+ if (!bce_->emit1(JSOp::CheckClassHeritage)) {
+ // [stack] HERITAGE
+ return false;
+ }
+
+ // [IF] (heritage !== null)
+ if (!bce_->emit1(JSOp::Dup)) {
+ // [stack] HERITAGE HERITAGE
+ return false;
+ }
+ if (!bce_->emit1(JSOp::Null)) {
+ // [stack] HERITAGE HERITAGE NULL
+ return false;
+ }
+ if (!bce_->emit1(JSOp::StrictNe)) {
+ // [stack] HERITAGE NE
+ return false;
+ }
+
+ // [THEN] funProto = heritage, objProto = heritage.prototype
+ if (!ifThenElse.emitThenElse()) {
+ return false;
+ }
+ if (!bce_->emit1(JSOp::Dup)) {
+ // [stack] HERITAGE HERITAGE
+ return false;
+ }
+ if (!bce_->emitAtomOp(JSOp::GetProp,
+ TaggedParserAtomIndex::WellKnown::prototype())) {
+ // [stack] HERITAGE PROTO
+ return false;
+ }
+
+ // [ELSE] funProto = %FunctionPrototype%, objProto = null
+ if (!ifThenElse.emitElse()) {
+ return false;
+ }
+ if (!bce_->emit1(JSOp::Pop)) {
+ // [stack]
+ return false;
+ }
+ if (!bce_->emitBuiltinObject(BuiltinObjectKind::FunctionPrototype)) {
+ // [stack] PROTO
+ return false;
+ }
+ if (!bce_->emit1(JSOp::Null)) {
+ // [stack] PROTO NULL
+ return false;
+ }
+
+ // [ENDIF]
+ if (!ifThenElse.emitEnd()) {
+ return false;
+ }
+
+ if (!bce_->emit1(JSOp::ObjWithProto)) {
+ // [stack] HERITAGE HOMEOBJ
+ return false;
+ }
+ if (!bce_->emit1(JSOp::Swap)) {
+ // [stack] HOMEOBJ HERITAGE
+ return false;
+ }
+
+#ifdef DEBUG
+ classState_ = ClassState::Class;
+#endif
+ return true;
+}
+
+bool ClassEmitter::emitInitConstructor(bool needsHomeObject) {
+ MOZ_ASSERT(propertyState_ == PropertyState::Start);
+ MOZ_ASSERT(classState_ == ClassState::Class ||
+ classState_ == ClassState::InstanceMemberInitializersEnd);
+
+ // [stack] HOMEOBJ CTOR
+
+ if (needsHomeObject) {
+ if (!bce_->emitDupAt(1)) {
+ // [stack] HOMEOBJ CTOR HOMEOBJ
+ return false;
+ }
+ if (!bce_->emit1(JSOp::InitHomeObject)) {
+ // [stack] HOMEOBJ CTOR
+ return false;
+ }
+ }
+
+ if (!initProtoAndCtor()) {
+ // [stack] CTOR HOMEOBJ
+ return false;
+ }
+
+#ifdef DEBUG
+ classState_ = ClassState::InitConstructor;
+#endif
+ return true;
+}
+
+bool ClassEmitter::initProtoAndCtor() {
+ // [stack] NAME? HOMEOBJ CTOR
+
+ if (hasNameOnStack_) {
+ if (!bce_->emitDupAt(2)) {
+ // [stack] NAME HOMEOBJ CTOR NAME
+ return false;
+ }
+ if (!bce_->emit2(JSOp::SetFunName, uint8_t(FunctionPrefixKind::None))) {
+ // [stack] NAME HOMEOBJ CTOR
+ return false;
+ }
+ }
+
+ if (!bce_->emit1(JSOp::Swap)) {
+ // [stack] NAME? CTOR HOMEOBJ
+ return false;
+ }
+ if (!bce_->emit1(JSOp::Dup2)) {
+ // [stack] NAME? CTOR HOMEOBJ CTOR HOMEOBJ
+ return false;
+ }
+ if (!bce_->emitAtomOp(JSOp::InitLockedProp,
+ TaggedParserAtomIndex::WellKnown::prototype())) {
+ // [stack] NAME? CTOR HOMEOBJ CTOR
+ return false;
+ }
+ if (!bce_->emitAtomOp(JSOp::InitHiddenProp,
+ TaggedParserAtomIndex::WellKnown::constructor())) {
+ // [stack] NAME? CTOR HOMEOBJ
+ return false;
+ }
+
+ return true;
+}
+
+bool ClassEmitter::prepareForMemberInitializers(size_t numInitializers,
+ bool isStatic) {
+ MOZ_ASSERT_IF(!isStatic, classState_ == ClassState::Class);
+ MOZ_ASSERT_IF(isStatic, classState_ == ClassState::InitConstructor);
+ MOZ_ASSERT(memberState_ == MemberState::Start);
+
+ // .initializers is a variable that stores an array of lambdas containing
+ // code (the initializer) for each field. Upon an object's construction,
+ // these lambdas will be called, defining the values.
+ auto initializers =
+ isStatic ? TaggedParserAtomIndex::WellKnown::dotStaticInitializers()
+ : TaggedParserAtomIndex::WellKnown::dotInitializers();
+ initializersAssignment_.emplace(bce_, initializers,
+ NameOpEmitter::Kind::Initialize);
+ if (!initializersAssignment_->prepareForRhs()) {
+ return false;
+ }
+
+ if (!bce_->emitUint32Operand(JSOp::NewArray, numInitializers)) {
+ // [stack] ARRAY
+ return false;
+ }
+
+ initializerIndex_ = 0;
+#ifdef DEBUG
+ if (isStatic) {
+ classState_ = ClassState::StaticMemberInitializers;
+ } else {
+ classState_ = ClassState::InstanceMemberInitializers;
+ }
+ numInitializers_ = numInitializers;
+#endif
+ return true;
+}
+
+bool ClassEmitter::prepareForMemberInitializer() {
+ MOZ_ASSERT(classState_ == ClassState::InstanceMemberInitializers ||
+ classState_ == ClassState::StaticMemberInitializers);
+ MOZ_ASSERT(memberState_ == MemberState::Start);
+
+#ifdef DEBUG
+ memberState_ = MemberState::Initializer;
+#endif
+ return true;
+}
+
+bool ClassEmitter::emitMemberInitializerHomeObject(bool isStatic) {
+ MOZ_ASSERT(memberState_ == MemberState::Initializer);
+ // [stack] OBJ HERITAGE? ARRAY METHOD
+ // or:
+ // [stack] CTOR HOMEOBJ ARRAY METHOD
+
+ if (isStatic) {
+ if (!bce_->emitDupAt(3)) {
+ // [stack] CTOR HOMEOBJ ARRAY METHOD CTOR
+ return false;
+ }
+ } else {
+ if (!bce_->emitDupAt(isDerived_ ? 3 : 2)) {
+ // [stack] OBJ HERITAGE? ARRAY METHOD OBJ
+ return false;
+ }
+ }
+ if (!bce_->emit1(JSOp::InitHomeObject)) {
+ // [stack] OBJ HERITAGE? ARRAY METHOD
+ // or:
+ // [stack] CTOR HOMEOBJ ARRAY METHOD
+ return false;
+ }
+
+#ifdef DEBUG
+ memberState_ = MemberState::InitializerWithHomeObject;
+#endif
+ return true;
+}
+
+bool ClassEmitter::emitStoreMemberInitializer() {
+ MOZ_ASSERT(memberState_ == MemberState::Initializer ||
+ memberState_ == MemberState::InitializerWithHomeObject);
+ MOZ_ASSERT(initializerIndex_ < numInitializers_);
+ // [stack] HOMEOBJ HERITAGE? ARRAY METHOD
+
+ if (!bce_->emitUint32Operand(JSOp::InitElemArray, initializerIndex_)) {
+ // [stack] HOMEOBJ HERITAGE? ARRAY
+ return false;
+ }
+
+ initializerIndex_++;
+#ifdef DEBUG
+ memberState_ = MemberState::Start;
+#endif
+ return true;
+}
+
+bool ClassEmitter::emitMemberInitializersEnd() {
+ MOZ_ASSERT(propertyState_ == PropertyState::Start ||
+ propertyState_ == PropertyState::Init);
+ MOZ_ASSERT(classState_ == ClassState::InstanceMemberInitializers ||
+ classState_ == ClassState::StaticMemberInitializers);
+ MOZ_ASSERT(memberState_ == MemberState::Start);
+ MOZ_ASSERT(initializerIndex_ == numInitializers_);
+
+ if (!initializersAssignment_->emitAssignment()) {
+ // [stack] HOMEOBJ HERITAGE? ARRAY
+ return false;
+ }
+ initializersAssignment_.reset();
+
+ if (!bce_->emit1(JSOp::Pop)) {
+ // [stack] HOMEOBJ HERITAGE?
+ return false;
+ }
+
+#ifdef DEBUG
+ if (classState_ == ClassState::InstanceMemberInitializers) {
+ classState_ = ClassState::InstanceMemberInitializersEnd;
+ } else {
+ classState_ = ClassState::StaticMemberInitializersEnd;
+ }
+#endif
+ return true;
+}
+
+bool ClassEmitter::emitBinding() {
+ MOZ_ASSERT(propertyState_ == PropertyState::Start ||
+ propertyState_ == PropertyState::Init);
+ MOZ_ASSERT(classState_ == ClassState::InitConstructor ||
+ classState_ == ClassState::InstanceMemberInitializersEnd ||
+ classState_ == ClassState::StaticMemberInitializersEnd);
+ // [stack] CTOR HOMEOBJ
+
+ if (!bce_->emit1(JSOp::Pop)) {
+ // [stack] CTOR
+ return false;
+ }
+
+ if (name_) {
+ MOZ_ASSERT(innerScope_.isSome());
+
+ if (!bce_->emitLexicalInitialization(name_)) {
+ // [stack] CTOR
+ return false;
+ }
+ }
+
+ // [stack] CTOR
+
+#ifdef DEBUG
+ classState_ = ClassState::BoundName;
+#endif
+ return true;
+}
+
+bool ClassEmitter::emitEnd(Kind kind) {
+ MOZ_ASSERT(classState_ == ClassState::BoundName);
+ // [stack] CTOR
+
+ if (bodyScope_.isSome()) {
+ MOZ_ASSERT(bodyTdzCache_.isSome());
+
+ if (!bodyScope_->leave(bce_)) {
+ return false;
+ }
+ bodyScope_.reset();
+ bodyTdzCache_.reset();
+ }
+
+ if (innerScope_.isSome()) {
+ MOZ_ASSERT(tdzCache_.isSome());
+
+ if (!innerScope_->leave(bce_)) {
+ return false;
+ }
+ innerScope_.reset();
+ tdzCache_.reset();
+ } else {
+ MOZ_ASSERT(kind == Kind::Expression);
+ MOZ_ASSERT(tdzCache_.isNothing());
+ }
+
+ if (kind == Kind::Declaration) {
+ MOZ_ASSERT(name_);
+
+ if (!bce_->emitLexicalInitialization(name_)) {
+ // [stack] CTOR
+ return false;
+ }
+ // Only class statements make outer bindings, and they do not leave
+ // themselves on the stack.
+ if (!bce_->emit1(JSOp::Pop)) {
+ // [stack]
+ return false;
+ }
+ }
+
+ // [stack] # class declaration
+ // [stack]
+ // [stack] # class expression
+ // [stack] CTOR
+
+ strictMode_.restore();
+
+#ifdef DEBUG
+ classState_ = ClassState::End;
+#endif
+ return true;
+}
diff --git a/js/src/frontend/ObjectEmitter.h b/js/src/frontend/ObjectEmitter.h
new file mode 100644
index 0000000000..67f11f57c3
--- /dev/null
+++ b/js/src/frontend/ObjectEmitter.h
@@ -0,0 +1,849 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef frontend_ObjectEmitter_h
+#define frontend_ObjectEmitter_h
+
+#include "mozilla/Attributes.h" // MOZ_STACK_CLASS, MOZ_ALWAYS_INLINE, MOZ_RAII
+#include "mozilla/Maybe.h" // Maybe
+
+#include <stddef.h> // size_t
+#include <stdint.h> // uint32_t
+
+#include "frontend/EmitterScope.h" // EmitterScope
+#include "frontend/NameOpEmitter.h" // NameOpEmitter
+#include "frontend/ParseNode.h" // AccessorType
+#include "frontend/ParserAtom.h" // TaggedParserAtomIndex
+#include "frontend/TDZCheckCache.h" // TDZCheckCache
+#include "vm/Opcodes.h" // JSOp
+#include "vm/Scope.h" // LexicalScope
+
+namespace js {
+
+namespace frontend {
+
+struct BytecodeEmitter;
+class SharedContext;
+
+// Class for emitting bytecode for object and class properties.
+// See ObjectEmitter and ClassEmitter for usage.
+class MOZ_STACK_CLASS PropertyEmitter {
+ public:
+ enum class Kind {
+ // Prototype property.
+ Prototype,
+
+ // Class static property.
+ Static
+ };
+
+ protected:
+ BytecodeEmitter* bce_;
+
+ // True if the object is class.
+ // Set by ClassEmitter.
+ bool isClass_ = false;
+
+ // True if the property is class static method.
+ bool isStatic_ = false;
+
+ // True if the property has computed or index key.
+ bool isIndexOrComputed_ = false;
+
+#ifdef DEBUG
+ // The state of this emitter.
+ //
+ // +-------+
+ // | Start |-+
+ // +-------+ |
+ // |
+ // +---------+
+ // |
+ // | +------------------------------------------------------------+
+ // | | |
+ // | | [normal property/method/accessor] |
+ // | v prepareForPropValue +-----------+ +------+ |
+ // +->+----------------------->| PropValue |-+ +->| Init |-+
+ // | +-----------+ | | +------+
+ // | | |
+ // | +-----------------------------------+ +-----------+
+ // | | |
+ // | +-+---------------------------------------+ |
+ // | | | |
+ // | | [method with super] | |
+ // | | emitInitHomeObject +-------------+ v |
+ // | +--------------------->| InitHomeObj |->+ |
+ // | +-------------+ | |
+ // | | |
+ // | +-------------------------------------- + |
+ // | | |
+ // | | emitInit |
+ // | +------------------------------------------------------>+
+ // | ^
+ // | [optimized private non-static method] |
+ // | prepareForPrivateMethod +--------------------+ |
+ // +---------------------------->| PrivateMethodValue |-+ |
+ // | +--------------------+ | |
+ // | | |
+ // | +-------------------------------------------------+ |
+ // | | |
+ // | +-+---------------------------------------------+ |
+ // | | | |
+ // | | [method with super | |
+ // | | emitInitHomeObject +-----------------+ v |
+ // | +--------------------->| InitHomeObjFor- |----+ |
+ // | | PrivateMethod | | |
+ // | +-----------------+ | |
+ // | | |
+ // | +---------------------------------------------+ |
+ // | | |
+ // | | skipInit |
+ // | +------------------------------------------------------>+
+ // | ^
+ // | [private static method] |
+ // | prepareForPrivateStaticMethod +---------------------+ |
+ // +--------------------------------->| PrivateStaticMethod |-+ |
+ // | +---------------------+ | |
+ // | | |
+ // | +-------------------------------------------------------+ |
+ // | | |
+ // | +-+-------------------------------------------------+ |
+ // | | | |
+ // | | [method with super | |
+ // | | emitInitHomeObject +---------------------+ v |
+ // | +--------------------->| InitHomeObjFor- |----+ |
+ // | | PrivateStaticMethod | | |
+ // | +---------------------+ | |
+ // | | |
+ // | +-----------------------------------------------+ |
+ // | | |
+ // | | emitPrivateStaticMethod |
+ // | +---------------------------------------------------->+
+ // | ^
+ // | [index property/method/accessor] |
+ // | prepareForIndexPropKey +----------+ |
+ // +-------------------------->| IndexKey |-+ |
+ // | +----------+ | |
+ // | | |
+ // | +-------------------------------------+ |
+ // | | |
+ // | | prepareForIndexPropValue +------------+ |
+ // | +------------------------->| IndexValue |-+ |
+ // | +------------+ | |
+ // | | |
+ // | +---------------------------------------+ |
+ // | | |
+ // | +-+--------------------------------------------------+ |
+ // | | | |
+ // | | [method with super] | |
+ // | | emitInitHomeObject +---------------------+ v |
+ // | +--------------------->| InitHomeObjForIndex |---->+ |
+ // | +---------------------+ | |
+ // | | |
+ // | +--------------------------------------------------+ |
+ // | | |
+ // | | emitInitIndexOrComputed |
+ // | +---------------------------------------------------->+
+ // | ^
+ // | [computed property/method/accessor] |
+ // | prepareForComputedPropKey +-------------+ |
+ // +----------------------------->| ComputedKey |-+ |
+ // | +-------------+ | |
+ // | | |
+ // | +-------------------------------------------+ |
+ // | | |
+ // | | prepareForComputedPropValue +---------------+ |
+ // | +---------------------------->| ComputedValue |-+ |
+ // | +---------------+ | |
+ // | | |
+ // | +---------------------------------------------+ |
+ // | | |
+ // | +-+--------------------------------------------------+ |
+ // | | | |
+ // | | [method with super] | |
+ // | | emitInitHomeObject +------------------------+ v |
+ // | +--------------------->| InitHomeObjForComputed |->+ |
+ // | +------------------------+ | |
+ // | | |
+ // | +--------------------------------------------------+ |
+ // | | |
+ // | | emitInitIndexOrComputed |
+ // | +---------------------------------------------------->+
+ // | ^
+ // | |
+ // | [__proto__] |
+ // | prepareForProtoValue +------------+ emitMutateProto |
+ // +------------------------>| ProtoValue |-------------------->+
+ // | +------------+ ^
+ // | |
+ // | [...prop] |
+ // | prepareForSpreadOperand +---------------+ emitSpread |
+ // +-------------------------->| SpreadOperand |----------------+
+ // +---------------+
+ enum class PropertyState {
+ // The initial state.
+ Start,
+
+ // After calling prepareForPropValue.
+ PropValue,
+
+ // After calling emitInitHomeObject, from PropValue.
+ InitHomeObj,
+
+ // After calling prepareForPrivateMethod.
+ PrivateMethodValue,
+
+ // After calling emitInitHomeObject, from PrivateMethod.
+ InitHomeObjForPrivateMethod,
+
+ // After calling prepareForPrivateStaticMethod.
+ PrivateStaticMethod,
+
+ // After calling emitInitHomeObject, from PrivateStaticMethod.
+ InitHomeObjForPrivateStaticMethod,
+
+ // After calling prepareForIndexPropKey.
+ IndexKey,
+
+ // prepareForIndexPropValue.
+ IndexValue,
+
+ // After calling emitInitHomeObject, from IndexValue.
+ InitHomeObjForIndex,
+
+ // After calling prepareForComputedPropKey.
+ ComputedKey,
+
+ // prepareForComputedPropValue.
+ ComputedValue,
+
+ // After calling emitInitHomeObject, from ComputedValue.
+ InitHomeObjForComputed,
+
+ // After calling prepareForProtoValue.
+ ProtoValue,
+
+ // After calling prepareForSpreadOperand.
+ SpreadOperand,
+
+ // After calling one of emitInit, emitInitIndexOrComputed, emitMutateProto,
+ // or emitSpread.
+ Init,
+ };
+ PropertyState propertyState_ = PropertyState::Start;
+#endif
+
+ public:
+ explicit PropertyEmitter(BytecodeEmitter* bce);
+
+ // Parameters are the offset in the source code for each character below:
+ //
+ // { __proto__: protoValue }
+ // ^
+ // |
+ // keyPos
+ [[nodiscard]] bool prepareForProtoValue(uint32_t keyPos);
+ [[nodiscard]] bool emitMutateProto();
+
+ // { ...obj }
+ // ^
+ // |
+ // spreadPos
+ [[nodiscard]] bool prepareForSpreadOperand(uint32_t spreadPos);
+ [[nodiscard]] bool emitSpread();
+
+ // { key: value }
+ // ^
+ // |
+ // keyPos
+ [[nodiscard]] bool prepareForPropValue(uint32_t keyPos, Kind kind);
+
+ [[nodiscard]] bool prepareForPrivateMethod();
+
+ [[nodiscard]] bool prepareForPrivateStaticMethod(uint32_t keyPos);
+
+ // { 1: value }
+ // ^
+ // |
+ // keyPos
+ [[nodiscard]] bool prepareForIndexPropKey(uint32_t keyPos, Kind kind);
+ [[nodiscard]] bool prepareForIndexPropValue();
+
+ // { [ key ]: value }
+ // ^
+ // |
+ // keyPos
+ [[nodiscard]] bool prepareForComputedPropKey(uint32_t keyPos, Kind kind);
+ [[nodiscard]] bool prepareForComputedPropValue();
+
+ [[nodiscard]] bool emitInitHomeObject();
+
+ // @param key
+ // Property key
+ [[nodiscard]] bool emitInit(AccessorType accessorType,
+ TaggedParserAtomIndex key);
+
+ [[nodiscard]] bool emitInitIndexOrComputed(AccessorType accessorType);
+
+ [[nodiscard]] bool emitPrivateStaticMethod(AccessorType accessorType);
+
+ [[nodiscard]] bool skipInit();
+
+ private:
+ [[nodiscard]] MOZ_ALWAYS_INLINE bool prepareForProp(uint32_t keyPos,
+ bool isStatic,
+ bool isComputed);
+
+ // @param op
+ // Opcode for initializing property
+ // @param key
+ // Atom of the property if the property key is not computed
+ [[nodiscard]] bool emitInit(JSOp op, TaggedParserAtomIndex key);
+ [[nodiscard]] bool emitInitIndexOrComputed(JSOp op);
+
+ [[nodiscard]] bool emitPopClassConstructor();
+};
+
+// Class for emitting bytecode for object literal.
+//
+// Usage: (check for the return value is omitted for simplicity)
+//
+// `{}`
+// ObjectEmitter oe(this);
+// oe.emitObject(0);
+// oe.emitEnd();
+//
+// `{ prop: 10 }`
+// ObjectEmitter oe(this);
+// oe.emitObject(1);
+//
+// oe.prepareForPropValue(offset_of_prop);
+// emit(10);
+// oe.emitInitProp(atom_of_prop);
+//
+// oe.emitEnd();
+//
+// `{ prop: function() {} }`, when property value is anonymous function
+// ObjectEmitter oe(this);
+// oe.emitObject(1);
+//
+// oe.prepareForPropValue(offset_of_prop);
+// emit(function);
+// oe.emitInitProp(atom_of_prop);
+//
+// oe.emitEnd();
+//
+// `{ get prop() { ... }, set prop(v) { ... } }`
+// ObjectEmitter oe(this);
+// oe.emitObject(2);
+//
+// oe.prepareForPropValue(offset_of_prop);
+// emit(function_for_getter);
+// oe.emitInitGetter(atom_of_prop);
+//
+// oe.prepareForPropValue(offset_of_prop);
+// emit(function_for_setter);
+// oe.emitInitSetter(atom_of_prop);
+//
+// oe.emitEnd();
+//
+// `{ 1: 10, get 2() { ... }, set 3(v) { ... } }`
+// ObjectEmitter oe(this);
+// oe.emitObject(3);
+//
+// oe.prepareForIndexPropKey(offset_of_prop);
+// emit(1);
+// oe.prepareForIndexPropValue();
+// emit(10);
+// oe.emitInitIndexedProp();
+//
+// oe.prepareForIndexPropKey(offset_of_opening_bracket);
+// emit(2);
+// oe.prepareForIndexPropValue();
+// emit(function_for_getter);
+// oe.emitInitIndexGetter();
+//
+// oe.prepareForIndexPropKey(offset_of_opening_bracket);
+// emit(3);
+// oe.prepareForIndexPropValue();
+// emit(function_for_setter);
+// oe.emitInitIndexSetter();
+//
+// oe.emitEnd();
+//
+// `{ [prop1]: 10, get [prop2]() { ... }, set [prop3](v) { ... } }`
+// ObjectEmitter oe(this);
+// oe.emitObject(3);
+//
+// oe.prepareForComputedPropKey(offset_of_opening_bracket);
+// emit(prop1);
+// oe.prepareForComputedPropValue();
+// emit(10);
+// oe.emitInitComputedProp();
+//
+// oe.prepareForComputedPropKey(offset_of_opening_bracket);
+// emit(prop2);
+// oe.prepareForComputedPropValue();
+// emit(function_for_getter);
+// oe.emitInitComputedGetter();
+//
+// oe.prepareForComputedPropKey(offset_of_opening_bracket);
+// emit(prop3);
+// oe.prepareForComputedPropValue();
+// emit(function_for_setter);
+// oe.emitInitComputedSetter();
+//
+// oe.emitEnd();
+//
+// `{ __proto__: obj }`
+// ObjectEmitter oe(this);
+// oe.emitObject(1);
+// oe.prepareForProtoValue(offset_of___proto__);
+// emit(obj);
+// oe.emitMutateProto();
+// oe.emitEnd();
+//
+// `{ ...obj }`
+// ObjectEmitter oe(this);
+// oe.emitObject(1);
+// oe.prepareForSpreadOperand(offset_of_triple_dots);
+// emit(obj);
+// oe.emitSpread();
+// oe.emitEnd();
+//
+class MOZ_STACK_CLASS ObjectEmitter : public PropertyEmitter {
+ private:
+#ifdef DEBUG
+ // The state of this emitter.
+ //
+ // +-------+ emitObject +--------+
+ // | Start |----------->| Object |-+
+ // +-------+ +--------+ |
+ // |
+ // +-----------------------------+
+ // |
+ // | (do PropertyEmitter operation) emitEnd +-----+
+ // +-------------------------------+--------->| End |
+ // +-----+
+ enum class ObjectState {
+ // The initial state.
+ Start,
+
+ // After calling emitObject.
+ Object,
+
+ // After calling emitEnd.
+ End,
+ };
+ ObjectState objectState_ = ObjectState::Start;
+#endif
+
+ public:
+ explicit ObjectEmitter(BytecodeEmitter* bce);
+
+ [[nodiscard]] bool emitObject(size_t propertyCount);
+ // Same as `emitObject()`, but start with an empty template object already on
+ // the stack.
+ [[nodiscard]] bool emitObjectWithTemplateOnStack();
+ [[nodiscard]] bool emitEnd();
+};
+
+// Save and restore the strictness.
+// Used by class declaration/expression to temporarily enable strict mode.
+class MOZ_RAII AutoSaveLocalStrictMode {
+ SharedContext* sc_;
+ bool savedStrictness_;
+
+ public:
+ explicit AutoSaveLocalStrictMode(SharedContext* sc);
+ ~AutoSaveLocalStrictMode();
+
+ // Force restore the strictness now.
+ void restore();
+};
+
+// Class for emitting bytecode for JS class.
+//
+// Usage: (check for the return value is omitted for simplicity)
+//
+// `class { constructor() { ... } }`
+// ClassEmitter ce(this);
+// ce.emitScope(scopeBindings);
+// ce.emitClass(nullptr, nullptr, false);
+//
+// emit(function_for_constructor);
+// ce.emitInitConstructor(/* needsHomeObject = */ false);
+//
+// ce.emitEnd(ClassEmitter::Kind::Expression);
+//
+// `class X { constructor() { ... } }`
+// ClassEmitter ce(this);
+// ce.emitScope(scopeBindings);
+// ce.emitClass(atom_of_X, nullptr, false);
+//
+// emit(function_for_constructor);
+// ce.emitInitConstructor(/* needsHomeObject = */ false);
+//
+// ce.emitEnd(ClassEmitter::Kind::Expression);
+//
+// `class X extends Y { constructor() { ... } }`
+// ClassEmitter ce(this);
+// ce.emitScope(scopeBindings);
+//
+// emit(Y);
+// ce.emitDerivedClass(atom_of_X, nullptr, false);
+//
+// emit(function_for_constructor);
+// ce.emitInitConstructor(/* needsHomeObject = */ false);
+//
+// ce.emitEnd(ClassEmitter::Kind::Expression);
+//
+// `class X extends Y { constructor() { ... super.f(); ... } }`
+// ClassEmitter ce(this);
+// ce.emitScope(scopeBindings);
+//
+// emit(Y);
+// ce.emitDerivedClass(atom_of_X, nullptr, false);
+//
+// emit(function_for_constructor);
+// // pass true if constructor contains super.prop access
+// ce.emitInitConstructor(/* needsHomeObject = */ true);
+//
+// ce.emitEnd(ClassEmitter::Kind::Expression);
+//
+// `class X extends Y { field0 = expr0; ... }`
+// ClassEmitter ce(this);
+// ce.emitScope(scopeBindings);
+// emit(Y);
+// ce.emitDerivedClass(atom_of_X, nullptr, false);
+//
+// ce.prepareForMemberInitializers(fields.length());
+// for (auto field : fields) {
+// emit(field.initializer_method());
+// ce.emitStoreMemberInitializer();
+// }
+// ce.emitMemberInitializersEnd();
+//
+// emit(function_for_constructor);
+// ce.emitInitConstructor(/* needsHomeObject = */ false);
+// ce.emitEnd(ClassEmitter::Kind::Expression);
+//
+// `class X { field0 = super.method(); ... }`
+// // after emitClass/emitDerivedClass
+// ce.prepareForMemberInitializers(1);
+// for (auto field : fields) {
+// emit(field.initializer_method());
+// if (field.initializer_contains_super_or_eval()) {
+// ce.emitMemberInitializerHomeObject();
+// }
+// ce.emitStoreMemberInitializer();
+// }
+// ce.emitMemberInitializersEnd();
+//
+// `m() {}` in class
+// // after emitInitConstructor
+// ce.prepareForPropValue(offset_of_m);
+// emit(function_for_m);
+// ce.emitInitProp(atom_of_m);
+//
+// `m() { super.f(); }` in class
+// // after emitInitConstructor
+// ce.prepareForPropValue(offset_of_m);
+// emit(function_for_m);
+// ce.emitInitHomeObject();
+// ce.emitInitProp(atom_of_m);
+//
+// `async m() { super.f(); }` in class
+// // after emitInitConstructor
+// ce.prepareForPropValue(offset_of_m);
+// emit(function_for_m);
+// ce.emitInitHomeObject();
+// ce.emitInitProp(atom_of_m);
+//
+// `get p() { super.f(); }` in class
+// // after emitInitConstructor
+// ce.prepareForPropValue(offset_of_p);
+// emit(function_for_p);
+// ce.emitInitHomeObject();
+// ce.emitInitGetter(atom_of_m);
+//
+// `static m() {}` in class
+// // after emitInitConstructor
+// ce.prepareForPropValue(offset_of_m,
+// PropertyEmitter::Kind::Static);
+// emit(function_for_m);
+// ce.emitInitProp(atom_of_m);
+//
+// `static get [p]() { super.f(); }` in class
+// // after emitInitConstructor
+// ce.prepareForComputedPropValue(offset_of_m,
+// PropertyEmitter::Kind::Static);
+// emit(p);
+// ce.prepareForComputedPropValue();
+// emit(function_for_m);
+// ce.emitInitHomeObject();
+// ce.emitInitComputedGetter();
+//
+class MOZ_STACK_CLASS ClassEmitter : public PropertyEmitter {
+ public:
+ enum class Kind {
+ // Class expression.
+ Expression,
+
+ // Class declaration.
+ Declaration,
+ };
+
+ private:
+ // Pseudocode for class declarations:
+ //
+ // class extends BaseExpression {
+ // constructor() { ... }
+ // ...
+ // }
+ //
+ //
+ // if defined <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 |-+
+ // +-----------------+ |
+ // |
+ // |
+ // |
+ // +-----------------------------------------------------+
+ // |
+ // | prepareForMemberInitializers(isStatic = true)
+ // +---------------+
+ // | |
+ // | +--------v-----------------+
+ // | | StaticMemberInitializers |
+ // | +--------------------------+
+ // | |
+ // | | emitMemberInitializersEnd
+ // | |
+ // | +--------v--------------------+
+ // | | StaticMemberInitializersEnd |
+ // | +-----------------------------+
+ // | |
+ // +<--------------+
+ // |
+ // | (do PropertyEmitter operation)
+ // +--------------------------------+
+ // |
+ // +-------------+ emitBinding |
+ // | BoundName |<-----------------+
+ // +--+----------+
+ // |
+ // | emitEnd
+ // |
+ // +--v----+
+ // | End |
+ // +-------+
+ //
+ // clang-format on
+ enum class ClassState {
+ // The initial state.
+ Start,
+
+ // After calling emitScope.
+ Scope,
+
+ // After calling emitBodyScope.
+ BodyScope,
+
+ // After calling emitClass or emitDerivedClass.
+ Class,
+
+ // After calling emitInitConstructor.
+ InitConstructor,
+
+ // After calling prepareForMemberInitializers(isStatic = false).
+ InstanceMemberInitializers,
+
+ // After calling emitMemberInitializersEnd.
+ InstanceMemberInitializersEnd,
+
+ // After calling prepareForMemberInitializers(isStatic = true).
+ StaticMemberInitializers,
+
+ // After calling emitMemberInitializersEnd.
+ StaticMemberInitializersEnd,
+
+ // After calling emitBinding.
+ BoundName,
+
+ // After calling emitEnd.
+ End,
+ };
+ ClassState classState_ = ClassState::Start;
+
+ // The state of the members emitter.
+ //
+ // clang-format off
+ //
+ // +-------+
+ // | Start +<-----------------------------+
+ // +-------+ |
+ // | |
+ // | prepareForMemberInitializer | emitStoreMemberInitializer
+ // v |
+ // +-------------+ |
+ // | Initializer +------------------------->+
+ // +-------------+ |
+ // | |
+ // | emitMemberInitializerHomeObject |
+ // v |
+ // +---------------------------+ |
+ // | InitializerWithHomeObject +------------+
+ // +---------------------------+
+ //
+ // clang-format on
+ enum class MemberState {
+ // After calling prepareForMemberInitializers
+ // and 0 or more calls to emitStoreMemberInitializer.
+ Start,
+
+ // After calling prepareForMemberInitializer
+ Initializer,
+
+ // After calling emitMemberInitializerHomeObject
+ InitializerWithHomeObject,
+ };
+ MemberState memberState_ = MemberState::Start;
+
+ size_t numInitializers_ = 0;
+#endif
+
+ TaggedParserAtomIndex name_;
+ TaggedParserAtomIndex nameForAnonymousClass_;
+ bool hasNameOnStack_ = false;
+ mozilla::Maybe<NameOpEmitter> initializersAssignment_;
+ size_t initializerIndex_ = 0;
+
+ public:
+ explicit ClassEmitter(BytecodeEmitter* bce);
+
+ bool emitScope(LexicalScope::ParserData* scopeBindings);
+ bool emitBodyScope(ClassBodyScope::ParserData* scopeBindings);
+
+ // @param name
+ // Name of the class (nullptr if this is anonymous class)
+ // @param nameForAnonymousClass
+ // Statically inferred name of the class (only for anonymous classes)
+ // @param hasNameOnStack
+ // If true the name is on the stack (only for anonymous classes)
+ [[nodiscard]] bool emitClass(TaggedParserAtomIndex name,
+ TaggedParserAtomIndex nameForAnonymousClass,
+ bool hasNameOnStack);
+ [[nodiscard]] bool emitDerivedClass(
+ TaggedParserAtomIndex name, TaggedParserAtomIndex nameForAnonymousClass,
+ bool hasNameOnStack);
+
+ // @param needsHomeObject
+ // True if the constructor contains `super.foo`
+ [[nodiscard]] bool emitInitConstructor(bool needsHomeObject);
+
+ [[nodiscard]] bool prepareForMemberInitializers(size_t numInitializers,
+ bool isStatic);
+ [[nodiscard]] bool prepareForMemberInitializer();
+ [[nodiscard]] bool emitMemberInitializerHomeObject(bool isStatic);
+ [[nodiscard]] bool emitStoreMemberInitializer();
+ [[nodiscard]] bool emitMemberInitializersEnd();
+
+ [[nodiscard]] bool emitBinding();
+
+ [[nodiscard]] bool emitEnd(Kind kind);
+
+ private:
+ [[nodiscard]] bool initProtoAndCtor();
+};
+
+} /* namespace frontend */
+} /* namespace js */
+
+#endif /* frontend_ObjectEmitter_h */
diff --git a/js/src/frontend/OptionalEmitter.cpp b/js/src/frontend/OptionalEmitter.cpp
new file mode 100644
index 0000000000..6fe5924099
--- /dev/null
+++ b/js/src/frontend/OptionalEmitter.cpp
@@ -0,0 +1,142 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "frontend/OptionalEmitter.h"
+
+#include "frontend/BytecodeEmitter.h"
+#include "frontend/IfEmitter.h" // IfEmitter, InternalIfEmitter, CondEmitter
+#include "vm/Opcodes.h"
+
+using namespace js;
+using namespace js::frontend;
+
+OptionalEmitter::OptionalEmitter(BytecodeEmitter* bce, int32_t initialDepth)
+ : bce_(bce), tdzCache_(bce), initialDepth_(initialDepth) {}
+
+bool OptionalEmitter::emitJumpShortCircuit() {
+ MOZ_ASSERT(state_ == State::Start || state_ == State::ShortCircuit ||
+ state_ == State::ShortCircuitForCall);
+ MOZ_ASSERT(initialDepth_ + 1 == bce_->bytecodeSection().stackDepth());
+
+ if (!bce_->emit1(JSOp::IsNullOrUndefined)) {
+ // [stack] OBJ NULL-OR-UNDEF
+ return false;
+ }
+ if (!bce_->emitJump(JSOp::JumpIfTrue, &jumpShortCircuit_)) {
+ // [stack] OBJ
+ return false;
+ }
+
+#ifdef DEBUG
+ state_ = State::ShortCircuit;
+#endif
+ return true;
+}
+
+bool OptionalEmitter::emitJumpShortCircuitForCall() {
+ MOZ_ASSERT(state_ == State::Start || state_ == State::ShortCircuit ||
+ state_ == State::ShortCircuitForCall);
+ int32_t depth = bce_->bytecodeSection().stackDepth();
+ MOZ_ASSERT(initialDepth_ + 2 == depth);
+ if (!bce_->emit1(JSOp::Swap)) {
+ // [stack] THIS CALLEE
+ return false;
+ }
+
+ InternalIfEmitter ifEmitter(bce_);
+ if (!bce_->emit1(JSOp::IsNullOrUndefined)) {
+ // [stack] THIS CALLEE NULL-OR-UNDEF
+ return false;
+ }
+
+ if (!ifEmitter.emitThen()) {
+ // [stack] THIS CALLEE
+ return false;
+ }
+
+ if (!bce_->emit1(JSOp::Pop)) {
+ // [stack] THIS
+ return false;
+ }
+
+ if (!bce_->emitJump(JSOp::Goto, &jumpShortCircuit_)) {
+ // [stack] THIS
+ return false;
+ }
+
+ if (!ifEmitter.emitEnd()) {
+ return false;
+ }
+
+ bce_->bytecodeSection().setStackDepth(depth);
+
+ if (!bce_->emit1(JSOp::Swap)) {
+ // [stack] THIS CALLEE
+ return false;
+ }
+#ifdef DEBUG
+ state_ = State::ShortCircuitForCall;
+#endif
+ return true;
+}
+
+bool OptionalEmitter::emitOptionalJumpTarget(JSOp op,
+ Kind kind /* = Kind::Other */) {
+#ifdef DEBUG
+ int32_t depth = bce_->bytecodeSection().stackDepth();
+#endif
+ MOZ_ASSERT(state_ == State::ShortCircuit ||
+ state_ == State::ShortCircuitForCall);
+
+ // if we get to this point, it means that the optional chain did not short
+ // circuit, so we should skip the short circuiting bytecode.
+ if (!bce_->emitJump(JSOp::Goto, &jumpFinish_)) {
+ // [stack] RESULT
+ return false;
+ }
+
+ if (!bce_->emitJumpTargetAndPatch(jumpShortCircuit_)) {
+ // [stack] # if call
+ // [stack] THIS
+ // [stack] # otherwise
+ // [stack] OBJ
+ return false;
+ }
+
+ // reset stack depth to the depth when we jumped
+ bce_->bytecodeSection().setStackDepth(initialDepth_ + 1);
+
+ if (!bce_->emit1(JSOp::Pop)) {
+ // [stack]
+ return false;
+ }
+
+ if (!bce_->emit1(op)) {
+ // [stack] JSOP
+ return false;
+ }
+
+ if (kind == Kind::Reference) {
+ if (!bce_->emit1(op)) {
+ // [stack] JSOP JSOP
+ return false;
+ }
+ }
+
+ MOZ_ASSERT(depth == bce_->bytecodeSection().stackDepth());
+
+ if (!bce_->emitJumpTargetAndPatch(jumpFinish_)) {
+ // [stack] # if call
+ // [stack] CALLEE THIS
+ // [stack] # otherwise
+ // [stack] VAL
+ return false;
+ }
+#ifdef DEBUG
+ state_ = State::JumpEnd;
+#endif
+ return true;
+}
diff --git a/js/src/frontend/OptionalEmitter.h b/js/src/frontend/OptionalEmitter.h
new file mode 100644
index 0000000000..4a4caa05c3
--- /dev/null
+++ b/js/src/frontend/OptionalEmitter.h
@@ -0,0 +1,220 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef frontend_OptionalEmitter_h
+#define frontend_OptionalEmitter_h
+
+#include "mozilla/Attributes.h"
+
+#include "frontend/JumpList.h"
+#include "frontend/TDZCheckCache.h"
+
+namespace js {
+namespace frontend {
+
+struct BytecodeEmitter;
+
+// Class for emitting bytecode for optional expressions.
+//
+// Usage: (check for the return value is omitted for simplicity)
+//
+// `obj?.prop;`
+// OptionalEmitter oe(this);
+// PropOpEmitter poe(this,
+// PropOpEmitter::Kind::Get,
+// PropOpEmitter::ObjKind::Other);
+// poe.prepareForObj();
+// emit(obj);
+// oe.emitJumpShortCircuit();
+// poe.emitGet(atom_of_prop);
+// oe.emitOptionalJumpTarget(JSOp::Undefined);
+//
+// `delete obj?.prop;`
+// OptionalEmitter oe(this);
+// OptionalPropOpEmitter poe(this,
+// PropOpEmitter::Kind::Delete,
+// PropOpEmitter::ObjKind::Other);
+// poe.prepareForObj();
+// emit(obj);
+// oe.emitJumpShortCircuit();
+// poe.emitDelete(atom_of_prop);
+// oe.emitOptionalJumpTarget(JSOp:True);
+//
+// `obj?.[key];`
+// OptionalEmitter oe(this);
+// ElemOpEmitter eoe(this,
+// ElemOpEmitter::Kind::Get,
+// ElemOpEmitter::ObjKind::Other);
+// eoe.prepareForObj();
+// emit(obj);
+// oe.emitJumpShortCircuit();
+// eoe.prepareForKey();
+// emit(key);
+// eoe.emitGet();
+// oe.emitOptionalJumpTarget(JSOp::Undefined);
+//
+// `delete obj?.[key];`
+// OptionalEmitter oe(this);
+// ElemOpEmitter eoe(this,
+// ElemOpEmitter::Kind::Delete,
+// ElemOpEmitter::ObjKind::Other);
+// eoe.prepareForObj();
+// emit(obj);
+// oe.emitJumpShortCircuit();
+// eoe.prepareForKey();
+// emit(key);
+// eoe.emitDelete();
+// oe.emitOptionalJumpTarget(JSOp::True);
+//
+// `print?.(arg);`
+// OptionalEmitter oe(this);
+// CallOrNewEmitter cone(this, JSOp::Call,
+// CallOrNewEmitter::ArgumentsKind::Other,
+// ValueUsage::WantValue);
+// cone.emitNameCallee(print);
+// cone.emitThis();
+// oe.emitShortCircuitForCall();
+// cone.prepareForNonSpreadArguments();
+// emit(arg);
+// cone.emitEnd(1, offset_of_callee);
+// oe.emitOptionalJumpTarget(JSOp::Undefined);
+//
+// `callee.prop?.(arg1, arg2);`
+// OptionalEmitter oe(this);
+// CallOrNewEmitter cone(this, JSOp::Call,
+// CallOrNewEmitter::ArgumentsKind::Other,
+// ValueUsage::WantValue);
+// PropOpEmitter& poe = cone.prepareForPropCallee(false);
+// ... emit `callee.prop` with `poe` here...
+// cone.emitThis();
+// oe.emitShortCircuitForCall();
+// cone.prepareForNonSpreadArguments();
+// emit(arg1);
+// emit(arg2);
+// cone.emitEnd(2, offset_of_callee);
+// oe.emitOptionalJumpTarget(JSOp::Undefined);
+//
+// `callee[key]?.(arg);`
+// OptionalEmitter oe(this);
+// CallOrNewEmitter cone(this, JSOp::Call,
+// CallOrNewEmitter::ArgumentsKind::Other,
+// ValueUsage::WantValue);
+// ElemOpEmitter& eoe = cone.prepareForElemCallee(false);
+// ... emit `callee[key]` with `eoe` here...
+// cone.emitThis();
+// oe.emitShortCircuitForCall();
+// cone.prepareForNonSpreadArguments();
+// emit(arg);
+// cone.emitEnd(1, offset_of_callee);
+// oe.emitOptionalJumpTarget(JSOp::Undefined);
+//
+// `(function() { ... })?.(arg);`
+// OptionalEmitter oe(this);
+// CallOrNewEmitter cone(this, JSOp::Call,
+// CallOrNewEmitter::ArgumentsKind::Other,
+// ValueUsage::WantValue);
+// cone.prepareForFunctionCallee();
+// emit(function);
+// cone.emitThis();
+// oe.emitShortCircuitForCall();
+// cone.prepareForNonSpreadArguments();
+// emit(arg);
+// cone.emitEnd(1, offset_of_callee);
+// oe.emitOptionalJumpTarget(JSOp::Undefined);
+//
+// `(a?b)();`
+// OptionalEmitter oe(this);
+// CallOrNewEmitter cone(this, JSOP_CALL,
+// CallOrNewEmitter::ArgumentsKind::Other,
+// ValueUsage::WantValue);
+// cone.prepareForFunctionCallee();
+// emit(optionalChain);
+// cone.emitThis();
+// oe.emitOptionalJumpTarget(JSOp::Undefined,
+// OptionalEmitter::Kind::Reference);
+// oe.emitShortCircuitForCall();
+// cone.prepareForNonSpreadArguments();
+// emit(arg);
+// cone.emitEnd(1, offset_of_callee);
+// oe.emitOptionalJumpTarget(JSOp::Undefined);
+//
+class MOZ_RAII OptionalEmitter {
+ public:
+ OptionalEmitter(BytecodeEmitter* bce, int32_t initialDepth);
+
+ private:
+ BytecodeEmitter* bce_;
+
+ TDZCheckCache tdzCache_;
+
+ // jumptarget for ShortCircuiting code, which has null or undefined values
+ JumpList jumpShortCircuit_;
+
+ // jumpTarget for code that does not shortCircuit
+ JumpList jumpFinish_;
+
+ // jumpTarget for code that does not shortCircuit
+ int32_t initialDepth_;
+
+ // The state of this emitter.
+ //
+ // +-------+ emitJumpShortCircuit +--------------+
+ // | Start |-+---------------------------->| ShortCircuit |-----------+
+ // +-------+ | +--------------+ |
+ // +----->| |
+ // | | emitJumpShortCircuitForCall +---------------------+ v
+ // | +---------------------------->| ShortCircuitForCall |--->+
+ // | +---------------------+ |
+ // | |
+ // ---------------------------------------------------------------+
+ // |
+ // |
+ // +------------------------------------------------------------------+
+ // |
+ // | emitOptionalJumpTarget +---------+
+ // +----------------------->| JumpEnd |
+ // +---------+
+ //
+#ifdef DEBUG
+ enum class State {
+ // The initial state.
+ Start,
+
+ // for shortcircuiting in most cases.
+ ShortCircuit,
+
+ // for shortcircuiting from references, which have two items on
+ // the stack. For example function calls.
+ ShortCircuitForCall,
+
+ // internally used, end of the jump code
+ JumpEnd
+ };
+
+ State state_ = State::Start;
+#endif
+
+ public:
+ enum class Kind {
+ // Requires two values on the stack
+ Reference,
+ // Requires one value on the stack
+ Other
+ };
+
+ [[nodiscard]] bool emitJumpShortCircuit();
+ [[nodiscard]] bool emitJumpShortCircuitForCall();
+
+ // JSOp is the op code to be emitted, Kind is if we are dealing with a
+ // reference (in which case we need two elements on the stack) or other value
+ // (which needs one element on the stack)
+ [[nodiscard]] bool emitOptionalJumpTarget(JSOp op, Kind kind = Kind::Other);
+};
+
+} /* namespace frontend */
+} /* namespace js */
+
+#endif /* frontend_OptionalEmitter_h */
diff --git a/js/src/frontend/ParseContext-inl.h b/js/src/frontend/ParseContext-inl.h
new file mode 100644
index 0000000000..ddaf56c8c2
--- /dev/null
+++ b/js/src/frontend/ParseContext-inl.h
@@ -0,0 +1,177 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef frontend_ParseContext_inl_h
+#define frontend_ParseContext_inl_h
+
+#include "frontend/ParseContext.h"
+
+#include "frontend/Parser.h"
+
+namespace js {
+namespace frontend {
+
+template <>
+inline bool ParseContext::Statement::is<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->fc_->nameCollectionPool()),
+ possibleAnnexBFunctionBoxes_(parser->fc_->nameCollectionPool()),
+ id_(parser->usedNames_.nextScopeId()) {}
+
+inline ParseContext::Scope::Scope(FrontendContext* fc, ParseContext* pc,
+ UsedNameTracker& usedNames)
+ : Nestable<Scope>(&pc->innermostScope_),
+ declared_(fc->nameCollectionPool()),
+ possibleAnnexBFunctionBoxes_(fc->nameCollectionPool()),
+ id_(usedNames.nextScopeId()) {}
+
+inline ParseContext::VarScope::VarScope(ParserBase* parser) : Scope(parser) {
+ useAsVarScope(parser->pc_);
+}
+
+inline ParseContext::VarScope::VarScope(FrontendContext* fc, ParseContext* pc,
+ UsedNameTracker& usedNames)
+ : Scope(fc, pc, usedNames) {
+ useAsVarScope(pc);
+}
+
+inline JS::Result<Ok, ParseContext::BreakStatementError>
+ParseContext::checkBreakStatement(TaggedParserAtomIndex label) {
+ // Labeled 'break' statements target the nearest labeled statements (could
+ // be any kind) with the same label. Unlabeled 'break' statements target
+ // the innermost loop or switch statement.
+ if (label) {
+ auto hasSameLabel = [&label](ParseContext::LabelStatement* stmt) {
+ MOZ_ASSERT(stmt);
+ return stmt->label() == label;
+ };
+
+ if (!findInnermostStatement<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(TaggedParserAtomIndex label) {
+ // Labeled 'continue' statements target the nearest labeled loop
+ // statements with the same label. Unlabeled 'continue' statements target
+ // the innermost loop statement.
+ auto isLoop = [](ParseContext::Statement* stmt) {
+ MOZ_ASSERT(stmt);
+ return StatementKindIsLoop(stmt->kind());
+ };
+
+ if (!label) {
+ // Unlabeled statement: we target the innermost loop, so make sure that
+ // there is an innermost loop.
+ if (!findInnermostStatement(isLoop)) {
+ return mozilla::Err(ParseContext::ContinueStatementError::NotInALoop);
+ }
+ return Ok();
+ }
+
+ // Labeled statement: targest the nearest labeled loop with the same label.
+ ParseContext::Statement* stmt = innermostStatement();
+ bool foundLoop = false; // True if we have encountered at least one loop.
+
+ for (;;) {
+ stmt = ParseContext::Statement::findNearest(stmt, isLoop);
+ if (!stmt) {
+ return foundLoop
+ ? mozilla::Err(
+ ParseContext::ContinueStatementError::LabelNotFound)
+ : mozilla::Err(
+ ParseContext::ContinueStatementError::NotInALoop);
+ }
+
+ foundLoop = true;
+
+ // Is it labeled by our label?
+ stmt = stmt->enclosing();
+ while (stmt && stmt->is<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..f6a7723d07
--- /dev/null
+++ b/js/src/frontend/ParseContext.cpp
@@ -0,0 +1,723 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "frontend/ParseContext-inl.h"
+
+#include "frontend/CompilationStencil.h" // ScopeContext
+#include "frontend/Parser.h" // ParserBase
+#include "js/friend/ErrorMessages.h" // JSMSG_*
+#include "vm/WellKnownAtom.h" // js_*_str
+
+using mozilla::Maybe;
+using mozilla::Nothing;
+using mozilla::Some;
+
+namespace js {
+namespace frontend {
+
+using AddDeclaredNamePtr = ParseContext::Scope::AddDeclaredNamePtr;
+using DeclaredNamePtr = ParseContext::Scope::DeclaredNamePtr;
+
+const char* DeclarationKindString(DeclarationKind kind) {
+ switch (kind) {
+ case DeclarationKind::PositionalFormalParameter:
+ case DeclarationKind::FormalParameter:
+ return "formal parameter";
+ case DeclarationKind::CoverArrowParameter:
+ return "cover arrow parameter";
+ case DeclarationKind::Var:
+ return "var";
+ case DeclarationKind::Let:
+ return "let";
+ case DeclarationKind::Const:
+ return "const";
+ case DeclarationKind::Class:
+ return "class";
+ case DeclarationKind::Import:
+ return "import";
+ case DeclarationKind::BodyLevelFunction:
+ case DeclarationKind::ModuleBodyLevelFunction:
+ case DeclarationKind::LexicalFunction:
+ case DeclarationKind::SloppyLexicalFunction:
+ return "function";
+ case DeclarationKind::VarForAnnexBLexicalFunction:
+ return "annex b var";
+ case DeclarationKind::SimpleCatchParameter:
+ case DeclarationKind::CatchParameter:
+ return "catch parameter";
+ case DeclarationKind::PrivateName:
+ return "private name";
+ case DeclarationKind::Synthetic:
+ return "synthetic";
+ case DeclarationKind::PrivateMethod:
+ return "private method";
+ }
+
+ MOZ_CRASH("Bad DeclarationKind");
+}
+
+bool DeclarationKindIsVar(DeclarationKind kind) {
+ return kind == DeclarationKind::Var ||
+ kind == DeclarationKind::BodyLevelFunction ||
+ kind == DeclarationKind::VarForAnnexBLexicalFunction;
+}
+
+bool DeclarationKindIsParameter(DeclarationKind kind) {
+ return kind == DeclarationKind::PositionalFormalParameter ||
+ kind == DeclarationKind::FormalParameter;
+}
+
+bool UsedNameTracker::noteUse(FrontendContext* fc, TaggedParserAtomIndex name,
+ NameVisibility visibility, uint32_t scriptId,
+ uint32_t scopeId,
+ mozilla::Maybe<TokenPos> tokenPosition) {
+ if (UsedNameMap::AddPtr p = map_.lookupForAdd(name)) {
+ p->value().maybeUpdatePos(tokenPosition);
+
+ if (!p->value().noteUsedInScope(scriptId, scopeId)) {
+ return false;
+ }
+ } else {
+ // We need a token position precisely where we have private visibility.
+ MOZ_ASSERT(tokenPosition.isSome() ==
+ (visibility == NameVisibility::Private));
+
+ if (visibility == NameVisibility::Private) {
+ // We have seen at least one private name
+ hasPrivateNames_ = true;
+ }
+
+ UsedNameInfo info(fc, visibility, tokenPosition);
+
+ if (!info.noteUsedInScope(scriptId, scopeId)) {
+ return false;
+ }
+ if (!map_.add(p, name, std::move(info))) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool UsedNameTracker::getUnboundPrivateNames(
+ Vector<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(
+ FrontendContext* fc, mozilla::Maybe<UnboundPrivateName>& maybeUnboundName) {
+ // We never saw any private names, so can just return early
+ if (!hasPrivateNames_) {
+ return true;
+ }
+
+ Vector<UnboundPrivateName, 8> unboundPrivateNames(fc);
+ if (!getUnboundPrivateNames(unboundPrivateNames)) {
+ return false;
+ }
+
+ if (unboundPrivateNames.empty()) {
+ return true;
+ }
+
+ // GetUnboundPrivateNames returns the list sorted.
+ maybeUnboundName.emplace(unboundPrivateNames[0]);
+ return true;
+}
+
+void UsedNameTracker::UsedNameInfo::resetToScope(uint32_t scriptId,
+ uint32_t scopeId) {
+ while (!uses_.empty()) {
+ Use& innermost = uses_.back();
+ if (innermost.scopeId < scopeId) {
+ break;
+ }
+ MOZ_ASSERT(innermost.scriptId >= scriptId);
+ uses_.popBack();
+ }
+}
+
+void UsedNameTracker::rewind(RewindToken token) {
+ scriptCounter_ = token.scriptId;
+ scopeCounter_ = token.scopeId;
+
+ for (UsedNameMap::Range r = map_.all(); !r.empty(); r.popFront()) {
+ r.front().value().resetToScope(token.scriptId, token.scopeId);
+ }
+}
+
+void ParseContext::Scope::dump(ParseContext* pc, ParserBase* parser) {
+ fprintf(stdout, "ParseScope %p", this);
+
+ fprintf(stdout, "\n decls:\n");
+ for (DeclaredNameMap::Range r = declared_->all(); !r.empty(); r.popFront()) {
+ auto index = r.front().key();
+ UniqueChars bytes = parser->parserAtoms().toPrintableString(index);
+ if (!bytes) {
+ ReportOutOfMemory(pc->sc()->fc_);
+ return;
+ }
+ DeclaredNameInfo& info = r.front().value().wrapped;
+ fprintf(stdout, " %s %s%s\n", DeclarationKindString(info.kind()),
+ bytes.get(), info.closedOver() ? " (closed over)" : "");
+ }
+
+ fprintf(stdout, "\n");
+}
+
+bool ParseContext::Scope::addPossibleAnnexBFunctionBox(ParseContext* pc,
+ FunctionBox* funbox) {
+ if (!possibleAnnexBFunctionBoxes_) {
+ if (!possibleAnnexBFunctionBoxes_.acquire(pc->sc()->fc_)) {
+ return false;
+ }
+ }
+
+ return maybeReportOOM(pc, possibleAnnexBFunctionBoxes_->append(funbox));
+}
+
+bool ParseContext::Scope::propagateAndMarkAnnexBFunctionBoxes(
+ ParseContext* pc, ParserBase* parser) {
+ // Strict mode doesn't have wack Annex B function semantics.
+ if (pc->sc()->strict() || !possibleAnnexBFunctionBoxes_ ||
+ possibleAnnexBFunctionBoxes_->empty()) {
+ return true;
+ }
+
+ if (this == &pc->varScope()) {
+ // Base case: actually declare the Annex B vars and mark applicable
+ // function boxes as Annex B.
+ Maybe<DeclarationKind> redeclaredKind;
+ uint32_t unused;
+ for (FunctionBox* funbox : *possibleAnnexBFunctionBoxes_) {
+ bool annexBApplies;
+ if (!pc->computeAnnexBAppliesToLexicalFunctionInInnermostScope(
+ funbox, parser, &annexBApplies)) {
+ return false;
+ }
+ if (annexBApplies) {
+ if (!pc->tryDeclareVar(funbox->explicitName(), parser,
+ DeclarationKind::VarForAnnexBLexicalFunction,
+ DeclaredNameInfo::npos, &redeclaredKind,
+ &unused)) {
+ return false;
+ }
+
+ MOZ_ASSERT(!redeclaredKind);
+ funbox->isAnnexB = true;
+ }
+ }
+ } else {
+ // Inner scope case: propagate still applicable function boxes to the
+ // enclosing scope.
+ for (FunctionBox* funbox : *possibleAnnexBFunctionBoxes_) {
+ bool annexBApplies;
+ if (!pc->computeAnnexBAppliesToLexicalFunctionInInnermostScope(
+ funbox, parser, &annexBApplies)) {
+ return false;
+ }
+ if (annexBApplies) {
+ if (!enclosing()->addPossibleAnnexBFunctionBox(pc, funbox)) {
+ return false;
+ }
+ }
+ }
+ }
+
+ return true;
+}
+
+static bool DeclarationKindIsCatchParameter(DeclarationKind kind) {
+ return kind == DeclarationKind::SimpleCatchParameter ||
+ kind == DeclarationKind::CatchParameter;
+}
+
+bool ParseContext::Scope::addCatchParameters(ParseContext* pc,
+ Scope& catchParamScope) {
+ if (pc->useAsmOrInsideUseAsm()) {
+ return true;
+ }
+
+ for (DeclaredNameMap::Range r = catchParamScope.declared_->all(); !r.empty();
+ r.popFront()) {
+ DeclarationKind kind = r.front().value()->kind();
+ uint32_t pos = r.front().value()->pos();
+ MOZ_ASSERT(DeclarationKindIsCatchParameter(kind));
+ auto name = r.front().key();
+ AddDeclaredNamePtr p = lookupDeclaredNameForAdd(name);
+ MOZ_ASSERT(!p);
+ if (!addDeclaredName(pc, p, name, kind, pos)) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+void ParseContext::Scope::removeCatchParameters(ParseContext* pc,
+ Scope& catchParamScope) {
+ if (pc->useAsmOrInsideUseAsm()) {
+ return;
+ }
+
+ for (DeclaredNameMap::Range r = catchParamScope.declared_->all(); !r.empty();
+ r.popFront()) {
+ auto name = r.front().key();
+ DeclaredNamePtr p = declared_->lookup(name);
+ MOZ_ASSERT(p);
+
+ // This check is needed because the catch body could have declared
+ // vars, which would have been added to catchParamScope.
+ if (DeclarationKindIsCatchParameter(r.front().value()->kind())) {
+ declared_->remove(p);
+ }
+ }
+}
+
+ParseContext::ParseContext(FrontendContext* fc, ParseContext*& parent,
+ SharedContext* sc, ErrorReporter& errorReporter,
+ CompilationState& compilationState,
+ Directives* newDirectives, bool isFull)
+ : Nestable<ParseContext>(&parent),
+ sc_(sc),
+ errorReporter_(errorReporter),
+ innermostStatement_(nullptr),
+ innermostScope_(nullptr),
+ varScope_(nullptr),
+ positionalFormalParameterNames_(fc->nameCollectionPool()),
+ closedOverBindingsForLazy_(fc->nameCollectionPool()),
+ innerFunctionIndexesForLazy(sc->fc_),
+ newDirectives(newDirectives),
+ lastYieldOffset(NoYieldOffset),
+ lastAwaitOffset(NoAwaitOffset),
+ scriptId_(compilationState.usedNames.nextScriptId()),
+ superScopeNeedsHomeObject_(false) {
+ if (isFunctionBox()) {
+ if (functionBox()->isNamedLambda()) {
+ namedLambdaScope_.emplace(fc, parent, compilationState.usedNames);
+ }
+ functionScope_.emplace(fc, parent, compilationState.usedNames);
+ }
+}
+
+bool ParseContext::init() {
+ if (scriptId_ == UINT32_MAX) {
+ errorReporter_.errorNoOffset(JSMSG_NEED_DIET, js_script_str);
+ return false;
+ }
+
+ FrontendContext* fc = sc()->fc_;
+
+ if (isFunctionBox()) {
+ // Named lambdas always need a binding for their own name. If this
+ // binding is closed over when we finish parsing the function iNn
+ // finishFunctionScopes, the function box needs to be marked as
+ // needing a dynamic DeclEnv object.
+ if (functionBox()->isNamedLambda()) {
+ if (!namedLambdaScope_->init(this)) {
+ return false;
+ }
+ AddDeclaredNamePtr p = namedLambdaScope_->lookupDeclaredNameForAdd(
+ functionBox()->explicitName());
+ MOZ_ASSERT(!p);
+ if (!namedLambdaScope_->addDeclaredName(
+ this, p, functionBox()->explicitName(), DeclarationKind::Const,
+ DeclaredNameInfo::npos)) {
+ return false;
+ }
+ }
+
+ if (!functionScope_->init(this)) {
+ return false;
+ }
+
+ if (!positionalFormalParameterNames_.acquire(fc)) {
+ return false;
+ }
+ }
+
+ if (!closedOverBindingsForLazy_.acquire(fc)) {
+ return false;
+ }
+
+ return true;
+}
+
+bool ParseContext::computeAnnexBAppliesToLexicalFunctionInInnermostScope(
+ FunctionBox* funbox, ParserBase* parser, bool* annexBApplies) {
+ MOZ_ASSERT(!sc()->strict());
+
+ TaggedParserAtomIndex name = funbox->explicitName();
+ Maybe<DeclarationKind> redeclaredKind;
+ if (!isVarRedeclaredInInnermostScope(
+ name, parser, DeclarationKind::VarForAnnexBLexicalFunction,
+ &redeclaredKind)) {
+ return false;
+ }
+
+ if (!redeclaredKind && isFunctionBox()) {
+ Scope& funScope = functionScope();
+ if (&funScope != &varScope()) {
+ // Annex B.3.3.1 disallows redeclaring parameter names. In the
+ // presence of parameter expressions, parameter names are on the
+ // function scope, which encloses the var scope. This means the
+ // isVarRedeclaredInInnermostScope call above would not catch this
+ // case, so test it manually.
+ if (DeclaredNamePtr p = funScope.lookupDeclaredName(name)) {
+ DeclarationKind declaredKind = p->value()->kind();
+ if (DeclarationKindIsParameter(declaredKind)) {
+ redeclaredKind = Some(declaredKind);
+ } else {
+ MOZ_ASSERT(FunctionScope::isSpecialName(name));
+ }
+ }
+ }
+ }
+
+ // If an early error would have occurred already, this function should not
+ // exhibit Annex B.3.3 semantics.
+ *annexBApplies = !redeclaredKind;
+ return true;
+}
+
+bool ParseContext::isVarRedeclaredInInnermostScope(
+ TaggedParserAtomIndex name, ParserBase* parser, DeclarationKind kind,
+ mozilla::Maybe<DeclarationKind>* out) {
+ uint32_t unused;
+ return tryDeclareVarHelper<DryRunInnermostScopeOnly>(
+ name, parser, kind, DeclaredNameInfo::npos, out, &unused);
+}
+
+bool ParseContext::isVarRedeclaredInEval(TaggedParserAtomIndex name,
+ ParserBase* parser,
+ DeclarationKind kind,
+ Maybe<DeclarationKind>* out) {
+ auto maybeKind = parser->getCompilationState()
+ .scopeContext.lookupLexicalBindingInEnclosingScope(name);
+ if (!maybeKind) {
+ *out = Nothing();
+ return true;
+ }
+
+ switch (*maybeKind) {
+ case ScopeContext::EnclosingLexicalBindingKind::Let:
+ *out = Some(DeclarationKind::Let);
+ break;
+ case ScopeContext::EnclosingLexicalBindingKind::Const:
+ *out = Some(DeclarationKind::Const);
+ break;
+ case ScopeContext::EnclosingLexicalBindingKind::CatchParameter:
+ *out = Some(DeclarationKind::CatchParameter);
+ break;
+ case ScopeContext::EnclosingLexicalBindingKind::Synthetic:
+ *out = Some(DeclarationKind::Synthetic);
+ break;
+ case ScopeContext::EnclosingLexicalBindingKind::PrivateMethod:
+ *out = Some(DeclarationKind::PrivateMethod);
+ break;
+ }
+ return true;
+}
+
+bool ParseContext::tryDeclareVar(TaggedParserAtomIndex name, ParserBase* parser,
+ DeclarationKind kind, uint32_t beginPos,
+ Maybe<DeclarationKind>* redeclaredKind,
+ uint32_t* prevPos) {
+ return tryDeclareVarHelper<NotDryRun>(name, parser, kind, beginPos,
+ redeclaredKind, prevPos);
+}
+
+template <ParseContext::DryRunOption dryRunOption>
+bool ParseContext::tryDeclareVarHelper(TaggedParserAtomIndex name,
+ ParserBase* parser, 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, parser, kind, redeclaredKind)) {
+ return false;
+ }
+ // We don't have position information at runtime.
+ *prevPos = DeclaredNameInfo::npos;
+ }
+
+ return true;
+}
+
+bool ParseContext::hasUsedName(const UsedNameTracker& usedNames,
+ TaggedParserAtomIndex name) {
+ if (auto p = usedNames.lookup(name)) {
+ return p->value().isUsedInScript(scriptId());
+ }
+ return false;
+}
+
+bool ParseContext::hasUsedFunctionSpecialName(const UsedNameTracker& usedNames,
+ TaggedParserAtomIndex name) {
+ MOZ_ASSERT(name == TaggedParserAtomIndex::WellKnown::arguments() ||
+ name == TaggedParserAtomIndex::WellKnown::dotThis() ||
+ name == TaggedParserAtomIndex::WellKnown::dotNewTarget());
+ return hasUsedName(usedNames, name) ||
+ functionBox()->bindingsAccessedDynamically();
+}
+
+bool ParseContext::declareFunctionThis(const UsedNameTracker& usedNames,
+ bool canSkipLazyClosedOverBindings) {
+ // The asm.js validator does all its own symbol-table management so, as an
+ // optimization, avoid doing any work here.
+ if (useAsmOrInsideUseAsm()) {
+ return true;
+ }
+
+ // Derived class constructors emit JSOp::CheckReturn, which requires
+ // '.this' to be bound. Class field initializers implicitly read `.this`.
+ // Therefore we unconditionally declare `.this` in all class constructors.
+ FunctionBox* funbox = functionBox();
+ auto dotThis = TaggedParserAtomIndex::WellKnown::dotThis();
+
+ bool declareThis;
+ if (canSkipLazyClosedOverBindings) {
+ declareThis = funbox->functionHasThisBinding();
+ } else {
+ declareThis = hasUsedFunctionSpecialName(usedNames, dotThis) ||
+ funbox->isClassConstructor();
+ }
+
+ if (declareThis) {
+ ParseContext::Scope& funScope = functionScope();
+ AddDeclaredNamePtr p = funScope.lookupDeclaredNameForAdd(dotThis);
+ MOZ_ASSERT(!p);
+ if (!funScope.addDeclaredName(this, p, dotThis, DeclarationKind::Var,
+ DeclaredNameInfo::npos)) {
+ return false;
+ }
+ funbox->setFunctionHasThisBinding();
+ }
+
+ return true;
+}
+
+bool ParseContext::declareFunctionArgumentsObject(
+ const UsedNameTracker& usedNames, bool canSkipLazyClosedOverBindings) {
+ FunctionBox* funbox = functionBox();
+ ParseContext::Scope& funScope = functionScope();
+ ParseContext::Scope& _varScope = varScope();
+
+ bool usesArguments = false;
+ bool hasExtraBodyVarScope = &funScope != &_varScope;
+
+ // Time to implement the odd semantics of 'arguments'.
+ auto argumentsName = TaggedParserAtomIndex::WellKnown::arguments();
+
+ bool tryDeclareArguments;
+ if (canSkipLazyClosedOverBindings) {
+ tryDeclareArguments = funbox->shouldDeclareArguments();
+ } else {
+ tryDeclareArguments = hasUsedFunctionSpecialName(usedNames, argumentsName);
+ }
+
+ // ES 9.2.12 steps 19 and 20 say formal parameters, lexical bindings,
+ // and body-level functions named 'arguments' shadow the arguments
+ // object.
+ //
+ // So even if there wasn't a free use of 'arguments' but there is a var
+ // binding of 'arguments', we still might need the arguments object.
+ //
+ // If we have an extra var scope due to parameter expressions and the body
+ // declared 'var arguments', we still need to declare 'arguments' in the
+ // function scope.
+ DeclaredNamePtr p = _varScope.lookupDeclaredName(argumentsName);
+ if (p && p->value()->kind() == DeclarationKind::Var) {
+ if (hasExtraBodyVarScope) {
+ tryDeclareArguments = true;
+ } else {
+ usesArguments = true;
+ }
+ }
+
+ if (tryDeclareArguments) {
+ AddDeclaredNamePtr p = funScope.lookupDeclaredNameForAdd(argumentsName);
+ if (!p) {
+ if (!funScope.addDeclaredName(this, p, argumentsName,
+ DeclarationKind::Var,
+ DeclaredNameInfo::npos)) {
+ return false;
+ }
+ funbox->setShouldDeclareArguments();
+ usesArguments = true;
+ } else if (hasExtraBodyVarScope) {
+ // Formal parameters shadow the arguments object.
+ return true;
+ }
+ }
+
+ if (usesArguments) {
+ funbox->setNeedsArgsObj();
+ }
+
+ return true;
+}
+
+bool ParseContext::declareNewTarget(const UsedNameTracker& usedNames,
+ bool canSkipLazyClosedOverBindings) {
+ // The asm.js validator does all its own symbol-table management so, as an
+ // optimization, avoid doing any work here.
+ if (useAsmOrInsideUseAsm()) {
+ return true;
+ }
+
+ FunctionBox* funbox = functionBox();
+ auto dotNewTarget = TaggedParserAtomIndex::WellKnown::dotNewTarget();
+
+ bool declareNewTarget;
+ if (canSkipLazyClosedOverBindings) {
+ declareNewTarget = funbox->functionHasNewTargetBinding();
+ } else {
+ declareNewTarget = hasUsedFunctionSpecialName(usedNames, dotNewTarget);
+ }
+
+ if (declareNewTarget) {
+ ParseContext::Scope& funScope = functionScope();
+ AddDeclaredNamePtr p = funScope.lookupDeclaredNameForAdd(dotNewTarget);
+ MOZ_ASSERT(!p);
+ if (!funScope.addDeclaredName(this, p, dotNewTarget, DeclarationKind::Var,
+ DeclaredNameInfo::npos)) {
+ return false;
+ }
+ funbox->setFunctionHasNewTargetBinding();
+ }
+
+ return true;
+}
+
+bool ParseContext::declareDotGeneratorName() {
+ // The special '.generator' binding must be on the function scope, and must
+ // be marked closed-over, as generators expect to find it on the CallObject.
+ ParseContext::Scope& funScope = functionScope();
+ auto dotGenerator = TaggedParserAtomIndex::WellKnown::dotGenerator();
+ AddDeclaredNamePtr p = funScope.lookupDeclaredNameForAdd(dotGenerator);
+ if (!p) {
+ if (!funScope.addDeclaredName(this, p, dotGenerator, DeclarationKind::Var,
+ DeclaredNameInfo::npos, ClosedOver::Yes)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+bool ParseContext::declareTopLevelDotGeneratorName() {
+ // Provide a .generator binding on the module scope for compatibility with
+ // generator code, which expect to find it on the CallObject for normal
+ // generators.
+ MOZ_ASSERT(
+ sc()->isModuleContext(),
+ "Tried to declare top level dot generator in a non-module context.");
+ ParseContext::Scope& modScope = varScope();
+ auto dotGenerator = TaggedParserAtomIndex::WellKnown::dotGenerator();
+ AddDeclaredNamePtr p = modScope.lookupDeclaredNameForAdd(dotGenerator);
+ return p ||
+ modScope.addDeclaredName(this, p, dotGenerator, DeclarationKind::Var,
+ DeclaredNameInfo::npos, ClosedOver::Yes);
+}
+
+} // namespace frontend
+
+} // namespace js
diff --git a/js/src/frontend/ParseContext.h b/js/src/frontend/ParseContext.h
new file mode 100644
index 0000000000..a4c9b5f04d
--- /dev/null
+++ b/js/src/frontend/ParseContext.h
@@ -0,0 +1,622 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef frontend_ParseContext_h
+#define frontend_ParseContext_h
+
+#include "ds/Nestable.h"
+#include "frontend/ErrorReporter.h"
+#include "frontend/NameAnalysisTypes.h" // DeclaredNameInfo, FunctionBoxVector
+#include "frontend/NameCollections.h"
+#include "frontend/ParserAtom.h" // TaggedParserAtomIndex
+#include "frontend/ScriptIndex.h" // ScriptIndex
+#include "frontend/SharedContext.h"
+#include "js/friend/ErrorMessages.h" // JSMSG_*
+#include "vm/GeneratorAndAsyncKind.h" // js::GeneratorKind, js::FunctionAsyncKind
+#include "vm/WellKnownAtom.h" // js_*_str
+
+namespace js {
+
+namespace frontend {
+
+class ParserBase;
+class UsedNameTracker;
+
+struct CompilationState;
+
+const char* DeclarationKindString(DeclarationKind kind);
+
+// Returns true if the declaration is `var` or equivalent.
+bool DeclarationKindIsVar(DeclarationKind kind);
+
+bool DeclarationKindIsParameter(DeclarationKind kind);
+
+/*
+ * The struct ParseContext stores information about the current parsing context,
+ * which is part of the parser state (see the field Parser::pc). The current
+ * parsing context is either the global context, or the function currently being
+ * parsed. When the parser encounters a function definition, it creates a new
+ * ParseContext, makes it the new current context.
+ */
+class ParseContext : public Nestable<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 {
+ TaggedParserAtomIndex label_;
+
+ public:
+ LabelStatement(ParseContext* pc, TaggedParserAtomIndex label)
+ : Statement(pc, StatementKind::Label), label_(label) {}
+
+ TaggedParserAtomIndex label() const { return label_; }
+ };
+
+ struct ClassStatement : public Statement {
+ FunctionBox* constructorBox;
+
+ explicit ClassStatement(ParseContext* pc)
+ : Statement(pc, StatementKind::Class), constructorBox(nullptr) {}
+ };
+
+ // The intra-function scope stack.
+ //
+ // Tracks declared and used names within a scope.
+ class Scope : public Nestable<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.
+ 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()->fc_);
+ }
+ return result;
+ }
+
+ public:
+ using DeclaredNamePtr = DeclaredNameMap::Ptr;
+ using AddDeclaredNamePtr = DeclaredNameMap::AddPtr;
+
+ using Nestable<Scope>::enclosing;
+
+ explicit inline Scope(ParserBase* parser);
+ explicit inline Scope(FrontendContext* fc, ParseContext* pc,
+ UsedNameTracker& usedNames);
+
+ void dump(ParseContext* pc, ParserBase* parser);
+
+ uint32_t id() const { return id_; }
+
+ [[nodiscard]] bool init(ParseContext* pc) {
+ if (id_ == UINT32_MAX) {
+ pc->errorReporter_.errorNoOffset(JSMSG_NEED_DIET, js_script_str);
+ return false;
+ }
+
+ return declared_.acquire(pc->sc()->fc_);
+ }
+
+ bool isEmpty() const { return declared_->all().empty(); }
+
+ uint32_t declaredCount() const {
+ size_t count = declared_->count();
+ MOZ_ASSERT(count <= UINT32_MAX);
+ return uint32_t(count);
+ }
+
+ DeclaredNamePtr lookupDeclaredName(TaggedParserAtomIndex name) {
+ return declared_->lookup(name);
+ }
+
+ AddDeclaredNamePtr lookupDeclaredNameForAdd(TaggedParserAtomIndex name) {
+ return declared_->lookupForAdd(name);
+ }
+
+ [[nodiscard]] bool addDeclaredName(ParseContext* pc, AddDeclaredNamePtr& p,
+ TaggedParserAtomIndex name,
+ DeclarationKind kind, uint32_t pos,
+ ClosedOver closedOver = ClosedOver::No) {
+ return maybeReportOOM(
+ pc, declared_->add(p, name, DeclaredNameInfo(kind, pos, closedOver)));
+ }
+
+ // Add a FunctionBox as a possible candidate for Annex B.3.3 semantics.
+ [[nodiscard]] bool addPossibleAnnexBFunctionBox(ParseContext* pc,
+ FunctionBox* funbox);
+
+ // Check if the candidate function boxes for Annex B.3.3 should in
+ // fact get Annex B semantics. Checked on Scope exit.
+ [[nodiscard]] bool propagateAndMarkAnnexBFunctionBoxes(ParseContext* pc,
+ ParserBase* parser);
+
+ // Add and remove catch parameter names. Used to implement the odd
+ // semantics of catch bodies.
+ bool addCatchParameters(ParseContext* pc, Scope& catchParamScope);
+ void removeCatchParameters(ParseContext* pc, Scope& catchParamScope);
+
+ void useAsVarScope(ParseContext* pc) {
+ MOZ_ASSERT(!pc->varScope_);
+ pc->varScope_ = this;
+ }
+
+ // Maximum number of fixed stack slots in a generator or async function
+ // script. If a script would have more, we instead store some variables in
+ // heap EnvironmentObjects.
+ //
+ // This limit is a performance heuristic. Stack slots reduce allocations,
+ // and `Local` opcodes are a bit faster than `AliasedVar` ones; but at each
+ // `yield` or `await` the stack slots must be memcpy'd into a
+ // GeneratorObject. At some point the memcpy is too much. The limit is
+ // plenty for typical human-authored code.
+ static constexpr uint32_t FixedSlotLimit = 256;
+
+ // This is called as we leave a function, var, or lexical scope in a
+ // generator or async function. `ownSlotCount` is the number of `bindings_`
+ // that are not closed over.
+ void setOwnStackSlotCount(uint32_t ownSlotCount) {
+ // Determine if this scope is too big to optimize bindings into stack
+ // slots. The meaning of sizeBits_ changes from "maximum nested slot
+ // count" to "UINT32_MAX if too big".
+ uint32_t slotCount = ownSlotCount + sizeBits_;
+ if (slotCount > FixedSlotLimit) {
+ slotCount = sizeBits_;
+ sizeBits_ = UINT32_MAX;
+ } else {
+ sizeBits_ = 0;
+ }
+
+ // Propagate total size to enclosing scope.
+ if (Scope* parent = enclosing()) {
+ if (slotCount > parent->sizeBits_) {
+ parent->sizeBits_ = slotCount;
+ }
+ }
+ }
+
+ bool tooBigToOptimize() const {
+ MOZ_ASSERT(sizeBits_ == 0 || sizeBits_ == UINT32_MAX,
+ "call this only after the parser leaves the scope");
+ return sizeBits_ != 0;
+ }
+
+ // An iterator for the set of names a scope binds: the set of all
+ // declared names for 'var' scopes, and the set of lexically declared
+ // names, plus synthetic names, for non-'var' scopes.
+ class BindingIter {
+ friend class Scope;
+
+ DeclaredNameMap::Range declaredRange_;
+ mozilla::DebugOnly<uint32_t> count_;
+ bool isVarScope_;
+
+ BindingIter(Scope& scope, bool isVarScope)
+ : declaredRange_(scope.declared_->all()),
+ count_(0),
+ isVarScope_(isVarScope) {
+ settle();
+ }
+
+ bool isLexicallyDeclared() {
+ return BindingKindIsLexical(kind()) ||
+ kind() == BindingKind::Synthetic ||
+ kind() == BindingKind::PrivateMethod;
+ }
+
+ void settle() {
+ // Both var and lexically declared names are binding in a var
+ // scope.
+ if (isVarScope_) {
+ return;
+ }
+
+ // Otherwise, only lexically declared names are binding. Pop the range
+ // until we find such a name.
+ while (!declaredRange_.empty()) {
+ if (isLexicallyDeclared()) {
+ break;
+ }
+ declaredRange_.popFront();
+ }
+ }
+
+ public:
+ bool done() const { return declaredRange_.empty(); }
+
+ explicit operator bool() const { return !done(); }
+
+ TaggedParserAtomIndex name() {
+ MOZ_ASSERT(!done());
+ return declaredRange_.front().key();
+ }
+
+ DeclarationKind declarationKind() {
+ MOZ_ASSERT(!done());
+ return declaredRange_.front().value()->kind();
+ }
+
+ BindingKind kind() {
+ return DeclarationKindToBindingKind(declarationKind());
+ }
+
+ bool closedOver() {
+ MOZ_ASSERT(!done());
+ return declaredRange_.front().value()->closedOver();
+ }
+
+ void setClosedOver() {
+ MOZ_ASSERT(!done());
+ return declaredRange_.front().value()->setClosedOver();
+ }
+
+ void operator++(int) {
+ MOZ_ASSERT(!done());
+ MOZ_ASSERT(count_ != UINT32_MAX);
+ declaredRange_.popFront();
+ settle();
+ }
+ };
+
+ inline BindingIter bindings(ParseContext* pc);
+ };
+
+ class VarScope : public Scope {
+ public:
+ explicit inline VarScope(ParserBase* parser);
+ explicit inline VarScope(FrontendContext* fc, ParseContext* pc,
+ UsedNameTracker& usedNames);
+ };
+
+ private:
+ // Context shared between parsing and bytecode generation.
+ SharedContext* sc_;
+
+ // A mechanism used for error reporting.
+ ErrorReporter& errorReporter_;
+
+ // The innermost statement, i.e., top of the statement stack.
+ Statement* innermostStatement_;
+
+ // The innermost scope, i.e., top of the scope stack.
+ //
+ // The outermost scope in the stack is usually varScope_. In the case of
+ // functions, the outermost scope is functionScope_, which may be
+ // varScope_. See comment above functionScope_.
+ Scope* innermostScope_;
+
+ // If isFunctionBox() and the function is a named lambda, the DeclEnv
+ // scope for named lambdas.
+ mozilla::Maybe<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(FrontendContext* fc, ParseContext*& parent, SharedContext* sc,
+ ErrorReporter& errorReporter, CompilationState& compilationState,
+ Directives* newDirectives, bool isFull);
+
+ [[nodiscard]] bool init();
+
+ SharedContext* sc() { return sc_; }
+
+ // `true` if we are in the body of a function definition.
+ bool isFunctionBox() const { return sc_->isFunctionBox(); }
+
+ FunctionBox* functionBox() { return sc_->asFunctionBox(); }
+
+ Statement* innermostStatement() { return innermostStatement_; }
+
+ Scope* innermostScope() {
+ // There is always at least one scope: the 'var' scope.
+ MOZ_ASSERT(innermostScope_);
+ return innermostScope_;
+ }
+
+ Scope& namedLambdaScope() {
+ MOZ_ASSERT(functionBox()->isNamedLambda());
+ return *namedLambdaScope_;
+ }
+
+ Scope& functionScope() {
+ MOZ_ASSERT(isFunctionBox());
+ return *functionScope_;
+ }
+
+ Scope& varScope() {
+ MOZ_ASSERT(varScope_);
+ return *varScope_;
+ }
+
+ bool isFunctionExtraBodyVarScopeInnermost() {
+ return isFunctionBox() && functionBox()->hasParameterExprs &&
+ innermostScope() == varScope_;
+ }
+
+ template <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 : uint8_t {
+ // Unlabeled break must be inside loop or switch.
+ ToughBreak,
+ LabelNotFound,
+ };
+
+ // Return Err(true) if we have encountered at least one loop,
+ // Err(false) otherwise.
+ [[nodiscard]] inline JS::Result<Ok, BreakStatementError> checkBreakStatement(
+ TaggedParserAtomIndex label);
+
+ enum class ContinueStatementError : uint8_t {
+ NotInALoop,
+ LabelNotFound,
+ };
+ [[nodiscard]] inline JS::Result<Ok, ContinueStatementError>
+ checkContinueStatement(TaggedParserAtomIndex label);
+
+ // True if we are at the topmost level of a entire script or function body.
+ // For example, while parsing this code we would encounter f1 and f2 at
+ // body level, but we would not encounter f3 or f4 at body level:
+ //
+ // function f1() { function f2() { } }
+ // if (cond) { function f3() { if (cond) { function f4() { } } } }
+ //
+ bool atBodyLevel() { return !innermostStatement_; }
+
+ bool atGlobalLevel() { return atBodyLevel() && sc_->isGlobalContext(); }
+
+ // True if we are at the topmost level of a module only.
+ bool atModuleLevel() { return atBodyLevel() && sc_->isModuleContext(); }
+
+ // True if we are at the topmost level of an entire script or module. For
+ // example, in the comment on |atBodyLevel()| above, we would encounter |f1|
+ // and the outermost |if (cond)| at top level, and everything else would not
+ // be at top level.
+ bool atTopLevel() { return atBodyLevel() && sc_->isTopLevelContext(); }
+
+ bool atModuleTopLevel() {
+ // True if we are at the topmost level of an entire module.
+ //
+ // For example, this is used to determine if an await statement should
+ // mark a module as an async module during parsing.
+ //
+ // Example module:
+ // import x from "y";
+ //
+ // await x.foo(); // mark as Top level await.
+ //
+ // if (cond) {
+ // await x.bar(); // mark as Top level await.
+ // }
+ //
+ // async function z() {
+ // await x.baz(); // do not mark as Top level await.
+ // }
+ return sc_->isModuleContext() && sc_->isTopLevelContext();
+ }
+
+ // True if this is the outermost ParserContext for current compile. For
+ // delazification, this lets us identify if the lazy PrivateScriptData is for
+ // current parser context.
+ bool isOutermostOfCurrentCompile() const {
+ MOZ_ASSERT(!!enclosing() == !!scriptId());
+ return (scriptId() == 0);
+ }
+
+ void setSuperScopeNeedsHomeObject() {
+ MOZ_ASSERT(sc_->allowSuperProperty());
+ superScopeNeedsHomeObject_ = true;
+ }
+
+ bool superScopeNeedsHomeObject() const { return superScopeNeedsHomeObject_; }
+
+ bool useAsmOrInsideUseAsm() const {
+ return sc_->isFunctionBox() && sc_->asFunctionBox()->useAsmOrInsideUseAsm();
+ }
+
+ // A generator is marked as a generator before its body is parsed.
+ GeneratorKind generatorKind() const {
+ return sc_->isFunctionBox() ? sc_->asFunctionBox()->generatorKind()
+ : GeneratorKind::NotGenerator;
+ }
+
+ bool isGenerator() const {
+ return generatorKind() == GeneratorKind::Generator;
+ }
+
+ bool isAsync() const {
+ return sc_->isSuspendableContext() &&
+ sc_->asSuspendableContext()->isAsync();
+ }
+
+ bool isGeneratorOrAsync() const { return isGenerator() || isAsync(); }
+
+ bool needsDotGeneratorName() const { return isGeneratorOrAsync(); }
+
+ FunctionAsyncKind asyncKind() const {
+ return isAsync() ? FunctionAsyncKind::AsyncFunction
+ : FunctionAsyncKind::SyncFunction;
+ }
+
+ bool isArrowFunction() const {
+ return sc_->isFunctionBox() && sc_->asFunctionBox()->isArrow();
+ }
+
+ bool isMethod() const {
+ return sc_->isFunctionBox() && sc_->asFunctionBox()->isMethod();
+ }
+
+ bool isGetterOrSetter() const {
+ return sc_->isFunctionBox() && (sc_->asFunctionBox()->isGetter() ||
+ sc_->asFunctionBox()->isSetter());
+ }
+
+ bool allowReturn() const {
+ return sc_->isFunctionBox() && sc_->asFunctionBox()->allowReturn();
+ }
+
+ uint32_t scriptId() const { return scriptId_; }
+
+ bool computeAnnexBAppliesToLexicalFunctionInInnermostScope(
+ FunctionBox* funbox, ParserBase* parser, bool* annexBApplies);
+
+ bool tryDeclareVar(TaggedParserAtomIndex name, ParserBase* parser,
+ DeclarationKind kind, uint32_t beginPos,
+ mozilla::Maybe<DeclarationKind>* redeclaredKind,
+ uint32_t* prevPos);
+
+ bool hasUsedName(const UsedNameTracker& usedNames,
+ TaggedParserAtomIndex name);
+ bool hasUsedFunctionSpecialName(const UsedNameTracker& usedNames,
+ TaggedParserAtomIndex name);
+
+ bool declareFunctionThis(const UsedNameTracker& usedNames,
+ bool canSkipLazyClosedOverBindings);
+ bool declareFunctionArgumentsObject(const UsedNameTracker& usedNames,
+ bool canSkipLazyClosedOverBindings);
+ bool declareNewTarget(const UsedNameTracker& usedNames,
+ bool canSkipLazyClosedOverBindings);
+ bool declareDotGeneratorName();
+ bool declareTopLevelDotGeneratorName();
+
+ private:
+ [[nodiscard]] bool isVarRedeclaredInInnermostScope(
+ TaggedParserAtomIndex name, ParserBase* parser, DeclarationKind kind,
+ mozilla::Maybe<DeclarationKind>* out);
+
+ [[nodiscard]] bool isVarRedeclaredInEval(
+ TaggedParserAtomIndex name, ParserBase* parser, DeclarationKind kind,
+ mozilla::Maybe<DeclarationKind>* out);
+
+ enum DryRunOption { NotDryRun, DryRunInnermostScopeOnly };
+ template <DryRunOption dryRunOption>
+ bool tryDeclareVarHelper(TaggedParserAtomIndex name, ParserBase* parser,
+ 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..b9d16f0be8
--- /dev/null
+++ b/js/src/frontend/ParseNode.cpp
@@ -0,0 +1,430 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "frontend/ParseNode.h"
+
+#include "mozilla/FloatingPoint.h"
+
+#include "jsnum.h"
+
+#include "frontend/CompilationStencil.h" // ExtensibleCompilationStencil
+#include "frontend/FullParseHandler.h"
+#include "frontend/ParseContext.h"
+#include "frontend/Parser.h" // ParserBase
+#include "frontend/ParserAtom.h" // ParserAtomsTable, TaggedParserAtomIndex
+#include "frontend/SharedContext.h"
+#include "js/Printer.h"
+#include "vm/Scope.h" // GetScopeDataTrailingNames
+
+using namespace js;
+using namespace js::frontend;
+
+#ifdef DEBUG
+void ListNode::checkConsistency() const {
+ ParseNode* const* tailNode;
+ uint32_t actualCount = 0;
+ if (const ParseNode* last = head()) {
+ const ParseNode* pn = last;
+ while (pn) {
+ last = pn;
+ pn = pn->pn_next;
+ actualCount++;
+ }
+
+ tailNode = &last->pn_next;
+ } else {
+ tailNode = &head_;
+ }
+ MOZ_ASSERT(tail() == tailNode);
+ MOZ_ASSERT(count() == actualCount);
+}
+#endif
+
+/*
+ * Allocate a ParseNode from parser's node freelist or, failing that, from
+ * cx's temporary arena.
+ */
+void* ParseNodeAllocator::allocNode(size_t size) {
+ LifoAlloc::AutoFallibleScope fallibleAllocator(&alloc);
+ void* p = alloc.alloc(size);
+ if (!p) {
+ ReportOutOfMemory(fc);
+ }
+ return p;
+}
+
+ParseNode* ParseNode::appendOrCreateList(ParseNodeKind kind, ParseNode* left,
+ ParseNode* right,
+ FullParseHandler* handler,
+ ParseContext* pc) {
+ // The asm.js specification is written in ECMAScript grammar terms that
+ // specify *only* a binary tree. It's a royal pain to implement the asm.js
+ // spec to act upon n-ary lists as created below. So for asm.js, form a
+ // binary tree of lists exactly as ECMAScript would by skipping the
+ // following optimization.
+ if (!pc->useAsmOrInsideUseAsm()) {
+ // Left-associative trees of a given operator (e.g. |a + b + c|) are
+ // binary trees in the spec: (+ (+ a b) c) in Lisp terms. Recursively
+ // processing such a tree, exactly implemented that way, would blow the
+ // the stack. We use a list node that uses O(1) stack to represent
+ // such operations: (+ a b c).
+ //
+ // (**) is right-associative; per spec |a ** b ** c| parses as
+ // (** a (** b c)). But we treat this the same way, creating a list
+ // node: (** a b c). All consumers must understand that this must be
+ // processed with a right fold, whereas the list (+ a b c) must be
+ // processed with a left fold because (+) is left-associative.
+ //
+ if (left->isKind(kind) &&
+ (kind == ParseNodeKind::PowExpr ? !left->isInParens()
+ : left->isBinaryOperation())) {
+ ListNode* list = &left->as<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
+};
+
+static void DumpParseTree(const ParserAtomsTable* parserAtoms, ParseNode* pn,
+ GenericPrinter& out, int indent) {
+ if (pn == nullptr) {
+ out.put("#NULL");
+ } else {
+ pn->dump(parserAtoms, out, indent);
+ }
+}
+
+void frontend::DumpParseTree(ParserBase* parser, ParseNode* pn,
+ GenericPrinter& out, int indent) {
+ ParserAtomsTable* parserAtoms = parser ? &parser->parserAtoms() : nullptr;
+ ::DumpParseTree(parserAtoms, pn, out, indent);
+}
+
+static void IndentNewLine(GenericPrinter& out, int indent) {
+ out.putChar('\n');
+ for (int i = 0; i < indent; ++i) {
+ out.putChar(' ');
+ }
+}
+
+void ParseNode::dump() { dump(nullptr); }
+
+void ParseNode::dump(const ParserAtomsTable* parserAtoms) {
+ js::Fprinter out(stderr);
+ dump(parserAtoms, out);
+}
+
+void ParseNode::dump(const ParserAtomsTable* parserAtoms, GenericPrinter& out) {
+ dump(parserAtoms, out, 0);
+ out.putChar('\n');
+}
+
+void ParseNode::dump(const ParserAtomsTable* parserAtoms, GenericPrinter& out,
+ int indent) {
+ switch (getKind()) {
+# define DUMP(K, T) \
+ case ParseNodeKind::K: \
+ as<T>().dumpImpl(parserAtoms, 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(const ParserAtomsTable* parserAtoms,
+ GenericPrinter& out, int indent) {
+ switch (getKind()) {
+ case ParseNodeKind::TrueExpr:
+ out.put("#true");
+ break;
+ case ParseNodeKind::FalseExpr:
+ out.put("#false");
+ break;
+ case ParseNodeKind::NullExpr:
+ out.put("#null");
+ break;
+ case ParseNodeKind::RawUndefinedExpr:
+ out.put("#undefined");
+ break;
+
+ default:
+ out.printf("(%s)", parseNodeNames[getKindAsIndex()]);
+ }
+}
+
+void NumericLiteral::dumpImpl(const ParserAtomsTable* parserAtoms,
+ GenericPrinter& out, int indent) {
+ ToCStringBuf cbuf;
+ const char* cstr = NumberToCString(&cbuf, value());
+ MOZ_ASSERT(cstr);
+ if (!std::isfinite(value())) {
+ out.put("#");
+ }
+ out.printf("%s", cstr);
+}
+
+void BigIntLiteral::dumpImpl(const ParserAtomsTable* parserAtoms,
+ GenericPrinter& out, int indent) {
+ out.printf("(%s)", parseNodeNames[getKindAsIndex()]);
+}
+
+void RegExpLiteral::dumpImpl(const ParserAtomsTable* parserAtoms,
+ GenericPrinter& out, int indent) {
+ out.printf("(%s)", parseNodeNames[getKindAsIndex()]);
+}
+
+static void DumpCharsNoNewline(const ParserAtomsTable* parserAtoms,
+ TaggedParserAtomIndex index,
+ GenericPrinter& out) {
+ out.put("\"");
+ if (parserAtoms) {
+ parserAtoms->dumpCharsNoQuote(out, index);
+ } else {
+ DumpTaggedParserAtomIndexNoQuote(out, index, nullptr);
+ }
+ out.put("\"");
+}
+
+void LoopControlStatement::dumpImpl(const ParserAtomsTable* parserAtoms,
+ GenericPrinter& out, int indent) {
+ const char* name = parseNodeNames[getKindAsIndex()];
+ out.printf("(%s", name);
+ if (label_) {
+ out.printf(" ");
+ DumpCharsNoNewline(parserAtoms, label_, out);
+ }
+ out.printf(")");
+}
+
+void UnaryNode::dumpImpl(const ParserAtomsTable* parserAtoms,
+ GenericPrinter& out, int indent) {
+ const char* name = parseNodeNames[getKindAsIndex()];
+ out.printf("(%s ", name);
+ indent += strlen(name) + 2;
+ ::DumpParseTree(parserAtoms, kid(), out, indent);
+ out.printf(")");
+}
+
+void BinaryNode::dumpImpl(const ParserAtomsTable* parserAtoms,
+ GenericPrinter& out, int indent) {
+ if (isKind(ParseNodeKind::DotExpr)) {
+ out.put("(.");
+
+ ::DumpParseTree(parserAtoms, right(), out, indent + 2);
+
+ out.putChar(' ');
+ if (as<PropertyAccess>().isSuper()) {
+ out.put("super");
+ } else {
+ ::DumpParseTree(parserAtoms, left(), out, indent + 2);
+ }
+
+ out.printf(")");
+ return;
+ }
+
+ const char* name = parseNodeNames[getKindAsIndex()];
+ out.printf("(%s ", name);
+ indent += strlen(name) + 2;
+ ::DumpParseTree(parserAtoms, left(), out, indent);
+ IndentNewLine(out, indent);
+ ::DumpParseTree(parserAtoms, right(), out, indent);
+ out.printf(")");
+}
+
+void TernaryNode::dumpImpl(const ParserAtomsTable* parserAtoms,
+ GenericPrinter& out, int indent) {
+ const char* name = parseNodeNames[getKindAsIndex()];
+ out.printf("(%s ", name);
+ indent += strlen(name) + 2;
+ ::DumpParseTree(parserAtoms, kid1(), out, indent);
+ IndentNewLine(out, indent);
+ ::DumpParseTree(parserAtoms, kid2(), out, indent);
+ IndentNewLine(out, indent);
+ ::DumpParseTree(parserAtoms, kid3(), out, indent);
+ out.printf(")");
+}
+
+void FunctionNode::dumpImpl(const ParserAtomsTable* parserAtoms,
+ GenericPrinter& out, int indent) {
+ const char* name = parseNodeNames[getKindAsIndex()];
+ out.printf("(%s ", name);
+ indent += strlen(name) + 2;
+ ::DumpParseTree(parserAtoms, body(), out, indent);
+ out.printf(")");
+}
+
+void ModuleNode::dumpImpl(const ParserAtomsTable* parserAtoms,
+ GenericPrinter& out, int indent) {
+ const char* name = parseNodeNames[getKindAsIndex()];
+ out.printf("(%s ", name);
+ indent += strlen(name) + 2;
+ ::DumpParseTree(parserAtoms, body(), out, indent);
+ out.printf(")");
+}
+
+void ListNode::dumpImpl(const ParserAtomsTable* parserAtoms,
+ GenericPrinter& out, int indent) {
+ const char* name = parseNodeNames[getKindAsIndex()];
+ out.printf("(%s [", name);
+ if (ParseNode* listHead = head()) {
+ indent += strlen(name) + 3;
+ ::DumpParseTree(parserAtoms, listHead, out, indent);
+ for (ParseNode* item : contentsFrom(listHead->pn_next)) {
+ IndentNewLine(out, indent);
+ ::DumpParseTree(parserAtoms, item, out, indent);
+ }
+ }
+ out.printf("])");
+}
+
+void NameNode::dumpImpl(const ParserAtomsTable* parserAtoms,
+ GenericPrinter& out, int indent) {
+ switch (getKind()) {
+ case ParseNodeKind::StringExpr:
+ case ParseNodeKind::TemplateStringExpr:
+ case ParseNodeKind::ObjectPropertyName:
+ DumpCharsNoNewline(parserAtoms, atom_, out);
+ return;
+
+ case ParseNodeKind::Name:
+ case ParseNodeKind::PrivateName: // atom() already includes the '#', no
+ // need to specially include it.
+ case ParseNodeKind::PropertyNameExpr:
+ if (!atom_) {
+ out.put("#<null name>");
+ } else if (parserAtoms) {
+ if (atom_ == TaggedParserAtomIndex::WellKnown::empty()) {
+ out.put("#<zero-length name>");
+ } else {
+ parserAtoms->dumpCharsNoQuote(out, atom_);
+ }
+ } else {
+ DumpTaggedParserAtomIndexNoQuote(out, atom_, nullptr);
+ }
+ return;
+
+ case ParseNodeKind::LabelStmt: {
+ this->as<LabeledStatement>().dumpImpl(parserAtoms, out, indent);
+ return;
+ }
+
+ default: {
+ const char* name = parseNodeNames[getKindAsIndex()];
+ out.printf("(%s)", name);
+ return;
+ }
+ }
+}
+
+void LabeledStatement::dumpImpl(const ParserAtomsTable* parserAtoms,
+ GenericPrinter& out, int indent) {
+ const char* name = parseNodeNames[getKindAsIndex()];
+ out.printf("(%s ", name);
+ DumpCharsNoNewline(parserAtoms, label(), out);
+ indent += strlen(name) + 2;
+ IndentNewLine(out, indent);
+ ::DumpParseTree(parserAtoms, statement(), out, indent);
+ out.printf(")");
+}
+
+template <ParseNodeKind Kind, typename ScopeType>
+void BaseScopeNode<Kind, ScopeType>::dumpImpl(
+ const ParserAtomsTable* parserAtoms, GenericPrinter& out, int indent) {
+ const char* name = parseNodeNames[getKindAsIndex()];
+ out.printf("(%s [", name);
+ int nameIndent = indent + strlen(name) + 3;
+ if (!isEmptyScope()) {
+ typename ScopeType::ParserData* bindings = scopeBindings();
+ auto names = GetScopeDataTrailingNames(bindings);
+ for (uint32_t i = 0; i < names.size(); i++) {
+ auto index = names[i].name();
+ if (parserAtoms) {
+ if (index == TaggedParserAtomIndex::WellKnown::empty()) {
+ out.put("#<zero-length name>");
+ } else {
+ parserAtoms->dumpCharsNoQuote(out, index);
+ }
+ } else {
+ DumpTaggedParserAtomIndexNoQuote(out, index, nullptr);
+ }
+ if (i < names.size() - 1) {
+ IndentNewLine(out, nameIndent);
+ }
+ }
+ }
+ out.putChar(']');
+ indent += 2;
+ IndentNewLine(out, indent);
+ ::DumpParseTree(parserAtoms, scopeBody(), out, indent);
+ out.printf(")");
+}
+#endif
+
+TaggedParserAtomIndex NumericLiteral::toAtom(
+ FrontendContext* fc, ParserAtomsTable& parserAtoms) const {
+ return NumberToParserAtom(fc, parserAtoms, value());
+}
+
+RegExpObject* RegExpLiteral::create(
+ JSContext* cx, FrontendContext* fc, ParserAtomsTable& parserAtoms,
+ CompilationAtomCache& atomCache,
+ ExtensibleCompilationStencil& stencil) const {
+ return stencil.regExpData[index_].createRegExpAndEnsureAtom(
+ cx, fc, parserAtoms, atomCache);
+}
+
+bool js::frontend::IsAnonymousFunctionDefinition(ParseNode* pn) {
+ // ES 2017 draft
+ // 12.15.2 (ArrowFunction, AsyncArrowFunction).
+ // 14.1.12 (FunctionExpression).
+ // 14.4.8 (Generatoression).
+ // 14.6.8 (AsyncFunctionExpression)
+ if (pn->is<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..959cfe0b0c
--- /dev/null
+++ b/js/src/frontend/ParseNode.h
@@ -0,0 +1,2556 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef frontend_ParseNode_h
+#define frontend_ParseNode_h
+
+#include "mozilla/Assertions.h"
+
+#include <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" // TaggedParserAtomIndex
+#include "frontend/Stencil.h" // BigIntStencil
+#include "frontend/Token.h"
+#include "js/TypeDecls.h"
+#include "vm/Opcodes.h"
+#include "vm/Scope.h"
+#include "vm/ScopeKind.h"
+
+// [SMDOC] ParseNode tree lifetime information
+//
+// - All the `ParseNode` instances MUST BE explicitly allocated in the context's
+// `LifoAlloc`. This is typically implemented by the `FullParseHandler` or it
+// can be reimplemented with a custom `new_`.
+//
+// - The tree is bulk-deallocated when the parser is deallocated. Consequently,
+// references to a subtree MUST NOT exist once the parser has been
+// deallocated.
+//
+// - This bulk-deallocation DOES NOT run destructors.
+//
+// - Instances of `LexicalScope::ParserData` and `ClassBodyScope::ParserData`
+// MUST BE allocated as instances of `ParseNode`, in the same `LifoAlloc`.
+// They are bulk-deallocated alongside the rest of the tree.
+
+struct JSContext;
+
+namespace js {
+
+class JS_PUBLIC_API GenericPrinter;
+class LifoAlloc;
+class RegExpObject;
+
+namespace frontend {
+
+class ParserAtomsTable;
+class ParserBase;
+class ParseContext;
+struct ExtensibleCompilationStencil;
+class ParserSharedBase;
+class FullParseHandler;
+
+class FunctionBox;
+
+#define FOR_EACH_PARSE_NODE_KIND(F) \
+ F(EmptyStmt, NullaryNode) \
+ F(ExpressionStmt, UnaryNode) \
+ F(CommaExpr, ListNode) \
+ F(ConditionalExpr, ConditionalExpression) \
+ F(PropertyDefinition, PropertyDefinition) \
+ F(Shorthand, BinaryNode) \
+ F(PosExpr, UnaryNode) \
+ F(NegExpr, UnaryNode) \
+ F(PreIncrementExpr, UnaryNode) \
+ F(PostIncrementExpr, UnaryNode) \
+ F(PreDecrementExpr, UnaryNode) \
+ F(PostDecrementExpr, UnaryNode) \
+ F(PropertyNameExpr, NameNode) \
+ F(DotExpr, PropertyAccess) \
+ F(ElemExpr, PropertyByValue) \
+ F(PrivateMemberExpr, PrivateMemberAccess) \
+ F(OptionalDotExpr, OptionalPropertyAccess) \
+ F(OptionalChain, UnaryNode) \
+ F(OptionalElemExpr, OptionalPropertyByValue) \
+ F(OptionalPrivateMemberExpr, OptionalPrivateMemberAccess) \
+ F(OptionalCallExpr, BinaryNode) \
+ F(ArrayExpr, ListNode) \
+ F(Elision, NullaryNode) \
+ F(StatementList, ListNode) \
+ F(LabelStmt, LabeledStatement) \
+ F(ObjectExpr, ListNode) \
+ F(CallExpr, BinaryNode) \
+ F(Arguments, ListNode) \
+ F(Name, NameNode) \
+ F(ObjectPropertyName, NameNode) \
+ F(PrivateName, NameNode) \
+ F(ComputedName, UnaryNode) \
+ F(NumberExpr, NumericLiteral) \
+ F(BigIntExpr, BigIntLiteral) \
+ F(StringExpr, NameNode) \
+ F(TemplateStringListExpr, ListNode) \
+ F(TemplateStringExpr, NameNode) \
+ F(TaggedTemplateExpr, BinaryNode) \
+ F(CallSiteObj, CallSiteNode) \
+ F(RegExpExpr, RegExpLiteral) \
+ F(TrueExpr, BooleanLiteral) \
+ F(FalseExpr, BooleanLiteral) \
+ F(NullExpr, NullLiteral) \
+ F(RawUndefinedExpr, RawUndefinedLiteral) \
+ F(ThisExpr, UnaryNode) \
+ IF_RECORD_TUPLE(F(RecordExpr, ListNode)) \
+ IF_RECORD_TUPLE(F(TupleExpr, ListNode)) \
+ F(Function, FunctionNode) \
+ F(Module, ModuleNode) \
+ F(IfStmt, TernaryNode) \
+ F(SwitchStmt, SwitchStatement) \
+ F(Case, CaseClause) \
+ F(WhileStmt, BinaryNode) \
+ F(DoWhileStmt, BinaryNode) \
+ F(ForStmt, ForNode) \
+ F(BreakStmt, BreakStatement) \
+ F(ContinueStmt, ContinueStatement) \
+ F(VarStmt, DeclarationListNode) \
+ F(ConstDecl, DeclarationListNode) \
+ F(WithStmt, BinaryNode) \
+ F(ReturnStmt, UnaryNode) \
+ F(NewExpr, BinaryNode) \
+ IF_DECORATORS(F(DecoratorList, ListNode)) \
+ /* Delete operations. These must be sequential. */ \
+ F(DeleteNameExpr, UnaryNode) \
+ F(DeletePropExpr, UnaryNode) \
+ F(DeleteElemExpr, UnaryNode) \
+ F(DeleteOptionalChainExpr, UnaryNode) \
+ F(DeleteExpr, UnaryNode) \
+ F(TryStmt, TernaryNode) \
+ F(Catch, BinaryNode) \
+ F(ThrowStmt, UnaryNode) \
+ F(DebuggerStmt, DebuggerStatement) \
+ F(Generator, NullaryNode) \
+ F(InitialYield, UnaryNode) \
+ F(YieldExpr, UnaryNode) \
+ F(YieldStarExpr, UnaryNode) \
+ F(LexicalScope, LexicalScopeNode) \
+ F(LetDecl, DeclarationListNode) \
+ F(ImportDecl, BinaryNode) \
+ F(ImportSpecList, ListNode) \
+ F(ImportSpec, BinaryNode) \
+ F(ImportNamespaceSpec, UnaryNode) \
+ F(ImportAssertionList, ListNode) \
+ F(ImportAssertion, BinaryNode) \
+ F(ImportModuleRequest, BinaryNode) \
+ F(ExportStmt, UnaryNode) \
+ F(ExportFromStmt, BinaryNode) \
+ F(ExportDefaultStmt, BinaryNode) \
+ F(ExportSpecList, ListNode) \
+ F(ExportSpec, BinaryNode) \
+ F(ExportNamespaceSpec, UnaryNode) \
+ F(ExportBatchSpecStmt, NullaryNode) \
+ F(ForIn, TernaryNode) \
+ F(ForOf, TernaryNode) \
+ F(ForHead, TernaryNode) \
+ F(ParamsBody, ParamsBodyNode) \
+ F(Spread, UnaryNode) \
+ F(MutateProto, UnaryNode) \
+ F(ClassDecl, ClassNode) \
+ F(DefaultConstructor, ClassMethod) \
+ F(ClassBodyScope, ClassBodyScopeNode) \
+ F(ClassMethod, ClassMethod) \
+ F(StaticClassBlock, StaticClassBlock) \
+ F(ClassField, ClassField) \
+ F(ClassMemberList, ListNode) \
+ F(ClassNames, ClassNames) \
+ F(NewTargetExpr, NewTargetNode) \
+ F(PosHolder, NullaryNode) \
+ F(SuperBase, UnaryNode) \
+ F(SuperCallExpr, BinaryNode) \
+ F(SetThis, BinaryNode) \
+ F(ImportMetaExpr, BinaryNode) \
+ F(CallImportExpr, BinaryNode) \
+ F(CallImportSpec, BinaryNode) \
+ F(InitExpr, BinaryNode) \
+ \
+ /* Unary operators. */ \
+ F(TypeOfNameExpr, UnaryNode) \
+ F(TypeOfExpr, UnaryNode) \
+ F(VoidExpr, UnaryNode) \
+ F(NotExpr, UnaryNode) \
+ F(BitNotExpr, UnaryNode) \
+ F(AwaitExpr, UnaryNode) \
+ \
+ /* \
+ * Binary operators. \
+ * This list must be kept in the same order in several places: \
+ * - The binary operators in ParseNode.h \
+ * - the binary operators in TokenKind.h \
+ * - the precedence list in Parser.cpp \
+ * - the JSOp code list in BytecodeEmitter.cpp \
+ */ \
+ F(CoalesceExpr, ListNode) \
+ F(OrExpr, ListNode) \
+ F(AndExpr, ListNode) \
+ F(BitOrExpr, ListNode) \
+ F(BitXorExpr, ListNode) \
+ F(BitAndExpr, ListNode) \
+ F(StrictEqExpr, ListNode) \
+ F(EqExpr, ListNode) \
+ F(StrictNeExpr, ListNode) \
+ F(NeExpr, ListNode) \
+ F(LtExpr, ListNode) \
+ F(LeExpr, ListNode) \
+ F(GtExpr, ListNode) \
+ F(GeExpr, ListNode) \
+ F(InstanceOfExpr, ListNode) \
+ F(InExpr, ListNode) \
+ F(PrivateInExpr, ListNode) \
+ F(LshExpr, ListNode) \
+ F(RshExpr, ListNode) \
+ F(UrshExpr, ListNode) \
+ F(AddExpr, ListNode) \
+ F(SubExpr, ListNode) \
+ F(MulExpr, ListNode) \
+ F(DivExpr, ListNode) \
+ F(ModExpr, ListNode) \
+ F(PowExpr, ListNode) \
+ \
+ /* Assignment operators (= += -= etc.). */ \
+ /* AssignmentNode::test assumes all these are consecutive. */ \
+ F(AssignExpr, AssignmentNode) \
+ F(AddAssignExpr, AssignmentNode) \
+ F(SubAssignExpr, AssignmentNode) \
+ F(CoalesceAssignExpr, AssignmentNode) \
+ F(OrAssignExpr, AssignmentNode) \
+ F(AndAssignExpr, AssignmentNode) \
+ F(BitOrAssignExpr, AssignmentNode) \
+ F(BitXorAssignExpr, AssignmentNode) \
+ F(BitAndAssignExpr, AssignmentNode) \
+ F(LshAssignExpr, AssignmentNode) \
+ F(RshAssignExpr, AssignmentNode) \
+ F(UrshAssignExpr, AssignmentNode) \
+ F(MulAssignExpr, AssignmentNode) \
+ F(DivAssignExpr, AssignmentNode) \
+ F(ModAssignExpr, AssignmentNode) \
+ F(PowAssignExpr, AssignmentNode)
+
+/*
+ * Parsing builds a tree of nodes that directs code generation. This tree is
+ * not a concrete syntax tree in all respects (for example, || and && are left
+ * associative, but (A && B && C) translates into the right-associated tree
+ * <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::CoalesceExpr,
+ BinOpLast = ParseNodeKind::PowExpr,
+ AssignmentStart = ParseNodeKind::AssignExpr,
+ AssignmentLast = ParseNodeKind::PowAssignExpr,
+};
+
+inline bool IsDeleteKind(ParseNodeKind kind) {
+ return ParseNodeKind::DeleteNameExpr <= kind &&
+ kind <= ParseNodeKind::DeleteExpr;
+}
+
+inline bool IsTypeofKind(ParseNodeKind kind) {
+ return ParseNodeKind::TypeOfNameExpr <= kind &&
+ kind <= ParseNodeKind::TypeOfExpr;
+}
+
+/*
+ * <Definitions>
+ * Function (FunctionNode)
+ * funbox: ptr to js::FunctionBox
+ * body: ParamsBody or null for lazily-parsed function
+ * syntaxKind: the syntax of the function
+ * ParamsBody (ListNode)
+ * head: list of formal parameters with
+ * * Name node with non-empty name for SingleNameBinding without
+ * Initializer
+ * * AssignExpr node for SingleNameBinding with Initializer
+ * * Name node with empty name for destructuring
+ * expr: Array or Object for BindingPattern without
+ * Initializer, Assign for BindingPattern with
+ * Initializer
+ * followed by:
+ * * LexicalScopeNode
+ * count: number of formal parameters + 1
+ * Spread (UnaryNode)
+ * kid: expression being spread
+ * ClassDecl (ClassNode)
+ * kid1: ClassNames for class name. can be null for anonymous class.
+ * kid2: expression after `extends`. null if no expression
+ * kid3: either of
+ * * ClassMemberList, if anonymous class
+ * * LexicalScopeNode which contains ClassMemberList as scopeBody,
+ * if named class
+ * ClassNames (ClassNames)
+ * left: Name node for outer binding, or null if the class is an expression
+ * that doesn't create an outer binding
+ * right: Name node for inner binding
+ * ClassMemberList (ListNode)
+ * head: list of N ClassMethod, ClassField or StaticClassBlock nodes
+ * count: N >= 0
+ * DefaultConstructor (ClassMethod)
+ * name: propertyName
+ * method: methodDefinition
+ * ClassMethod (ClassMethod)
+ * name: propertyName
+ * method: methodDefinition
+ * initializerIfPrivate: initializer to stamp private method onto instance
+ * Module (ModuleNode)
+ * body: statement list of the module
+ *
+ * <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 (DeclarationListNode)
+ * head: list of N Name or AssignExpr nodes
+ * each name node has either
+ * atom: variable name
+ * expr: initializer or null
+ * or
+ * atom: variable name
+ * each assignment node has
+ * left: pattern
+ * right: initializer
+ * count: N > 0
+ * ReturnStmt (UnaryNode)
+ * kid: returned expression, or null if none
+ * ExpressionStmt (UnaryNode)
+ * kid: expr
+ * EmptyStmt (NullaryNode)
+ * (no fields)
+ * LabelStmt (LabeledStatement)
+ * atom: label
+ * expr: labeled statement
+ * ImportDecl (BinaryNode)
+ * left: ImportSpecList import specifiers
+ * right: String module specifier
+ * ImportSpecList (ListNode)
+ * head: list of N ImportSpec nodes
+ * count: N >= 0 (N = 0 for `import {} from ...`)
+ * ImportSpec (BinaryNode)
+ * left: import name
+ * right: local binding name
+ * ImportNamespaceSpec (UnaryNode)
+ * kid: local binding name
+ * ExportStmt (UnaryNode)
+ * kid: declaration expression
+ * ExportFromStmt (BinaryNode)
+ * left: ExportSpecList export specifiers
+ * right: String module specifier
+ * ExportSpecList (ListNode)
+ * head: list of N ExportSpec nodes
+ * count: N >= 0 (N = 0 for `export {}`)
+ * ExportSpec (BinaryNode)
+ * left: local binding name
+ * right: export name
+ * ExportNamespaceSpec (UnaryNode)
+ * kid: export name
+ * ExportDefaultStmt (BinaryNode)
+ * left: export default declaration or expression
+ * right: Name node for assignment
+ *
+ * <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
+ * CoalesceExpr, OrExpr, AndExpr, BitOrExpr, BitXorExpr,
+ * BitAndExpr, StrictEqExpr, EqExpr, StrictNeExpr, NeExpr, LtExpr, LeExpr,
+ * GtExpr, GeExpr, InstanceOfExpr, InExpr, LshExpr, RshExpr, UrshExpr, AddExpr,
+ * SubExpr, MulExpr, DivExpr, ModExpr, PowExpr (ListNode)
+ * head: list of N subexpressions
+ * All of these operators are left-associative except Pow which is
+ * right-associative, but still forms a list (see comments in
+ * ParseNode::appendOrCreateList).
+ * count: N >= 2
+ * PosExpr, NegExpr, VoidExpr, NotExpr, BitNotExpr, TypeOfNameExpr,
+ * TypeOfExpr (UnaryNode)
+ * kid: unary expr
+ * PreIncrementExpr, PostIncrementExpr, PreDecrementExpr,
+ * PostDecrementExpr (UnaryNode)
+ * kid: member expr
+ * NewExpr (BinaryNode)
+ * left: ctor expression on the left of the '('
+ * right: Arguments
+ * DecoratorList (ListNode)
+ * head: list of N nodes, each item is one of:
+ * * NameNode (DecoratorMemberExpression)
+ * * CallNode (DecoratorCallExpression)
+ * * Node (DecoratorParenthesizedExpression)
+ * count: N > 0
+ * DeleteNameExpr, DeletePropExpr, DeleteElemExpr, DeleteExpr (UnaryNode)
+ * kid: expression that's evaluated, then the overall delete evaluates to
+ * true; can't be a kind for a more-specific ParseNodeKind::Delete*
+ * unless constant folding (or a similar parse tree manipulation) has
+ * occurred
+ * * DeleteNameExpr: Name expr
+ * * DeletePropExpr: Dot expr
+ * * DeleteElemExpr: Elem expr
+ * * DeleteOptionalChainExpr: Member expr
+ * * DeleteExpr: Member expr
+ * DeleteOptionalChainExpr (UnaryNode)
+ * kid: expression that's evaluated, then the overall delete evaluates to
+ * true; If constant folding occurs, Elem expr may become Dot expr.
+ * OptionalElemExpr does not get folded into OptionalDot.
+ * OptionalChain (UnaryNode)
+ * kid: expression that is evaluated as a chain. An Optional chain contains
+ * one or more optional nodes. It's first node (kid) is always an
+ * optional node, for example: an OptionalElemExpr, OptionalDotExpr, or
+ * OptionalCall. An OptionalChain will shortcircuit and return
+ * Undefined without evaluating the rest of the expression if any of the
+ * optional nodes it contains are nullish. An optionalChain also can
+ * contain nodes such as DotExpr, ElemExpr, NameExpr CallExpr, etc.
+ * These are evaluated normally.
+ * * OptionalDotExpr: Dot expr with jump
+ * * OptionalElemExpr: Elem expr with jump
+ * * OptionalCallExpr: Call expr with jump
+ * * DotExpr: Dot expr without jump
+ * * ElemExpr: Elem expr without jump
+ * * CallExpr: Call expr without jump
+ * PropertyNameExpr (NameNode)
+ * atom: property name being accessed
+ * privateNameKind: kind of the name if private
+ * DotExpr (PropertyAccess)
+ * left: MEMBER expr to left of '.'
+ * right: PropertyName to right of '.'
+ * OptionalDotExpr (OptionalPropertyAccess)
+ * left: MEMBER expr to left of '.', short circuits back to OptionalChain
+ * if nullish.
+ * right: PropertyName to right of '.'
+ * ElemExpr (PropertyByValue)
+ * left: MEMBER expr to left of '['
+ * right: expr between '[' and ']'
+ * OptionalElemExpr (OptionalPropertyByValue)
+ * left: MEMBER expr to left of '[', short circuits back to OptionalChain
+ * if nullish.
+ * right: expr between '[' and ']'
+ * CallExpr (BinaryNode)
+ * left: callee expression on the left of the '('
+ * right: Arguments
+ * OptionalCallExpr (BinaryNode)
+ * left: callee expression on the left of the '(', short circuits back to
+ * OptionalChain if nullish.
+ * right: Arguments
+ * Arguments (ListNode)
+ * head: list of arg1, arg2, ... argN
+ * count: N >= 0
+ * ArrayExpr (ListNode)
+ * head: list of N array element expressions
+ * holes ([,,]) are represented by Elision nodes,
+ * spread elements ([...X]) are represented by Spread nodes
+ * count: N >= 0
+ * ObjectExpr (ListNode)
+ * head: list of N nodes, each item is one of:
+ * * MutateProto
+ * * PropertyDefinition
+ * * Shorthand
+ * * Spread
+ * count: N >= 0
+ * PropertyDefinition (PropertyDefinition)
+ * key-value pair in object initializer or destructuring lhs
+ * left: property id
+ * right: value
+ * Shorthand (BinaryNode)
+ * Same fields as PropertyDefinition. This is used for object literal
+ * properties using shorthand ({x}).
+ * ComputedName (UnaryNode)
+ * ES6 ComputedPropertyName.
+ * kid: the AssignmentExpression inside the square brackets
+ * Name (NameNode)
+ * atom: name, or object atom
+ * StringExpr (NameNode)
+ * atom: string
+ * TemplateStringListExpr (ListNode)
+ * head: list of alternating expr and template strings
+ * TemplateString [, expression, TemplateString]+
+ * there's at least one expression. If the template literal contains
+ * no ${}-delimited expression, it's parsed as a single TemplateString
+ * TemplateStringExpr (NameNode)
+ * atom: template string atom
+ * TaggedTemplateExpr (BinaryNode)
+ * left: tag expression
+ * right: Arguments, with the first being the call site object, then
+ * arg1, arg2, ... argN
+ * CallSiteObj (CallSiteNode)
+ * head: an Array of raw TemplateString, then corresponding cooked
+ * TemplateString nodes
+ * Array [, cooked TemplateString]+
+ * where the Array is
+ * [raw TemplateString]+
+ * RegExpExpr (RegExpLiteral)
+ * regexp: RegExp model object
+ * NumberExpr (NumericLiteral)
+ * value: double value of numeric literal
+ * BigIntExpr (BigIntLiteral)
+ * stencil: script compilation struct that has |bigIntData| vector
+ * index: index into the script compilation's |bigIntData| vector
+ * TrueExpr, FalseExpr (BooleanLiteral)
+ * NullExpr (NullLiteral)
+ * RawUndefinedExpr (RawUndefinedLiteral)
+ *
+ * ThisExpr (UnaryNode)
+ * kid: '.this' Name if function `this`, else nullptr
+ * SuperBase (UnaryNode)
+ * kid: '.this' Name
+ * SuperCallExpr (BinaryNode)
+ * left: SuperBase
+ * right: Arguments
+ * SetThis (BinaryNode)
+ * left: '.this' Name
+ * right: SuperCall
+ *
+ * LexicalScope (LexicalScopeNode)
+ * scopeBindings: scope bindings
+ * scopeBody: scope body
+ * Generator (NullaryNode)
+ * InitialYield (UnaryNode)
+ * kid: generator object
+ * YieldExpr, YieldStarExpr, AwaitExpr (UnaryNode)
+ * kid: expr or null
+ */
+
+// FIXME: Remove `*Type` (bug 1489008)
+#define FOR_EACH_PARSENODE_SUBCLASS(MACRO) \
+ MACRO(BinaryNode, BinaryNodeType, asBinary) \
+ MACRO(AssignmentNode, AssignmentNodeType, asAssignment) \
+ MACRO(CaseClause, CaseClauseType, asCaseClause) \
+ MACRO(ClassMethod, ClassMethodType, asClassMethod) \
+ MACRO(ClassField, ClassFieldType, asClassField) \
+ MACRO(StaticClassBlock, StaticClassBlockType, asStaticClassBlock) \
+ MACRO(PropertyDefinition, PropertyDefinitionType, asPropertyDefinition) \
+ MACRO(ClassNames, ClassNamesType, asClassNames) \
+ MACRO(ForNode, ForNodeType, asFor) \
+ MACRO(PropertyAccess, PropertyAccessType, asPropertyAccess) \
+ MACRO(OptionalPropertyAccess, OptionalPropertyAccessType, \
+ asOptionalPropertyAccess) \
+ MACRO(PropertyByValue, PropertyByValueType, asPropertyByValue) \
+ MACRO(OptionalPropertyByValue, OptionalPropertyByValueType, \
+ asOptionalPropertyByValue) \
+ MACRO(PrivateMemberAccess, PrivateMemberAccessType, asPrivateMemberAccess) \
+ MACRO(OptionalPrivateMemberAccess, OptionalPrivateMemberAccessType, \
+ asOptionalPrivateMemberAccess) \
+ MACRO(NewTargetNode, NewTargetNodeType, asNewTargetNode) \
+ MACRO(SwitchStatement, SwitchStatementType, asSwitchStatement) \
+ MACRO(DeclarationListNode, DeclarationListNodeType, asDeclarationList) \
+ \
+ MACRO(ParamsBodyNode, ParamsBodyNodeType, asParamsBody) \
+ MACRO(FunctionNode, FunctionNodeType, asFunction) \
+ MACRO(ModuleNode, ModuleNodeType, asModule) \
+ \
+ MACRO(LexicalScopeNode, LexicalScopeNodeType, asLexicalScope) \
+ MACRO(ClassBodyScopeNode, ClassBodyScopeNodeType, asClassBodyScope) \
+ \
+ MACRO(ListNode, ListNodeType, asList) \
+ MACRO(CallSiteNode, CallSiteNodeType, asCallSite) \
+ MACRO(CallNode, CallNodeType, asCallNode) \
+ MACRO(CallNode, OptionalCallNodeType, asOptionalCallNode) \
+ \
+ MACRO(LoopControlStatement, LoopControlStatementType, \
+ asLoopControlStatement) \
+ MACRO(BreakStatement, BreakStatementType, asBreakStatement) \
+ MACRO(ContinueStatement, ContinueStatementType, asContinueStatement) \
+ \
+ MACRO(NameNode, NameNodeType, asName) \
+ MACRO(LabeledStatement, LabeledStatementType, asLabeledStatement) \
+ \
+ MACRO(NullaryNode, NullaryNodeType, asNullary) \
+ MACRO(BooleanLiteral, BooleanLiteralType, asBooleanLiteral) \
+ MACRO(DebuggerStatement, DebuggerStatementType, asDebuggerStatement) \
+ MACRO(NullLiteral, NullLiteralType, asNullLiteral) \
+ MACRO(RawUndefinedLiteral, RawUndefinedLiteralType, asRawUndefinedLiteral) \
+ \
+ MACRO(NumericLiteral, NumericLiteralType, asNumericLiteral) \
+ MACRO(BigIntLiteral, BigIntLiteralType, asBigIntLiteral) \
+ \
+ MACRO(RegExpLiteral, RegExpLiteralType, asRegExpLiteral) \
+ \
+ MACRO(TernaryNode, TernaryNodeType, asTernary) \
+ MACRO(ClassNode, ClassNodeType, asClass) \
+ MACRO(ConditionalExpression, ConditionalExpressionType, \
+ asConditionalExpression) \
+ MACRO(TryNode, TryNodeType, asTry) \
+ \
+ MACRO(UnaryNode, UnaryNodeType, asUnary) \
+ MACRO(ThisLiteral, ThisLiteralType, asThisLiteral)
+
+#define DECLARE_CLASS(typeName, longTypeName, asMethodName) class typeName;
+FOR_EACH_PARSENODE_SUBCLASS(DECLARE_CLASS)
+#undef DECLARE_CLASS
+
+enum class AccessorType { None, Getter, Setter };
+
+static inline bool IsConstructorKind(FunctionSyntaxKind kind) {
+ return kind == FunctionSyntaxKind::ClassConstructor ||
+ kind == FunctionSyntaxKind::DerivedClassConstructor;
+}
+
+static inline bool IsMethodDefinitionKind(FunctionSyntaxKind kind) {
+ return IsConstructorKind(kind) || kind == FunctionSyntaxKind::Method ||
+ kind == FunctionSyntaxKind::FieldInitializer ||
+ kind == FunctionSyntaxKind::Getter ||
+ kind == FunctionSyntaxKind::Setter;
+}
+
+// To help diagnose sporadic crashes in the frontend, a few assertions are
+// enabled in early beta builds. (Most are not; those still use MOZ_ASSERT.)
+// See bug 1547561.
+#if defined(EARLY_BETA_OR_EARLIER)
+# define JS_PARSE_NODE_ASSERT MOZ_RELEASE_ASSERT
+#else
+# define JS_PARSE_NODE_ASSERT MOZ_ASSERT
+#endif
+
+class ParseNode {
+ const ParseNodeKind pn_type; /* ParseNodeKind::PNK_* type */
+
+ bool pn_parens : 1; /* this expr was enclosed in parens */
+ bool pn_rhs_anon_fun : 1; /* this expr is anonymous function or class that
+ * is a direct RHS of ParseNodeKind::Assign or
+ * ParseNodeKind::PropertyDefinition of property,
+ * that needs SetFunctionName. */
+
+ protected:
+ // Used by ComputedName to indicate if the ComputedName is a
+ // a synthetic construct. This allows us to avoid needing to
+ // compute ToString on uncommon property values such as BigInt.
+ // Instead we parse as though they were computed names.
+ //
+ // We need this bit to distinguish a synthetic computed name like
+ // this however to undo this transformation in Reflect.parse and
+ // name guessing.
+ bool pn_synthetic_computed : 1;
+
+ ParseNode(const ParseNode& other) = delete;
+ void operator=(const ParseNode& other) = delete;
+
+ public:
+ explicit ParseNode(ParseNodeKind kind)
+ : pn_type(kind),
+ pn_parens(false),
+ pn_rhs_anon_fun(false),
+ pn_synthetic_computed(false),
+ pn_pos(0, 0),
+ pn_next(nullptr) {
+ JS_PARSE_NODE_ASSERT(ParseNodeKind::Start <= kind);
+ JS_PARSE_NODE_ASSERT(kind < ParseNodeKind::Limit);
+ }
+
+ ParseNode(ParseNodeKind kind, const TokenPos& pos)
+ : pn_type(kind),
+ pn_parens(false),
+ pn_rhs_anon_fun(false),
+ pn_synthetic_computed(false),
+ pn_pos(pos),
+ pn_next(nullptr) {
+ JS_PARSE_NODE_ASSERT(ParseNodeKind::Start <= kind);
+ JS_PARSE_NODE_ASSERT(kind < ParseNodeKind::Limit);
+ }
+
+ ParseNodeKind getKind() const {
+ JS_PARSE_NODE_ASSERT(ParseNodeKind::Start <= pn_type);
+ JS_PARSE_NODE_ASSERT(pn_type < ParseNodeKind::Limit);
+ return pn_type;
+ }
+ bool isKind(ParseNodeKind kind) const { return getKind() == kind; }
+
+ protected:
+ size_t getKindAsIndex() const {
+ return size_t(getKind()) - size_t(ParseNodeKind::Start);
+ }
+
+ // Used to implement test() on a few ParseNodes efficiently.
+ // (This enum doesn't fully reflect the ParseNode class hierarchy,
+ // so don't use it for anything else.)
+ enum class TypeCode : uint8_t {
+ Nullary,
+ Unary,
+ Binary,
+ Ternary,
+ List,
+ Name,
+ Other
+ };
+
+ // typeCodeTable[getKindAsIndex()] is the type code of a ParseNode of kind
+ // pnk.
+ static const TypeCode typeCodeTable[];
+
+ private:
+#ifdef DEBUG
+ static const size_t sizeTable[];
+#endif
+
+ public:
+ TypeCode typeCode() const { return typeCodeTable[getKindAsIndex()]; }
+
+ bool isBinaryOperation() const {
+ ParseNodeKind kind = getKind();
+ return ParseNodeKind::BinOpFirst <= kind &&
+ kind <= ParseNodeKind::BinOpLast;
+ }
+ inline bool isName(TaggedParserAtomIndex name) const;
+
+ /* Boolean attributes. */
+ bool isInParens() const { return pn_parens; }
+ bool isLikelyIIFE() const { return isInParens(); }
+ void setInParens(bool enabled) { pn_parens = enabled; }
+
+ bool isDirectRHSAnonFunction() const { return pn_rhs_anon_fun; }
+ void setDirectRHSAnonFunction(bool enabled) { pn_rhs_anon_fun = enabled; }
+
+ TokenPos pn_pos; /* two 16-bit pairs here, for 64 bits */
+ ParseNode* pn_next; /* intrinsic link in parent PN_LIST */
+
+ public:
+ /*
+ * If |left| is a list of the given kind/left-associative op, append
+ * |right| to it and return |left|. Otherwise return a [left, right] list.
+ */
+ static ParseNode* appendOrCreateList(ParseNodeKind kind, ParseNode* left,
+ ParseNode* right,
+ FullParseHandler* handler,
+ ParseContext* pc);
+
+ /* True if pn is a parsenode representing a literal constant. */
+ bool isLiteral() const {
+ return isKind(ParseNodeKind::NumberExpr) ||
+ isKind(ParseNodeKind::BigIntExpr) ||
+ isKind(ParseNodeKind::StringExpr) ||
+ isKind(ParseNodeKind::TrueExpr) ||
+ isKind(ParseNodeKind::FalseExpr) ||
+ isKind(ParseNodeKind::NullExpr) ||
+ isKind(ParseNodeKind::RawUndefinedExpr);
+ }
+
+ inline bool isConstant();
+
+ template <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(const ParserAtomsTable* parserAtoms);
+ void dump(const ParserAtomsTable* parserAtoms, GenericPrinter& out);
+ void dump(const ParserAtomsTable* parserAtoms, GenericPrinter& out,
+ int indent);
+
+ // The size of this node, in bytes.
+ size_t size() const { return sizeTable[getKindAsIndex()]; }
+#endif
+};
+
+// Remove a ParseNode, **pnp, from a parse tree, putting another ParseNode,
+// *pn, in its place.
+//
+// pnp points to a ParseNode pointer. This must be the only pointer that points
+// to the parse node being replaced. The replacement, *pn, is unchanged except
+// for its pn_next pointer; updating that is necessary if *pn's new parent is a
+// list node.
+inline void ReplaceNode(ParseNode** pnp, ParseNode* pn) {
+ pn->pn_next = (*pnp)->pn_next;
+ *pnp = pn;
+}
+
+class NullaryNode : public ParseNode {
+ public:
+ NullaryNode(ParseNodeKind kind, const TokenPos& pos) : ParseNode(kind, pos) {
+ MOZ_ASSERT(is<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(const ParserAtomsTable* parserAtoms, GenericPrinter& out,
+ int indent);
+#endif
+};
+
+class NameNode : public ParseNode {
+ TaggedParserAtomIndex atom_; /* lexical name or label atom */
+ PrivateNameKind privateNameKind_ = PrivateNameKind::None;
+
+ public:
+ NameNode(ParseNodeKind kind, TaggedParserAtomIndex atom, const TokenPos& pos)
+ : ParseNode(kind, pos), atom_(atom) {
+ MOZ_ASSERT(atom);
+ MOZ_ASSERT(is<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(const ParserAtomsTable* parserAtoms, GenericPrinter& out,
+ int indent);
+#endif
+
+ TaggedParserAtomIndex atom() const { return atom_; }
+
+ TaggedParserAtomIndex name() const {
+ MOZ_ASSERT(isKind(ParseNodeKind::Name) ||
+ isKind(ParseNodeKind::PrivateName));
+ return atom_;
+ }
+
+ void setAtom(TaggedParserAtomIndex atom) { atom_ = atom; }
+
+ void setPrivateNameKind(PrivateNameKind privateNameKind) {
+ privateNameKind_ = privateNameKind;
+ }
+
+ PrivateNameKind privateNameKind() { return privateNameKind_; }
+};
+
+inline bool ParseNode::isName(TaggedParserAtomIndex name) const {
+ return getKind() == ParseNodeKind::Name && as<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(const ParserAtomsTable* parserAtoms, GenericPrinter& out,
+ int indent);
+#endif
+
+ ParseNode* kid() const { return kid_; }
+
+ /*
+ * Non-null if this is a statement node which could be a member of a
+ * Directive Prologue: an expression statement consisting of a single
+ * string literal.
+ *
+ * This considers only the node and its children, not its context. After
+ * parsing, check the node's prologue flag to see if it is indeed part of
+ * a directive prologue.
+ *
+ * Note that a Directive Prologue can contain statements that cannot
+ * themselves be directives (string literals that include escape sequences
+ * or escaped newlines, say). This member function returns true for such
+ * nodes; we use it to determine the extent of the prologue.
+ */
+ TaggedParserAtomIndex isStringExprStatement() const {
+ if (isKind(ParseNodeKind::ExpressionStmt)) {
+ if (kid()->isKind(ParseNodeKind::StringExpr) && !kid()->isInParens()) {
+ return kid()->as<NameNode>().atom();
+ }
+ }
+ return TaggedParserAtomIndex::null();
+ }
+
+ // Methods used by FoldConstants.cpp.
+ ParseNode** unsafeKidReference() { return &kid_; }
+
+ void setSyntheticComputedName() { pn_synthetic_computed = true; }
+ bool isSyntheticComputedName() {
+ MOZ_ASSERT(isKind(ParseNodeKind::ComputedName));
+ return pn_synthetic_computed;
+ }
+};
+
+class BinaryNode : public ParseNode {
+ ParseNode* left_;
+ ParseNode* right_;
+
+ public:
+ BinaryNode(ParseNodeKind kind, const TokenPos& pos, ParseNode* left,
+ ParseNode* right)
+ : ParseNode(kind, pos), left_(left), right_(right) {
+ MOZ_ASSERT(is<BinaryNode>());
+ }
+
+ 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(const ParserAtomsTable* parserAtoms, GenericPrinter& out,
+ int indent);
+#endif
+
+ ParseNode* left() const { return left_; }
+
+ ParseNode* right() const { return right_; }
+
+ // Methods used by FoldConstants.cpp.
+ // callers are responsible for keeping the list consistent.
+ ParseNode** unsafeLeftReference() { return &left_; }
+
+ ParseNode** unsafeRightReference() { return &right_; }
+};
+
+class AssignmentNode : public BinaryNode {
+ public:
+ AssignmentNode(ParseNodeKind kind, ParseNode* left, ParseNode* right)
+ : BinaryNode(kind, TokenPos(left->pn_pos.begin, right->pn_pos.end), left,
+ right) {
+ MOZ_ASSERT(is<AssignmentNode>());
+ }
+
+ 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(const ParserAtomsTable* parserAtoms, GenericPrinter& out,
+ int indent);
+#endif
+
+ ParseNode* kid1() const { return kid1_; }
+
+ ParseNode* kid2() const { return kid2_; }
+
+ ParseNode* kid3() const { return kid3_; }
+
+ // Methods used by FoldConstants.cpp.
+ ParseNode** unsafeKid1Reference() { return &kid1_; }
+
+ ParseNode** unsafeKid2Reference() { return &kid2_; }
+
+ ParseNode** unsafeKid3Reference() { return &kid3_; }
+};
+
+class ListNode : public ParseNode {
+ ParseNode* head_; /* first node in list */
+ ParseNode** tail_; /* ptr to last node's pn_next in list */
+ uint32_t count_; /* number of nodes in list */
+ uint32_t xflags;
+
+ private:
+ // xflags bits.
+
+ // Statement list has top-level function statements.
+ static constexpr uint32_t hasTopLevelFunctionDeclarationsBit = Bit(0);
+
+ // Array/Object/Class initializer has non-constants.
+ // * array has holes
+ // * array has spread node
+ // * array has element which is known not to be constant
+ // * array has no element
+ // * object/class has __proto__
+ // * object/class has property which is known not to be constant
+ // * object/class shorthand property
+ // * object/class spread property
+ // * object/class has method
+ // * object/class has computed property
+ static constexpr uint32_t hasNonConstInitializerBit = Bit(1);
+
+ // Flag set by the emitter after emitting top-level function statements.
+ static constexpr uint32_t emittedTopLevelFunctionDeclarationsBit = Bit(2);
+
+ public:
+ ListNode(ParseNodeKind kind, const TokenPos& pos)
+ : ParseNode(kind, pos),
+ head_(nullptr),
+ tail_(&head_),
+ count_(0),
+ xflags(0) {
+ MOZ_ASSERT(is<ListNode>());
+ }
+
+ 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(const ParserAtomsTable* parserAtoms, GenericPrinter& out,
+ int indent);
+#endif
+
+ ParseNode* head() const { return head_; }
+
+ ParseNode** tail() const { return tail_; }
+
+ uint32_t count() const { return count_; }
+
+ bool empty() const { return count() == 0; }
+
+ void checkConsistency() const
+#ifndef DEBUG
+ {}
+#endif
+ ;
+
+ [[nodiscard]] bool hasTopLevelFunctionDeclarations() const {
+ MOZ_ASSERT(isKind(ParseNodeKind::StatementList));
+ return xflags & hasTopLevelFunctionDeclarationsBit;
+ }
+
+ [[nodiscard]] bool emittedTopLevelFunctionDeclarations() const {
+ MOZ_ASSERT(isKind(ParseNodeKind::StatementList));
+ MOZ_ASSERT(hasTopLevelFunctionDeclarations());
+ return xflags & emittedTopLevelFunctionDeclarationsBit;
+ }
+
+ [[nodiscard]] bool hasNonConstInitializer() const {
+ MOZ_ASSERT(isKind(ParseNodeKind::ArrayExpr) ||
+ isKind(ParseNodeKind::ObjectExpr));
+ return xflags & hasNonConstInitializerBit;
+ }
+
+ void setHasTopLevelFunctionDeclarations() {
+ MOZ_ASSERT(isKind(ParseNodeKind::StatementList));
+ xflags |= hasTopLevelFunctionDeclarationsBit;
+ }
+
+ void setEmittedTopLevelFunctionDeclarations() {
+ MOZ_ASSERT(isKind(ParseNodeKind::StatementList));
+ MOZ_ASSERT(hasTopLevelFunctionDeclarations());
+ xflags |= emittedTopLevelFunctionDeclarationsBit;
+ }
+
+ void setHasNonConstInitializer() {
+ MOZ_ASSERT(isKind(ParseNodeKind::ArrayExpr) ||
+ isKind(ParseNodeKind::ObjectExpr) ||
+ IF_RECORD_TUPLE(isKind(ParseNodeKind::TupleExpr), false) ||
+ IF_RECORD_TUPLE(isKind(ParseNodeKind::RecordExpr), false));
+ xflags |= hasNonConstInitializerBit;
+ }
+
+ void unsetHasNonConstInitializer() {
+ MOZ_ASSERT(isKind(ParseNodeKind::ArrayExpr) ||
+ isKind(ParseNodeKind::ObjectExpr) ||
+ IF_RECORD_TUPLE(isKind(ParseNodeKind::TupleExpr), false) ||
+ IF_RECORD_TUPLE(isKind(ParseNodeKind::RecordExpr), false));
+ xflags &= ~hasNonConstInitializerBit;
+ }
+
+ /*
+ * Compute a pointer to the last element in a singly-linked list. NB: list
+ * must be non-empty -- this is asserted!
+ */
+ ParseNode* last() const {
+ MOZ_ASSERT(!empty());
+ //
+ // ParseNode ParseNode
+ // +-----+---------+-----+ +-----+---------+-----+
+ // | ... | pn_next | ... | +-...->| ... | pn_next | ... |
+ // +-----+---------+-----+ | +-----+---------+-----+
+ // ^ | | ^ ^
+ // | +---------------+ | |
+ // | | tail()
+ // | |
+ // head() last()
+ //
+ return (ParseNode*)(uintptr_t(tail()) - offsetof(ParseNode, pn_next));
+ }
+
+ void replaceLast(ParseNode* node) {
+ MOZ_ASSERT(!empty());
+ pn_pos.end = node->pn_pos.end;
+
+ ParseNode* item = head();
+ ParseNode* lastNode = last();
+ MOZ_ASSERT(item);
+ if (item == lastNode) {
+ head_ = node;
+ } else {
+ while (item->pn_next != lastNode) {
+ MOZ_ASSERT(item->pn_next);
+ item = item->pn_next;
+ }
+ item->pn_next = node;
+ }
+ tail_ = &node->pn_next;
+ }
+
+ void append(ParseNode* item) {
+ MOZ_ASSERT(item->pn_pos.begin >= pn_pos.begin);
+ pn_pos.end = item->pn_pos.end;
+ *tail_ = item;
+ tail_ = &item->pn_next;
+ count_++;
+ }
+
+ void prepend(ParseNode* item) {
+ item->pn_next = head_;
+ head_ = item;
+ if (tail_ == &head_) {
+ tail_ = &item->pn_next;
+ }
+ count_++;
+ }
+
+ // Methods used by FoldConstants.cpp.
+ // Caller is responsible for keeping the list consistent.
+ ParseNode** unsafeHeadReference() { return &head_; }
+
+ void unsafeReplaceTail(ParseNode** newTail) {
+ tail_ = newTail;
+ checkConsistency();
+ }
+
+ void unsafeDecrementCount() {
+ MOZ_ASSERT(count() > 1);
+ count_--;
+ }
+
+ private:
+ // Classes to iterate over ListNode contents:
+ //
+ // Usage:
+ // ListNode* list;
+ // for (ParseNode* item : list->contents()) {
+ // // item is ParseNode* typed.
+ // }
+ class iterator {
+ private:
+ ParseNode* node_;
+
+ friend class ListNode;
+ explicit iterator(ParseNode* node) : node_(node) {}
+
+ public:
+ // Implement std::iterator_traits.
+ using iterator_category = std::input_iterator_tag;
+ using value_type = ParseNode*;
+ using difference_type = ptrdiff_t;
+ using pointer = ParseNode**;
+ using reference = ParseNode*&;
+
+ bool operator==(const iterator& other) const {
+ return node_ == other.node_;
+ }
+
+ bool operator!=(const iterator& other) const { return !(*this == other); }
+
+ iterator& operator++() {
+ node_ = node_->pn_next;
+ return *this;
+ }
+
+ ParseNode* operator*() { return node_; }
+
+ const ParseNode* operator*() const { return node_; }
+ };
+
+ class range {
+ private:
+ ParseNode* begin_;
+ ParseNode* end_;
+
+ friend class ListNode;
+ range(ParseNode* begin, ParseNode* end) : begin_(begin), end_(end) {}
+
+ public:
+ iterator begin() { return iterator(begin_); }
+
+ iterator end() { return iterator(end_); }
+
+ const iterator begin() const { return iterator(begin_); }
+
+ const iterator end() const { return iterator(end_); }
+
+ const iterator cbegin() const { return begin(); }
+
+ const iterator cend() const { return end(); }
+ };
+
+#ifdef DEBUG
+ [[nodiscard]] bool contains(ParseNode* target) const {
+ MOZ_ASSERT(target);
+ for (ParseNode* node : contents()) {
+ if (target == node) {
+ return true;
+ }
+ }
+ return false;
+ }
+#endif
+
+ public:
+ range contents() { return range(head(), nullptr); }
+
+ const range contents() const { return range(head(), nullptr); }
+
+ range contentsFrom(ParseNode* begin) {
+ MOZ_ASSERT_IF(begin, contains(begin));
+ return range(begin, nullptr);
+ }
+
+ const range contentsFrom(ParseNode* begin) const {
+ MOZ_ASSERT_IF(begin, contains(begin));
+ return range(begin, nullptr);
+ }
+
+ range contentsTo(ParseNode* end) {
+ MOZ_ASSERT_IF(end, contains(end));
+ return range(head(), end);
+ }
+
+ const range contentsTo(ParseNode* end) const {
+ MOZ_ASSERT_IF(end, contains(end));
+ return range(head(), end);
+ }
+};
+
+class DeclarationListNode : public ListNode {
+ public:
+ DeclarationListNode(ParseNodeKind kind, const TokenPos& pos)
+ : ListNode(kind, pos) {
+ MOZ_ASSERT(is<DeclarationListNode>());
+ }
+
+ static bool test(const ParseNode& node) {
+ bool match = node.isKind(ParseNodeKind::VarStmt) ||
+ node.isKind(ParseNodeKind::LetDecl) ||
+ node.isKind(ParseNodeKind::ConstDecl);
+ MOZ_ASSERT_IF(match, node.is<ListNode>());
+ return match;
+ }
+
+ auto* singleBinding() const {
+ MOZ_ASSERT(count() == 1);
+ return head();
+ }
+};
+
+class ParamsBodyNode : public ListNode {
+ public:
+ explicit ParamsBodyNode(const TokenPos& pos)
+ : ListNode(ParseNodeKind::ParamsBody, pos) {
+ MOZ_ASSERT(is<ParamsBodyNode>());
+ }
+
+ static bool test(const ParseNode& node) {
+ bool match = node.isKind(ParseNodeKind::ParamsBody);
+ MOZ_ASSERT_IF(match, node.is<ListNode>());
+ return match;
+ }
+
+ auto parameters() const {
+ MOZ_ASSERT(last()->is<LexicalScopeNode>());
+ return contentsTo(last());
+ }
+
+ auto* body() const {
+ MOZ_ASSERT(last()->is<LexicalScopeNode>());
+ return &last()->as<LexicalScopeNode>();
+ }
+};
+
+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;
+ }
+ MOZ_ASSERT(body_->is<ParamsBodyNode>());
+ }
+ return true;
+ }
+
+#ifdef DEBUG
+ void dumpImpl(const ParserAtomsTable* parserAtoms, GenericPrinter& out,
+ int indent);
+#endif
+
+ FunctionBox* funbox() const { return funbox_; }
+
+ ParamsBodyNode* body() const {
+ return body_ ? &body_->as<ParamsBodyNode>() : nullptr;
+ }
+
+ void setFunbox(FunctionBox* funbox) { funbox_ = funbox; }
+
+ void setBody(ParamsBodyNode* body) { body_ = body; }
+
+ FunctionSyntaxKind syntaxKind() const { return syntaxKind_; }
+
+ bool functionIsHoisted() const {
+ return syntaxKind() == FunctionSyntaxKind::Statement;
+ }
+};
+
+class ModuleNode : public ParseNode {
+ ParseNode* body_;
+
+ public:
+ explicit ModuleNode(const TokenPos& pos)
+ : ParseNode(ParseNodeKind::Module, pos), body_(nullptr) {
+ MOZ_ASSERT(!body_);
+ MOZ_ASSERT(is<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(const ParserAtomsTable* parserAtoms, 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(const ParserAtomsTable* parserAtoms, GenericPrinter& out,
+ int indent);
+#endif
+
+ double value() const { return value_; }
+
+ DecimalPoint decimalPoint() const { return decimalPoint_; }
+
+ // Return the decimal string representation of this numeric literal.
+ TaggedParserAtomIndex toAtom(FrontendContext* fc,
+ ParserAtomsTable& parserAtoms) const;
+};
+
+class BigIntLiteral : public ParseNode {
+ BigIntIndex index_;
+ bool isZero_;
+
+ public:
+ BigIntLiteral(BigIntIndex index, bool isZero, const TokenPos& pos)
+ : ParseNode(ParseNodeKind::BigIntExpr, pos),
+ index_(index),
+ isZero_(isZero) {}
+
+ static bool test(const ParseNode& node) {
+ return node.isKind(ParseNodeKind::BigIntExpr);
+ }
+
+ static constexpr TypeCode classTypeCode() { return TypeCode::Other; }
+
+ template <typename Visitor>
+ bool accept(Visitor& visitor) {
+ return true;
+ }
+
+#ifdef DEBUG
+ void dumpImpl(const ParserAtomsTable* parserAtoms, GenericPrinter& out,
+ int indent);
+#endif
+
+ BigIntIndex index() { return index_; }
+
+ bool isZero() const { return isZero_; }
+};
+
+template <ParseNodeKind NodeKind, typename ScopeType>
+class BaseScopeNode : public ParseNode {
+ using ParserData = typename ScopeType::ParserData;
+ ParserData* bindings;
+ ParseNode* body;
+ ScopeKind kind_;
+
+ public:
+ BaseScopeNode(ParserData* bindings, ParseNode* body,
+ ScopeKind kind = ScopeKind::Lexical)
+ : ParseNode(NodeKind, body->pn_pos),
+ bindings(bindings),
+ body(body),
+ kind_(kind) {}
+
+ static bool test(const ParseNode& node) { return node.isKind(NodeKind); }
+
+ static constexpr TypeCode classTypeCode() { return TypeCode::Other; }
+
+ template <typename Visitor>
+ bool accept(Visitor& visitor) {
+ return visitor.visit(body);
+ }
+
+#ifdef DEBUG
+ void dumpImpl(const ParserAtomsTable* parserAtoms, GenericPrinter& out,
+ int indent);
+#endif
+
+ ParserData* scopeBindings() const {
+ MOZ_ASSERT(!isEmptyScope());
+ return bindings;
+ }
+
+ ParseNode* scopeBody() const { return body; }
+
+ void setScopeBody(ParseNode* body) { this->body = body; }
+
+ bool isEmptyScope() const { return !bindings; }
+
+ ScopeKind kind() const { return kind_; }
+};
+
+class LexicalScopeNode
+ : public BaseScopeNode<ParseNodeKind::LexicalScope, LexicalScope> {
+ public:
+ LexicalScopeNode(LexicalScope::ParserData* bindings, ParseNode* body,
+ ScopeKind kind = ScopeKind::Lexical)
+ : BaseScopeNode(bindings, body, kind) {}
+};
+
+class ClassBodyScopeNode
+ : public BaseScopeNode<ParseNodeKind::ClassBodyScope, ClassBodyScope> {
+ public:
+ ClassBodyScopeNode(ClassBodyScope::ParserData* bindings, ListNode* memberList)
+ : BaseScopeNode(bindings, memberList, ScopeKind::ClassBody) {
+ MOZ_ASSERT(memberList->isKind(ParseNodeKind::ClassMemberList));
+ }
+
+ ListNode* memberList() const {
+ ListNode* list = &scopeBody()->as<ListNode>();
+ MOZ_ASSERT(list->isKind(ParseNodeKind::ClassMemberList));
+ return list;
+ }
+};
+
+class LabeledStatement : public NameNode {
+ ParseNode* statement_;
+
+ public:
+ LabeledStatement(TaggedParserAtomIndex label, ParseNode* stmt, uint32_t begin)
+ : NameNode(ParseNodeKind::LabelStmt, label,
+ TokenPos(begin, stmt->pn_pos.end)),
+ statement_(stmt) {}
+
+ TaggedParserAtomIndex label() const { return atom(); }
+
+ ParseNode* statement() const { return statement_; }
+
+ static bool test(const ParseNode& node) {
+ return node.isKind(ParseNodeKind::LabelStmt);
+ }
+
+ template <typename Visitor>
+ bool accept(Visitor& visitor) {
+ if (statement_) {
+ if (!visitor.visit(statement_)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+#ifdef DEBUG
+ void dumpImpl(const ParserAtomsTable* parserAtoms, GenericPrinter& out,
+ int indent);
+#endif
+};
+
+// Inside a switch statement, a CaseClause is a case-label and the subsequent
+// statements. The same node type is used for DefaultClauses. The only
+// difference is that their caseExpression() is null.
+class CaseClause : public BinaryNode {
+ public:
+ CaseClause(ParseNode* expr, ParseNode* stmts, uint32_t begin)
+ : BinaryNode(ParseNodeKind::Case, TokenPos(begin, stmts->pn_pos.end),
+ expr, stmts) {}
+
+ ParseNode* caseExpression() const { return left(); }
+
+ bool isDefault() const { return !caseExpression(); }
+
+ ListNode* statementList() const { return &right()->as<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 {
+ TaggedParserAtomIndex label_; /* target of break/continue statement */
+
+ protected:
+ LoopControlStatement(ParseNodeKind kind, TaggedParserAtomIndex label,
+ const TokenPos& pos)
+ : ParseNode(kind, pos), label_(label) {
+ MOZ_ASSERT(kind == ParseNodeKind::BreakStmt ||
+ kind == ParseNodeKind::ContinueStmt);
+ MOZ_ASSERT(is<LoopControlStatement>());
+ }
+
+ public:
+ /* Label associated with this break/continue statement, if any. */
+ TaggedParserAtomIndex label() const { return label_; }
+
+#ifdef DEBUG
+ void dumpImpl(const ParserAtomsTable* parserAtoms, GenericPrinter& out,
+ int indent);
+#endif
+
+ static bool test(const ParseNode& node) {
+ return node.isKind(ParseNodeKind::BreakStmt) ||
+ node.isKind(ParseNodeKind::ContinueStmt);
+ }
+
+ static constexpr TypeCode classTypeCode() { return TypeCode::Other; }
+
+ template <typename Visitor>
+ bool accept(Visitor& visitor) {
+ return true;
+ }
+};
+
+class BreakStatement : public LoopControlStatement {
+ public:
+ BreakStatement(TaggedParserAtomIndex label, const TokenPos& pos)
+ : LoopControlStatement(ParseNodeKind::BreakStmt, label, pos) {}
+
+ static bool test(const ParseNode& node) {
+ bool match = node.isKind(ParseNodeKind::BreakStmt);
+ MOZ_ASSERT_IF(match, node.is<LoopControlStatement>());
+ return match;
+ }
+};
+
+class ContinueStatement : public LoopControlStatement {
+ public:
+ ContinueStatement(TaggedParserAtomIndex label, const TokenPos& pos)
+ : LoopControlStatement(ParseNodeKind::ContinueStmt, label, pos) {}
+
+ static bool test(const ParseNode& node) {
+ bool match = node.isKind(ParseNodeKind::ContinueStmt);
+ MOZ_ASSERT_IF(match, node.is<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, FrontendContext* fc,
+ ParserAtomsTable& parserAtoms,
+ CompilationAtomCache& atomCache,
+ ExtensibleCompilationStencil& stencil) const;
+
+#ifdef DEBUG
+ void dumpImpl(const ParserAtomsTable* parserAtoms, GenericPrinter& out,
+ int indent);
+#endif
+
+ static bool test(const ParseNode& node) {
+ return node.isKind(ParseNodeKind::RegExpExpr);
+ }
+
+ static constexpr TypeCode classTypeCode() { return TypeCode::Other; }
+
+ template <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; }
+
+ TaggedParserAtomIndex name() const { return right()->as<NameNode>().atom(); }
+};
+
+class PropertyAccess : public PropertyAccessBase {
+ public:
+ PropertyAccess(ParseNode* lhs, NameNode* name, uint32_t begin, uint32_t end)
+ : PropertyAccessBase(ParseNodeKind::DotExpr, lhs, name, begin, end) {
+ MOZ_ASSERT(lhs);
+ MOZ_ASSERT(name);
+ }
+
+ static bool test(const ParseNode& node) {
+ bool match = node.isKind(ParseNodeKind::DotExpr);
+ MOZ_ASSERT_IF(match, node.is<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;
+ }
+};
+
+class PrivateMemberAccessBase : public BinaryNode {
+ public:
+ PrivateMemberAccessBase(ParseNodeKind kind, ParseNode* lhs, NameNode* name,
+ uint32_t begin, uint32_t end)
+ : BinaryNode(kind, TokenPos(begin, end), lhs, name) {
+ MOZ_ASSERT(name->isKind(ParseNodeKind::PrivateName));
+ }
+
+ ParseNode& expression() const { return *left(); }
+
+ NameNode& privateName() const {
+ NameNode& name = right()->as<NameNode>();
+ MOZ_ASSERT(name.isKind(ParseNodeKind::PrivateName));
+ return name;
+ }
+
+ static bool test(const ParseNode& node) {
+ bool match = node.isKind(ParseNodeKind::PrivateMemberExpr) ||
+ node.isKind(ParseNodeKind::OptionalPrivateMemberExpr);
+ MOZ_ASSERT_IF(match, node.is<BinaryNode>());
+ MOZ_ASSERT_IF(match, node.as<BinaryNode>().right()->isKind(
+ ParseNodeKind::PrivateName));
+ return match;
+ }
+};
+
+class PrivateMemberAccess : public PrivateMemberAccessBase {
+ public:
+ PrivateMemberAccess(ParseNode* lhs, NameNode* name, uint32_t begin,
+ uint32_t end)
+ : PrivateMemberAccessBase(ParseNodeKind::PrivateMemberExpr, lhs, name,
+ begin, end) {}
+
+ static bool test(const ParseNode& node) {
+ return node.isKind(ParseNodeKind::PrivateMemberExpr);
+ }
+};
+
+class OptionalPrivateMemberAccess : public PrivateMemberAccessBase {
+ public:
+ OptionalPrivateMemberAccess(ParseNode* lhs, NameNode* name, uint32_t begin,
+ uint32_t end)
+ : PrivateMemberAccessBase(ParseNodeKind::OptionalPrivateMemberExpr, lhs,
+ name, begin, end) {}
+
+ static bool test(const ParseNode& node) {
+ return node.isKind(ParseNodeKind::OptionalPrivateMemberExpr);
+ }
+};
+
+class NewTargetNode : public TernaryNode {
+ public:
+ NewTargetNode(NullaryNode* newHolder, NullaryNode* targetHolder,
+ NameNode* newTargetName)
+ : TernaryNode(ParseNodeKind::NewTargetExpr, newHolder, targetHolder,
+ newTargetName) {}
+
+ static bool test(const ParseNode& node) {
+ bool match = node.isKind(ParseNodeKind::NewTargetExpr);
+ MOZ_ASSERT_IF(match, node.is<TernaryNode>());
+ return match;
+ }
+
+ auto* newHolder() const { return &kid1()->as<NullaryNode>(); }
+ auto* targetHolder() const { return &kid2()->as<NullaryNode>(); }
+ auto* newTargetName() const { return &kid3()->as<NameNode>(); }
+};
+
+/*
+ * 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_;
+
+#ifdef ENABLE_DECORATORS
+ ListNode* decorators_;
+#endif
+
+ public:
+ /*
+ * Method definitions often keep a name and function body that overlap,
+ * so explicitly define the beginning and end here.
+ */
+ ClassMethod(ParseNodeKind kind, ParseNode* name, ParseNode* body,
+ AccessorType accessorType, bool isStatic,
+ FunctionNode* initializerIfPrivate
+#ifdef ENABLE_DECORATORS
+ ,
+ ListNode* decorators
+#endif
+ )
+ : BinaryNode(kind, TokenPos(name->pn_pos.begin, body->pn_pos.end), name,
+ body),
+ isStatic_(isStatic),
+ accessorType_(accessorType),
+ initializerIfPrivate_(initializerIfPrivate)
+#ifdef ENABLE_DECORATORS
+ ,
+ decorators_(decorators)
+#endif
+ {
+ MOZ_ASSERT(kind == ParseNodeKind::DefaultConstructor ||
+ kind == ParseNodeKind::ClassMethod);
+ }
+
+ static bool test(const ParseNode& node) {
+ bool match = node.isKind(ParseNodeKind::DefaultConstructor) ||
+ node.isKind(ParseNodeKind::ClassMethod);
+ MOZ_ASSERT_IF(match, node.is<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_; }
+
+#ifdef ENABLE_DECORATORS
+ ListNode* decorators() const { return decorators_; }
+#endif
+};
+
+class ClassField : public BinaryNode {
+ bool isStatic_;
+#ifdef ENABLE_DECORATORS
+ bool hasAccessor_;
+ ListNode* decorators_;
+#endif
+
+ public:
+ ClassField(ParseNode* name, ParseNode* initializer, bool isStatic
+#ifdef ENABLE_DECORATORS
+ ,
+ ListNode* decorators, bool hasAccessor
+#endif
+ )
+ : BinaryNode(ParseNodeKind::ClassField, initializer->pn_pos, name,
+ initializer),
+ isStatic_(isStatic)
+#ifdef ENABLE_DECORATORS
+ ,
+ hasAccessor_(hasAccessor),
+ decorators_(decorators)
+#endif
+ {
+ }
+
+ static bool test(const ParseNode& node) {
+ bool match = node.isKind(ParseNodeKind::ClassField);
+ MOZ_ASSERT_IF(match, node.is<BinaryNode>());
+ return match;
+ }
+
+ ParseNode& name() const { return *left(); }
+
+ FunctionNode* initializer() const { return &right()->as<FunctionNode>(); }
+
+ bool isStatic() const { return isStatic_; }
+
+#ifdef ENABLE_DECORATORS
+ ListNode* decorators() const { return decorators_; }
+ bool hasAccessor() const { return hasAccessor_; }
+#endif
+};
+
+// Hold onto the function generated for a class static block like
+//
+// class A {
+// static { /* this static block */ }
+// }
+//
+class StaticClassBlock : public UnaryNode {
+ public:
+ explicit StaticClassBlock(FunctionNode* function)
+ : UnaryNode(ParseNodeKind::StaticClassBlock, function->pn_pos, function) {
+ }
+
+ static bool test(const ParseNode& node) {
+ bool match = node.isKind(ParseNodeKind::StaticClassBlock);
+ MOZ_ASSERT_IF(match, node.is<UnaryNode>());
+ return match;
+ }
+ FunctionNode* function() const { return &kid()->as<FunctionNode>(); }
+};
+
+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>();
+ }
+
+ ClassBodyScopeNode* bodyScope() const {
+ return &innerScope()->scopeBody()->as<ClassBodyScopeNode>();
+ }
+
+#ifdef ENABLE_DECORATORS
+ ListNode* decorators_;
+#endif
+
+ public:
+ ClassNode(ParseNode* names, ParseNode* heritage,
+ LexicalScopeNode* memberBlock,
+#ifdef ENABLE_DECORATORS
+ ListNode* decorators,
+#endif
+ const TokenPos& pos)
+ : TernaryNode(ParseNodeKind::ClassDecl, names, heritage, memberBlock, pos)
+#ifdef ENABLE_DECORATORS
+ ,
+ decorators_(decorators)
+#endif
+ {
+ MOZ_ASSERT(innerScope()->scopeBody()->is<ClassBodyScopeNode>());
+ 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 { return bodyScope()->memberList(); }
+
+ LexicalScopeNode* scopeBindings() const {
+ LexicalScopeNode* scope = innerScope();
+ return scope->isEmptyScope() ? nullptr : scope;
+ }
+
+ ClassBodyScopeNode* bodyScopeBindings() const {
+ ClassBodyScopeNode* scope = bodyScope();
+ return scope->isEmptyScope() ? nullptr : scope;
+ }
+#ifdef ENABLE_DECORATORS
+ ListNode* decorators() const { return decorators_; }
+#endif
+};
+
+#ifdef DEBUG
+void DumpParseTree(ParserBase* parser, ParseNode* pn, GenericPrinter& out,
+ int indent = 0);
+#endif
+
+class ParseNodeAllocator {
+ public:
+ explicit ParseNodeAllocator(FrontendContext* fc, LifoAlloc& alloc)
+ : fc(fc), alloc(alloc) {}
+
+ void* allocNode(size_t size);
+
+ private:
+ FrontendContext* fc;
+ LifoAlloc& alloc;
+};
+
+inline bool ParseNode::isConstant() {
+ switch (pn_type) {
+ case ParseNodeKind::NumberExpr:
+ case ParseNodeKind::StringExpr:
+ case ParseNodeKind::TemplateStringExpr:
+ case ParseNodeKind::NullExpr:
+ case ParseNodeKind::RawUndefinedExpr:
+ case ParseNodeKind::FalseExpr:
+ case ParseNodeKind::TrueExpr:
+ return true;
+ case ParseNodeKind::ArrayExpr:
+ case ParseNodeKind::ObjectExpr:
+ return !as<ListNode>().hasNonConstInitializer();
+ default:
+ return false;
+ }
+}
+
+bool IsAnonymousFunctionDefinition(ParseNode* pn);
+
+} /* namespace frontend */
+} /* namespace js */
+
+#endif /* frontend_ParseNode_h */
diff --git a/js/src/frontend/ParseNodeVerify.cpp b/js/src/frontend/ParseNodeVerify.cpp
new file mode 100644
index 0000000000..b23a60649e
--- /dev/null
+++ b/js/src/frontend/ParseNodeVerify.cpp
@@ -0,0 +1,50 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "frontend/ParseNodeVerify.h"
+
+#include "frontend/ParseNodeVisitor.h"
+
+using namespace js;
+
+#ifdef DEBUG
+
+namespace js {
+namespace frontend {
+
+class ParseNodeVerifier : public ParseNodeVisitor<ParseNodeVerifier> {
+ using Base = ParseNodeVisitor<ParseNodeVerifier>;
+
+ const LifoAlloc& alloc_;
+
+ public:
+ ParseNodeVerifier(FrontendContext* fc, const LifoAlloc& alloc)
+ : Base(fc), alloc_(alloc) {}
+
+ [[nodiscard]] bool visit(ParseNode* pn) {
+ // pn->size() asserts that pn->pn_kind is valid, so we don't redundantly
+ // assert that here.
+ JS_PARSE_NODE_ASSERT(alloc_.contains(pn),
+ "start of parse node is in alloc");
+ JS_PARSE_NODE_ASSERT(alloc_.contains((unsigned char*)pn + pn->size()),
+ "end of parse node is in alloc");
+ if (pn->is<ListNode>()) {
+ pn->as<ListNode>().checkConsistency();
+ }
+ return Base::visit(pn);
+ }
+};
+
+} // namespace frontend
+} // namespace js
+
+bool frontend::CheckParseTree(FrontendContext* fc, const LifoAlloc& alloc,
+ ParseNode* pn) {
+ ParseNodeVerifier verifier(fc, alloc);
+ return verifier.visit(pn);
+}
+
+#endif // DEBUG
diff --git a/js/src/frontend/ParseNodeVerify.h b/js/src/frontend/ParseNodeVerify.h
new file mode 100644
index 0000000000..1898e122d4
--- /dev/null
+++ b/js/src/frontend/ParseNodeVerify.h
@@ -0,0 +1,48 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef frontend_ParseNodeVerify_h
+#define frontend_ParseNodeVerify_h
+
+#include "frontend/SyntaxParseHandler.h" // SyntaxParseHandler::Node
+
+namespace js {
+
+class FrontendContext;
+class LifoAlloc;
+
+namespace frontend {
+
+class ParseNode;
+
+// In most builds, examine the given ParseNode and crash if it's not
+// well-formed. (In late beta and shipping builds of Firefox, this does
+// nothing.)
+//
+// This returns true on success, and false only if we hit the recursion limit.
+// If the ParseNode is actually bad, we crash.
+
+#ifdef DEBUG
+[[nodiscard]] extern bool CheckParseTree(FrontendContext* fc,
+ const LifoAlloc& alloc, ParseNode* pn);
+#else
+[[nodiscard]] inline bool CheckParseTree(FrontendContext* fc,
+ const LifoAlloc& alloc,
+ ParseNode* pn) {
+ return true;
+}
+#endif
+
+[[nodiscard]] inline bool CheckParseTree(FrontendContext* fc,
+ const LifoAlloc& alloc,
+ SyntaxParseHandler::Node pn) {
+ return true;
+}
+
+} /* namespace frontend */
+} /* namespace js */
+
+#endif // frontend_ParseNodeVerify_h
diff --git a/js/src/frontend/ParseNodeVisitor.h b/js/src/frontend/ParseNodeVisitor.h
new file mode 100644
index 0000000000..18e57d68e6
--- /dev/null
+++ b/js/src/frontend/ParseNodeVisitor.h
@@ -0,0 +1,136 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef frontend_ParseNodeVisitor_h
+#define frontend_ParseNodeVisitor_h
+
+#include "mozilla/Assertions.h"
+
+#include "frontend/ParseNode.h"
+#include "js/friend/StackLimits.h" // js::AutoCheckRecursionLimit
+
+namespace js {
+
+class FrontendContext;
+
+namespace frontend {
+
+/**
+ * Utility class for walking a JS AST.
+ *
+ * Simple usage:
+ *
+ * class HowTrueVisitor : public ParseNodeVisitor<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:
+ FrontendContext* fc_;
+
+ explicit ParseNodeVisitor(FrontendContext* fc) : fc_(fc) {}
+
+ [[nodiscard]] bool visit(ParseNode* pn) {
+ AutoCheckRecursionLimit recursion(fc_);
+ if (!recursion.check(fc_)) {
+ return false;
+ }
+
+ switch (pn->getKind()) {
+#define VISIT_CASE(KIND, TYPE) \
+ case ParseNodeKind::KIND: \
+ return static_cast<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) \
+ [[nodiscard]] 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:
+ FrontendContext* fc_;
+
+ explicit RewritingParseNodeVisitor(FrontendContext* fc) : fc_(fc) {}
+
+ [[nodiscard]] bool visit(ParseNode*& pn) {
+ AutoCheckRecursionLimit recursion(fc_);
+ if (!recursion.check(fc_)) {
+ return false;
+ }
+
+ switch (pn->getKind()) {
+#define VISIT_CASE(KIND, _type) \
+ case ParseNodeKind::KIND: \
+ return static_cast<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) \
+ [[nodiscard]] 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..b4d0e76cb7
--- /dev/null
+++ b/js/src/frontend/Parser.cpp
@@ -0,0 +1,13078 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * JS parser.
+ *
+ * This is a recursive-descent parser for the JavaScript language specified by
+ * "The ECMAScript Language Specification" (Standard ECMA-262). It uses
+ * lexical and semantic feedback to disambiguate non-LL(1) structures. It
+ * generates trees of nodes induced by the recursive parsing (not precise
+ * syntax trees, see Parser.h). After tree construction, it rewrites trees to
+ * fold constants and evaluate compile-time expressions.
+ *
+ * This parser attempts no error recovery.
+ */
+
+#include "frontend/Parser.h"
+
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/Casting.h"
+#include "mozilla/Range.h"
+#include "mozilla/Sprintf.h"
+#include "mozilla/Utf8.h"
+#include "mozilla/Variant.h"
+
+#include <memory>
+#include <new>
+#include <type_traits>
+
+#include "jsnum.h"
+#include "jstypes.h"
+
+#include "frontend/BytecodeCompiler.h"
+#include "frontend/FoldConstants.h"
+#include "frontend/FunctionSyntaxKind.h" // FunctionSyntaxKind
+#include "frontend/ModuleSharedContext.h"
+#include "frontend/ParseNode.h"
+#include "frontend/ParseNodeVerify.h"
+#include "frontend/ParserAtom.h" // TaggedParserAtomIndex, ParserAtomsTable, ParserAtom
+#include "frontend/ScriptIndex.h" // ScriptIndex
+#include "frontend/TokenStream.h"
+#include "irregexp/RegExpAPI.h"
+#include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_*
+#include "js/HashTable.h"
+#include "js/RegExpFlags.h" // JS::RegExpFlags
+#include "js/Stack.h" // JS::NativeStackLimit
+#include "util/StringBuffer.h" // StringBuffer
+#include "vm/BytecodeUtil.h"
+#include "vm/FunctionFlags.h" // js::FunctionFlags
+#include "vm/GeneratorAndAsyncKind.h" // js::GeneratorKind, js::FunctionAsyncKind
+#include "vm/JSContext.h"
+#include "vm/JSScript.h"
+#include "vm/ModuleBuilder.h" // js::ModuleBuilder
+#include "vm/Scope.h" // GetScopeDataTrailingNames
+#include "vm/WellKnownAtom.h" // js_*_str
+#include "wasm/AsmJS.h"
+
+#include "frontend/ParseContext-inl.h"
+#include "frontend/SharedContext-inl.h"
+
+using namespace js;
+
+using mozilla::AssertedCast;
+using mozilla::AsVariant;
+using mozilla::Maybe;
+using mozilla::Nothing;
+using mozilla::PointerRangeSize;
+using mozilla::Some;
+using mozilla::Utf8Unit;
+
+using JS::ReadOnlyCompileOptions;
+using JS::RegExpFlags;
+
+namespace js::frontend {
+
+using DeclaredNamePtr = ParseContext::Scope::DeclaredNamePtr;
+using AddDeclaredNamePtr = ParseContext::Scope::AddDeclaredNamePtr;
+using BindingIter = ParseContext::Scope::BindingIter;
+using UsedNamePtr = UsedNameTracker::UsedNameMap::Ptr;
+
+using ParserBindingNameVector = Vector<ParserBindingName, 6>;
+
+static inline void PropagateTransitiveParseFlags(const FunctionBox* inner,
+ SharedContext* outer) {
+ if (inner->bindingsAccessedDynamically()) {
+ outer->setBindingsAccessedDynamically();
+ }
+ if (inner->hasDirectEval()) {
+ outer->setHasDirectEval();
+ }
+}
+
+static bool StatementKindIsBraced(StatementKind kind) {
+ return kind == StatementKind::Block || kind == StatementKind::Switch ||
+ kind == StatementKind::Try || kind == StatementKind::Catch ||
+ kind == StatementKind::Finally;
+}
+
+template <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(FrontendContext* fc,
+ CompilationState& compilationState,
+ Kind kind)
+ : fc_(fc),
+ alloc_(compilationState.parserAllocScope.alloc()),
+ compilationState_(compilationState),
+ pc_(nullptr),
+ usedNames_(compilationState.usedNames) {
+ fc_->nameCollectionPool().addActiveCompilation();
+}
+
+ParserSharedBase::~ParserSharedBase() {
+ fc_->nameCollectionPool().removeActiveCompilation();
+}
+
+#if defined(DEBUG) || defined(JS_JITSPEW)
+void ParserSharedBase::dumpAtom(TaggedParserAtomIndex index) const {
+ parserAtoms().dump(index);
+}
+#endif
+
+ParserBase::ParserBase(FrontendContext* fc,
+ const ReadOnlyCompileOptions& options,
+ bool foldConstants, CompilationState& compilationState)
+ : ParserSharedBase(fc, compilationState, ParserSharedBase::Kind::Parser),
+ anyChars(fc, options, this),
+ ss(nullptr),
+ foldConstants_(foldConstants),
+#ifdef DEBUG
+ checkOptionsCalled_(false),
+#endif
+ isUnexpectedEOF_(false),
+ awaitHandling_(AwaitIsName),
+ inParametersOfAsyncFunction_(false) {
+}
+
+bool ParserBase::checkOptions() {
+#ifdef DEBUG
+ checkOptionsCalled_ = true;
+#endif
+
+ return anyChars.checkOptions();
+}
+
+ParserBase::~ParserBase() { MOZ_ASSERT(checkOptionsCalled_); }
+
+JSAtom* ParserBase::liftParserAtomToJSAtom(TaggedParserAtomIndex index) {
+ JSContext* cx = fc_->maybeCurrentJSContext();
+ MOZ_ASSERT(cx);
+ return parserAtoms().toJSAtom(cx, fc_, index,
+ compilationState_.input.atomCache);
+}
+
+template <class ParseHandler>
+PerHandlerParser<ParseHandler>::PerHandlerParser(
+ FrontendContext* fc, const ReadOnlyCompileOptions& options,
+ bool foldConstants, CompilationState& compilationState,
+ void* internalSyntaxParser)
+ : ParserBase(fc, options, foldConstants, compilationState),
+ handler_(fc, compilationState),
+ internalSyntaxParser_(internalSyntaxParser) {
+ MOZ_ASSERT(compilationState.isInitialStencil() ==
+ compilationState.input.isInitialStencil());
+}
+
+template <class ParseHandler, typename Unit>
+GeneralParser<ParseHandler, Unit>::GeneralParser(
+ FrontendContext* fc, const ReadOnlyCompileOptions& options,
+ const Unit* units, size_t length, bool foldConstants,
+ CompilationState& compilationState, SyntaxParser* syntaxParser)
+ : Base(fc, options, foldConstants, compilationState, syntaxParser),
+ tokenStream(fc, &compilationState.parserAtoms, options, units, length) {}
+
+template <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, TaggedParserAtomIndex explicitName,
+ FunctionFlags flags, uint32_t toStringStart, Directives inheritedDirectives,
+ GeneratorKind generatorKind, FunctionAsyncKind asyncKind) {
+ MOZ_ASSERT(funNode);
+
+ ScriptIndex index = ScriptIndex(compilationState_.scriptData.length());
+ if (uint32_t(index) >= TaggedScriptThingIndex::IndexLimit) {
+ ReportAllocationOverflow(fc_);
+ return nullptr;
+ }
+ if (!compilationState_.appendScriptStencilAndData(fc_)) {
+ return nullptr;
+ }
+
+ bool isInitialStencil = compilationState_.isInitialStencil();
+
+ // This source extent will be further filled in during the remainder of parse.
+ SourceExtent extent;
+ extent.toStringStart = toStringStart;
+
+ /*
+ * We use JSContext.tempLifoAlloc to allocate parsed objects and place them
+ * on a list in this Parser to ensure GC safety. Thus the tempLifoAlloc
+ * arenas containing the entries must be alive until we are done with
+ * scanning, parsing and code generation for the whole script or top-level
+ * function.
+ */
+ FunctionBox* funbox = alloc_.new_<FunctionBox>(
+ fc_, extent, compilationState_, inheritedDirectives, generatorKind,
+ asyncKind, isInitialStencil, explicitName, flags, index);
+ if (!funbox) {
+ ReportOutOfMemory(fc_);
+ return nullptr;
+ }
+
+ handler_.setFunctionBox(funNode, funbox);
+
+ return funbox;
+}
+
+template <class ParseHandler>
+FunctionBox* PerHandlerParser<ParseHandler>::newFunctionBox(
+ FunctionNodeType funNode, const ScriptStencil& cachedScriptData,
+ const ScriptStencilExtra& cachedScriptExtra) {
+ MOZ_ASSERT(funNode);
+
+ ScriptIndex index = ScriptIndex(compilationState_.scriptData.length());
+ if (uint32_t(index) >= TaggedScriptThingIndex::IndexLimit) {
+ ReportAllocationOverflow(fc_);
+ return nullptr;
+ }
+ if (!compilationState_.appendScriptStencilAndData(fc_)) {
+ return nullptr;
+ }
+
+ /*
+ * We use JSContext.tempLifoAlloc to allocate parsed objects and place them
+ * on a list in this Parser to ensure GC safety. Thus the tempLifoAlloc
+ * arenas containing the entries must be alive until we are done with
+ * scanning, parsing and code generation for the whole script or top-level
+ * function.
+ */
+ FunctionBox* funbox = alloc_.new_<FunctionBox>(
+ fc_, cachedScriptExtra.extent, compilationState_,
+ Directives(/* strict = */ false), cachedScriptExtra.generatorKind(),
+ cachedScriptExtra.asyncKind(), compilationState_.isInitialStencil(),
+ cachedScriptData.functionAtom, cachedScriptData.functionFlags, index);
+ if (!funbox) {
+ ReportOutOfMemory(fc_);
+ return nullptr;
+ }
+
+ handler_.setFunctionBox(funNode, funbox);
+ funbox->initFromScriptStencilExtra(cachedScriptExtra);
+
+ return funbox;
+}
+
+bool ParserBase::setSourceMapInfo() {
+ // If support for source pragmas have been fully disabled, we can skip
+ // processing of all of these values.
+ if (!options().sourcePragmas()) {
+ return true;
+ }
+
+ // Not all clients initialize ss. Can't update info to an object that isn't
+ // there.
+ if (!ss) {
+ return true;
+ }
+
+ if (anyChars.hasDisplayURL()) {
+ if (!ss->setDisplayURL(fc_, anyChars.displayURL())) {
+ return false;
+ }
+ }
+
+ if (anyChars.hasSourceMapURL()) {
+ MOZ_ASSERT(!ss->hasSourceMapURL());
+ if (!ss->setSourceMapURL(fc_, anyChars.sourceMapURL())) {
+ return false;
+ }
+ }
+
+ /*
+ * Source map URLs passed as a compile option (usually via a HTTP source map
+ * header) override any source map urls passed as comment pragmas.
+ */
+ if (options().sourceMapURL()) {
+ // Warn about the replacement, but use the new one.
+ if (ss->hasSourceMapURL()) {
+ if (!warningNoOffset(JSMSG_ALREADY_HAS_PRAGMA, ss->filename(),
+ "//# sourceMappingURL")) {
+ return false;
+ }
+ }
+
+ if (!ss->setSourceMapURL(fc_, options().sourceMapURL())) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+/*
+ * Parse a top-level JS script.
+ */
+template <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(this->fc_, ScopeKind::Global, options(),
+ directives, extent);
+ SourceParseContext globalpc(this, &globalsc, /* newDirectives = */ nullptr);
+ if (!globalpc.init()) {
+ return null();
+ }
+
+ ParseContext::VarScope varScope(this);
+ if (!varScope.init(pc_)) {
+ return null();
+ }
+
+ ListNodeType stmtList = statementList(YieldIsName);
+ if (!stmtList) {
+ return null();
+ }
+
+ TokenKind tt;
+ if (!tokenStream.getToken(&tt, TokenStream::SlashIsRegExp)) {
+ return null();
+ }
+ if (tt != TokenKind::Eof) {
+ error(JSMSG_GARBAGE_AFTER_INPUT, "script", TokenKindToDesc(tt));
+ return null();
+ }
+
+ if (!CheckParseTree(this->fc_, alloc_, stmtList)) {
+ return null();
+ }
+
+ if (foldConstants_) {
+ Node node = stmtList;
+ // Don't constant-fold inside "use asm" code, as this could create a parse
+ // tree that doesn't type-check as asm.js.
+ if (!pc_->useAsmOrInsideUseAsm()) {
+ if (!FoldConstants(this->fc_, this->parserAtoms(), &node, &handler_)) {
+ return null();
+ }
+ }
+ stmtList = handler_.asList(node);
+ }
+
+ return stmtList;
+}
+
+/*
+ * Strict mode forbids introducing new definitions for 'eval', 'arguments',
+ * 'let', 'static', 'yield', or for any strict mode reserved word.
+ */
+bool ParserBase::isValidStrictBinding(TaggedParserAtomIndex name) {
+ TokenKind tt = ReservedWordTokenKind(name);
+ if (tt == TokenKind::Limit) {
+ return name != TaggedParserAtomIndex::WellKnown::eval() &&
+ name != TaggedParserAtomIndex::WellKnown::arguments();
+ }
+ return tt != TokenKind::Let && tt != TokenKind::Static &&
+ tt != TokenKind::Yield && !TokenKindIsStrictReservedWord(tt);
+}
+
+/*
+ * Returns true if all parameter names are valid strict mode binding names and
+ * no duplicate parameter names are present.
+ */
+bool ParserBase::hasValidSimpleStrictParameterNames() {
+ MOZ_ASSERT(pc_->isFunctionBox() &&
+ pc_->functionBox()->hasSimpleParameterList());
+
+ if (pc_->functionBox()->hasDuplicateParameters) {
+ return false;
+ }
+
+ for (auto name : pc_->positionalFormalParameterNames()) {
+ MOZ_ASSERT(name);
+ if (!isValidStrictBinding(name)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+template <class ParseHandler, typename Unit>
+void GeneralParser<ParseHandler, Unit>::reportMissingClosing(
+ unsigned errorNumber, unsigned noteNumber, uint32_t openedPos) {
+ auto notes = MakeUnique<JSErrorNotes>();
+ if (!notes) {
+ ReportOutOfMemory(this->fc_);
+ return;
+ }
+
+ uint32_t line, column;
+ tokenStream.computeLineAndColumn(openedPos, &line, &column);
+
+ const size_t MaxWidth = sizeof("4294967295");
+ char columnNumber[MaxWidth];
+ SprintfLiteral(columnNumber, "%" PRIu32, column);
+ char lineNumber[MaxWidth];
+ SprintfLiteral(lineNumber, "%" PRIu32, line);
+
+ if (!notes->addNoteASCII(this->fc_, getFilename(), 0, line, column,
+ GetErrorMessage, nullptr, noteNumber, lineNumber,
+ columnNumber)) {
+ return;
+ }
+
+ errorWithNotes(std::move(notes), errorNumber);
+}
+
+template <class ParseHandler, typename Unit>
+void GeneralParser<ParseHandler, Unit>::reportRedeclaration(
+ TaggedParserAtomIndex name, DeclarationKind prevKind, TokenPos pos,
+ uint32_t prevPos) {
+ UniqueChars bytes = this->parserAtoms().toPrintableString(name);
+ if (!bytes) {
+ ReportOutOfMemory(this->fc_);
+ return;
+ }
+
+ if (prevPos == DeclaredNameInfo::npos) {
+ errorAt(pos.begin, JSMSG_REDECLARED_VAR, DeclarationKindString(prevKind),
+ bytes.get());
+ return;
+ }
+
+ auto notes = MakeUnique<JSErrorNotes>();
+ if (!notes) {
+ ReportOutOfMemory(this->fc_);
+ return;
+ }
+
+ uint32_t line, column;
+ tokenStream.computeLineAndColumn(prevPos, &line, &column);
+
+ const size_t MaxWidth = sizeof("4294967295");
+ char columnNumber[MaxWidth];
+ SprintfLiteral(columnNumber, "%" PRIu32, column);
+ char lineNumber[MaxWidth];
+ SprintfLiteral(lineNumber, "%" PRIu32, line);
+
+ if (!notes->addNoteASCII(this->fc_, getFilename(), 0, line, column,
+ GetErrorMessage, nullptr, JSMSG_REDECLARED_PREV,
+ lineNumber, columnNumber)) {
+ return;
+ }
+
+ errorWithNotesAt(std::move(notes), pos.begin, JSMSG_REDECLARED_VAR,
+ DeclarationKindString(prevKind), bytes.get());
+}
+
+// notePositionalFormalParameter is called for both the arguments of a regular
+// function definition and the arguments specified by the Function
+// constructor.
+//
+// The 'disallowDuplicateParams' bool indicates whether the use of another
+// feature (destructuring or default arguments) disables duplicate arguments.
+// (ECMA-262 requires us to support duplicate parameter names, but, for newer
+// features, we consider the code to have "opted in" to higher standards and
+// forbid duplicates.)
+template <class ParseHandler, typename Unit>
+bool GeneralParser<ParseHandler, Unit>::notePositionalFormalParameter(
+ FunctionNodeType funNode, TaggedParserAtomIndex name, uint32_t beginPos,
+ bool disallowDuplicateParams, bool* duplicatedParam) {
+ if (AddDeclaredNamePtr p =
+ pc_->functionScope().lookupDeclaredNameForAdd(name)) {
+ if (disallowDuplicateParams) {
+ error(JSMSG_BAD_DUP_ARGS);
+ return false;
+ }
+
+ // Strict-mode disallows duplicate args. We may not know whether we are
+ // in strict mode or not (since the function body hasn't been parsed).
+ // In such cases, report will queue up the potential error and return
+ // 'true'.
+ if (pc_->sc()->strict()) {
+ UniqueChars bytes = this->parserAtoms().toPrintableString(name);
+ if (!bytes) {
+ ReportOutOfMemory(this->fc_);
+ return false;
+ }
+ if (!strictModeError(JSMSG_DUPLICATE_FORMAL, bytes.get())) {
+ return false;
+ }
+ }
+
+ *duplicatedParam = true;
+ } else {
+ DeclarationKind kind = DeclarationKind::PositionalFormalParameter;
+ if (!pc_->functionScope().addDeclaredName(pc_, p, name, kind, beginPos)) {
+ return false;
+ }
+ }
+
+ if (!pc_->positionalFormalParameterNames().append(
+ TrivialTaggedParserAtomIndex::from(name))) {
+ ReportOutOfMemory(this->fc_);
+ return false;
+ }
+
+ NameNodeType paramNode = newName(name);
+ if (!paramNode) {
+ return false;
+ }
+
+ handler_.addFunctionFormalParameter(funNode, paramNode);
+ return true;
+}
+
+template <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(
+ TrivialTaggedParserAtomIndex::null())) {
+ ReportOutOfMemory(fc_);
+ return false;
+ }
+
+ handler_.addFunctionFormalParameter(funNode, destruct);
+ return true;
+}
+
+template <class ParseHandler, typename Unit>
+bool GeneralParser<ParseHandler, Unit>::noteDeclaredName(
+ TaggedParserAtomIndex name, DeclarationKind kind, TokenPos pos,
+ ClosedOver isClosedOver) {
+ // The asm.js validator does all its own symbol-table management so, as an
+ // optimization, avoid doing any work here.
+ if (pc_->useAsmOrInsideUseAsm()) {
+ return true;
+ }
+
+ switch (kind) {
+ case DeclarationKind::Var:
+ case DeclarationKind::BodyLevelFunction: {
+ Maybe<DeclarationKind> redeclaredKind;
+ uint32_t prevPos;
+ if (!pc_->tryDeclareVar(name, this, kind, pos.begin, &redeclaredKind,
+ &prevPos)) {
+ return false;
+ }
+
+ if (redeclaredKind) {
+ reportRedeclaration(name, *redeclaredKind, pos, prevPos);
+ return false;
+ }
+
+ break;
+ }
+
+ case DeclarationKind::ModuleBodyLevelFunction: {
+ MOZ_ASSERT(pc_->atModuleLevel());
+
+ AddDeclaredNamePtr p = pc_->varScope().lookupDeclaredNameForAdd(name);
+ if (p) {
+ reportRedeclaration(name, p->value()->kind(), pos, p->value()->pos());
+ return false;
+ }
+
+ if (!pc_->varScope().addDeclaredName(pc_, p, name, kind, pos.begin,
+ isClosedOver)) {
+ return false;
+ }
+
+ // Body-level functions in modules are always closed over.
+ pc_->varScope().lookupDeclaredName(name)->value()->setClosedOver();
+
+ break;
+ }
+
+ case DeclarationKind::FormalParameter: {
+ // It is an early error if any non-positional formal parameter name
+ // (e.g., destructuring formal parameter) is duplicated.
+
+ AddDeclaredNamePtr p =
+ pc_->functionScope().lookupDeclaredNameForAdd(name);
+ if (p) {
+ error(JSMSG_BAD_DUP_ARGS);
+ return false;
+ }
+
+ if (!pc_->functionScope().addDeclaredName(pc_, p, name, kind, pos.begin,
+ isClosedOver)) {
+ return false;
+ }
+
+ break;
+ }
+
+ case DeclarationKind::LexicalFunction:
+ case DeclarationKind::PrivateName:
+ case DeclarationKind::Synthetic:
+ case DeclarationKind::PrivateMethod: {
+ ParseContext::Scope* scope = pc_->innermostScope();
+ AddDeclaredNamePtr p = scope->lookupDeclaredNameForAdd(name);
+ if (p) {
+ reportRedeclaration(name, p->value()->kind(), pos, p->value()->pos());
+ return false;
+ }
+
+ if (!scope->addDeclaredName(pc_, p, name, kind, pos.begin,
+ isClosedOver)) {
+ return false;
+ }
+
+ break;
+ }
+
+ case DeclarationKind::SloppyLexicalFunction: {
+ // Functions in block have complex allowances in sloppy mode for being
+ // labelled that other lexical declarations do not have. Those checks
+ // are more complex than calling checkLexicalDeclarationDirectlyWithin-
+ // Block and are done in checkFunctionDefinition.
+
+ ParseContext::Scope* scope = pc_->innermostScope();
+ if (AddDeclaredNamePtr p = scope->lookupDeclaredNameForAdd(name)) {
+ // It is usually an early error if there is another declaration
+ // with the same name in the same scope.
+ //
+ // Sloppy lexical functions may redeclare other sloppy lexical
+ // functions for web compatibility reasons.
+ if (p->value()->kind() != DeclarationKind::SloppyLexicalFunction) {
+ reportRedeclaration(name, p->value()->kind(), pos, p->value()->pos());
+ return false;
+ }
+ } else {
+ if (!scope->addDeclaredName(pc_, p, name, kind, pos.begin,
+ isClosedOver)) {
+ return false;
+ }
+ }
+
+ break;
+ }
+
+ case DeclarationKind::Let:
+ case DeclarationKind::Const:
+ case DeclarationKind::Class:
+ // The BoundNames of LexicalDeclaration and ForDeclaration must not
+ // contain 'let'. (CatchParameter is the only lexical binding form
+ // without this restriction.)
+ if (name == TaggedParserAtomIndex::WellKnown::let()) {
+ errorAt(pos.begin, JSMSG_LEXICAL_DECL_DEFINES_LET);
+ return false;
+ }
+
+ // For body-level lexically declared names in a function, it is an
+ // early error if there is a formal parameter of the same name. This
+ // needs a special check if there is an extra var scope due to
+ // parameter expressions.
+ if (pc_->isFunctionExtraBodyVarScopeInnermost()) {
+ DeclaredNamePtr p = pc_->functionScope().lookupDeclaredName(name);
+ if (p && DeclarationKindIsParameter(p->value()->kind())) {
+ reportRedeclaration(name, p->value()->kind(), pos, p->value()->pos());
+ return false;
+ }
+ }
+
+ [[fallthrough]];
+
+ case DeclarationKind::Import:
+ // Module code is always strict, so 'let' is always a keyword and never a
+ // name.
+ MOZ_ASSERT(name != TaggedParserAtomIndex::WellKnown::let());
+ [[fallthrough]];
+
+ case DeclarationKind::SimpleCatchParameter:
+ case DeclarationKind::CatchParameter: {
+ ParseContext::Scope* scope = pc_->innermostScope();
+
+ // It is an early error if there is another declaration with the same
+ // name in the same scope.
+ AddDeclaredNamePtr p = scope->lookupDeclaredNameForAdd(name);
+ if (p) {
+ reportRedeclaration(name, p->value()->kind(), pos, p->value()->pos());
+ return false;
+ }
+
+ if (!scope->addDeclaredName(pc_, p, name, kind, pos.begin,
+ isClosedOver)) {
+ return false;
+ }
+
+ break;
+ }
+
+ case DeclarationKind::CoverArrowParameter:
+ // CoverArrowParameter is only used as a placeholder declaration kind.
+ break;
+
+ case DeclarationKind::PositionalFormalParameter:
+ MOZ_CRASH(
+ "Positional formal parameter names should use "
+ "notePositionalFormalParameter");
+ break;
+
+ case DeclarationKind::VarForAnnexBLexicalFunction:
+ MOZ_CRASH(
+ "Synthesized Annex B vars should go through "
+ "tryDeclareVarForAnnexBLexicalFunction");
+ break;
+ }
+
+ return true;
+}
+
+template <class ParseHandler, typename Unit>
+bool GeneralParser<ParseHandler, Unit>::noteDeclaredPrivateName(
+ Node nameNode, TaggedParserAtomIndex name, PropertyType propType,
+ FieldPlacement placement, TokenPos pos) {
+ ParseContext::Scope* scope = pc_->innermostScope();
+ AddDeclaredNamePtr p = scope->lookupDeclaredNameForAdd(name);
+
+ DeclarationKind declKind = DeclarationKind::PrivateName;
+ ClosedOver closedOver = ClosedOver::No;
+ PrivateNameKind kind;
+ switch (propType) {
+ case PropertyType::Field:
+ case PropertyType::FieldWithAccessor:
+ kind = PrivateNameKind::Field;
+ break;
+ case PropertyType::Method:
+ case PropertyType::GeneratorMethod:
+ case PropertyType::AsyncMethod:
+ case PropertyType::AsyncGeneratorMethod:
+ if (placement == FieldPlacement::Instance) {
+ // Optimized private method. Non-optimized paths still get
+ // DeclarationKind::Synthetic.
+ declKind = DeclarationKind::PrivateMethod;
+ }
+
+ // Methods must be marked closed-over so that
+ // EmitterScope::lookupPrivate() works even if the method is used, but not
+ // within any method (from a computed property name, or debugger frame)
+ closedOver = ClosedOver::Yes;
+ kind = PrivateNameKind::Method;
+ break;
+ case PropertyType::Getter:
+ kind = PrivateNameKind::Getter;
+ break;
+ case PropertyType::Setter:
+ kind = PrivateNameKind::Setter;
+ break;
+ default:
+ kind = PrivateNameKind::None;
+ }
+
+ if (p) {
+ PrivateNameKind prevKind = p->value()->privateNameKind();
+ if ((prevKind == PrivateNameKind::Getter &&
+ kind == PrivateNameKind::Setter) ||
+ (prevKind == PrivateNameKind::Setter &&
+ kind == PrivateNameKind::Getter)) {
+ // Private methods demands that
+ //
+ // class A {
+ // static set #x(_) {}
+ // get #x() { }
+ // }
+ //
+ // Report a SyntaxError.
+ if (placement == p->value()->placement()) {
+ p->value()->setPrivateNameKind(PrivateNameKind::GetterSetter);
+ handler_.setPrivateNameKind(nameNode, PrivateNameKind::GetterSetter);
+ return true;
+ }
+ }
+
+ reportRedeclaration(name, p->value()->kind(), pos, p->value()->pos());
+ return false;
+ }
+
+ if (!scope->addDeclaredName(pc_, p, name, declKind, pos.begin, closedOver)) {
+ return false;
+ }
+
+ DeclaredNamePtr declared = scope->lookupDeclaredName(name);
+ declared->value()->setPrivateNameKind(kind);
+ declared->value()->setFieldPlacement(placement);
+ handler_.setPrivateNameKind(nameNode, kind);
+
+ return true;
+}
+
+bool ParserBase::noteUsedNameInternal(TaggedParserAtomIndex name,
+ NameVisibility visibility,
+ mozilla::Maybe<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(fc_, 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_, this)) {
+ return false;
+ }
+
+ if (handler_.reuseClosedOverBindings()) {
+ MOZ_ASSERT(pc_->isOutermostOfCurrentCompile());
+
+ // Closed over bindings for all scopes are stored in a contiguous array, in
+ // the same order as the order in which scopes are visited, and seprated by
+ // TaggedParserAtomIndex::null().
+ uint32_t slotCount = scope.declaredCount();
+ while (auto parserAtom = handler_.nextLazyClosedOverBinding()) {
+ scope.lookupDeclaredName(parserAtom)->value()->setClosedOver();
+ MOZ_ASSERT(slotCount > 0);
+ slotCount--;
+ }
+
+ if (pc_->isGeneratorOrAsync()) {
+ scope.setOwnStackSlotCount(slotCount);
+ }
+ return true;
+ }
+
+ constexpr bool isSyntaxParser =
+ std::is_same_v<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(
+ TrivialTaggedParserAtomIndex::from(bi.name()))) {
+ ReportOutOfMemory(fc_);
+ return false;
+ }
+ }
+ }
+ }
+
+ if constexpr (!isSyntaxParser) {
+ if (!closedOver) {
+ slotCount++;
+ }
+ }
+ }
+ if constexpr (!isSyntaxParser) {
+ if (pc_->isGeneratorOrAsync()) {
+ scope.setOwnStackSlotCount(slotCount);
+ }
+ }
+
+ // Append a nullptr to denote end-of-scope.
+ if constexpr (isSyntaxParser) {
+ if (!pc_->closedOverBindingsForLazy().append(
+ TrivialTaggedParserAtomIndex::null())) {
+ ReportOutOfMemory(fc_);
+ return false;
+ }
+ }
+
+ return true;
+}
+
+template <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(FrontendContext* fc,
+ 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(fc);
+ }
+ return bindings;
+}
+
+GlobalScope::ParserData* NewEmptyGlobalScopeData(FrontendContext* fc,
+ LifoAlloc& alloc,
+ uint32_t numBindings) {
+ return NewEmptyBindingData<GlobalScope>(fc, alloc, numBindings);
+}
+
+LexicalScope::ParserData* NewEmptyLexicalScopeData(FrontendContext* fc,
+ LifoAlloc& alloc,
+ uint32_t numBindings) {
+ return NewEmptyBindingData<LexicalScope>(fc, alloc, numBindings);
+}
+
+FunctionScope::ParserData* NewEmptyFunctionScopeData(FrontendContext* fc,
+ LifoAlloc& alloc,
+ uint32_t numBindings) {
+ return NewEmptyBindingData<FunctionScope>(fc, 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 the trailing name bindings of |data|, then set |data->length| to
+// the count of bindings added (which must equal |count|).
+//
+// First, |firstBindings| are added to the trailing names. Then any
+// "steps" present are performed first to last. Each step is 1) a pointer to a
+// member of |data| to be set to the current number of bindings added, and 2) a
+// vector of |ParserBindingName|s to then copy into |data->trailingNames|.
+// (Thus each |data| member field indicates where the corresponding vector's
+// names start.)
+template <class Data, typename... Step>
+static MOZ_ALWAYS_INLINE void InitializeBindingData(
+ Data* data, uint32_t count, const ParserBindingNameVector& firstBindings,
+ Step&&... step) {
+ MOZ_ASSERT(data->length == 0, "data shouldn't be filled yet");
+
+ ParserBindingName* start = GetScopeDataTrailingNamesPointer(data);
+ ParserBindingName* cursor = std::uninitialized_copy(
+ firstBindings.begin(), firstBindings.end(), start);
+
+#ifdef DEBUG
+ ParserBindingName* end =
+#endif
+ detail::InitializeIndexedBindings(data->slotInfo, start, cursor,
+ std::forward<Step>(step)...);
+
+ MOZ_ASSERT(PointerRangeSize(start, end) == count);
+ data->length = count;
+}
+
+Maybe<GlobalScope::ParserData*> NewGlobalScopeData(FrontendContext* fc,
+ ParseContext::Scope& scope,
+ LifoAlloc& alloc,
+ ParseContext* pc) {
+ ParserBindingNameVector vars(fc);
+ ParserBindingNameVector lets(fc);
+ ParserBindingNameVector consts(fc);
+
+ bool allBindingsClosedOver = pc->sc()->allBindingsClosedOver();
+ for (BindingIter bi = scope.bindings(pc); bi; bi++) {
+ bool closedOver = allBindingsClosedOver || bi.closedOver();
+
+ switch (bi.kind()) {
+ case BindingKind::Var: {
+ bool isTopLevelFunction =
+ bi.declarationKind() == DeclarationKind::BodyLevelFunction;
+
+ ParserBindingName binding(bi.name(), closedOver, isTopLevelFunction);
+ if (!vars.append(binding)) {
+ return Nothing();
+ }
+ break;
+ }
+ case BindingKind::Let: {
+ ParserBindingName binding(bi.name(), closedOver);
+ if (!lets.append(binding)) {
+ return Nothing();
+ }
+ break;
+ }
+ case BindingKind::Const: {
+ ParserBindingName binding(bi.name(), closedOver);
+ if (!consts.append(binding)) {
+ return Nothing();
+ }
+ break;
+ }
+ default:
+ MOZ_CRASH("Bad global scope BindingKind");
+ }
+ }
+
+ GlobalScope::ParserData* bindings = nullptr;
+ uint32_t numBindings = vars.length() + lets.length() + consts.length();
+
+ if (numBindings > 0) {
+ bindings = NewEmptyBindingData<GlobalScope>(fc, alloc, numBindings);
+ if (!bindings) {
+ return Nothing();
+ }
+
+ // The ordering here is important. See comments in GlobalScope.
+ InitializeBindingData(bindings, numBindings, vars,
+ &ParserGlobalScopeSlotInfo::letStart, lets,
+ &ParserGlobalScopeSlotInfo::constStart, consts);
+ }
+
+ return Some(bindings);
+}
+
+Maybe<GlobalScope::ParserData*> ParserBase::newGlobalScopeData(
+ ParseContext::Scope& scope) {
+ return NewGlobalScopeData(fc_, scope, stencilAlloc(), pc_);
+}
+
+Maybe<ModuleScope::ParserData*> NewModuleScopeData(FrontendContext* fc,
+ ParseContext::Scope& scope,
+ LifoAlloc& alloc,
+ ParseContext* pc) {
+ ParserBindingNameVector imports(fc);
+ ParserBindingNameVector vars(fc);
+ ParserBindingNameVector lets(fc);
+ ParserBindingNameVector consts(fc);
+
+ bool allBindingsClosedOver =
+ pc->sc()->allBindingsClosedOver() || scope.tooBigToOptimize();
+
+ for (BindingIter bi = scope.bindings(pc); bi; bi++) {
+ // Imports are indirect bindings and must not be given known slots.
+ ParserBindingName binding(bi.name(),
+ (allBindingsClosedOver || bi.closedOver()) &&
+ bi.kind() != BindingKind::Import);
+ switch (bi.kind()) {
+ case BindingKind::Import:
+ if (!imports.append(binding)) {
+ return Nothing();
+ }
+ break;
+ case BindingKind::Var:
+ if (!vars.append(binding)) {
+ return Nothing();
+ }
+ break;
+ case BindingKind::Let:
+ if (!lets.append(binding)) {
+ return Nothing();
+ }
+ break;
+ case BindingKind::Const:
+ if (!consts.append(binding)) {
+ return Nothing();
+ }
+ break;
+ default:
+ MOZ_CRASH("Bad module scope BindingKind");
+ }
+ }
+
+ ModuleScope::ParserData* bindings = nullptr;
+ uint32_t numBindings =
+ imports.length() + vars.length() + lets.length() + consts.length();
+
+ if (numBindings > 0) {
+ bindings = NewEmptyBindingData<ModuleScope>(fc, alloc, numBindings);
+ if (!bindings) {
+ return Nothing();
+ }
+
+ // The ordering here is important. See comments in ModuleScope.
+ InitializeBindingData(bindings, numBindings, imports,
+ &ParserModuleScopeSlotInfo::varStart, vars,
+ &ParserModuleScopeSlotInfo::letStart, lets,
+ &ParserModuleScopeSlotInfo::constStart, consts);
+ }
+
+ return Some(bindings);
+}
+
+Maybe<ModuleScope::ParserData*> ParserBase::newModuleScopeData(
+ ParseContext::Scope& scope) {
+ return NewModuleScopeData(fc_, scope, stencilAlloc(), pc_);
+}
+
+Maybe<EvalScope::ParserData*> NewEvalScopeData(FrontendContext* fc,
+ ParseContext::Scope& scope,
+ LifoAlloc& alloc,
+ ParseContext* pc) {
+ ParserBindingNameVector vars(fc);
+
+ // Treat all bindings as closed over in non-strict eval.
+ bool allBindingsClosedOver =
+ !pc->sc()->strict() || pc->sc()->allBindingsClosedOver();
+ for (BindingIter bi = scope.bindings(pc); bi; bi++) {
+ // Eval scopes only contain 'var' bindings.
+ MOZ_ASSERT(bi.kind() == BindingKind::Var);
+ bool isTopLevelFunction =
+ bi.declarationKind() == DeclarationKind::BodyLevelFunction;
+ bool closedOver = allBindingsClosedOver || bi.closedOver();
+
+ ParserBindingName binding(bi.name(), closedOver, isTopLevelFunction);
+ if (!vars.append(binding)) {
+ return Nothing();
+ }
+ }
+
+ EvalScope::ParserData* bindings = nullptr;
+ uint32_t numBindings = vars.length();
+
+ if (numBindings > 0) {
+ bindings = NewEmptyBindingData<EvalScope>(fc, alloc, numBindings);
+ if (!bindings) {
+ return Nothing();
+ }
+
+ InitializeBindingData(bindings, numBindings, vars);
+ }
+
+ return Some(bindings);
+}
+
+Maybe<EvalScope::ParserData*> ParserBase::newEvalScopeData(
+ ParseContext::Scope& scope) {
+ return NewEvalScopeData(fc_, scope, stencilAlloc(), pc_);
+}
+
+Maybe<FunctionScope::ParserData*> NewFunctionScopeData(
+ FrontendContext* fc, ParseContext::Scope& scope, bool hasParameterExprs,
+ LifoAlloc& alloc, ParseContext* pc) {
+ ParserBindingNameVector positionalFormals(fc);
+ ParserBindingNameVector formals(fc);
+ ParserBindingNameVector vars(fc);
+
+ bool allBindingsClosedOver =
+ pc->sc()->allBindingsClosedOver() || scope.tooBigToOptimize();
+ bool argumentBindingsClosedOver =
+ allBindingsClosedOver || pc->isGeneratorOrAsync();
+ bool hasDuplicateParams = pc->functionBox()->hasDuplicateParameters;
+
+ // Positional parameter names must be added in order of appearance as they are
+ // referenced using argument slots.
+ for (size_t i = 0; i < pc->positionalFormalParameterNames().length(); i++) {
+ TaggedParserAtomIndex name = pc->positionalFormalParameterNames()[i];
+
+ ParserBindingName bindName;
+ if (name) {
+ DeclaredNamePtr p = scope.lookupDeclaredName(name);
+
+ // Do not consider any positional formal parameters closed over if
+ // there are parameter defaults. It is the binding in the defaults
+ // scope that is closed over instead.
+ bool closedOver =
+ argumentBindingsClosedOver || (p && p->value()->closedOver());
+
+ // If the parameter name has duplicates, only the final parameter
+ // name should be on the environment, as otherwise the environment
+ // object would have multiple, same-named properties.
+ if (hasDuplicateParams) {
+ for (size_t j = pc->positionalFormalParameterNames().length() - 1;
+ j > i; j--) {
+ if (TaggedParserAtomIndex(pc->positionalFormalParameterNames()[j]) ==
+ name) {
+ closedOver = false;
+ break;
+ }
+ }
+ }
+
+ bindName = ParserBindingName(name, closedOver);
+ }
+
+ if (!positionalFormals.append(bindName)) {
+ return Nothing();
+ }
+ }
+
+ for (BindingIter bi = scope.bindings(pc); bi; bi++) {
+ ParserBindingName binding(bi.name(),
+ allBindingsClosedOver || bi.closedOver());
+ switch (bi.kind()) {
+ case BindingKind::FormalParameter:
+ // Positional parameter names are already handled above.
+ if (bi.declarationKind() == DeclarationKind::FormalParameter) {
+ if (!formals.append(binding)) {
+ return Nothing();
+ }
+ }
+ break;
+ case BindingKind::Var:
+ // The only vars in the function scope when there are parameter
+ // exprs, which induces a separate var environment, should be the
+ // special bindings.
+ MOZ_ASSERT_IF(hasParameterExprs,
+ FunctionScope::isSpecialName(bi.name()));
+ if (!vars.append(binding)) {
+ return Nothing();
+ }
+ break;
+ case BindingKind::Let:
+ case BindingKind::Const:
+ break;
+ default:
+ MOZ_CRASH("bad function scope BindingKind");
+ break;
+ }
+ }
+
+ FunctionScope::ParserData* bindings = nullptr;
+ uint32_t numBindings =
+ positionalFormals.length() + formals.length() + vars.length();
+
+ if (numBindings > 0) {
+ bindings = NewEmptyBindingData<FunctionScope>(fc, alloc, numBindings);
+ if (!bindings) {
+ return Nothing();
+ }
+
+ // The ordering here is important. See comments in FunctionScope.
+ InitializeBindingData(
+ bindings, numBindings, positionalFormals,
+ &ParserFunctionScopeSlotInfo::nonPositionalFormalStart, formals,
+ &ParserFunctionScopeSlotInfo::varStart, vars);
+ }
+
+ return Some(bindings);
+}
+
+// Compute if `NewFunctionScopeData` would return any binding list with any
+// entry marked as closed-over. This is done without the need to allocate the
+// binding list. If true, an EnvironmentObject will be needed at runtime.
+bool FunctionScopeHasClosedOverBindings(ParseContext* pc) {
+ bool allBindingsClosedOver = pc->sc()->allBindingsClosedOver() ||
+ pc->functionScope().tooBigToOptimize();
+
+ for (BindingIter bi = pc->functionScope().bindings(pc); bi; bi++) {
+ switch (bi.kind()) {
+ case BindingKind::FormalParameter:
+ case BindingKind::Var:
+ if (allBindingsClosedOver || bi.closedOver()) {
+ return true;
+ }
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ return false;
+}
+
+Maybe<FunctionScope::ParserData*> ParserBase::newFunctionScopeData(
+ ParseContext::Scope& scope, bool hasParameterExprs) {
+ return NewFunctionScopeData(fc_, scope, hasParameterExprs, stencilAlloc(),
+ pc_);
+}
+
+VarScope::ParserData* NewEmptyVarScopeData(FrontendContext* fc,
+ LifoAlloc& alloc,
+ uint32_t numBindings) {
+ return NewEmptyBindingData<VarScope>(fc, alloc, numBindings);
+}
+
+Maybe<VarScope::ParserData*> NewVarScopeData(FrontendContext* fc,
+ ParseContext::Scope& scope,
+ LifoAlloc& alloc,
+ ParseContext* pc) {
+ ParserBindingNameVector vars(fc);
+
+ bool allBindingsClosedOver =
+ pc->sc()->allBindingsClosedOver() || scope.tooBigToOptimize();
+
+ for (BindingIter bi = scope.bindings(pc); bi; bi++) {
+ if (bi.kind() == BindingKind::Var) {
+ ParserBindingName binding(bi.name(),
+ allBindingsClosedOver || bi.closedOver());
+ if (!vars.append(binding)) {
+ return Nothing();
+ }
+ } else {
+ MOZ_ASSERT(
+ bi.kind() == BindingKind::Let || bi.kind() == BindingKind::Const,
+ "bad var scope BindingKind");
+ }
+ }
+
+ VarScope::ParserData* bindings = nullptr;
+ uint32_t numBindings = vars.length();
+
+ if (numBindings > 0) {
+ bindings = NewEmptyBindingData<VarScope>(fc, alloc, numBindings);
+ if (!bindings) {
+ return Nothing();
+ }
+
+ InitializeBindingData(bindings, numBindings, vars);
+ }
+
+ return Some(bindings);
+}
+
+// Compute if `NewVarScopeData` would return any binding list. This is done
+// without allocate the binding list.
+static bool VarScopeHasBindings(ParseContext* pc) {
+ for (BindingIter bi = pc->varScope().bindings(pc); bi; bi++) {
+ if (bi.kind() == BindingKind::Var) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+Maybe<VarScope::ParserData*> ParserBase::newVarScopeData(
+ ParseContext::Scope& scope) {
+ return NewVarScopeData(fc_, scope, stencilAlloc(), pc_);
+}
+
+Maybe<LexicalScope::ParserData*> NewLexicalScopeData(FrontendContext* fc,
+ ParseContext::Scope& scope,
+ LifoAlloc& alloc,
+ ParseContext* pc) {
+ ParserBindingNameVector lets(fc);
+ ParserBindingNameVector consts(fc);
+
+ bool allBindingsClosedOver =
+ pc->sc()->allBindingsClosedOver() || scope.tooBigToOptimize();
+
+ for (BindingIter bi = scope.bindings(pc); bi; bi++) {
+ ParserBindingName binding(bi.name(),
+ allBindingsClosedOver || bi.closedOver());
+ switch (bi.kind()) {
+ case BindingKind::Let:
+ if (!lets.append(binding)) {
+ return Nothing();
+ }
+ break;
+ case BindingKind::Const:
+ if (!consts.append(binding)) {
+ return Nothing();
+ }
+ break;
+ case BindingKind::Var:
+ case BindingKind::FormalParameter:
+ break;
+ default:
+ MOZ_CRASH("Bad lexical scope BindingKind");
+ break;
+ }
+ }
+
+ LexicalScope::ParserData* bindings = nullptr;
+ uint32_t numBindings = lets.length() + consts.length();
+
+ if (numBindings > 0) {
+ bindings = NewEmptyBindingData<LexicalScope>(fc, alloc, numBindings);
+ if (!bindings) {
+ return Nothing();
+ }
+
+ // The ordering here is important. See comments in LexicalScope.
+ InitializeBindingData(bindings, numBindings, lets,
+ &ParserLexicalScopeSlotInfo::constStart, consts);
+ }
+
+ return Some(bindings);
+}
+
+// Compute if `NewLexicalScopeData` would return any binding list with any entry
+// marked as closed-over. This is done without the need to allocate the binding
+// list. If true, an EnvironmentObject will be needed at runtime.
+bool LexicalScopeHasClosedOverBindings(ParseContext* pc,
+ ParseContext::Scope& scope) {
+ bool allBindingsClosedOver =
+ pc->sc()->allBindingsClosedOver() || scope.tooBigToOptimize();
+
+ for (BindingIter bi = scope.bindings(pc); bi; bi++) {
+ switch (bi.kind()) {
+ case BindingKind::Let:
+ case BindingKind::Const:
+ if (allBindingsClosedOver || bi.closedOver()) {
+ return true;
+ }
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ return false;
+}
+
+Maybe<LexicalScope::ParserData*> ParserBase::newLexicalScopeData(
+ ParseContext::Scope& scope) {
+ return NewLexicalScopeData(fc_, scope, stencilAlloc(), pc_);
+}
+
+Maybe<ClassBodyScope::ParserData*> NewClassBodyScopeData(
+ FrontendContext* fc, ParseContext::Scope& scope, LifoAlloc& alloc,
+ ParseContext* pc) {
+ ParserBindingNameVector privateBrand(fc);
+ ParserBindingNameVector synthetics(fc);
+ ParserBindingNameVector privateMethods(fc);
+
+ bool allBindingsClosedOver =
+ pc->sc()->allBindingsClosedOver() || scope.tooBigToOptimize();
+
+ for (BindingIter bi = scope.bindings(pc); bi; bi++) {
+ ParserBindingName binding(bi.name(),
+ allBindingsClosedOver || bi.closedOver());
+ switch (bi.kind()) {
+ case BindingKind::Synthetic:
+ if (bi.name() == TaggedParserAtomIndex::WellKnown::dotPrivateBrand()) {
+ MOZ_ASSERT(privateBrand.empty());
+ if (!privateBrand.append(binding)) {
+ return Nothing();
+ }
+ } else {
+ if (!synthetics.append(binding)) {
+ return Nothing();
+ }
+ }
+ break;
+
+ case BindingKind::PrivateMethod:
+ if (!privateMethods.append(binding)) {
+ return Nothing();
+ }
+ break;
+
+ default:
+ MOZ_CRASH("bad class body scope BindingKind");
+ break;
+ }
+ }
+
+ // We should have zero or one private brands.
+ MOZ_ASSERT(privateBrand.length() == 0 || privateBrand.length() == 1);
+
+ ClassBodyScope::ParserData* bindings = nullptr;
+ uint32_t numBindings =
+ privateBrand.length() + synthetics.length() + privateMethods.length();
+
+ if (numBindings > 0) {
+ bindings = NewEmptyBindingData<ClassBodyScope>(fc, alloc, numBindings);
+ if (!bindings) {
+ return Nothing();
+ }
+ // To simplify initialization of the bindings, we concatenate the
+ // synthetics+privateBrand vector such that the private brand is always the
+ // first element, as ordering is important. See comments in ClassBodyScope.
+ ParserBindingNameVector brandAndSynthetics(fc);
+ if (!brandAndSynthetics.appendAll(privateBrand)) {
+ return Nothing();
+ }
+ if (!brandAndSynthetics.appendAll(synthetics)) {
+ return Nothing();
+ }
+
+ // The ordering here is important. See comments in ClassBodyScope.
+ InitializeBindingData(bindings, numBindings, brandAndSynthetics,
+ &ParserClassBodyScopeSlotInfo::privateMethodStart,
+ privateMethods);
+ }
+
+ // `EmitterScope::lookupPrivate()` requires `.privateBrand` to be stored in a
+ // predictable slot: the first slot available in the environment object,
+ // `ClassBodyLexicalEnvironmentObject::privateBrandSlot()`. We assume that
+ // if `.privateBrand` is first in the scope, it will be stored there.
+ MOZ_ASSERT_IF(!privateBrand.empty(),
+ GetScopeDataTrailingNames(bindings)[0].name() ==
+ TaggedParserAtomIndex::WellKnown::dotPrivateBrand());
+
+ return Some(bindings);
+}
+
+Maybe<ClassBodyScope::ParserData*> ParserBase::newClassBodyScopeData(
+ ParseContext::Scope& scope) {
+ return NewClassBodyScopeData(fc_, 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 <>
+SyntaxParseHandler::ClassBodyScopeNodeType
+PerHandlerParser<SyntaxParseHandler>::finishClassBodyScope(
+ ParseContext::Scope& scope, ListNodeType body) {
+ if (!propagateFreeNamesAndMarkClosedOverBindings(scope)) {
+ return null();
+ }
+
+ return handler_.newClassBodyScope(body);
+}
+
+template <>
+ClassBodyScopeNode* PerHandlerParser<FullParseHandler>::finishClassBodyScope(
+ ParseContext::Scope& scope, ListNode* body) {
+ if (!propagateFreeNamesAndMarkClosedOverBindings(scope)) {
+ return nullptr;
+ }
+
+ Maybe<ClassBodyScope::ParserData*> bindings = newClassBodyScopeData(scope);
+ if (!bindings) {
+ return nullptr;
+ }
+
+ return handler_.newClassBodyScope(*bindings, body);
+}
+
+template <class ParseHandler>
+bool PerHandlerParser<ParseHandler>::checkForUndefinedPrivateFields(
+ EvalSharedContext* evalSc) {
+ if (!this->compilationState_.isInitialStencil()) {
+ // We're delazifying -- so we already checked private names during first
+ // parse.
+ return true;
+ }
+
+ Vector<UnboundPrivateName, 8> unboundPrivateNames(fc_);
+ if (!usedNames_.getUnboundPrivateNames(unboundPrivateNames)) {
+ return false;
+ }
+
+ // No unbound names, let's get out of here!
+ if (unboundPrivateNames.empty()) {
+ return true;
+ }
+
+ // It is an early error if there's private name references unbound,
+ // unless it's an eval, in which case we need to check the scope
+ // chain.
+ if (!evalSc) {
+ // The unbound private names are sorted, so just grab the first one.
+ UnboundPrivateName minimum = unboundPrivateNames[0];
+ UniqueChars str = this->parserAtoms().toPrintableString(minimum.atom);
+ if (!str) {
+ ReportOutOfMemory(this->fc_);
+ return false;
+ }
+
+ errorAt(minimum.position.begin, JSMSG_MISSING_PRIVATE_DECL, str.get());
+ return false;
+ }
+
+ // It's important that the unbound private names are sorted, as we
+ // want our errors to always be issued to the first textually.
+ for (UnboundPrivateName unboundName : unboundPrivateNames) {
+ // If the enclosingScope is non-syntactic, then we are in a
+ // Debugger.Frame.prototype.eval call. In order to find the declared private
+ // names, we must use the effective scope that was determined when creating
+ // the scopeContext.
+ if (!this->compilationState_.scopeContext
+ .effectiveScopePrivateFieldCacheHas(unboundName.atom)) {
+ UniqueChars str = this->parserAtoms().toPrintableString(unboundName.atom);
+ if (!str) {
+ ReportOutOfMemory(this->fc_);
+ return false;
+ }
+ errorAt(unboundName.position.begin, JSMSG_MISSING_PRIVATE_DECL,
+ str.get());
+ return false;
+ }
+ }
+
+ return true;
+}
+
+template <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->compilationState_.input.enclosingScope.isNull()) {
+ // If superScopeNeedsHomeObject_ is set and we are an entry-point
+ // ParseContext, then we must be emitting an eval script, and the
+ // outer function must already be marked as needing a home object
+ // since it contains an eval.
+ MOZ_ASSERT(
+ this->compilationState_.scopeContext.hasFunctionNeedsHomeObjectOnChain,
+ "Eval must have found an enclosing function box scope that "
+ "allows super.property");
+ }
+#endif
+
+ if (!CheckParseTree(this->fc_, alloc_, body)) {
+ return null();
+ }
+
+ ParseNode* node = body;
+ // Don't constant-fold inside "use asm" code, as this could create a parse
+ // tree that doesn't type-check as asm.js.
+ if (!pc_->useAsmOrInsideUseAsm()) {
+ if (!FoldConstants(this->fc_, this->parserAtoms(), &node, &handler_)) {
+ return null();
+ }
+ }
+ body = handler_.asLexicalScope(node);
+
+ if (!this->setSourceMapInfo()) {
+ return nullptr;
+ }
+
+ if (pc_->sc()->strict()) {
+ if (!propagateFreeNamesAndMarkClosedOverBindings(varScope)) {
+ return nullptr;
+ }
+ } else {
+ // For non-strict eval scripts, since all bindings are automatically
+ // considered closed over, we don't need to call propagateFreeNames-
+ // AndMarkClosedOverBindings. However, Annex B.3.3 functions still need to
+ // be marked.
+ if (!varScope.propagateAndMarkAnnexBFunctionBoxes(pc_, this)) {
+ return nullptr;
+ }
+ }
+
+ Maybe<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(this->fc_, alloc_, body)) {
+ return null();
+ }
+
+ if (!checkForUndefinedPrivateFields()) {
+ return null();
+ }
+
+ ParseNode* node = body;
+ // Don't constant-fold inside "use asm" code, as this could create a parse
+ // tree that doesn't type-check as asm.js.
+ if (!pc_->useAsmOrInsideUseAsm()) {
+ if (!FoldConstants(this->fc_, this->parserAtoms(), &node, &handler_)) {
+ return null();
+ }
+ }
+ body = &node->as<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_, this)) {
+ 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->compilationState_.moduleMetadata =
+ fc_->getAllocator()->template new_<StencilModuleMetadata>();
+ if (!this->compilationState_.moduleMetadata) {
+ return null();
+ }
+
+ SourceParseContext modulepc(this, modulesc, nullptr);
+ if (!modulepc.init()) {
+ return null();
+ }
+
+ ParseContext::VarScope varScope(this);
+ if (!varScope.init(pc_)) {
+ return null();
+ }
+
+ ModuleNodeType moduleNode = handler_.newModule(pos());
+ if (!moduleNode) {
+ return null();
+ }
+
+ AutoAwaitIsKeyword<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(TaggedParserAtomIndex::WellKnown::dotGenerator())) {
+ return null();
+ }
+
+ if (!pc_->declareTopLevelDotGeneratorName()) {
+ return null();
+ }
+ }
+
+ TokenKind tt;
+ if (!tokenStream.getToken(&tt, TokenStream::SlashIsRegExp)) {
+ return null();
+ }
+ if (tt != TokenKind::Eof) {
+ error(JSMSG_GARBAGE_AFTER_INPUT, "module", TokenKindToDesc(tt));
+ return null();
+ }
+
+ // Set the module to async if an await keyword was found at the top level.
+ if (pc_->isAsync()) {
+ pc_->sc()->asModuleContext()->builder.noteAsync(
+ *this->compilationState_.moduleMetadata);
+ }
+
+ // Generate the Import/Export tables and store in CompilationState.
+ if (!modulesc->builder.buildTables(*this->compilationState_.moduleMetadata)) {
+ return null();
+ }
+
+ // Check exported local bindings exist and mark them as closed over.
+ StencilModuleMetadata& moduleMetadata =
+ *this->compilationState_.moduleMetadata;
+ for (auto entry : moduleMetadata.localExportEntries) {
+ DeclaredNamePtr p = modulepc.varScope().lookupDeclaredName(entry.localName);
+ if (!p) {
+ UniqueChars str = this->parserAtoms().toPrintableString(entry.localName);
+ if (!str) {
+ ReportOutOfMemory(this->fc_);
+ return null();
+ }
+
+ errorNoOffset(JSMSG_MISSING_EXPORT, str.get());
+ return null();
+ }
+
+ p->value()->setClosedOver();
+ }
+
+ // Reserve an environment slot for a "*namespace*" psuedo-binding and mark as
+ // closed-over. We do not know until module linking if this will be used.
+ if (!noteDeclaredName(TaggedParserAtomIndex::WellKnown::starNamespaceStar(),
+ DeclarationKind::Const, pos())) {
+ return nullptr;
+ }
+ modulepc.varScope()
+ .lookupDeclaredName(TaggedParserAtomIndex::WellKnown::starNamespaceStar())
+ ->value()
+ ->setClosedOver();
+
+ if (options().deoptimizeModuleGlobalVars) {
+ for (BindingIter bi = modulepc.varScope().bindings(pc_); bi; bi++) {
+ bi.setClosedOver();
+ }
+ }
+
+ if (!CheckParseTree(this->fc_, alloc_, stmtList)) {
+ return null();
+ }
+
+ ParseNode* node = stmtList;
+ // Don't constant-fold inside "use asm" code, as this could create a parse
+ // tree that doesn't type-check as asm.js.
+ if (!pc_->useAsmOrInsideUseAsm()) {
+ if (!FoldConstants(this->fc_, this->parserAtoms(), &node, &handler_)) {
+ return null();
+ }
+ }
+ stmtList = &node->as<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(TaggedParserAtomIndex 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(TaggedParserAtomIndex::WellKnown::dotThis());
+}
+
+template <class ParseHandler>
+typename ParseHandler::NameNodeType
+PerHandlerParser<ParseHandler>::newNewTargetName() {
+ return newInternalDotName(TaggedParserAtomIndex::WellKnown::dotNewTarget());
+}
+
+template <class ParseHandler>
+typename ParseHandler::NameNodeType
+PerHandlerParser<ParseHandler>::newDotGeneratorName() {
+ return newInternalDotName(TaggedParserAtomIndex::WellKnown::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);
+
+ if (this->compilationState_.isInitialStencil()) {
+ 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);
+
+ ScriptStencilExtra& scriptExtra = funbox->functionExtraStencil();
+ funbox->copyFunctionExtraFields(scriptExtra);
+ funbox->copyScriptExtraFields(scriptExtra);
+
+ // Elide nullptr sentinels from end of binding list. These are inserted for
+ // each scope regardless of if any bindings are actually closed over.
+ {
+ AtomVector& closedOver = pc_->closedOverBindingsForLazy();
+ while (!closedOver.empty() && !closedOver.back()) {
+ closedOver.popBack();
+ }
+ }
+
+ // Check if we will overflow the `ngcthings` field later.
+ mozilla::CheckedUint32 ngcthings =
+ mozilla::CheckedUint32(pc_->innerFunctionIndexesForLazy.length()) +
+ mozilla::CheckedUint32(pc_->closedOverBindingsForLazy().length());
+ if (!ngcthings.isValid()) {
+ ReportAllocationOverflow(fc_);
+ return false;
+ }
+
+ // If there are no script-things, we can return early without allocating.
+ if (ngcthings.value() == 0) {
+ MOZ_ASSERT(!script.hasGCThings());
+ return true;
+ }
+
+ TaggedScriptThingIndex* cursor = nullptr;
+ if (!this->compilationState_.allocateGCThingsUninitialized(
+ fc_, funbox->index(), ngcthings.value(), &cursor)) {
+ return false;
+ }
+
+ // Copy inner-function and closed-over-binding info for the stencil. The order
+ // is important here. We emit functions first, followed by the bindings info.
+ // The bindings list uses nullptr as delimiter to separates the bindings per
+ // scope.
+ //
+ // See: FullParseHandler::nextLazyInnerFunction(),
+ // FullParseHandler::nextLazyClosedOverBinding()
+ for (const ScriptIndex& index : pc_->innerFunctionIndexesForLazy) {
+ void* raw = &(*cursor++);
+ new (raw) TaggedScriptThingIndex(index);
+ }
+ for (auto binding : pc_->closedOverBindingsForLazy()) {
+ void* raw = &(*cursor++);
+ if (binding) {
+ this->parserAtoms().markUsedByStencil(binding, ParserAtom::Atomize::Yes);
+ new (raw) TaggedScriptThingIndex(binding);
+ } else {
+ new (raw) TaggedScriptThingIndex();
+ }
+ }
+
+ return true;
+}
+
+static YieldHandling GetYieldHandling(GeneratorKind generatorKind) {
+ if (generatorKind == GeneratorKind::NotGenerator) {
+ return YieldIsName;
+ }
+ return YieldIsKeyword;
+}
+
+static AwaitHandling GetAwaitHandling(FunctionAsyncKind asyncKind) {
+ if (asyncKind == FunctionAsyncKind::SyncFunction) {
+ return AwaitIsName;
+ }
+ return AwaitIsKeyword;
+}
+
+static FunctionFlags InitialFunctionFlags(FunctionSyntaxKind kind,
+ GeneratorKind generatorKind,
+ FunctionAsyncKind asyncKind,
+ bool isSelfHosting) {
+ FunctionFlags flags = {};
+
+ switch (kind) {
+ case FunctionSyntaxKind::Expression:
+ flags = (generatorKind == GeneratorKind::NotGenerator &&
+ asyncKind == FunctionAsyncKind::SyncFunction
+ ? FunctionFlags::INTERPRETED_LAMBDA
+ : FunctionFlags::INTERPRETED_LAMBDA_GENERATOR_OR_ASYNC);
+ break;
+ case FunctionSyntaxKind::Arrow:
+ flags = FunctionFlags::INTERPRETED_LAMBDA_ARROW;
+ break;
+ case FunctionSyntaxKind::Method:
+ case FunctionSyntaxKind::FieldInitializer:
+ case FunctionSyntaxKind::StaticClassBlock:
+ flags = FunctionFlags::INTERPRETED_METHOD;
+ break;
+ case FunctionSyntaxKind::ClassConstructor:
+ case FunctionSyntaxKind::DerivedClassConstructor:
+ flags = FunctionFlags::INTERPRETED_CLASS_CTOR;
+ break;
+ case FunctionSyntaxKind::Getter:
+ flags = FunctionFlags::INTERPRETED_GETTER;
+ break;
+ case FunctionSyntaxKind::Setter:
+ flags = FunctionFlags::INTERPRETED_SETTER;
+ break;
+ default:
+ MOZ_ASSERT(kind == FunctionSyntaxKind::Statement);
+ flags = (generatorKind == GeneratorKind::NotGenerator &&
+ asyncKind == FunctionAsyncKind::SyncFunction
+ ? FunctionFlags::INTERPRETED_NORMAL
+ : FunctionFlags::INTERPRETED_GENERATOR_OR_ASYNC);
+ }
+
+ if (isSelfHosting) {
+ flags.setIsSelfHostedBuiltin();
+ }
+
+ return flags;
+}
+
+template <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.
+ TaggedParserAtomIndex explicitName;
+ if (TokenKindIsPossibleIdentifierName(tt)) {
+ explicitName = anyChars.currentName();
+ } else {
+ anyChars.ungetToken();
+ }
+
+ FunctionNodeType funNode = handler_.newFunction(syntaxKind, pos());
+ if (!funNode) {
+ return null();
+ }
+
+ ParamsBodyNodeType argsbody = handler_.newParamsBody(pos());
+ if (!argsbody) {
+ return null();
+ }
+ funNode->setBody(argsbody);
+
+ bool isSelfHosting = options().selfHostingMode;
+ FunctionFlags flags =
+ InitialFunctionFlags(syntaxKind, generatorKind, asyncKind, isSelfHosting);
+ FunctionBox* funbox =
+ newFunctionBox(funNode, explicitName, flags, /* toStringStart = */ 0,
+ inheritedDirectives, generatorKind, asyncKind);
+ if (!funbox) {
+ return null();
+ }
+
+ // Function is not syntactically part of another script.
+ MOZ_ASSERT(funbox->index() == CompilationStencil::TopLevelIndex);
+
+ funbox->initStandalone(this->compilationState_.scopeContext, syntaxKind);
+
+ SourceParseContext funpc(this, funbox, newDirectives);
+ if (!funpc.init()) {
+ return null();
+ }
+
+ YieldHandling yieldHandling = GetYieldHandling(generatorKind);
+ AwaitHandling awaitHandling = GetAwaitHandling(asyncKind);
+ AutoAwaitIsKeyword<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(this->fc_, alloc_, funNode)) {
+ return null();
+ }
+
+ ParseNode* node = funNode;
+ // Don't constant-fold inside "use asm" code, as this could create a parse
+ // tree that doesn't type-check as asm.js.
+ if (!pc_->useAsmOrInsideUseAsm()) {
+ if (!FoldConstants(this->fc_, this->parserAtoms(), &node, &handler_)) {
+ return null();
+ }
+ }
+ funNode = &node->as<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', 'this', and 'new.target' bindings if necessary
+ // before finishing up the scope so these special bindings get marked as
+ // closed over if necessary. Arrow functions don't have these bindings.
+ if (kind != FunctionSyntaxKind::Arrow) {
+ bool canSkipLazyClosedOverBindings = handler_.reuseClosedOverBindings();
+ if (!pc_->declareFunctionArgumentsObject(usedNames_,
+ canSkipLazyClosedOverBindings)) {
+ return null();
+ }
+ if (!pc_->declareFunctionThis(usedNames_, canSkipLazyClosedOverBindings)) {
+ return null();
+ }
+ if (!pc_->declareNewTarget(usedNames_, canSkipLazyClosedOverBindings)) {
+ return null();
+ }
+ }
+
+ return finishLexicalScope(pc_->varScope(), body, ScopeKind::FunctionLexical);
+}
+
+template <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) {
+ 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;
+}
+
+TaggedParserAtomIndex ParserBase::prefixAccessorName(
+ PropertyType propType, TaggedParserAtomIndex propAtom) {
+ StringBuffer prefixed(fc_);
+ if (propType == PropertyType::Setter) {
+ if (!prefixed.append("set ")) {
+ return TaggedParserAtomIndex::null();
+ }
+ } else {
+ if (!prefixed.append("get ")) {
+ return TaggedParserAtomIndex::null();
+ }
+ }
+ if (!prefixed.append(this->parserAtoms(), propAtom)) {
+ return TaggedParserAtomIndex::null();
+ }
+ return prefixed.finishParserAtom(this->parserAtoms(), fc_);
+}
+
+template <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();
+
+ // Modifier for the following tokens.
+ // TokenStream::SlashIsDiv for the following cases:
+ // async a => 1
+ // ^
+ //
+ // (a) => 1
+ // ^
+ //
+ // async (a) => 1
+ // ^
+ //
+ // function f(a) {}
+ // ^
+ //
+ // TokenStream::SlashIsRegExp for the following case:
+ // a => 1
+ // ^
+ Modifier firstTokenModifier =
+ kind != FunctionSyntaxKind::Arrow || funbox->isAsync()
+ ? TokenStream::SlashIsDiv
+ : TokenStream::SlashIsRegExp;
+ TokenKind tt;
+ if (!tokenStream.getToken(&tt, firstTokenModifier)) {
+ return false;
+ }
+
+ if (kind == FunctionSyntaxKind::Arrow && TokenKindIsPossibleIdentifier(tt)) {
+ // Record the start of function source (for FunctionToString).
+ setFunctionStartAtCurrentToken(funbox);
+
+ ParamsBodyNodeType argsbody = handler_.newParamsBody(pos());
+ if (!argsbody) {
+ return false;
+ }
+ handler_.setFunctionFormalParametersAndBody(funNode, argsbody);
+
+ TaggedParserAtomIndex name = bindingIdentifier(yieldHandling);
+ if (!name) {
+ return false;
+ }
+
+ constexpr bool disallowDuplicateParams = true;
+ bool duplicatedParam = false;
+ if (!notePositionalFormalParameter(funNode, name, pos().begin,
+ disallowDuplicateParams,
+ &duplicatedParam)) {
+ return false;
+ }
+ MOZ_ASSERT(!duplicatedParam);
+ MOZ_ASSERT(pc_->positionalFormalParameterNames().length() == 1);
+
+ funbox->setLength(1);
+ funbox->setArgCount(1);
+ return true;
+ }
+
+ if (tt != TokenKind::LeftParen) {
+ error(kind == FunctionSyntaxKind::Arrow ? JSMSG_BAD_ARROW_ARGS
+ : JSMSG_PAREN_BEFORE_FORMAL);
+ return false;
+ }
+
+ // Record the start of function source (for FunctionToString).
+ setFunctionStartAtCurrentToken(funbox);
+
+ ParamsBodyNodeType argsbody = handler_.newParamsBody(pos());
+ if (!argsbody) {
+ return false;
+ }
+ handler_.setFunctionFormalParametersAndBody(funNode, argsbody);
+
+ bool matched;
+ if (!tokenStream.matchToken(&matched, TokenKind::RightParen,
+ TokenStream::SlashIsRegExp)) {
+ return false;
+ }
+ if (!matched) {
+ bool hasRest = false;
+ bool hasDefault = false;
+ bool duplicatedParam = false;
+ bool disallowDuplicateParams =
+ kind == FunctionSyntaxKind::Arrow ||
+ kind == FunctionSyntaxKind::Method ||
+ kind == FunctionSyntaxKind::FieldInitializer ||
+ kind == FunctionSyntaxKind::ClassConstructor;
+ AtomVector& positionalFormals = pc_->positionalFormalParameterNames();
+
+ if (kind == FunctionSyntaxKind::Getter) {
+ error(JSMSG_ACCESSOR_WRONG_ARGS, "getter", "no", "s");
+ return false;
+ }
+
+ while (true) {
+ if (hasRest) {
+ error(JSMSG_PARAMETER_AFTER_REST);
+ return false;
+ }
+
+ TokenKind tt;
+ if (!tokenStream.getToken(&tt, TokenStream::SlashIsRegExp)) {
+ return false;
+ }
+
+ if (tt == TokenKind::TripleDot) {
+ if (kind == FunctionSyntaxKind::Setter) {
+ error(JSMSG_ACCESSOR_WRONG_ARGS, "setter", "one", "");
+ return false;
+ }
+
+ disallowDuplicateParams = true;
+ if (duplicatedParam) {
+ // Has duplicated args before the rest parameter.
+ error(JSMSG_BAD_DUP_ARGS);
+ return false;
+ }
+
+ hasRest = true;
+ funbox->setHasRest();
+
+ if (!tokenStream.getToken(&tt)) {
+ return false;
+ }
+
+ if (!TokenKindIsPossibleIdentifier(tt) &&
+ tt != TokenKind::LeftBracket && tt != TokenKind::LeftCurly) {
+ error(JSMSG_NO_REST_NAME);
+ return false;
+ }
+ }
+
+ switch (tt) {
+ case TokenKind::LeftBracket:
+ case TokenKind::LeftCurly: {
+ disallowDuplicateParams = true;
+ if (duplicatedParam) {
+ // Has duplicated args before the destructuring parameter.
+ error(JSMSG_BAD_DUP_ARGS);
+ return false;
+ }
+
+ funbox->hasDestructuringArgs = true;
+
+ Node destruct = destructuringDeclarationWithoutYieldOrAwait(
+ DeclarationKind::FormalParameter, yieldHandling, tt);
+ if (!destruct) {
+ return false;
+ }
+
+ if (!noteDestructuredPositionalFormalParameter(funNode, destruct)) {
+ return false;
+ }
+
+ break;
+ }
+
+ default: {
+ if (!TokenKindIsPossibleIdentifier(tt)) {
+ error(JSMSG_MISSING_FORMAL);
+ return false;
+ }
+
+ TaggedParserAtomIndex name = bindingIdentifier(yieldHandling);
+ if (!name) {
+ return false;
+ }
+
+ if (!notePositionalFormalParameter(funNode, name, pos().begin,
+ disallowDuplicateParams,
+ &duplicatedParam)) {
+ return false;
+ }
+ if (duplicatedParam) {
+ funbox->hasDuplicateParameters = true;
+ }
+
+ break;
+ }
+ }
+
+ if (positionalFormals.length() >= ARGNO_LIMIT) {
+ error(JSMSG_TOO_MANY_FUN_ARGS);
+ return false;
+ }
+
+ bool matched;
+ if (!tokenStream.matchToken(&matched, TokenKind::Assign,
+ TokenStream::SlashIsRegExp)) {
+ return false;
+ }
+ if (matched) {
+ if (hasRest) {
+ error(JSMSG_REST_WITH_DEFAULT);
+ return false;
+ }
+ disallowDuplicateParams = true;
+ if (duplicatedParam) {
+ error(JSMSG_BAD_DUP_ARGS);
+ return false;
+ }
+
+ if (!hasDefault) {
+ hasDefault = true;
+
+ // The Function.length property is the number of formals
+ // before the first default argument.
+ funbox->setLength(positionalFormals.length() - 1);
+ }
+ funbox->hasParameterExprs = true;
+
+ Node def_expr = assignExprWithoutYieldOrAwait(yieldHandling);
+ if (!def_expr) {
+ return false;
+ }
+ if (!handler_.setLastFunctionFormalParameterDefault(funNode,
+ def_expr)) {
+ return false;
+ }
+ }
+
+ // Setter syntax uniquely requires exactly one argument.
+ if (kind == FunctionSyntaxKind::Setter) {
+ break;
+ }
+
+ if (!tokenStream.matchToken(&matched, TokenKind::Comma,
+ TokenStream::SlashIsRegExp)) {
+ return false;
+ }
+ if (!matched) {
+ break;
+ }
+
+ if (!hasRest) {
+ if (!tokenStream.peekToken(&tt, TokenStream::SlashIsRegExp)) {
+ return false;
+ }
+ if (tt == TokenKind::RightParen) {
+ break;
+ }
+ }
+ }
+
+ TokenKind tt;
+ if (!tokenStream.getToken(&tt, TokenStream::SlashIsRegExp)) {
+ return false;
+ }
+ if (tt != TokenKind::RightParen) {
+ if (kind == FunctionSyntaxKind::Setter) {
+ error(JSMSG_ACCESSOR_WRONG_ARGS, "setter", "one", "");
+ return false;
+ }
+
+ error(JSMSG_PAREN_AFTER_FORMAL);
+ return false;
+ }
+
+ if (!hasDefault) {
+ funbox->setLength(positionalFormals.length() - hasRest);
+ }
+
+ funbox->setArgCount(positionalFormals.length());
+ } else if (kind == FunctionSyntaxKind::Setter) {
+ error(JSMSG_ACCESSOR_WRONG_ARGS, "setter", "one", "");
+ return false;
+ }
+
+ return true;
+}
+
+template <typename Unit>
+bool Parser<FullParseHandler, Unit>::skipLazyInnerFunction(
+ FunctionNode* funNode, uint32_t toStringStart, bool tryAnnexB) {
+ // When a lazily-parsed function is called, we only fully parse (and emit)
+ // that function, not any of its nested children. The initial syntax-only
+ // parse recorded the free variables of nested functions and their extents,
+ // so we can skip over them after accounting for their free variables.
+
+ MOZ_ASSERT(pc_->isOutermostOfCurrentCompile());
+ handler_.nextLazyInnerFunction();
+ const ScriptStencil& cachedData = handler_.cachedScriptData();
+ const ScriptStencilExtra& cachedExtra = handler_.cachedScriptExtra();
+ MOZ_ASSERT(toStringStart == cachedExtra.extent.toStringStart);
+
+ FunctionBox* funbox = newFunctionBox(funNode, cachedData, cachedExtra);
+ if (!funbox) {
+ return false;
+ }
+
+ ScriptStencil& script = funbox->functionStencil();
+ funbox->copyFunctionFields(script);
+
+ // If the inner lazy function is class constructor, connect it to the class
+ // statement/expression we are parsing.
+ if (funbox->isClassConstructor()) {
+ auto classStmt =
+ pc_->template findInnermostStatement<ParseContext::ClassStatement>();
+ MOZ_ASSERT(!classStmt->constructorBox);
+ classStmt->constructorBox = funbox;
+ }
+
+ MOZ_ASSERT_IF(pc_->isFunctionBox(),
+ pc_->functionBox()->index() < funbox->index());
+
+ PropagateTransitiveParseFlags(funbox, pc_->sc());
+
+ if (!tokenStream.advance(funbox->extent().sourceEnd)) {
+ return false;
+ }
+
+ // Append possible Annex B function box only upon successfully parsing.
+ if (tryAnnexB &&
+ !pc_->innermostScope()->addPossibleAnnexBFunctionBox(pc_, funbox)) {
+ return false;
+ }
+
+ return true;
+}
+
+template <typename Unit>
+bool Parser<SyntaxParseHandler, Unit>::skipLazyInnerFunction(
+ FunctionNodeType funNode, uint32_t toStringStart, 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, bool tryAnnexB) {
+ return asFinalParser()->skipLazyInnerFunction(funNode, toStringStart,
+ 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, TaggedParserAtomIndex funName,
+ FunctionSyntaxKind kind, GeneratorKind generatorKind,
+ FunctionAsyncKind asyncKind, bool tryAnnexB /* = false */) {
+ MOZ_ASSERT_IF(kind == FunctionSyntaxKind::Statement, funName);
+
+ // If we see any inner function, note it on our current context. The bytecode
+ // emitter may eliminate the function later, but we use a conservative
+ // definition for consistency between lazy and full parsing.
+ pc_->sc()->setHasInnerFunctions();
+
+ // When fully parsing a lazy script, we do not fully reparse its inner
+ // functions, which are also lazy. Instead, their free variables and source
+ // extents are recorded and may be skipped.
+ if (handler_.reuseLazyInnerFunctions()) {
+ if (!skipLazyInnerFunction(funNode, toStringStart, tryAnnexB)) {
+ return null();
+ }
+
+ return funNode;
+ }
+
+ bool isSelfHosting = options().selfHostingMode;
+ FunctionFlags flags =
+ InitialFunctionFlags(kind, generatorKind, asyncKind, isSelfHosting);
+
+ // Self-hosted functions with special function names require extended slots
+ // for various purposes.
+ bool forceExtended =
+ isSelfHosting && funName &&
+ this->parserAtoms().isExtendedUnclonedSelfHostedFunctionName(funName);
+ if (forceExtended) {
+ flags.setIsExtended();
+ }
+
+ // Speculatively parse using the directives of the parent parsing context.
+ // If a directive is encountered (e.g., "use strict") that changes how the
+ // function should have been parsed, we backup and reparse with the new set
+ // of directives.
+ Directives directives(pc_);
+ Directives newDirectives = directives;
+
+ Position start(tokenStream);
+ auto startObj = this->compilationState_.getPosition();
+
+ // Parse the inner function. The following is a loop as we may attempt to
+ // reparse a function due to failed syntax parsing and encountering new
+ // "use foo" directives.
+ while (true) {
+ if (trySyntaxParseInnerFunction(&funNode, funName, flags, toStringStart,
+ inHandling, yieldHandling, kind,
+ generatorKind, asyncKind, tryAnnexB,
+ directives, &newDirectives)) {
+ break;
+ }
+
+ // Return on error.
+ if (anyChars.hadError() || directives == newDirectives) {
+ return null();
+ }
+
+ // Assignment must be monotonic to prevent infinitely attempting to
+ // reparse.
+ MOZ_ASSERT_IF(directives.strict(), newDirectives.strict());
+ MOZ_ASSERT_IF(directives.asmJS(), newDirectives.asmJS());
+ directives = newDirectives;
+
+ // Rewind to retry parsing with new directives applied.
+ tokenStream.rewind(start);
+ this->compilationState_.rewind(startObj);
+
+ // functionFormalParametersAndBody may have already set body before failing.
+ handler_.setFunctionFormalParametersAndBody(funNode, null());
+ }
+
+ return funNode;
+}
+
+template <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, TaggedParserAtomIndex explicitName,
+ FunctionFlags flags, uint32_t toStringStart, InHandling inHandling,
+ YieldHandling yieldHandling, FunctionSyntaxKind kind,
+ GeneratorKind generatorKind, FunctionAsyncKind asyncKind, bool tryAnnexB,
+ Directives inheritedDirectives, Directives* newDirectives) {
+ // Try a syntax parse for this inner function.
+ do {
+ // If we're assuming this function is an IIFE, always perform a full
+ // parse to avoid the overhead of a lazy syntax-only parse. Although
+ // the prediction may be incorrect, IIFEs are common enough that it
+ // pays off for lots of code.
+ if ((*funNode)->isLikelyIIFE() &&
+ generatorKind == GeneratorKind::NotGenerator &&
+ asyncKind == FunctionAsyncKind::SyncFunction) {
+ break;
+ }
+
+ SyntaxParser* syntaxParser = getSyntaxParser();
+ if (!syntaxParser) {
+ break;
+ }
+
+ UsedNameTracker::RewindToken token = usedNames_.getRewindToken();
+ auto statePosition = this->compilationState_.getPosition();
+
+ // Move the syntax parser to the current position in the stream. In the
+ // common case this seeks forward, but it'll also seek backward *at least*
+ // when arrow functions appear inside arrow function argument defaults
+ // (because we rewind to reparse arrow functions once we're certain they're
+ // arrow functions):
+ //
+ // var x = (y = z => 2) => q;
+ // // ^ we first seek to here to syntax-parse this function
+ // // ^ then we seek back to here to syntax-parse the outer function
+ Position currentPosition(tokenStream);
+ if (!syntaxParser->tokenStream.seekTo(currentPosition, anyChars)) {
+ return false;
+ }
+
+ // Make a FunctionBox before we enter the syntax parser, because |pn|
+ // still expects a FunctionBox to be attached to it during BCE, and
+ // the syntax parser cannot attach one to it.
+ FunctionBox* funbox =
+ newFunctionBox(*funNode, explicitName, flags, toStringStart,
+ inheritedDirectives, generatorKind, asyncKind);
+ if (!funbox) {
+ return false;
+ }
+ funbox->initWithEnclosingParseContext(pc_, kind);
+
+ SyntaxParseHandler::Node syntaxNode =
+ syntaxParser->innerFunctionForFunctionBox(
+ SyntaxParseHandler::NodeGeneric, pc_, funbox, inHandling,
+ yieldHandling, kind, newDirectives);
+ if (!syntaxNode) {
+ if (syntaxParser->hadAbortedSyntaxParse()) {
+ // Try again with a full parse. UsedNameTracker needs to be
+ // rewound to just before we tried the syntax parse for
+ // correctness.
+ syntaxParser->clearAbortedSyntaxParse();
+ usedNames_.rewind(token);
+ this->compilationState_.rewind(statePosition);
+ MOZ_ASSERT(!fc_->hadErrors());
+ break;
+ }
+ return false;
+ }
+
+ if (!advancePastSyntaxParsedFunction(syntaxParser)) {
+ return false;
+ }
+
+ // Update the end position of the parse node.
+ (*funNode)->pn_pos.end = anyChars.currentToken().pos.end;
+
+ // Append possible Annex B function box only upon successfully parsing.
+ if (tryAnnexB) {
+ if (!pc_->innermostScope()->addPossibleAnnexBFunctionBox(pc_, funbox)) {
+ return false;
+ }
+ }
+
+ return true;
+ } while (false);
+
+ // We failed to do a syntax parse above, so do the full parse.
+ FunctionNodeType innerFunc =
+ innerFunction(*funNode, pc_, explicitName, flags, toStringStart,
+ inHandling, yieldHandling, kind, generatorKind, asyncKind,
+ tryAnnexB, inheritedDirectives, newDirectives);
+ if (!innerFunc) {
+ return false;
+ }
+
+ *funNode = innerFunc;
+ return true;
+}
+
+template <typename Unit>
+bool Parser<SyntaxParseHandler, Unit>::trySyntaxParseInnerFunction(
+ FunctionNodeType* funNode, TaggedParserAtomIndex explicitName,
+ FunctionFlags flags, uint32_t toStringStart, InHandling inHandling,
+ YieldHandling yieldHandling, FunctionSyntaxKind kind,
+ GeneratorKind generatorKind, FunctionAsyncKind asyncKind, bool tryAnnexB,
+ Directives inheritedDirectives, Directives* newDirectives) {
+ // This is already a syntax parser, so just parse the inner function.
+ FunctionNodeType innerFunc =
+ innerFunction(*funNode, pc_, explicitName, flags, toStringStart,
+ inHandling, yieldHandling, kind, generatorKind, asyncKind,
+ tryAnnexB, inheritedDirectives, newDirectives);
+
+ if (!innerFunc) {
+ return false;
+ }
+
+ *funNode = innerFunc;
+ return true;
+}
+
+template <class ParseHandler, typename Unit>
+inline bool GeneralParser<ParseHandler, Unit>::trySyntaxParseInnerFunction(
+ FunctionNodeType* funNode, TaggedParserAtomIndex explicitName,
+ FunctionFlags flags, uint32_t toStringStart, InHandling inHandling,
+ YieldHandling yieldHandling, FunctionSyntaxKind kind,
+ GeneratorKind generatorKind, FunctionAsyncKind asyncKind, bool tryAnnexB,
+ Directives inheritedDirectives, Directives* newDirectives) {
+ return asFinalParser()->trySyntaxParseInnerFunction(
+ funNode, explicitName, flags, toStringStart, inHandling, yieldHandling,
+ kind, generatorKind, asyncKind, tryAnnexB, inheritedDirectives,
+ newDirectives);
+}
+
+template <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,
+ TaggedParserAtomIndex explicitName, FunctionFlags flags,
+ uint32_t toStringStart, InHandling inHandling, YieldHandling yieldHandling,
+ FunctionSyntaxKind kind, GeneratorKind generatorKind,
+ FunctionAsyncKind asyncKind, bool tryAnnexB, Directives inheritedDirectives,
+ Directives* newDirectives) {
+ // Note that it is possible for outerpc != this->pc_, as we may be
+ // attempting to syntax parse an inner function from an outer full
+ // parser. In that case, outerpc is a SourceParseContext from the full parser
+ // instead of the current top of the stack of the syntax parser.
+
+ FunctionBox* funbox =
+ newFunctionBox(funNode, explicitName, flags, toStringStart,
+ inheritedDirectives, generatorKind, asyncKind);
+ if (!funbox) {
+ return null();
+ }
+ funbox->initWithEnclosingParseContext(outerpc, kind);
+
+ FunctionNodeType innerFunc = innerFunctionForFunctionBox(
+ funNode, outerpc, funbox, inHandling, yieldHandling, kind, newDirectives);
+ if (!innerFunc) {
+ return null();
+ }
+
+ // Append possible Annex B function box only upon successfully parsing.
+ if (tryAnnexB) {
+ if (!pc_->innermostScope()->addPossibleAnnexBFunctionBox(pc_, funbox)) {
+ return null();
+ }
+ }
+
+ return innerFunc;
+}
+
+template <class ParseHandler, typename Unit>
+bool GeneralParser<ParseHandler, Unit>::appendToCallSiteObj(
+ CallSiteNodeType callSiteObj) {
+ Node cookedNode = noSubstitutionTaggedTemplate();
+ if (!cookedNode) {
+ return false;
+ }
+
+ auto atom = tokenStream.getRawTemplateStringAtom();
+ if (!atom) {
+ return false;
+ }
+ NameNodeType rawNode = handler_.newTemplateStringLiteral(atom, pos());
+ if (!rawNode) {
+ return false;
+ }
+
+ handler_.addToCallSiteObject(callSiteObj, rawNode, cookedNode);
+ return true;
+}
+
+template <typename Unit>
+FunctionNode* Parser<FullParseHandler, Unit>::standaloneLazyFunction(
+ CompilationInput& input, uint32_t toStringStart, bool strict,
+ GeneratorKind generatorKind, FunctionAsyncKind asyncKind) {
+ MOZ_ASSERT(checkOptionsCalled_);
+
+ FunctionSyntaxKind syntaxKind = input.functionSyntaxKind();
+ FunctionNodeType funNode = handler_.newFunction(syntaxKind, pos());
+ if (!funNode) {
+ return null();
+ }
+
+ TaggedParserAtomIndex displayAtom =
+ this->getCompilationState().previousParseCache.displayAtom();
+
+ Directives directives(strict);
+ FunctionBox* funbox =
+ newFunctionBox(funNode, displayAtom, input.functionFlags(), toStringStart,
+ directives, generatorKind, asyncKind);
+ if (!funbox) {
+ return null();
+ }
+ const ScriptStencilExtra& funExtra =
+ this->getCompilationState().previousParseCache.funExtra();
+ funbox->initFromLazyFunction(
+ funExtra, this->getCompilationState().scopeContext, syntaxKind);
+ if (funbox->useMemberInitializers()) {
+ funbox->setMemberInitializers(funExtra.memberInitializers());
+ }
+
+ Directives newDirectives = directives;
+ SourceParseContext funpc(this, funbox, &newDirectives);
+ if (!funpc.init()) {
+ return null();
+ }
+
+ // Our tokenStream has no current token, so funNode's position is garbage.
+ // Substitute the position of the first token in our source. If the
+ // function is a not-async arrow, use TokenStream::SlashIsRegExp to keep
+ // verifyConsistentModifier from complaining (we will use
+ // TokenStream::SlashIsRegExp in functionArguments).
+ Modifier modifier = (input.functionFlags().isArrow() &&
+ asyncKind == FunctionAsyncKind::SyncFunction)
+ ? TokenStream::SlashIsRegExp
+ : TokenStream::SlashIsDiv;
+ if (!tokenStream.peekTokenPos(&funNode->pn_pos, modifier)) {
+ return null();
+ }
+
+ YieldHandling yieldHandling = GetYieldHandling(generatorKind);
+
+ if (funbox->isSyntheticFunction()) {
+ // Currently default class constructors are the only synthetic function that
+ // supports delazification.
+ MOZ_ASSERT(funbox->isClassConstructor());
+ MOZ_ASSERT(funbox->extent().toStringStart == funbox->extent().sourceStart);
+
+ HasHeritage hasHeritage = funbox->isDerivedClassConstructor()
+ ? HasHeritage::Yes
+ : HasHeritage::No;
+ TokenPos synthesizedBodyPos(funbox->extent().toStringStart,
+ funbox->extent().toStringEnd);
+
+ // Reset pos() to the `class` keyword for predictable results.
+ tokenStream.consumeKnownToken(TokenKind::Class);
+
+ if (!this->synthesizeConstructorBody(synthesizedBodyPos, hasHeritage,
+ funNode, funbox)) {
+ return null();
+ }
+ } else {
+ if (!functionFormalParametersAndBody(InAllowed, yieldHandling, &funNode,
+ syntaxKind)) {
+ MOZ_ASSERT(directives == newDirectives);
+ return null();
+ }
+ }
+
+ if (!CheckParseTree(this->fc_, alloc_, funNode)) {
+ return null();
+ }
+
+ ParseNode* node = funNode;
+ // Don't constant-fold inside "use asm" code, as this could create a parse
+ // tree that doesn't type-check as asm.js.
+ if (!pc_->useAsmOrInsideUseAsm()) {
+ if (!FoldConstants(this->fc_, this->parserAtoms(), &node, &handler_)) {
+ return null();
+ }
+ }
+ funNode = &node->as<FunctionNode>();
+
+ return funNode;
+}
+
+void ParserBase::setFunctionEndFromCurrentToken(FunctionBox* funbox) const {
+ if (compilationState_.isInitialStencil()) {
+ MOZ_ASSERT(anyChars.currentToken().type != TokenKind::Eof);
+ MOZ_ASSERT(anyChars.currentToken().type < TokenKind::Limit);
+ funbox->setEnd(anyChars.currentToken().pos.end);
+ } else {
+ // If we're delazifying an arrow function with expression body and
+ // the expression is also a function, we arrive here immediately after
+ // skipping the function by Parser::skipLazyInnerFunction.
+ //
+ // a => b => c
+ // ^
+ // |
+ // we're here
+ //
+ // In that case, the current token's type field is either Limit or
+ // poisoned.
+ // We shouldn't read the value if it's poisoned.
+ // See TokenStreamSpecific<Unit, AnyCharsAccess>::advance and
+ // mfbt/MemoryChecking.h for more details.
+ //
+ // Also, in delazification, the FunctionBox should already have the
+ // correct extent, and we shouldn't overwrite it here.
+ // See ScriptStencil variant of PerHandlerParser::newFunctionBox.
+#if !defined(MOZ_ASAN) && !defined(MOZ_MSAN) && !defined(MOZ_VALGRIND)
+ MOZ_ASSERT(anyChars.currentToken().type != TokenKind::Eof);
+#endif
+ MOZ_ASSERT(funbox->extent().sourceEnd == anyChars.currentToken().pos.end);
+ }
+}
+
+template <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(TaggedParserAtomIndex::WellKnown::dotInitializers())) {
+ return false;
+ }
+ }
+
+ // See below for an explanation why arrow function parameters and arrow
+ // function bodies are parsed with different yield/await settings.
+ {
+ AwaitHandling awaitHandling =
+ kind == FunctionSyntaxKind::StaticClassBlock ? AwaitIsDisallowed
+ : (funbox->isAsync() ||
+ (kind == FunctionSyntaxKind::Arrow && awaitIsKeyword()))
+ ? AwaitIsKeyword
+ : AwaitIsName;
+ AutoAwaitIsKeyword<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");
+
+ auto propertyName = funbox->explicitName();
+ YieldHandling nameYieldHandling;
+ if (kind == FunctionSyntaxKind::Expression) {
+ // Named lambda has binding inside it.
+ nameYieldHandling = bodyYieldHandling;
+ } else {
+ // Otherwise YieldHandling cannot be checked at this point
+ // because of different context.
+ // It should already be checked before this point.
+ nameYieldHandling = YieldIsName;
+ }
+
+ // We already use the correct await-handling at this point, therefore
+ // we don't need call AutoAwaitIsKeyword here.
+
+ uint32_t nameOffset = handler_.getFunctionNameOffset(*funNode, anyChars);
+ if (!checkBindingIdentifier(propertyName, nameOffset, nameYieldHandling)) {
+ return false;
+ }
+ }
+
+ if (bodyType == StatementListBody) {
+ // Cannot use mustMatchToken here because of internal compiler error on
+ // gcc 6.4.0, with linux 64 SM hazard build.
+ TokenKind actual;
+ if (!tokenStream.getToken(&actual, TokenStream::SlashIsRegExp)) {
+ return false;
+ }
+ if (actual != TokenKind::RightCurly) {
+ reportMissingClosing(JSMSG_CURLY_AFTER_BODY, JSMSG_CURLY_OPENED,
+ openedPos);
+ return false;
+ }
+
+ setFunctionEndFromCurrentToken(funbox);
+ } else {
+ MOZ_ASSERT(kind == FunctionSyntaxKind::Arrow);
+
+ if (anyChars.hadError()) {
+ return false;
+ }
+
+ setFunctionEndFromCurrentToken(funbox);
+
+ if (kind == FunctionSyntaxKind::Statement) {
+ if (!matchOrInsertSemicolon()) {
+ return false;
+ }
+ }
+ }
+
+ if (IsMethodDefinitionKind(kind) && pc_->superScopeNeedsHomeObject()) {
+ funbox->setNeedsHomeObject();
+ }
+
+ if (!finishFunction(isStandaloneFunction)) {
+ return false;
+ }
+
+ handler_.setEndPosition(body, pos().begin);
+ handler_.setEndPosition(*funNode, pos().end);
+ handler_.setFunctionBody(*funNode, body);
+
+ return true;
+}
+
+template <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();
+ }
+ }
+
+ TaggedParserAtomIndex name;
+ if (TokenKindIsPossibleIdentifier(tt)) {
+ name = bindingIdentifier(yieldHandling);
+ if (!name) {
+ return null();
+ }
+ } else if (defaultHandling == AllowDefaultName) {
+ name = TaggedParserAtomIndex::WellKnown::default_();
+ anyChars.ungetToken();
+ } else {
+ /* Unnamed function expressions are forbidden in statement context. */
+ error(JSMSG_UNNAMED_FUNCTION_STMT);
+ return null();
+ }
+
+ // Note the declared name and check for early errors.
+ DeclarationKind kind;
+ if (declaredInStmt) {
+ MOZ_ASSERT(declaredInStmt->kind() != StatementKind::Label);
+ MOZ_ASSERT(StatementKindIsBraced(declaredInStmt->kind()));
+
+ kind =
+ (!pc_->sc()->strict() && generatorKind == GeneratorKind::NotGenerator &&
+ asyncKind == FunctionAsyncKind::SyncFunction)
+ ? DeclarationKind::SloppyLexicalFunction
+ : DeclarationKind::LexicalFunction;
+ } else {
+ kind = pc_->atModuleLevel() ? DeclarationKind::ModuleBodyLevelFunction
+ : DeclarationKind::BodyLevelFunction;
+ }
+
+ if (!noteDeclaredName(name, kind, pos())) {
+ return null();
+ }
+
+ FunctionSyntaxKind syntaxKind = FunctionSyntaxKind::Statement;
+ FunctionNodeType funNode = handler_.newFunction(syntaxKind, pos());
+ if (!funNode) {
+ return null();
+ }
+
+ // Under sloppy mode, try Annex B.3.3 semantics. If making an additional
+ // 'var' binding of the same name does not throw an early error, do so.
+ // This 'var' binding would be assigned the function object when its
+ // declaration is reached, not at the start of the block.
+ //
+ // This semantics is implemented upon Scope exit in
+ // Scope::propagateAndMarkAnnexBFunctionBoxes.
+ bool tryAnnexB = kind == DeclarationKind::SloppyLexicalFunction;
+
+ YieldHandling newYieldHandling = GetYieldHandling(generatorKind);
+ return functionDefinition(funNode, toStringStart, InAllowed, newYieldHandling,
+ name, syntaxKind, generatorKind, asyncKind,
+ tryAnnexB);
+}
+
+template <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);
+
+ TaggedParserAtomIndex name;
+ if (TokenKindIsPossibleIdentifier(tt)) {
+ name = bindingIdentifier(yieldHandling);
+ if (!name) {
+ return null();
+ }
+ } else {
+ anyChars.ungetToken();
+ }
+
+ FunctionSyntaxKind syntaxKind = FunctionSyntaxKind::Expression;
+ FunctionNodeType funNode = handler_.newFunction(syntaxKind, pos());
+ if (!funNode) {
+ return null();
+ }
+
+ if (invoked) {
+ funNode = handler_.setLikelyIIFE(funNode);
+ }
+
+ return functionDefinition(funNode, toStringStart, InAllowed, yieldHandling,
+ name, syntaxKind, generatorKind, asyncKind);
+}
+
+/*
+ * Return true if this node, known to be an unparenthesized string literal
+ * that never contain escape sequences, could be the string of a directive in a
+ * Directive Prologue. Directive strings never contain escape sequences or line
+ * continuations.
+ */
+static inline bool IsUseStrictDirective(const TokenPos& pos,
+ TaggedParserAtomIndex atom) {
+ // the length of "use strict", including quotation.
+ static constexpr size_t useStrictLength = 12;
+ return atom == TaggedParserAtomIndex::WellKnown::useStrict() &&
+ pos.begin + useStrictLength == pos.end;
+}
+static inline bool IsUseAsmDirective(const TokenPos& pos,
+ TaggedParserAtomIndex atom) {
+ // the length of "use asm", including quotation.
+ static constexpr size_t useAsmLength = 9;
+ return atom == TaggedParserAtomIndex::WellKnown::useAsm() &&
+ pos.begin + useAsmLength == pos.end;
+}
+
+template <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());
+ 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;
+ }
+
+ pc_->functionBox()->useAsm = true;
+
+ // Attempt to validate and compile this asm.js module. On success, the
+ // tokenStream has been advanced to the closing }. On failure, the
+ // tokenStream is in an indeterminate state and we must reparse the
+ // function from the beginning. Reparsing is triggered by marking that a
+ // new directive has been encountered and returning 'false'.
+ bool validated;
+ if (!CompileAsmJS(this->fc_, this->parserAtoms(), *this, list, &validated)) {
+ return false;
+ }
+ if (!validated) {
+ pc_->newDirectives->setAsmJS();
+ return false;
+ }
+
+ return true;
+}
+
+template <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;
+ TaggedParserAtomIndex directive =
+ handler_.isStringExprStatement(possibleDirective, &directivePos);
+
+ *cont = !!directive;
+ if (!*cont) {
+ return true;
+ }
+
+ if (IsUseStrictDirective(directivePos, directive)) {
+ // Functions with non-simple parameter lists (destructuring,
+ // default or rest parameters) must not contain a "use strict"
+ // directive.
+ if (pc_->isFunctionBox()) {
+ FunctionBox* funbox = pc_->functionBox();
+ if (!funbox->hasSimpleParameterList()) {
+ const char* parameterKind = funbox->hasDestructuringArgs
+ ? "destructuring"
+ : funbox->hasParameterExprs ? "default"
+ : "rest";
+ errorAt(directivePos.begin, JSMSG_STRICT_NON_SIMPLE_PARAMS,
+ parameterKind);
+ return false;
+ }
+ }
+
+ // We're going to be in strict mode. Note that this scope explicitly
+ // had "use strict";
+ pc_->sc()->setExplicitUseStrict();
+ if (!pc_->sc()->strict()) {
+ // Some strict mode violations can appear before a Use Strict Directive
+ // is applied. (See the |DeprecatedContent| enum initializers.) These
+ // violations can manifest in two ways.
+ //
+ // First, the violation can appear *before* the Use Strict Directive.
+ // Numeric literals (and therefore octal literals) can only precede a
+ // Use Strict Directive if this function's parameter list is not simple,
+ // but we reported an error for non-simple parameter lists above, so
+ // octal literals present no issue. But octal escapes and \8 and \9 can
+ // appear in the directive prologue before a Use Strict Directive:
+ //
+ // function f()
+ // {
+ // "hell\157 world"; // octal escape
+ // "\8"; "\9"; // NonOctalDecimalEscape
+ // "use strict"; // retroactively makes all the above errors
+ // }
+ //
+ // Second, the violation can appear *after* the Use Strict Directive but
+ // *before* the directive is recognized as terminated. This only
+ // happens when a directive is terminated by ASI, and the next token
+ // contains a violation:
+ //
+ // function a()
+ // {
+ // "use strict" // ASI
+ // 0755;
+ // }
+ // function b()
+ // {
+ // "use strict" // ASI
+ // "hell\157 world";
+ // }
+ // function c()
+ // {
+ // "use strict" // ASI
+ // "\8";
+ // }
+ //
+ // We note such violations when tokenizing. Then, if a violation has
+ // been observed at the time a "use strict" is applied, we report the
+ // error.
+ switch (anyChars.sawDeprecatedContent()) {
+ case DeprecatedContent::None:
+ break;
+ case DeprecatedContent::OctalLiteral:
+ error(JSMSG_DEPRECATED_OCTAL_LITERAL);
+ return false;
+ case DeprecatedContent::OctalEscape:
+ error(JSMSG_DEPRECATED_OCTAL_ESCAPE);
+ return false;
+ case DeprecatedContent::EightOrNineEscape:
+ error(JSMSG_DEPRECATED_EIGHT_OR_NINE_ESCAPE);
+ return false;
+ }
+
+ pc_->sc()->setStrictScript();
+ }
+ } else if (IsUseAsmDirective(directivePos, directive)) {
+ if (pc_->isFunctionBox()) {
+ return asmJS(list);
+ }
+ return warningAt(directivePos.begin, JSMSG_USE_ASM_DIRECTIVE_FAIL);
+ }
+ return true;
+}
+
+template <class ParseHandler, typename Unit>
+typename ParseHandler::ListNodeType
+GeneralParser<ParseHandler, Unit>::statementList(YieldHandling yieldHandling) {
+ AutoCheckRecursionLimit recursion(this->fc_);
+ if (!recursion.check(this->fc_)) {
+ return null();
+ }
+
+ ListNodeType stmtList = handler_.newStatementList(pos());
+ if (!stmtList) {
+ return null();
+ }
+
+ bool canHaveDirectives = pc_->atBodyLevel();
+ if (canHaveDirectives) {
+ // Clear flags for deprecated content that might have been seen in an
+ // enclosing context.
+ anyChars.clearSawDeprecatedContent();
+ }
+
+ bool canHaveHashbangComment = pc_->atTopLevel();
+ if (canHaveHashbangComment) {
+ tokenStream.consumeOptionalHashbangComment();
+ }
+
+ bool afterReturn = false;
+ bool warnedAboutStatementsAfterReturn = false;
+ uint32_t statementBegin = 0;
+ for (;;) {
+ TokenKind tt = TokenKind::Eof;
+ if (!tokenStream.peekToken(&tt, TokenStream::SlashIsRegExp)) {
+ if (anyChars.isEOF()) {
+ isUnexpectedEOF_ = true;
+ }
+ return null();
+ }
+ if (tt == TokenKind::Eof || tt == TokenKind::RightCurly) {
+ TokenPos pos;
+ if (!tokenStream.peekTokenPos(&pos, TokenStream::SlashIsRegExp)) {
+ return null();
+ }
+ handler_.setListEndPosition(stmtList, pos);
+ break;
+ }
+ if (afterReturn) {
+ if (!tokenStream.peekOffset(&statementBegin,
+ TokenStream::SlashIsRegExp)) {
+ return null();
+ }
+ }
+ Node next = statementListItem(yieldHandling, canHaveDirectives);
+ if (!next) {
+ if (anyChars.isEOF()) {
+ isUnexpectedEOF_ = true;
+ }
+ return null();
+ }
+ if (!warnedAboutStatementsAfterReturn) {
+ if (afterReturn) {
+ if (!handler_.isStatementPermittedAfterReturnStatement(next)) {
+ if (!warningAt(statementBegin, JSMSG_STMT_AFTER_RETURN)) {
+ return null();
+ }
+
+ warnedAboutStatementsAfterReturn = true;
+ }
+ } else if (handler_.isReturnStatement(next)) {
+ afterReturn = true;
+ }
+ }
+
+ if (canHaveDirectives) {
+ if (!maybeParseDirective(stmtList, next, &canHaveDirectives)) {
+ return null();
+ }
+ }
+
+ handler_.addStatementToList(stmtList, next);
+ }
+
+ return stmtList;
+}
+
+template <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, TaggedParserAtomIndex* labelOut) {
+ MOZ_ASSERT(labelOut != nullptr);
+ TokenKind tt = TokenKind::Eof;
+ if (!tokenStream.peekTokenSameLine(&tt, TokenStream::SlashIsRegExp)) {
+ return false;
+ }
+
+ if (TokenKindIsPossibleIdentifier(tt)) {
+ tokenStream.consumeKnownToken(tt, TokenStream::SlashIsRegExp);
+
+ *labelOut = labelIdentifier(yieldHandling);
+ if (!*labelOut) {
+ return false;
+ }
+ } else {
+ *labelOut = TaggedParserAtomIndex::null();
+ }
+ return true;
+}
+
+template <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) {
+ TaggedParserAtomIndex 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));
+
+ AutoCheckRecursionLimit recursion(this->fc_);
+ if (!recursion.check(this->fc_)) {
+ return null();
+ }
+
+ uint32_t begin = pos().begin;
+ ListNodeType literal = handler_.newObjectLiteral(begin);
+ if (!literal) {
+ return null();
+ }
+
+ Maybe<DeclarationKind> declKind = Some(kind);
+ TaggedParserAtomIndex propAtom;
+ for (;;) {
+ TokenKind tt;
+ if (!tokenStream.peekToken(&tt)) {
+ return null();
+ }
+ if (tt == TokenKind::RightCurly) {
+ break;
+ }
+
+ if (tt == TokenKind::TripleDot) {
+ tokenStream.consumeKnownToken(TokenKind::TripleDot);
+ uint32_t begin = pos().begin;
+
+ TokenKind tt;
+ if (!tokenStream.getToken(&tt)) {
+ return null();
+ }
+
+ if (!TokenKindIsPossibleIdentifierName(tt)) {
+ error(JSMSG_NO_VARIABLE_NAME);
+ return null();
+ }
+
+ NameNodeType inner = bindingIdentifier(kind, yieldHandling);
+ if (!inner) {
+ return null();
+ }
+
+ if (!handler_.addSpreadProperty(literal, begin, inner)) {
+ return null();
+ }
+ } else {
+ TokenPos namePos = anyChars.nextToken().pos;
+
+ PropertyType propType;
+ Node propName =
+ propertyOrMethodName(yieldHandling, PropertyNameInPattern, declKind,
+ literal, &propType, &propAtom);
+ if (!propName) {
+ return null();
+ }
+
+ if (propType == PropertyType::Normal) {
+ // Handle e.g., |var {p: x} = o| and |var {p: x=0} = o|.
+
+ if (!tokenStream.getToken(&tt, TokenStream::SlashIsRegExp)) {
+ return null();
+ }
+
+ Node binding = bindingIdentifierOrPattern(kind, yieldHandling, tt);
+ if (!binding) {
+ return null();
+ }
+
+ bool hasInitializer;
+ if (!tokenStream.matchToken(&hasInitializer, TokenKind::Assign,
+ TokenStream::SlashIsRegExp)) {
+ return null();
+ }
+
+ Node bindingExpr =
+ hasInitializer ? bindingInitializer(binding, kind, yieldHandling)
+ : binding;
+ if (!bindingExpr) {
+ return null();
+ }
+
+ if (!handler_.addPropertyDefinition(literal, propName, bindingExpr)) {
+ return null();
+ }
+ } else if (propType == PropertyType::Shorthand) {
+ // Handle e.g., |var {x, y} = o| as destructuring shorthand
+ // for |var {x: x, y: y} = o|.
+ MOZ_ASSERT(TokenKindIsPossibleIdentifierName(tt));
+
+ NameNodeType binding = bindingIdentifier(kind, yieldHandling);
+ if (!binding) {
+ return null();
+ }
+
+ if (!handler_.addShorthand(literal, handler_.asName(propName),
+ binding)) {
+ return null();
+ }
+ } else if (propType == PropertyType::CoverInitializedName) {
+ // Handle e.g., |var {x=1, y=2} = o| as destructuring
+ // shorthand with default values.
+ MOZ_ASSERT(TokenKindIsPossibleIdentifierName(tt));
+
+ NameNodeType binding = bindingIdentifier(kind, yieldHandling);
+ if (!binding) {
+ return null();
+ }
+
+ tokenStream.consumeKnownToken(TokenKind::Assign);
+
+ BinaryNodeType bindingExpr =
+ bindingInitializer(binding, kind, yieldHandling);
+ if (!bindingExpr) {
+ return null();
+ }
+
+ if (!handler_.addPropertyDefinition(literal, propName, bindingExpr)) {
+ return null();
+ }
+ } else {
+ errorAt(namePos.begin, JSMSG_NO_VARIABLE_NAME);
+ return null();
+ }
+ }
+
+ bool matched;
+ if (!tokenStream.matchToken(&matched, TokenKind::Comma,
+ TokenStream::SlashIsInvalid)) {
+ return null();
+ }
+ if (!matched) {
+ break;
+ }
+ if (tt == TokenKind::TripleDot) {
+ error(JSMSG_REST_WITH_COMMA);
+ return null();
+ }
+ }
+
+ if (!mustMatchToken(TokenKind::RightCurly, [this, begin](TokenKind actual) {
+ this->reportMissingClosing(JSMSG_CURLY_AFTER_LIST, JSMSG_CURLY_OPENED,
+ begin);
+ })) {
+ return null();
+ }
+
+ handler_.setEndPosition(literal, pos().end);
+ return literal;
+}
+
+template <class ParseHandler, typename Unit>
+typename ParseHandler::ListNodeType
+GeneralParser<ParseHandler, Unit>::arrayBindingPattern(
+ DeclarationKind kind, YieldHandling yieldHandling) {
+ MOZ_ASSERT(anyChars.isCurrentTokenType(TokenKind::LeftBracket));
+
+ AutoCheckRecursionLimit recursion(this->fc_);
+ if (!recursion.check(this->fc_)) {
+ return null();
+ }
+
+ uint32_t begin = pos().begin;
+ ListNodeType literal = handler_.newArrayLiteral(begin);
+ if (!literal) {
+ return null();
+ }
+
+ uint32_t index = 0;
+ for (;; index++) {
+ if (index >= NativeObject::MAX_DENSE_ELEMENTS_COUNT) {
+ error(JSMSG_ARRAY_INIT_TOO_BIG);
+ return null();
+ }
+
+ TokenKind tt;
+ if (!tokenStream.getToken(&tt)) {
+ return null();
+ }
+
+ if (tt == TokenKind::RightBracket) {
+ anyChars.ungetToken();
+ break;
+ }
+
+ if (tt == TokenKind::Comma) {
+ if (!handler_.addElision(literal, pos())) {
+ return null();
+ }
+ } else if (tt == TokenKind::TripleDot) {
+ uint32_t begin = pos().begin;
+
+ TokenKind tt;
+ if (!tokenStream.getToken(&tt)) {
+ return null();
+ }
+
+ Node inner = bindingIdentifierOrPattern(kind, yieldHandling, tt);
+ if (!inner) {
+ return null();
+ }
+
+ if (!handler_.addSpreadElement(literal, begin, inner)) {
+ return null();
+ }
+ } else {
+ Node binding = bindingIdentifierOrPattern(kind, yieldHandling, tt);
+ if (!binding) {
+ return null();
+ }
+
+ bool hasInitializer;
+ if (!tokenStream.matchToken(&hasInitializer, TokenKind::Assign,
+ TokenStream::SlashIsRegExp)) {
+ return null();
+ }
+
+ Node element = hasInitializer
+ ? bindingInitializer(binding, kind, yieldHandling)
+ : binding;
+ if (!element) {
+ return null();
+ }
+
+ handler_.addArrayElement(literal, element);
+ }
+
+ if (tt != TokenKind::Comma) {
+ // If we didn't already match TokenKind::Comma in above case.
+ bool matched;
+ if (!tokenStream.matchToken(&matched, TokenKind::Comma,
+ TokenStream::SlashIsRegExp)) {
+ return null();
+ }
+ if (!matched) {
+ break;
+ }
+
+ if (tt == TokenKind::TripleDot) {
+ error(JSMSG_REST_WITH_COMMA);
+ return null();
+ }
+ }
+ }
+
+ if (!mustMatchToken(TokenKind::RightBracket, [this, begin](TokenKind actual) {
+ this->reportMissingClosing(JSMSG_BRACKET_AFTER_LIST,
+ JSMSG_BRACKET_OPENED, begin);
+ })) {
+ return null();
+ }
+
+ handler_.setEndPosition(literal, pos().end);
+ return literal;
+}
+
+template <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();
+ }
+
+ TaggedParserAtomIndex name = bindingIdentifier(yieldHandling);
+ if (!name) {
+ return null();
+ }
+
+ NameNodeType binding = newName(name);
+ if (!binding) {
+ return null();
+ }
+
+ TokenPos namePos = pos();
+
+ // The '=' context after a variable name in a declaration is an opportunity
+ // for ASI, and thus for the next token to start an ExpressionStatement:
+ //
+ // var foo // VariableDeclaration
+ // /bar/g; // ExpressionStatement
+ //
+ // Therefore get the token here with SlashIsRegExp.
+ bool matched;
+ if (!tokenStream.matchToken(&matched, TokenKind::Assign,
+ TokenStream::SlashIsRegExp)) {
+ return null();
+ }
+
+ Node declaration;
+ if (matched) {
+ declaration = initializerInNameDeclaration(
+ binding, declKind, initialDeclaration, yieldHandling, forHeadKind,
+ forInOrOfExpression);
+ if (!declaration) {
+ return null();
+ }
+ } else {
+ declaration = binding;
+
+ if (initialDeclaration && forHeadKind) {
+ bool isForIn, isForOf;
+ if (!matchInOrOf(&isForIn, &isForOf)) {
+ return null();
+ }
+
+ if (isForIn) {
+ *forHeadKind = ParseNodeKind::ForIn;
+ } else if (isForOf) {
+ *forHeadKind = ParseNodeKind::ForOf;
+ } else {
+ *forHeadKind = ParseNodeKind::ForHead;
+ }
+ }
+
+ if (forHeadKind && *forHeadKind != ParseNodeKind::ForHead) {
+ *forInOrOfExpression =
+ expressionAfterForInOrOf(*forHeadKind, yieldHandling);
+ if (!*forInOrOfExpression) {
+ return null();
+ }
+ } else {
+ // Normal const declarations, and const declarations in for(;;)
+ // heads, must be initialized.
+ if (declKind == DeclarationKind::Const) {
+ errorAt(namePos.begin, JSMSG_BAD_CONST_DECL);
+ return null();
+ }
+ }
+ }
+
+ // Note the declared name after knowing whether or not we are in a for-of
+ // loop, due to special early error semantics in Annex B.3.5.
+ if (!noteDeclaredName(name, declKind, namePos)) {
+ return null();
+ }
+
+ return declaration;
+}
+
+template <class ParseHandler, typename Unit>
+typename ParseHandler::DeclarationListNodeType
+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");
+ }
+
+ DeclarationListNodeType decl = handler_.newDeclarationList(kind, pos());
+ if (!decl) {
+ return null();
+ }
+
+ bool moreDeclarations;
+ bool initialDeclaration = true;
+ do {
+ MOZ_ASSERT_IF(!initialDeclaration && forHeadKind,
+ *forHeadKind == ParseNodeKind::ForHead);
+
+ TokenKind tt;
+ if (!tokenStream.getToken(&tt)) {
+ return null();
+ }
+
+ Node binding =
+ (tt == TokenKind::LeftBracket || tt == TokenKind::LeftCurly)
+ ? declarationPattern(declKind, tt, initialDeclaration,
+ yieldHandling, forHeadKind,
+ forInOrOfExpression)
+ : declarationName(declKind, tt, initialDeclaration, yieldHandling,
+ forHeadKind, forInOrOfExpression);
+ if (!binding) {
+ return null();
+ }
+
+ handler_.addList(decl, binding);
+
+ // If we have a for-in/of loop, the above call matches the entirety
+ // of the loop head (up to the closing parenthesis).
+ if (forHeadKind && *forHeadKind != ParseNodeKind::ForHead) {
+ break;
+ }
+
+ initialDeclaration = false;
+
+ if (!tokenStream.matchToken(&moreDeclarations, TokenKind::Comma,
+ TokenStream::SlashIsRegExp)) {
+ return null();
+ }
+ } while (moreDeclarations);
+
+ return decl;
+}
+
+template <class ParseHandler, typename Unit>
+typename ParseHandler::DeclarationListNodeType
+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.
+ */
+ DeclarationListNodeType decl = declarationList(
+ yieldHandling, kind == DeclarationKind::Const ? ParseNodeKind::ConstDecl
+ : ParseNodeKind::LetDecl);
+ if (!decl || !matchOrInsertSemicolon()) {
+ return null();
+ }
+
+ return decl;
+}
+
+template <class ParseHandler, typename Unit>
+typename ParseHandler::NameNodeType
+GeneralParser<ParseHandler, Unit>::moduleExportName() {
+ MOZ_ASSERT(anyChars.currentToken().type == TokenKind::String);
+ TaggedParserAtomIndex name = anyChars.currentToken().atom();
+ if (!this->parserAtoms().isModuleExportName(name)) {
+ error(JSMSG_UNPAIRED_SURROGATE_EXPORT);
+ return null();
+ }
+ return handler_.newStringLiteral(name, pos());
+}
+
+template <class ParseHandler, typename Unit>
+bool GeneralParser<ParseHandler, Unit>::assertClause(
+ ListNodeType assertionsSet) {
+ MOZ_ASSERT(anyChars.isCurrentTokenType(TokenKind::Assert));
+
+ if (!options().importAssertions) {
+ error(JSMSG_IMPORT_ASSERTIONS_NOT_SUPPORTED);
+ return false;
+ }
+
+ if (!abortIfSyntaxParser()) {
+ return false;
+ }
+
+ if (!mustMatchToken(TokenKind::LeftCurly, JSMSG_CURLY_AFTER_ASSERT)) {
+ return false;
+ }
+
+ // Handle the form |... assert {}|
+ TokenKind token;
+ if (!tokenStream.getToken(&token)) {
+ return false;
+ }
+ if (token == TokenKind::RightCurly) {
+ return true;
+ }
+
+ js::HashSet<TaggedParserAtomIndex, TaggedParserAtomIndexHasher,
+ js::SystemAllocPolicy>
+ usedAssertionKeys;
+
+ for (;;) {
+ TaggedParserAtomIndex keyName;
+ if (TokenKindIsPossibleIdentifierName(token)) {
+ keyName = anyChars.currentName();
+ } else if (token == TokenKind::String) {
+ keyName = anyChars.currentToken().atom();
+ } else {
+ error(JSMSG_ASSERT_KEY_EXPECTED);
+ return false;
+ }
+
+ auto p = usedAssertionKeys.lookupForAdd(keyName);
+ if (p) {
+ UniqueChars str = this->parserAtoms().toPrintableString(keyName);
+ if (!str) {
+ ReportOutOfMemory(this->fc_);
+ return false;
+ }
+ error(JSMSG_DUPLICATE_ASSERT_KEY, str.get());
+ return false;
+ }
+ if (!usedAssertionKeys.add(p, keyName)) {
+ ReportOutOfMemory(this->fc_);
+ return false;
+ }
+
+ NameNodeType keyNode = newName(keyName);
+ if (!keyNode) {
+ return false;
+ }
+
+ if (!mustMatchToken(TokenKind::Colon, JSMSG_COLON_AFTER_ASSERT_KEY)) {
+ return false;
+ }
+ if (!mustMatchToken(TokenKind::String, JSMSG_ASSERT_STRING_LITERAL)) {
+ return false;
+ }
+
+ NameNodeType valueNode = stringLiteral();
+ if (!valueNode) {
+ return false;
+ }
+
+ BinaryNodeType importAssertionNode =
+ handler_.newImportAssertion(keyNode, valueNode);
+ if (!importAssertionNode) {
+ return false;
+ }
+
+ handler_.addList(assertionsSet, importAssertionNode);
+
+ if (!tokenStream.getToken(&token)) {
+ return false;
+ }
+ if (token == TokenKind::Comma) {
+ if (!tokenStream.getToken(&token)) {
+ return false;
+ }
+ }
+ if (token == TokenKind::RightCurly) {
+ break;
+ }
+ }
+
+ return true;
+}
+
+template <class ParseHandler, typename Unit>
+bool GeneralParser<ParseHandler, Unit>::namedImports(
+ ListNodeType importSpecSet) {
+ if (!abortIfSyntaxParser()) {
+ return false;
+ }
+
+ while (true) {
+ // Handle the forms |import {} from 'a'| and
+ // |import { ..., } from 'a'| (where ... is non empty), by
+ // escaping the loop early if the next token is }.
+ TokenKind tt;
+ if (!tokenStream.getToken(&tt)) {
+ return false;
+ }
+
+ if (tt == TokenKind::RightCurly) {
+ break;
+ }
+
+ TaggedParserAtomIndex importName;
+ NameNodeType importNameNode = null();
+ if (TokenKindIsPossibleIdentifierName(tt)) {
+ importName = anyChars.currentName();
+ importNameNode = newName(importName);
+ } else if (tt == TokenKind::String) {
+ importNameNode = moduleExportName();
+ } else {
+ error(JSMSG_NO_IMPORT_NAME);
+ }
+ if (!importNameNode) {
+ return false;
+ }
+
+ bool matched;
+ if (!tokenStream.matchToken(&matched, TokenKind::As)) {
+ return false;
+ }
+
+ if (matched) {
+ TokenKind afterAs;
+ if (!tokenStream.getToken(&afterAs)) {
+ return false;
+ }
+
+ if (!TokenKindIsPossibleIdentifierName(afterAs)) {
+ error(JSMSG_NO_BINDING_NAME);
+ return false;
+ }
+ } else {
+ // String export names can't refer to local bindings.
+ if (tt == TokenKind::String) {
+ error(JSMSG_AS_AFTER_STRING);
+ return false;
+ }
+
+ // Keywords cannot be bound to themselves, so an import name
+ // that is a keyword is a syntax error if it is not followed
+ // by the keyword 'as'.
+ // See the ImportSpecifier production in ES6 section 15.2.2.
+ MOZ_ASSERT(importName);
+ if (IsKeyword(importName)) {
+ error(JSMSG_AS_AFTER_RESERVED_WORD, ReservedWordToCharZ(importName));
+ return false;
+ }
+ }
+
+ TaggedParserAtomIndex bindingAtom = importedBinding();
+ if (!bindingAtom) {
+ return false;
+ }
+
+ NameNodeType bindingName = newName(bindingAtom);
+ if (!bindingName) {
+ return false;
+ }
+ if (!noteDeclaredName(bindingAtom, DeclarationKind::Import, pos())) {
+ return false;
+ }
+
+ BinaryNodeType importSpec =
+ handler_.newImportSpec(importNameNode, bindingName);
+ if (!importSpec) {
+ return false;
+ }
+
+ handler_.addList(importSpecSet, importSpec);
+
+ TokenKind next;
+ if (!tokenStream.getToken(&next)) {
+ return false;
+ }
+
+ if (next == TokenKind::RightCurly) {
+ break;
+ }
+
+ if (next != TokenKind::Comma) {
+ error(JSMSG_RC_AFTER_IMPORT_SPEC_LIST);
+ return false;
+ }
+ }
+
+ return true;
+}
+
+template <class ParseHandler, typename Unit>
+bool GeneralParser<ParseHandler, Unit>::namespaceImport(
+ ListNodeType importSpecSet) {
+ if (!abortIfSyntaxParser()) {
+ return false;
+ }
+
+ if (!mustMatchToken(TokenKind::As, JSMSG_AS_AFTER_IMPORT_STAR)) {
+ return false;
+ }
+ uint32_t begin = pos().begin;
+
+ if (!mustMatchToken(TokenKindIsPossibleIdentifierName,
+ JSMSG_NO_BINDING_NAME)) {
+ return false;
+ }
+
+ // Namespace imports are not indirect bindings but lexical
+ // definitions that hold a module namespace object. They are treated
+ // as const variables which are initialized during the
+ // ModuleInstantiate step.
+ TaggedParserAtomIndex bindingName = importedBinding();
+ if (!bindingName) {
+ return false;
+ }
+ NameNodeType bindingNameNode = newName(bindingName);
+ if (!bindingNameNode) {
+ return false;
+ }
+ if (!noteDeclaredName(bindingName, DeclarationKind::Const, pos())) {
+ return false;
+ }
+
+ // The namespace import name is currently required to live on the
+ // environment.
+ pc_->varScope().lookupDeclaredName(bindingName)->value()->setClosedOver();
+
+ UnaryNodeType importSpec =
+ handler_.newImportNamespaceSpec(begin, bindingNameNode);
+ if (!importSpec) {
+ return false;
+ }
+
+ handler_.addList(importSpecSet, importSpec);
+
+ return true;
+}
+
+template <class ParseHandler, typename Unit>
+typename ParseHandler::BinaryNodeType
+GeneralParser<ParseHandler, Unit>::importDeclaration() {
+ if (!abortIfSyntaxParser()) {
+ return null();
+ }
+
+ MOZ_ASSERT(anyChars.isCurrentTokenType(TokenKind::Import));
+
+ if (!pc_->atModuleLevel()) {
+ error(JSMSG_IMPORT_DECL_AT_TOP_LEVEL);
+ return null();
+ }
+
+ uint32_t begin = pos().begin;
+ TokenKind tt;
+ if (!tokenStream.getToken(&tt)) {
+ return null();
+ }
+
+ ListNodeType importSpecSet =
+ handler_.newList(ParseNodeKind::ImportSpecList, pos());
+ if (!importSpecSet) {
+ return null();
+ }
+
+ if (tt == TokenKind::String) {
+ // Handle the form |import 'a'| by leaving the list empty. This is
+ // equivalent to |import {} from 'a'|.
+ handler_.setEndPosition(importSpecSet, pos().begin);
+ } else {
+ if (tt == TokenKind::LeftCurly) {
+ if (!namedImports(importSpecSet)) {
+ return null();
+ }
+ } else if (tt == TokenKind::Mul) {
+ if (!namespaceImport(importSpecSet)) {
+ return null();
+ }
+ } else if (TokenKindIsPossibleIdentifierName(tt)) {
+ // Handle the form |import a from 'b'|, by adding a single import
+ // specifier to the list, with 'default' as the import name and
+ // 'a' as the binding name. This is equivalent to
+ // |import { default as a } from 'b'|.
+ NameNodeType importName =
+ newName(TaggedParserAtomIndex::WellKnown::default_());
+ if (!importName) {
+ return null();
+ }
+
+ TaggedParserAtomIndex bindingAtom = importedBinding();
+ if (!bindingAtom) {
+ return null();
+ }
+
+ NameNodeType bindingName = newName(bindingAtom);
+ if (!bindingName) {
+ return null();
+ }
+
+ if (!noteDeclaredName(bindingAtom, DeclarationKind::Import, pos())) {
+ return null();
+ }
+
+ BinaryNodeType importSpec =
+ handler_.newImportSpec(importName, bindingName);
+ if (!importSpec) {
+ return null();
+ }
+
+ handler_.addList(importSpecSet, importSpec);
+
+ if (!tokenStream.peekToken(&tt)) {
+ return null();
+ }
+
+ if (tt == TokenKind::Comma) {
+ tokenStream.consumeKnownToken(tt);
+ if (!tokenStream.getToken(&tt)) {
+ return null();
+ }
+
+ if (tt == TokenKind::LeftCurly) {
+ if (!namedImports(importSpecSet)) {
+ return null();
+ }
+ } else if (tt == TokenKind::Mul) {
+ if (!namespaceImport(importSpecSet)) {
+ return null();
+ }
+ } else {
+ error(JSMSG_NAMED_IMPORTS_OR_NAMESPACE_IMPORT);
+ return null();
+ }
+ }
+ } else {
+ error(JSMSG_DECLARATION_AFTER_IMPORT);
+ return null();
+ }
+
+ if (!mustMatchToken(TokenKind::From, JSMSG_FROM_AFTER_IMPORT_CLAUSE)) {
+ return null();
+ }
+
+ if (!mustMatchToken(TokenKind::String, JSMSG_MODULE_SPEC_AFTER_FROM)) {
+ return null();
+ }
+ }
+
+ NameNodeType moduleSpec = stringLiteral();
+ if (!moduleSpec) {
+ return null();
+ }
+
+ if (!tokenStream.peekTokenSameLine(&tt, TokenStream::SlashIsRegExp)) {
+ return null();
+ }
+
+ ListNodeType importAssertionList =
+ handler_.newList(ParseNodeKind::ImportAssertionList, pos());
+ if (!importAssertionList) {
+ return null();
+ }
+
+ if (tt == TokenKind::Assert) {
+ tokenStream.consumeKnownToken(TokenKind::Assert,
+ TokenStream::SlashIsRegExp);
+
+ if (!assertClause(importAssertionList)) {
+ return null();
+ }
+ }
+
+ if (!matchOrInsertSemicolon(TokenStream::SlashIsRegExp)) {
+ return null();
+ }
+
+ BinaryNodeType moduleRequest = handler_.newModuleRequest(
+ moduleSpec, importAssertionList, TokenPos(begin, pos().end));
+ if (!moduleRequest) {
+ return null();
+ }
+
+ BinaryNodeType node = handler_.newImportDeclaration(
+ importSpecSet, moduleRequest, TokenPos(begin, pos().end));
+ if (!node || !processImport(node)) {
+ return null();
+ }
+
+ return node;
+}
+
+template <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(
+ TaggedParserAtomIndex exportName) {
+ if (!pc_->sc()->asModuleContext()->builder.hasExportedName(exportName)) {
+ return true;
+ }
+
+ UniqueChars str = this->parserAtoms().toPrintableString(exportName);
+ if (!str) {
+ ReportOutOfMemory(this->fc_);
+ return false;
+ }
+
+ error(JSMSG_DUPLICATE_EXPORT_NAME, str.get());
+ return false;
+}
+
+template <typename Unit>
+inline bool Parser<SyntaxParseHandler, Unit>::checkExportedName(
+ TaggedParserAtomIndex exportName) {
+ MOZ_ALWAYS_FALSE(abortIfSyntaxParser());
+ return false;
+}
+
+template <class ParseHandler, typename Unit>
+inline bool GeneralParser<ParseHandler, Unit>::checkExportedName(
+ TaggedParserAtomIndex 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(
+ DeclarationListNodeType 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(
+ DeclarationListNodeType node) {
+ MOZ_ALWAYS_FALSE(abortIfSyntaxParser());
+ return false;
+}
+
+template <class ParseHandler, typename Unit>
+inline bool
+GeneralParser<ParseHandler, Unit>::checkExportedNamesForDeclarationList(
+ DeclarationListNodeType 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 <>
+inline bool PerHandlerParser<FullParseHandler>::processImport(
+ BinaryNodeType node) {
+ return pc_->sc()->asModuleContext()->builder.processImport(node);
+}
+
+template <>
+inline bool PerHandlerParser<SyntaxParseHandler>::processImport(
+ 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 (!mustMatchToken(TokenKind::String, JSMSG_MODULE_SPEC_AFTER_FROM)) {
+ return null();
+ }
+
+ NameNodeType moduleSpec = stringLiteral();
+ if (!moduleSpec) {
+ return null();
+ }
+
+ TokenKind tt;
+ if (!tokenStream.peekTokenSameLine(&tt, TokenStream::SlashIsRegExp)) {
+ return null();
+ }
+ uint32_t moduleSpecPos = pos().begin;
+
+ ListNodeType importAssertionList =
+ handler_.newList(ParseNodeKind::ImportAssertionList, pos());
+ if (!importAssertionList) {
+ return null();
+ }
+ if (tt == TokenKind::Assert) {
+ tokenStream.consumeKnownToken(TokenKind::Assert,
+ TokenStream::SlashIsRegExp);
+
+ if (!assertClause(importAssertionList)) {
+ return null();
+ }
+ }
+
+ if (!matchOrInsertSemicolon(TokenStream::SlashIsRegExp)) {
+ return null();
+ }
+
+ BinaryNodeType moduleRequest = handler_.newModuleRequest(
+ moduleSpec, importAssertionList, TokenPos(moduleSpecPos, pos().end));
+ if (!moduleRequest) {
+ return null();
+ }
+
+ BinaryNodeType node =
+ handler_.newExportFromDeclaration(begin, specList, moduleRequest);
+ if (!node) {
+ return null();
+ }
+
+ if (!processExportFrom(node)) {
+ return null();
+ }
+
+ return node;
+}
+
+template <class ParseHandler, typename Unit>
+typename ParseHandler::BinaryNodeType
+GeneralParser<ParseHandler, Unit>::exportBatch(uint32_t begin) {
+ if (!abortIfSyntaxParser()) {
+ return null();
+ }
+
+ MOZ_ASSERT(anyChars.isCurrentTokenType(TokenKind::Mul));
+ uint32_t beginExportSpec = pos().begin;
+
+ ListNodeType kid = handler_.newList(ParseNodeKind::ExportSpecList, pos());
+ if (!kid) {
+ return null();
+ }
+
+ bool foundAs;
+ if (!tokenStream.matchToken(&foundAs, TokenKind::As)) {
+ return null();
+ }
+
+ if (foundAs) {
+ TokenKind tt;
+ if (!tokenStream.getToken(&tt)) {
+ return null();
+ }
+
+ NameNodeType exportName = null();
+ if (TokenKindIsPossibleIdentifierName(tt)) {
+ exportName = newName(anyChars.currentName());
+ } else if (tt == TokenKind::String) {
+ exportName = moduleExportName();
+ } else {
+ error(JSMSG_NO_EXPORT_NAME);
+ }
+ if (!exportName) {
+ return null();
+ }
+
+ if (!checkExportedNameForClause(exportName)) {
+ return null();
+ }
+
+ UnaryNodeType exportSpec =
+ handler_.newExportNamespaceSpec(beginExportSpec, exportName);
+ if (!exportSpec) {
+ return null();
+ }
+
+ handler_.addList(kid, exportSpec);
+ } else {
+ // Handle the form |export *| by adding a special export batch
+ // specifier to the list.
+ NullaryNodeType exportSpec = handler_.newExportBatchSpec(pos());
+ if (!exportSpec) {
+ return null();
+ }
+
+ handler_.addList(kid, exportSpec);
+ }
+
+ if (!mustMatchToken(TokenKind::From, JSMSG_FROM_AFTER_EXPORT_STAR)) {
+ return null();
+ }
+
+ return exportFrom(begin, kid);
+}
+
+template <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();
+
+ if (name->isKind(ParseNodeKind::StringExpr)) {
+ errorAt(name->pn_pos.begin, JSMSG_BAD_LOCAL_STRING_EXPORT);
+ return false;
+ }
+
+ MOZ_ASSERT(name->isKind(ParseNodeKind::Name));
+
+ TaggedParserAtomIndex ident = name->as<NameNode>().atom();
+ 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;
+ }
+
+ NameNodeType bindingName = null();
+ if (TokenKindIsPossibleIdentifierName(tt)) {
+ bindingName = newName(anyChars.currentName());
+ } else if (tt == TokenKind::String) {
+ bindingName = moduleExportName();
+ } else {
+ error(JSMSG_NO_BINDING_NAME);
+ }
+ if (!bindingName) {
+ return null();
+ }
+
+ bool foundAs;
+ if (!tokenStream.matchToken(&foundAs, TokenKind::As)) {
+ return null();
+ }
+
+ NameNodeType exportName = null();
+ if (foundAs) {
+ TokenKind tt;
+ if (!tokenStream.getToken(&tt)) {
+ return null();
+ }
+
+ if (TokenKindIsPossibleIdentifierName(tt)) {
+ exportName = newName(anyChars.currentName());
+ } else if (tt == TokenKind::String) {
+ exportName = moduleExportName();
+ } else {
+ error(JSMSG_NO_EXPORT_NAME);
+ }
+ } else {
+ if (tt != TokenKind::String) {
+ exportName = newName(anyChars.currentName());
+ } else {
+ exportName = moduleExportName();
+ }
+ }
+ if (!exportName) {
+ return null();
+ }
+
+ if (!checkExportedNameForClause(exportName)) {
+ return null();
+ }
+
+ BinaryNodeType exportSpec = handler_.newExportSpec(bindingName, exportName);
+ if (!exportSpec) {
+ return null();
+ }
+
+ handler_.addList(kid, exportSpec);
+
+ TokenKind next;
+ if (!tokenStream.getToken(&next)) {
+ return null();
+ }
+
+ if (next == TokenKind::RightCurly) {
+ break;
+ }
+
+ if (next != TokenKind::Comma) {
+ error(JSMSG_RC_AFTER_EXPORT_SPEC_LIST);
+ return null();
+ }
+ }
+
+ // Careful! If |from| follows, even on a new line, it must start a
+ // FromClause:
+ //
+ // export { x }
+ // from "foo"; // a single ExportDeclaration
+ //
+ // But if it doesn't, we might have an ASI opportunity in SlashIsRegExp
+ // context:
+ //
+ // export { x } // ExportDeclaration, terminated by ASI
+ // fro\u006D // ExpressionStatement, the name "from"
+ //
+ // In that case let matchOrInsertSemicolon sort out ASI or any necessary
+ // error.
+ bool matched;
+ if (!tokenStream.matchToken(&matched, TokenKind::From,
+ TokenStream::SlashIsRegExp)) {
+ return null();
+ }
+
+ if (matched) {
+ return exportFrom(begin, kid);
+ }
+
+ if (!matchOrInsertSemicolon()) {
+ return null();
+ }
+
+ if (!checkLocalExportNames(kid)) {
+ return null();
+ }
+
+ UnaryNodeType node =
+ handler_.newExportDeclaration(kid, TokenPos(begin, pos().end));
+ if (!node) {
+ return null();
+ }
+
+ if (!processExport(node)) {
+ return null();
+ }
+
+ return node;
+}
+
+template <class ParseHandler, typename Unit>
+typename ParseHandler::UnaryNodeType
+GeneralParser<ParseHandler, Unit>::exportVariableStatement(uint32_t begin) {
+ if (!abortIfSyntaxParser()) {
+ return null();
+ }
+
+ MOZ_ASSERT(anyChars.isCurrentTokenType(TokenKind::Var));
+
+ DeclarationListNodeType kid =
+ declarationList(YieldIsName, ParseNodeKind::VarStmt);
+ if (!kid) {
+ return null();
+ }
+ if (!matchOrInsertSemicolon()) {
+ return null();
+ }
+ if (!checkExportedNamesForDeclarationList(kid)) {
+ return null();
+ }
+
+ UnaryNodeType node =
+ handler_.newExportDeclaration(kid, TokenPos(begin, pos().end));
+ if (!node) {
+ return null();
+ }
+
+ if (!processExport(node)) {
+ return null();
+ }
+
+ return node;
+}
+
+template <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));
+
+ DeclarationListNodeType kid = lexicalDeclaration(YieldIsName, kind);
+ if (!kid) {
+ return null();
+ }
+ if (!checkExportedNamesForDeclarationList(kid)) {
+ return null();
+ }
+
+ UnaryNodeType node =
+ handler_.newExportDeclaration(kid, TokenPos(begin, pos().end));
+ if (!node) {
+ return null();
+ }
+
+ if (!processExport(node)) {
+ return null();
+ }
+
+ return node;
+}
+
+template <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();
+ }
+
+ TaggedParserAtomIndex name = TaggedParserAtomIndex::WellKnown::default_();
+ NameNodeType nameNode = newName(name);
+ if (!nameNode) {
+ return null();
+ }
+ if (!noteDeclaredName(name, DeclarationKind::Const, pos())) {
+ return null();
+ }
+
+ Node kid = assignExpr(InAllowed, YieldIsName, TripledotProhibited);
+ if (!kid) {
+ return null();
+ }
+
+ if (!matchOrInsertSemicolon()) {
+ return null();
+ }
+
+ BinaryNodeType node = handler_.newExportDefaultDeclaration(
+ kid, nameNode, TokenPos(begin, pos().end));
+ if (!node) {
+ return null();
+ }
+
+ if (!processExport(node)) {
+ return null();
+ }
+
+ return node;
+}
+
+template <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(TaggedParserAtomIndex::WellKnown::default_())) {
+ return null();
+ }
+
+ switch (tt) {
+ case TokenKind::Function:
+ return exportDefaultFunctionDeclaration(begin, pos().begin);
+
+ case TokenKind::Async: {
+ TokenKind nextSameLine = TokenKind::Eof;
+ if (!tokenStream.peekTokenSameLine(&nextSameLine)) {
+ return null();
+ }
+
+ if (nextSameLine == TokenKind::Function) {
+ uint32_t toStringStart = pos().begin;
+ tokenStream.consumeKnownToken(TokenKind::Function);
+ return exportDefaultFunctionDeclaration(
+ begin, toStringStart, FunctionAsyncKind::AsyncFunction);
+ }
+
+ anyChars.ungetToken();
+ return exportDefaultAssignExpr(begin);
+ }
+
+ case TokenKind::Class:
+ return exportDefaultClassDeclaration(begin);
+
+ default:
+ anyChars.ungetToken();
+ return exportDefaultAssignExpr(begin);
+ }
+}
+
+template <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(fc_), thenList(fc_);
+ Vector<uint32_t, 4> posList(fc_);
+ Node elseBranch;
+
+ ParseContext::Statement stmt(pc_, StatementKind::If);
+
+ while (true) {
+ uint32_t begin = pos().begin;
+
+ /* An IF node has three kids: condition, then, and optional else. */
+ Node cond = condition(InAllowed, yieldHandling);
+ if (!cond) {
+ return null();
+ }
+
+ TokenKind tt;
+ if (!tokenStream.peekToken(&tt, TokenStream::SlashIsRegExp)) {
+ return null();
+ }
+
+ Node thenBranch = consequentOrAlternative(yieldHandling);
+ if (!thenBranch) {
+ return null();
+ }
+
+ if (!condList.append(cond) || !thenList.append(thenBranch) ||
+ !posList.append(begin)) {
+ return null();
+ }
+
+ bool matched;
+ if (!tokenStream.matchToken(&matched, TokenKind::Else,
+ TokenStream::SlashIsRegExp)) {
+ return null();
+ }
+ if (matched) {
+ if (!tokenStream.matchToken(&matched, TokenKind::If,
+ TokenStream::SlashIsRegExp)) {
+ return null();
+ }
+ if (matched) {
+ continue;
+ }
+ elseBranch = consequentOrAlternative(yieldHandling);
+ if (!elseBranch) {
+ return null();
+ }
+ } else {
+ elseBranch = null();
+ }
+ break;
+ }
+
+ TernaryNodeType ifNode;
+ for (int i = condList.length() - 1; i >= 0; i--) {
+ ifNode = handler_.newIfStatement(posList[i], condList[i], thenList[i],
+ elseBranch);
+ if (!ifNode) {
+ return null();
+ }
+ elseBranch = ifNode;
+ }
+
+ return ifNode;
+}
+
+template <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_.isPropertyOrPrivateMemberAccess(*forInitialPart)) {
+ // Permitted: no additional testing/fixup needed.
+ } else if (handler_.isFunctionCall(*forInitialPart)) {
+ if (!strictModeErrorAt(exprOffset, JSMSG_BAD_FOR_LEFTSIDE)) {
+ return false;
+ }
+ } else {
+ errorAt(exprOffset, JSMSG_BAD_FOR_LEFTSIDE);
+ return false;
+ }
+
+ if (!possibleError.checkForExpressionError()) {
+ return false;
+ }
+
+ // Finally, parse the iterated expression, making the for-loop's closing
+ // ')' the next token.
+ *forInOrOfExpression = expressionAfterForInOrOf(*forHeadKind, yieldHandling);
+ return *forInOrOfExpression != null();
+}
+
+template <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() || pc_->sc()->isModuleContext()) {
+ bool matched;
+ if (!tokenStream.matchToken(&matched, TokenKind::Await)) {
+ return null();
+ }
+
+ // If we come across a top level await here, mark the module as async.
+ if (matched && pc_->sc()->isModuleContext() && !pc_->isAsync()) {
+ if (!options().topLevelAwait) {
+ error(JSMSG_TOP_LEVEL_AWAIT_NOT_SUPPORTED);
+ return null();
+ }
+ pc_->sc()->asModuleContext()->setIsAsync();
+ MOZ_ASSERT(pc_->isAsync());
+ }
+
+ if (matched) {
+ iflags |= JSITER_FORAWAITOF;
+ iterKind = IteratorKind::Async;
+ }
+ }
+
+ if (!mustMatchToken(TokenKind::LeftParen, [this](TokenKind actual) {
+ this->error((actual == TokenKind::Await && !this->pc_->isAsync())
+ ? JSMSG_FOR_AWAIT_OUTSIDE_ASYNC
+ : JSMSG_PAREN_AFTER_FOR);
+ })) {
+ return null();
+ }
+
+ // ParseNodeKind::ForHead, ParseNodeKind::ForIn, or
+ // ParseNodeKind::ForOf depending on the loop type.
+ ParseNodeKind headKind;
+
+ // |x| in either |for (x; ...; ...)| or |for (x in/of ...)|.
+ Node startNode;
+
+ // The next two variables are used to implement `for (let/const ...)`.
+ //
+ // We generate an implicit block, wrapping the whole loop, to store loop
+ // variables declared this way. Note that if the loop uses `for (var...)`
+ // instead, those variables go on some existing enclosing scope, so no
+ // implicit block scope is created.
+ //
+ // Both variables remain null/none if the loop is any other form.
+
+ // The static block scope for the implicit block scope.
+ Maybe<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;
+
+ TaggedParserAtomIndex label;
+ if (!matchLabel(yieldHandling, &label)) {
+ return null();
+ }
+
+ auto validity = pc_->checkContinueStatement(label);
+ if (validity.isErr()) {
+ switch (validity.unwrapErr()) {
+ case ParseContext::ContinueStatementError::NotInALoop:
+ errorAt(begin, JSMSG_BAD_CONTINUE);
+ break;
+ case ParseContext::ContinueStatementError::LabelNotFound:
+ error(JSMSG_LABEL_NOT_FOUND);
+ break;
+ }
+ return null();
+ }
+
+ if (!matchOrInsertSemicolon()) {
+ return null();
+ }
+
+ return handler_.newContinueStatement(label, TokenPos(begin, pos().end));
+}
+
+template <class ParseHandler, typename Unit>
+typename ParseHandler::BreakStatementType
+GeneralParser<ParseHandler, Unit>::breakStatement(YieldHandling yieldHandling) {
+ MOZ_ASSERT(anyChars.isCurrentTokenType(TokenKind::Break));
+ uint32_t begin = pos().begin;
+
+ TaggedParserAtomIndex label;
+ if (!matchLabel(yieldHandling, &label)) {
+ return null();
+ }
+
+ auto validity = pc_->checkBreakStatement(label);
+ if (validity.isErr()) {
+ switch (validity.unwrapErr()) {
+ case ParseContext::BreakStatementError::ToughBreak:
+ errorAt(begin, JSMSG_TOUGH_BREAK);
+ return null();
+ case ParseContext::BreakStatementError::LabelNotFound:
+ error(JSMSG_LABEL_NOT_FOUND);
+ return null();
+ }
+ }
+
+ if (!matchOrInsertSemicolon()) {
+ return null();
+ }
+
+ return handler_.newBreakStatement(label, TokenPos(begin, pos().end));
+}
+
+template <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) {
+ TaggedParserAtomIndex label = labelIdentifier(yieldHandling);
+ if (!label) {
+ return null();
+ }
+
+ auto hasSameLabel = [&label](ParseContext::LabelStatement* stmt) {
+ return stmt->label() == label;
+ };
+
+ uint32_t begin = pos().begin;
+
+ if (pc_->template findInnermostStatement<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");
+ }
+}
+
+#ifdef ENABLE_DECORATORS
+template <class ParseHandler, typename Unit>
+typename ParseHandler::ListNodeType
+GeneralParser<ParseHandler, Unit>::decoratorList(YieldHandling yieldHandling) {
+ ListNodeType decorators =
+ handler_.newList(ParseNodeKind::DecoratorList, pos());
+
+ // Build a decorator list element. At each entry point to this loop we have
+ // already consumed the |@| token
+ TokenKind tt;
+ for (;;) {
+ if (!tokenStream.getToken(&tt, TokenStream::SlashIsInvalid)) {
+ return null();
+ }
+
+ Node decorator = null();
+ if (tt == TokenKind::LeftParen) {
+ // Handle DecoratorParenthesizedExpression
+ decorator = exprInParens(InAllowed, yieldHandling, TripledotProhibited);
+ if (!decorator) {
+ return null();
+ }
+
+ if (!mustMatchToken(TokenKind::RightParen, JSMSG_PAREN_AFTER_DECORATOR)) {
+ return null();
+ }
+
+ if (!tokenStream.getToken(&tt)) {
+ return null();
+ }
+ } else {
+ // Get decorator identifier
+ if (!(tt == TokenKind::Name || TokenKindIsContextualKeyword(tt))) {
+ error(JSMSG_DECORATOR_NAME_EXPECTED);
+ return null();
+ }
+ TaggedParserAtomIndex name = anyChars.currentName();
+ if (!tokenStream.getToken(&tt)) {
+ return null();
+ }
+
+ // Handle DecoratorMemberExpression
+ decorator = handler_.newName(name, pos());
+
+ // Ok, this DecoratorMemberExpression is actually a list of
+ // Identifiers separated by `.`
+ if (tt == TokenKind::Dot) {
+ ListNodeType ids =
+ handler_.newList(ParseNodeKind::DecoratorList, pos());
+ handler_.addList(ids, decorator);
+ for (;;) {
+ if (!tokenStream.getToken(&tt)) {
+ return null();
+ }
+
+ // Reject invalid or missing identifiers, like dec1.(
+ if (!(tt == TokenKind::Name || TokenKindIsContextualKeyword(tt))) {
+ error(JSMSG_DECORATOR_NAME_EXPECTED);
+ return null();
+ }
+ TaggedParserAtomIndex name = anyChars.currentName();
+ Node id = handler_.newName(name, pos());
+ handler_.addList(ids, id);
+
+ if (!tokenStream.getToken(&tt)) {
+ return null();
+ }
+
+ if (tt != TokenKind::Dot) {
+ break;
+ }
+ }
+ decorator = ids;
+ }
+
+ // We've hit a `(`, so it's actually a DecoratorCallExpression
+ if (tt == TokenKind::LeftParen) {
+ bool isSpread = false;
+ Node args = argumentList(yieldHandling, &isSpread);
+ if (!args) {
+ return null();
+ }
+ decorator = handler_.newCall(decorator, args,
+ isSpread ? JSOp::SpreadCall : JSOp::Call);
+
+ if (!tokenStream.getToken(&tt)) {
+ return null();
+ }
+ }
+ }
+ MOZ_ASSERT(decorator, "Decorator should exist");
+ handler_.addList(decorators, decorator);
+
+ if (tt != TokenKind::At) {
+ anyChars.ungetToken();
+ break;
+ }
+ }
+ return decorators;
+}
+#endif
+
+template <class ParseHandler, typename Unit>
+bool GeneralParser<ParseHandler, Unit>::classMember(
+ YieldHandling yieldHandling, const ParseContext::ClassStatement& classStmt,
+ TaggedParserAtomIndex className, uint32_t classStartOffset,
+ HasHeritage hasHeritage, ClassInitializedMembers& classInitializedMembers,
+ ListNodeType& classMembers, bool* done) {
+ *done = false;
+
+ TokenKind tt;
+ if (!tokenStream.getToken(&tt, TokenStream::SlashIsInvalid)) {
+ return false;
+ }
+ if (tt == TokenKind::RightCurly) {
+ *done = true;
+ return true;
+ }
+
+ if (tt == TokenKind::Semi) {
+ return true;
+ }
+
+#ifdef ENABLE_DECORATORS
+ ListNodeType decorators = null();
+ if (tt == TokenKind::At) {
+ decorators = decoratorList(yieldHandling);
+ if (!decorators) {
+ return false;
+ }
+
+ if (!tokenStream.getToken(&tt, TokenStream::SlashIsInvalid)) {
+ return false;
+ }
+ }
+#endif
+
+ bool isStatic = false;
+ if (tt == TokenKind::Static) {
+ if (!tokenStream.peekToken(&tt)) {
+ return false;
+ }
+
+ if (tt == TokenKind::LeftCurly) {
+ /* Parsing static class block: static { ... } */
+ FunctionNodeType staticBlockBody =
+ staticClassBlock(classInitializedMembers);
+ if (!staticBlockBody) {
+ return false;
+ }
+
+ StaticClassBlockType classBlock =
+ handler_.newStaticClassBlock(staticBlockBody);
+ if (!classBlock) {
+ return false;
+ }
+
+ return handler_.addClassMemberDefinition(classMembers, classBlock);
+ }
+
+ if (tt != TokenKind::LeftParen && tt != TokenKind::Assign &&
+ tt != TokenKind::Semi && tt != TokenKind::RightCurly) {
+ isStatic = true;
+ } else {
+ anyChars.ungetToken();
+ }
+ } else {
+ anyChars.ungetToken();
+ }
+
+ uint32_t propNameOffset;
+ if (!tokenStream.peekOffset(&propNameOffset, TokenStream::SlashIsInvalid)) {
+ return false;
+ }
+
+ TaggedParserAtomIndex propAtom;
+ PropertyType propType;
+ Node propName = propertyOrMethodName(yieldHandling, PropertyNameInClass,
+ /* maybeDecl = */ Nothing(),
+ classMembers, &propType, &propAtom);
+ if (!propName) {
+ return false;
+ }
+
+ if (propType == PropertyType::Field ||
+ propType == PropertyType::FieldWithAccessor) {
+ if (isStatic) {
+ if (propAtom == TaggedParserAtomIndex::WellKnown::prototype()) {
+ errorAt(propNameOffset, JSMSG_BAD_METHOD_DEF);
+ return false;
+ }
+ }
+
+ if (propAtom == TaggedParserAtomIndex::WellKnown::constructor()) {
+ errorAt(propNameOffset, JSMSG_BAD_METHOD_DEF);
+ return false;
+ }
+
+ if (handler_.isPrivateName(propName)) {
+ if (propAtom == TaggedParserAtomIndex::WellKnown::hashConstructor()) {
+ errorAt(propNameOffset, JSMSG_BAD_METHOD_DEF);
+ return false;
+ }
+
+ auto privateName = propAtom;
+ if (!noteDeclaredPrivateName(
+ propName, privateName, propType,
+ isStatic ? FieldPlacement::Static : FieldPlacement::Instance,
+ pos())) {
+ return false;
+ }
+ }
+
+#ifdef ENABLE_DECORATORS
+ if (propType == PropertyType::FieldWithAccessor) {
+ // Decorators Proposal
+ // https://arai-a.github.io/ecma262-compare/?pr=2417&id=sec-runtime-semantics-classfielddefinitionevaluation
+ //
+ // FieldDefinition : accessor ClassElementName Initializeropt
+ //
+ // Step 1. Let name be the result of evaluating ClassElementName.
+ // ...
+ // Step 3. Let privateStateDesc be the string-concatenation of name
+ // and " accessor storage".
+ StringBuffer privateStateDesc(fc_);
+ if (!privateStateDesc.append(this->parserAtoms(), propAtom)) {
+ return false;
+ }
+ if (!privateStateDesc.append(" accessor storage")) {
+ return false;
+ }
+ // Step 4. Let privateStateName be a new Private Name whose
+ // [[Description]] value is privateStateDesc.
+ TokenPos propNamePos(propNameOffset, pos().end);
+ auto privateStateName =
+ privateStateDesc.finishParserAtom(this->parserAtoms(), fc_);
+ if (!noteDeclaredPrivateName(
+ propName, privateStateName, propType,
+ isStatic ? FieldPlacement::Static : FieldPlacement::Instance,
+ propNamePos)) {
+ return false;
+ }
+
+ // Step 5. Let getter be MakeAutoAccessorGetter(homeObject, name,
+ // privateStateName).
+ Node method = synthesizeAccessor(
+ propName, propNamePos, propAtom, privateStateName, isStatic,
+ FunctionSyntaxKind::Getter, decorators, classInitializedMembers);
+ if (!method) {
+ return false;
+ }
+ if (!handler_.addClassMemberDefinition(classMembers, method)) {
+ return false;
+ }
+
+ // Step 6. Let setter be MakeAutoAccessorSetter(homeObject, name,
+ // privateStateName).
+ method = synthesizeAccessor(
+ propName, propNamePos, propAtom, privateStateName, isStatic,
+ FunctionSyntaxKind::Setter, decorators, classInitializedMembers);
+ if (!method) {
+ return false;
+ }
+ if (!handler_.addClassMemberDefinition(classMembers, method)) {
+ return false;
+ }
+
+ // Step 10. Return ClassElementDefinition Record { [[Key]]: name,
+ // [[Kind]]: accessor, [[Get]]: getter, [[Set]]: setter,
+ // [[BackingStorageKey]]: privateStateName, [[Initializers]]:
+ // initializers, [[Decorators]]: empty }.
+ propName = handler_.newPrivateName(privateStateName, pos());
+ propAtom = privateStateName;
+ decorators = handler_.newList(ParseNodeKind::DecoratorList, pos());
+ }
+#endif
+ if (isStatic) {
+ classInitializedMembers.staticFields++;
+ } else {
+ classInitializedMembers.instanceFields++;
+ }
+
+ TokenPos propNamePos(propNameOffset, pos().end);
+ FunctionNodeType initializer =
+ fieldInitializerOpt(propNamePos, propName, propAtom,
+ classInitializedMembers, isStatic, hasHeritage);
+ if (!initializer) {
+ return false;
+ }
+
+ if (!matchOrInsertSemicolon(TokenStream::SlashIsInvalid)) {
+ return false;
+ }
+
+ ClassFieldType field = handler_.newClassFieldDefinition(
+ propName, initializer, isStatic
+#ifdef ENABLE_DECORATORS
+ ,
+ decorators, propType == PropertyType::FieldWithAccessor
+#endif
+ );
+ if (!field) {
+ return false;
+ }
+
+ return handler_.addClassMemberDefinition(classMembers, field);
+ }
+
+ if (propType != PropertyType::Getter && propType != PropertyType::Setter &&
+ propType != PropertyType::Method &&
+ propType != PropertyType::GeneratorMethod &&
+ propType != PropertyType::AsyncMethod &&
+ propType != PropertyType::AsyncGeneratorMethod) {
+ errorAt(propNameOffset, JSMSG_BAD_METHOD_DEF);
+ return false;
+ }
+
+ bool isConstructor =
+ !isStatic && propAtom == TaggedParserAtomIndex::WellKnown::constructor();
+ if (isConstructor) {
+ if (propType != PropertyType::Method) {
+ errorAt(propNameOffset, JSMSG_BAD_METHOD_DEF);
+ return false;
+ }
+ if (classStmt.constructorBox) {
+ errorAt(propNameOffset, JSMSG_DUPLICATE_PROPERTY, "constructor");
+ return false;
+ }
+ propType = hasHeritage == HasHeritage::Yes
+ ? PropertyType::DerivedConstructor
+ : PropertyType::Constructor;
+ } else if (isStatic &&
+ propAtom == TaggedParserAtomIndex::WellKnown::prototype()) {
+ errorAt(propNameOffset, JSMSG_BAD_METHOD_DEF);
+ return false;
+ }
+
+ TaggedParserAtomIndex funName;
+ switch (propType) {
+ case PropertyType::Getter:
+ case PropertyType::Setter: {
+ bool hasStaticName =
+ !anyChars.isCurrentTokenType(TokenKind::RightBracket) && propAtom;
+ if (hasStaticName) {
+ funName = prefixAccessorName(propType, propAtom);
+ if (!funName) {
+ return false;
+ }
+ }
+ break;
+ }
+ case PropertyType::Constructor:
+ case PropertyType::DerivedConstructor:
+ funName = className;
+ break;
+ default:
+ if (!anyChars.isCurrentTokenType(TokenKind::RightBracket)) {
+ funName = propAtom;
+ }
+ }
+
+ // When |super()| is invoked, we search for the nearest scope containing
+ // |.initializers| to initialize the class fields. This set-up precludes
+ // declaring |.initializers| in the class scope, because in some syntactic
+ // contexts |super()| can appear nested in a class, while actually belonging
+ // to an outer class definition.
+ //
+ // Example:
+ // class Outer extends Base {
+ // field = 1;
+ // constructor() {
+ // class Inner {
+ // field = 2;
+ //
+ // // The super() call in the computed property name mustn't access
+ // // Inner's |.initializers| array, but instead Outer's.
+ // [super()]() {}
+ // }
+ // }
+ // }
+ Maybe<ParseContext::Scope> dotInitializersScope;
+ if (isConstructor && !options().selfHostingMode) {
+ dotInitializersScope.emplace(this);
+ if (!dotInitializersScope->init(pc_)) {
+ return false;
+ }
+
+ if (!noteDeclaredName(TaggedParserAtomIndex::WellKnown::dotInitializers(),
+ DeclarationKind::Let, pos())) {
+ return false;
+ }
+ }
+
+ // Calling toString on constructors need to return the source text for
+ // the entire class. The end offset is unknown at this point in
+ // parsing and will be amended when class parsing finishes below.
+ FunctionNodeType funNode = methodDefinition(
+ isConstructor ? classStartOffset : propNameOffset, propType, funName);
+ if (!funNode) {
+ return false;
+ }
+
+ AccessorType atype = ToAccessorType(propType);
+
+ Maybe<FunctionNodeType> initializerIfPrivate = Nothing();
+ if (handler_.isPrivateName(propName)) {
+ if (propAtom == TaggedParserAtomIndex::WellKnown::hashConstructor()) {
+ // #constructor is an invalid private name.
+ errorAt(propNameOffset, JSMSG_BAD_METHOD_DEF);
+ return false;
+ }
+
+ TaggedParserAtomIndex privateName = propAtom;
+ if (!noteDeclaredPrivateName(
+ propName, privateName, propType,
+ isStatic ? FieldPlacement::Static : FieldPlacement::Instance,
+ pos())) {
+ return false;
+ }
+
+ // Private non-static methods are stored in the class body environment.
+ // Private non-static accessors are stamped onto every instance using
+ // initializers. Private static methods are stamped onto the constructor
+ // during class evaluation; see BytecodeEmitter::emitPropertyList.
+ if (!isStatic) {
+ if (atype == AccessorType::Getter || atype == AccessorType::Setter) {
+ classInitializedMembers.privateAccessors++;
+ TokenPos propNamePos(propNameOffset, pos().end);
+ auto initializerNode =
+ synthesizePrivateMethodInitializer(propAtom, atype, propNamePos);
+ if (!initializerNode) {
+ return false;
+ }
+ initializerIfPrivate = Some(initializerNode);
+ } else {
+ MOZ_ASSERT(atype == AccessorType::None);
+ classInitializedMembers.privateMethods++;
+ }
+ }
+ }
+
+ Node method = handler_.newClassMethodDefinition(propName, funNode, atype,
+ isStatic, initializerIfPrivate
+#ifdef ENABLE_DECORATORS
+ ,
+ decorators
+#endif
+ );
+ if (!method) {
+ return false;
+ }
+
+ if (dotInitializersScope.isSome()) {
+ method = finishLexicalScope(*dotInitializersScope, method);
+ if (!method) {
+ return false;
+ }
+ dotInitializersScope.reset();
+ }
+
+ return handler_.addClassMemberDefinition(classMembers, method);
+}
+
+template <class ParseHandler, typename Unit>
+bool GeneralParser<ParseHandler, Unit>::finishClassConstructor(
+ const ParseContext::ClassStatement& classStmt,
+ TaggedParserAtomIndex className, HasHeritage hasHeritage,
+ uint32_t classStartOffset, uint32_t classEndOffset,
+ const ClassInitializedMembers& classInitializedMembers,
+ ListNodeType& classMembers) {
+ if (classStmt.constructorBox == nullptr) {
+ MOZ_ASSERT(!options().selfHostingMode);
+ // Unconditionally create the scope here, because it's always the
+ // constructor.
+ ParseContext::Scope dotInitializersScope(this);
+ if (!dotInitializersScope.init(pc_)) {
+ return false;
+ }
+
+ if (!noteDeclaredName(TaggedParserAtomIndex::WellKnown::dotInitializers(),
+ DeclarationKind::Let, pos())) {
+ return false;
+ }
+
+ // synthesizeConstructor assigns to classStmt.constructorBox
+ TokenPos synthesizedBodyPos(classStartOffset, classEndOffset);
+ FunctionNodeType synthesizedCtor =
+ synthesizeConstructor(className, synthesizedBodyPos, hasHeritage);
+ if (!synthesizedCtor) {
+ return false;
+ }
+
+ // Note: the *function* has the name of the class, but the *property*
+ // containing the function has the name "constructor"
+ Node constructorNameNode = handler_.newObjectLiteralPropertyName(
+ TaggedParserAtomIndex::WellKnown::constructor(), pos());
+ if (!constructorNameNode) {
+ return false;
+ }
+ ClassMethodType method = handler_.newDefaultClassConstructor(
+ constructorNameNode, synthesizedCtor);
+ if (!method) {
+ return false;
+ }
+ LexicalScopeNodeType scope =
+ finishLexicalScope(dotInitializersScope, method);
+ if (!scope) {
+ return false;
+ }
+ if (!handler_.addClassMemberDefinition(classMembers, scope)) {
+ return false;
+ }
+ }
+
+ MOZ_ASSERT(classStmt.constructorBox);
+ FunctionBox* ctorbox = classStmt.constructorBox;
+
+ // Amend the toStringEnd offset for the constructor now that we've
+ // finished parsing the class.
+ ctorbox->setCtorToStringEnd(classEndOffset);
+
+ size_t numMemberInitializers = classInitializedMembers.privateAccessors +
+ classInitializedMembers.instanceFields;
+ bool hasPrivateBrand = classInitializedMembers.hasPrivateBrand();
+ if (hasPrivateBrand || numMemberInitializers > 0) {
+ // Now that we have full set of initializers, update the constructor.
+ MemberInitializers initializers(hasPrivateBrand, numMemberInitializers);
+ ctorbox->setMemberInitializers(initializers);
+
+ // Field initialization need access to `this`.
+ ctorbox->setCtorFunctionHasThisBinding();
+ }
+
+ return true;
+}
+
+template <class ParseHandler, typename Unit>
+typename ParseHandler::ClassNodeType
+GeneralParser<ParseHandler, Unit>::classDefinition(
+ YieldHandling yieldHandling, ClassContext classContext,
+ DefaultHandling defaultHandling) {
+#ifdef ENABLE_DECORATORS
+ MOZ_ASSERT(anyChars.isCurrentTokenType(TokenKind::At) ||
+ anyChars.isCurrentTokenType(TokenKind::Class));
+
+ ListNodeType decorators = null();
+ if (anyChars.isCurrentTokenType(TokenKind::At)) {
+ decorators = decoratorList(yieldHandling);
+ if (!decorators) {
+ return null();
+ }
+ TokenKind next;
+ if (!tokenStream.getToken(&next)) {
+ return null();
+ }
+ if (next != TokenKind::Class) {
+ error(JSMSG_CLASS_EXPECTED);
+ return null();
+ }
+ }
+#else
+ MOZ_ASSERT(anyChars.isCurrentTokenType(TokenKind::Class));
+#endif
+
+ uint32_t classStartOffset = pos().begin;
+ bool savedStrictness = setLocalStrictMode(true);
+
+ TokenKind tt;
+ if (!tokenStream.getToken(&tt)) {
+ return null();
+ }
+
+ TaggedParserAtomIndex className;
+ if (TokenKindIsPossibleIdentifier(tt)) {
+ className = bindingIdentifier(yieldHandling);
+ if (!className) {
+ return null();
+ }
+ } else if (classContext == ClassStatement) {
+ if (defaultHandling == AllowDefaultName) {
+ className = TaggedParserAtomIndex::WellKnown::default_();
+ anyChars.ungetToken();
+ } else {
+ // Class statements must have a bound name
+ error(JSMSG_UNNAMED_CLASS_STMT);
+ return null();
+ }
+ } else {
+ // Make sure to put it back, whatever it was
+ anyChars.ungetToken();
+ }
+
+ // Because the binding definitions keep track of their blockId, we need to
+ // create at least the inner binding later. Keep track of the name's
+ // position in order to provide it for the nodes created later.
+ TokenPos namePos = pos();
+
+ bool isInClass = pc_->sc()->inClass();
+
+ // Push a ParseContext::ClassStatement to keep track of the constructor
+ // funbox.
+ ParseContext::ClassStatement classStmt(pc_);
+
+ NameNodeType innerName;
+ Node nameNode = null();
+ Node classHeritage = null();
+ LexicalScopeNodeType classBlock = null();
+ ClassBodyScopeNodeType classBodyBlock = null();
+ uint32_t classEndOffset;
+ {
+ // A named class creates a new lexical scope with a const binding of the
+ // class name for the "inner name".
+ ParseContext::Statement innerScopeStmt(pc_, StatementKind::Block);
+ ParseContext::Scope innerScope(this);
+ if (!innerScope.init(pc_)) {
+ return null();
+ }
+
+ bool hasHeritageBool;
+ if (!tokenStream.matchToken(&hasHeritageBool, TokenKind::Extends)) {
+ return null();
+ }
+ HasHeritage hasHeritage =
+ hasHeritageBool ? HasHeritage::Yes : HasHeritage::No;
+ if (hasHeritage == HasHeritage::Yes) {
+ if (!tokenStream.getToken(&tt)) {
+ return null();
+ }
+ classHeritage = optionalExpr(yieldHandling, TripledotProhibited, tt);
+ if (!classHeritage) {
+ return null();
+ }
+ }
+
+ if (!mustMatchToken(TokenKind::LeftCurly, JSMSG_CURLY_BEFORE_CLASS)) {
+ return null();
+ }
+
+ {
+ ParseContext::Statement bodyScopeStmt(pc_, StatementKind::Block);
+ ParseContext::Scope bodyScope(this);
+ if (!bodyScope.init(pc_)) {
+ return null();
+ }
+
+ ListNodeType classMembers = handler_.newClassMemberList(pos().begin);
+ if (!classMembers) {
+ return null();
+ }
+
+ ClassInitializedMembers classInitializedMembers{};
+ for (;;) {
+ bool done;
+ if (!classMember(yieldHandling, classStmt, className, classStartOffset,
+ hasHeritage, classInitializedMembers, classMembers,
+ &done)) {
+ return null();
+ }
+ if (done) {
+ break;
+ }
+ }
+
+ if (classInitializedMembers.privateMethods +
+ classInitializedMembers.privateAccessors >
+ 0) {
+ // We declare `.privateBrand` as ClosedOver because the constructor
+ // always uses it, even a default constructor. We could equivalently
+ // `noteNameUsed` when parsing the constructor, except that at that
+ // time, we don't necessarily know if the class has a private brand.
+ if (!noteDeclaredName(
+ TaggedParserAtomIndex::WellKnown::dotPrivateBrand(),
+ DeclarationKind::Synthetic, namePos, ClosedOver::Yes)) {
+ return null();
+ }
+ }
+
+ if (classInitializedMembers.instanceFieldKeys > 0) {
+ if (!noteDeclaredName(TaggedParserAtomIndex::WellKnown::dotFieldKeys(),
+ DeclarationKind::Synthetic, namePos)) {
+ return null();
+ }
+ }
+
+ if (classInitializedMembers.staticFields > 0) {
+ if (!noteDeclaredName(
+ TaggedParserAtomIndex::WellKnown::dotStaticInitializers(),
+ DeclarationKind::Synthetic, namePos)) {
+ return null();
+ }
+ }
+
+ if (classInitializedMembers.staticFieldKeys > 0) {
+ if (!noteDeclaredName(
+ TaggedParserAtomIndex::WellKnown::dotStaticFieldKeys(),
+ DeclarationKind::Synthetic, namePos)) {
+ return null();
+ }
+ }
+
+ classEndOffset = pos().end;
+ if (!finishClassConstructor(classStmt, className, hasHeritage,
+ classStartOffset, classEndOffset,
+ classInitializedMembers, classMembers)) {
+ return null();
+ }
+
+ classBodyBlock = finishClassBodyScope(bodyScope, classMembers);
+ if (!classBodyBlock) {
+ return null();
+ }
+
+ // Pop the class body scope
+ }
+
+ if (className) {
+ // The inner name is immutable.
+ if (!noteDeclaredName(className, DeclarationKind::Const, namePos)) {
+ return null();
+ }
+
+ innerName = newName(className, namePos);
+ if (!innerName) {
+ return null();
+ }
+ }
+
+ classBlock = finishLexicalScope(innerScope, classBodyBlock);
+ if (!classBlock) {
+ return null();
+ }
+
+ // Pop the inner scope.
+ }
+
+ if (className) {
+ NameNodeType outerName = null();
+ if (classContext == ClassStatement) {
+ // The outer name is mutable.
+ if (!noteDeclaredName(className, DeclarationKind::Class, namePos)) {
+ return null();
+ }
+
+ outerName = newName(className, namePos);
+ if (!outerName) {
+ return null();
+ }
+ }
+
+ nameNode = handler_.newClassNames(outerName, innerName, namePos);
+ if (!nameNode) {
+ return null();
+ }
+ }
+ MOZ_ALWAYS_TRUE(setLocalStrictMode(savedStrictness));
+ // We're leaving a class definition that was not itself nested within a class
+ if (!isInClass) {
+ mozilla::Maybe<UnboundPrivateName> maybeUnboundName;
+ if (!usedNames_.hasUnboundPrivateNames(fc_, maybeUnboundName)) {
+ return null();
+ }
+ if (maybeUnboundName) {
+ UniqueChars str =
+ this->parserAtoms().toPrintableString(maybeUnboundName->atom);
+ if (!str) {
+ ReportOutOfMemory(this->fc_);
+ return null();
+ }
+
+ errorAt(maybeUnboundName->position.begin, JSMSG_MISSING_PRIVATE_DECL,
+ str.get());
+ return null();
+ }
+ }
+
+ return handler_.newClass(nameNode, classHeritage, classBlock,
+#ifdef ENABLE_DECORATORS
+ decorators,
+#endif
+ TokenPos(classStartOffset, classEndOffset));
+}
+
+template <class ParseHandler, typename Unit>
+typename ParseHandler::FunctionNodeType
+GeneralParser<ParseHandler, Unit>::synthesizeConstructor(
+ TaggedParserAtomIndex className, TokenPos synthesizedBodyPos,
+ HasHeritage hasHeritage) {
+ FunctionSyntaxKind functionSyntaxKind =
+ hasHeritage == HasHeritage::Yes
+ ? FunctionSyntaxKind::DerivedClassConstructor
+ : FunctionSyntaxKind::ClassConstructor;
+
+ bool isSelfHosting = options().selfHostingMode;
+ FunctionFlags flags =
+ InitialFunctionFlags(functionSyntaxKind, GeneratorKind::NotGenerator,
+ FunctionAsyncKind::SyncFunction, isSelfHosting);
+
+ // Create the top-level field initializer node.
+ FunctionNodeType funNode =
+ handler_.newFunction(functionSyntaxKind, synthesizedBodyPos);
+ if (!funNode) {
+ return null();
+ }
+
+ // If we see any inner function, note it on our current context. The bytecode
+ // emitter may eliminate the function later, but we use a conservative
+ // definition for consistency between lazy and full parsing.
+ pc_->sc()->setHasInnerFunctions();
+
+ // When fully parsing a lazy script, we do not fully reparse its inner
+ // functions, which are also lazy. Instead, their free variables and source
+ // extents are recorded and may be skipped.
+ if (handler_.reuseLazyInnerFunctions()) {
+ if (!skipLazyInnerFunction(funNode, synthesizedBodyPos.begin,
+ /* tryAnnexB = */ false)) {
+ return null();
+ }
+
+ return funNode;
+ }
+
+ // Create the FunctionBox and link it to the function object.
+ Directives directives(true);
+ FunctionBox* funbox = newFunctionBox(
+ funNode, className, flags, synthesizedBodyPos.begin, directives,
+ GeneratorKind::NotGenerator, FunctionAsyncKind::SyncFunction);
+ if (!funbox) {
+ return null();
+ }
+ funbox->initWithEnclosingParseContext(pc_, functionSyntaxKind);
+ setFunctionEndFromCurrentToken(funbox);
+
+ // Mark this function as being synthesized by the parser. This means special
+ // handling in delazification will be used since we don't have typical
+ // function syntax.
+ funbox->setSyntheticFunction();
+
+ // Push a SourceParseContext on to the stack.
+ ParseContext* outerpc = pc_;
+ SourceParseContext funpc(this, funbox, /* newDirectives = */ nullptr);
+ if (!funpc.init()) {
+ return null();
+ }
+
+ if (!synthesizeConstructorBody(synthesizedBodyPos, hasHeritage, funNode,
+ funbox)) {
+ return null();
+ }
+
+ if (!leaveInnerFunction(outerpc)) {
+ return null();
+ }
+
+ return funNode;
+}
+
+template <class ParseHandler, typename Unit>
+typename ParseHandler::FunctionNodeType
+GeneralParser<ParseHandler, Unit>::synthesizeConstructorBody(
+ TokenPos synthesizedBodyPos, HasHeritage hasHeritage,
+ FunctionNodeType funNode, FunctionBox* funbox) {
+ MOZ_ASSERT(funbox->isClassConstructor());
+
+ // Create a ParamsBodyNode for the parameters + body (there are no
+ // parameters).
+ ParamsBodyNodeType argsbody = handler_.newParamsBody(synthesizedBodyPos);
+ if (!argsbody) {
+ return null();
+ }
+ handler_.setFunctionFormalParametersAndBody(funNode, argsbody);
+ setFunctionStartAtPosition(funbox, synthesizedBodyPos);
+
+ if (hasHeritage == HasHeritage::Yes) {
+ // Synthesize the equivalent to `function f(...args)`
+ funbox->setHasRest();
+ if (!notePositionalFormalParameter(
+ funNode, TaggedParserAtomIndex::WellKnown::dotArgs(),
+ synthesizedBodyPos.begin,
+ /* disallowDuplicateParams = */ false,
+ /* duplicatedParam = */ nullptr)) {
+ return null();
+ }
+ funbox->setArgCount(1);
+ } else {
+ funbox->setArgCount(0);
+ }
+
+ pc_->functionScope().useAsVarScope(pc_);
+
+ auto stmtList = handler_.newStatementList(synthesizedBodyPos);
+ if (!stmtList) {
+ return null();
+ }
+
+ if (!noteUsedName(TaggedParserAtomIndex::WellKnown::dotThis())) {
+ return null();
+ }
+
+ if (!noteUsedName(TaggedParserAtomIndex::WellKnown::dotInitializers())) {
+ return null();
+ }
+
+ if (hasHeritage == HasHeritage::Yes) {
+ // |super()| implicitly reads |new.target|.
+ if (!noteUsedName(TaggedParserAtomIndex::WellKnown::dotNewTarget())) {
+ return null();
+ }
+
+ NameNodeType thisName = newThisName();
+ if (!thisName) {
+ return null();
+ }
+
+ UnaryNodeType superBase =
+ handler_.newSuperBase(thisName, synthesizedBodyPos);
+ if (!superBase) {
+ return null();
+ }
+
+ ListNodeType arguments = handler_.newArguments(synthesizedBodyPos);
+ if (!arguments) {
+ return null();
+ }
+
+ NameNodeType argsNameNode = newName(
+ TaggedParserAtomIndex::WellKnown::dotArgs(), synthesizedBodyPos);
+ if (!argsNameNode) {
+ return null();
+ }
+ if (!noteUsedName(TaggedParserAtomIndex::WellKnown::dotArgs())) {
+ return null();
+ }
+
+ UnaryNodeType spreadArgs =
+ handler_.newSpread(synthesizedBodyPos.begin, argsNameNode);
+ if (!spreadArgs) {
+ return null();
+ }
+ handler_.addList(arguments, spreadArgs);
+
+ CallNodeType superCall =
+ handler_.newSuperCall(superBase, arguments, /* isSpread = */ true);
+ if (!superCall) {
+ return null();
+ }
+
+ BinaryNodeType setThis = handler_.newSetThis(thisName, superCall);
+ if (!setThis) {
+ return null();
+ }
+
+ UnaryNodeType exprStatement =
+ handler_.newExprStatement(setThis, synthesizedBodyPos.end);
+ if (!exprStatement) {
+ return null();
+ }
+
+ handler_.addStatementToList(stmtList, exprStatement);
+ }
+
+ bool canSkipLazyClosedOverBindings = handler_.reuseClosedOverBindings();
+ if (!pc_->declareFunctionThis(usedNames_, canSkipLazyClosedOverBindings)) {
+ return null();
+ }
+ if (!pc_->declareNewTarget(usedNames_, canSkipLazyClosedOverBindings)) {
+ return null();
+ }
+
+ auto initializerBody =
+ finishLexicalScope(pc_->varScope(), stmtList, ScopeKind::FunctionLexical);
+ if (!initializerBody) {
+ return null();
+ }
+ handler_.setBeginPosition(initializerBody, stmtList);
+ handler_.setEndPosition(initializerBody, stmtList);
+
+ handler_.setFunctionBody(funNode, initializerBody);
+
+ if (!finishFunction()) {
+ return null();
+ }
+
+ return funNode;
+}
+
+template <class ParseHandler, typename Unit>
+typename ParseHandler::FunctionNodeType
+GeneralParser<ParseHandler, Unit>::privateMethodInitializer(
+ TokenPos propNamePos, TaggedParserAtomIndex propAtom,
+ TaggedParserAtomIndex storedMethodAtom) {
+ if (!abortIfSyntaxParser()) {
+ return null();
+ }
+
+ // Synthesize an initializer function that the constructor can use to stamp a
+ // private method onto an instance object.
+ FunctionSyntaxKind syntaxKind = FunctionSyntaxKind::FieldInitializer;
+ FunctionAsyncKind asyncKind = FunctionAsyncKind::SyncFunction;
+ GeneratorKind generatorKind = GeneratorKind::NotGenerator;
+ bool isSelfHosting = options().selfHostingMode;
+ FunctionFlags flags =
+ InitialFunctionFlags(syntaxKind, generatorKind, asyncKind, isSelfHosting);
+
+ FunctionNodeType funNode = handler_.newFunction(syntaxKind, propNamePos);
+ if (!funNode) {
+ return null();
+ }
+
+ Directives directives(true);
+ FunctionBox* funbox =
+ newFunctionBox(funNode, TaggedParserAtomIndex::null(), flags,
+ propNamePos.begin, directives, generatorKind, asyncKind);
+ if (!funbox) {
+ return null();
+ }
+ funbox->initWithEnclosingParseContext(pc_, syntaxKind);
+
+ // Push a SourceParseContext on to the stack.
+ ParseContext* outerpc = pc_;
+ SourceParseContext funpc(this, funbox, /* newDirectives = */ nullptr);
+ if (!funpc.init()) {
+ return null();
+ }
+ pc_->functionScope().useAsVarScope(pc_);
+
+ // Add empty parameter list.
+ ParamsBodyNodeType argsbody = handler_.newParamsBody(propNamePos);
+ if (!argsbody) {
+ return null();
+ }
+ handler_.setFunctionFormalParametersAndBody(funNode, argsbody);
+ setFunctionStartAtCurrentToken(funbox);
+ funbox->setArgCount(0);
+
+ // Note both the stored private method body and it's private name as being
+ // used in the initializer. They will be emitted into the method body in the
+ // BCE.
+ if (!noteUsedName(storedMethodAtom)) {
+ return null();
+ }
+ NameNodeType privateNameNode = privateNameReference(propAtom);
+ if (!privateNameNode) {
+ return null();
+ }
+
+ // Unlike field initializers, private method initializers are not created with
+ // a body of synthesized AST nodes. Instead, the body is left empty and the
+ // initializer is synthesized at the bytecode level.
+ // See BytecodeEmitter::emitPrivateMethodInitializer.
+ ListNodeType stmtList = handler_.newStatementList(propNamePos);
+ if (!stmtList) {
+ return null();
+ }
+
+ bool canSkipLazyClosedOverBindings = handler_.reuseClosedOverBindings();
+ if (!pc_->declareFunctionThis(usedNames_, canSkipLazyClosedOverBindings)) {
+ return null();
+ }
+ if (!pc_->declareNewTarget(usedNames_, canSkipLazyClosedOverBindings)) {
+ return null();
+ }
+
+ LexicalScopeNodeType initializerBody =
+ finishLexicalScope(pc_->varScope(), stmtList, ScopeKind::FunctionLexical);
+ if (!initializerBody) {
+ return null();
+ }
+ handler_.setBeginPosition(initializerBody, stmtList);
+ handler_.setEndPosition(initializerBody, stmtList);
+ handler_.setFunctionBody(funNode, initializerBody);
+
+ // Set field-initializer lambda boundary to start at property name and end
+ // after method body.
+ setFunctionStartAtPosition(funbox, propNamePos);
+ setFunctionEndFromCurrentToken(funbox);
+
+ if (!finishFunction()) {
+ return null();
+ }
+
+ if (!leaveInnerFunction(outerpc)) {
+ return null();
+ }
+
+ return funNode;
+}
+
+template <class ParseHandler, typename Unit>
+typename ParseHandler::FunctionNodeType
+GeneralParser<ParseHandler, Unit>::staticClassBlock(
+ ClassInitializedMembers& classInitializedMembers) {
+ // Both for getting-this-done, and because this will invariably be executed,
+ // syntax parsing should be aborted.
+ if (!abortIfSyntaxParser()) {
+ return null();
+ }
+
+ FunctionSyntaxKind syntaxKind = FunctionSyntaxKind::StaticClassBlock;
+ FunctionAsyncKind asyncKind = FunctionAsyncKind::SyncFunction;
+ GeneratorKind generatorKind = GeneratorKind::NotGenerator;
+ bool isSelfHosting = options().selfHostingMode;
+ FunctionFlags flags =
+ InitialFunctionFlags(syntaxKind, generatorKind, asyncKind, isSelfHosting);
+
+ AutoAwaitIsKeyword awaitIsKeyword(this, AwaitHandling::AwaitIsDisallowed);
+
+ // Create the function node for the static class body.
+ FunctionNodeType funNode = handler_.newFunction(syntaxKind, pos());
+ if (!funNode) {
+ return null();
+ }
+
+ // Create the FunctionBox and link it to the function object.
+ Directives directives(true);
+ FunctionBox* funbox =
+ newFunctionBox(funNode, TaggedParserAtomIndex::null(), flags, pos().begin,
+ directives, generatorKind, asyncKind);
+ if (!funbox) {
+ return null();
+ }
+ funbox->initWithEnclosingParseContext(pc_, syntaxKind);
+ MOZ_ASSERT(funbox->isSyntheticFunction());
+ MOZ_ASSERT(!funbox->allowSuperCall());
+ MOZ_ASSERT(!funbox->allowArguments());
+ MOZ_ASSERT(!funbox->allowReturn());
+
+ // Set start at `static` token.
+ MOZ_ASSERT(anyChars.isCurrentTokenType(TokenKind::Static));
+ setFunctionStartAtCurrentToken(funbox);
+
+ // Push a SourceParseContext on to the stack.
+ ParseContext* outerpc = pc_;
+ SourceParseContext funpc(this, funbox, /* newDirectives = */ nullptr);
+ if (!funpc.init()) {
+ return null();
+ }
+
+ pc_->functionScope().useAsVarScope(pc_);
+
+ uint32_t start = pos().begin;
+
+ tokenStream.consumeKnownToken(TokenKind::LeftCurly);
+
+ // Static class blocks are code-generated as if they were static field
+ // initializers, so we bump the staticFields count here, which ensures
+ // .staticInitializers is noted as used.
+ classInitializedMembers.staticFields++;
+
+ LexicalScopeNodeType body =
+ functionBody(InHandling::InAllowed, YieldHandling::YieldIsKeyword,
+ syntaxKind, FunctionBodyType::StatementListBody);
+ if (!body) {
+ return null();
+ }
+
+ if (anyChars.isEOF()) {
+ error(JSMSG_UNTERMINATED_STATIC_CLASS_BLOCK);
+ return null();
+ }
+
+ tokenStream.consumeKnownToken(TokenKind::RightCurly,
+ TokenStream::Modifier::SlashIsRegExp);
+
+ TokenPos wholeBodyPos(start, pos().end);
+
+ handler_.setEndPosition(funNode, wholeBodyPos.end);
+ setFunctionEndFromCurrentToken(funbox);
+
+ // Create a ParamsBodyNode for the parameters + body (there are no
+ // parameters).
+ ParamsBodyNodeType argsbody = handler_.newParamsBody(wholeBodyPos);
+ if (!argsbody) {
+ return null();
+ }
+
+ handler_.setFunctionFormalParametersAndBody(funNode, argsbody);
+ funbox->setArgCount(0);
+
+ if (pc_->superScopeNeedsHomeObject()) {
+ funbox->setNeedsHomeObject();
+ }
+
+ handler_.setEndPosition(body, pos().begin);
+ handler_.setEndPosition(funNode, pos().end);
+ handler_.setFunctionBody(funNode, body);
+
+ if (!finishFunction()) {
+ return null();
+ }
+
+ if (!leaveInnerFunction(outerpc)) {
+ return null();
+ }
+
+ return funNode;
+}
+
+template <class ParseHandler, typename Unit>
+typename ParseHandler::FunctionNodeType
+GeneralParser<ParseHandler, Unit>::fieldInitializerOpt(
+ TokenPos propNamePos, Node propName, TaggedParserAtomIndex propAtom,
+ ClassInitializedMembers& classInitializedMembers, bool isStatic,
+ HasHeritage hasHeritage) {
+ if (!abortIfSyntaxParser()) {
+ return null();
+ }
+
+ bool hasInitializer = false;
+ if (!tokenStream.matchToken(&hasInitializer, TokenKind::Assign,
+ TokenStream::SlashIsDiv)) {
+ return null();
+ }
+
+ FunctionSyntaxKind syntaxKind = FunctionSyntaxKind::FieldInitializer;
+ FunctionAsyncKind asyncKind = FunctionAsyncKind::SyncFunction;
+ GeneratorKind generatorKind = GeneratorKind::NotGenerator;
+ bool isSelfHosting = options().selfHostingMode;
+ FunctionFlags flags =
+ InitialFunctionFlags(syntaxKind, generatorKind, asyncKind, isSelfHosting);
+
+ // Create the top-level field initializer node.
+ FunctionNodeType funNode = handler_.newFunction(syntaxKind, propNamePos);
+ if (!funNode) {
+ return null();
+ }
+
+ // Create the FunctionBox and link it to the function object.
+ Directives directives(true);
+ FunctionBox* funbox =
+ newFunctionBox(funNode, TaggedParserAtomIndex::null(), flags,
+ propNamePos.begin, directives, generatorKind, asyncKind);
+ if (!funbox) {
+ return null();
+ }
+ funbox->initWithEnclosingParseContext(pc_, syntaxKind);
+ MOZ_ASSERT(funbox->isSyntheticFunction());
+
+ // We can't use setFunctionStartAtCurrentToken because that uses pos().begin,
+ // which is incorrect for fields without initializers (pos() points to the
+ // field identifier)
+ setFunctionStartAtPosition(funbox, propNamePos);
+
+ // Push a SourceParseContext on to the stack.
+ ParseContext* outerpc = pc_;
+ SourceParseContext funpc(this, funbox, /* newDirectives = */ nullptr);
+ if (!funpc.init()) {
+ return null();
+ }
+
+ pc_->functionScope().useAsVarScope(pc_);
+
+ Node initializerExpr;
+ if (hasInitializer) {
+ // Parse the expression for the field initializer.
+ {
+ AutoAwaitIsKeyword awaitHandling(this, AwaitIsName);
+ initializerExpr = assignExpr(InAllowed, YieldIsName, TripledotProhibited);
+ if (!initializerExpr) {
+ return null();
+ }
+ }
+
+ handler_.checkAndSetIsDirectRHSAnonFunction(initializerExpr);
+ } else {
+ initializerExpr = handler_.newRawUndefinedLiteral(propNamePos);
+ if (!initializerExpr) {
+ return null();
+ }
+ }
+
+ TokenPos wholeInitializerPos(propNamePos.begin, pos().end);
+
+ // Update the end position of the parse node.
+ handler_.setEndPosition(funNode, wholeInitializerPos.end);
+ setFunctionEndFromCurrentToken(funbox);
+
+ // Create a ParamsBodyNode for the parameters + body (there are no
+ // parameters).
+ ParamsBodyNodeType argsbody = handler_.newParamsBody(wholeInitializerPos);
+ if (!argsbody) {
+ return null();
+ }
+ handler_.setFunctionFormalParametersAndBody(funNode, argsbody);
+ funbox->setArgCount(0);
+
+ NameNodeType thisName = newThisName();
+ if (!thisName) {
+ return null();
+ }
+
+ // Build `this.field` expression.
+ ThisLiteralType propAssignThis =
+ handler_.newThisLiteral(wholeInitializerPos, thisName);
+ if (!propAssignThis) {
+ return null();
+ }
+
+ Node propAssignFieldAccess;
+ uint32_t indexValue;
+ if (!propAtom) {
+ // See BytecodeEmitter::emitCreateFieldKeys for an explanation of what
+ // .fieldKeys means and its purpose.
+ NameNodeType fieldKeysName;
+ if (isStatic) {
+ fieldKeysName = newInternalDotName(
+ TaggedParserAtomIndex::WellKnown::dotStaticFieldKeys());
+ } else {
+ fieldKeysName =
+ newInternalDotName(TaggedParserAtomIndex::WellKnown::dotFieldKeys());
+ }
+ if (!fieldKeysName) {
+ return null();
+ }
+
+ double fieldKeyIndex;
+ if (isStatic) {
+ fieldKeyIndex = classInitializedMembers.staticFieldKeys++;
+ } else {
+ fieldKeyIndex = classInitializedMembers.instanceFieldKeys++;
+ }
+ Node fieldKeyIndexNode = handler_.newNumber(
+ fieldKeyIndex, DecimalPoint::NoDecimal, wholeInitializerPos);
+ if (!fieldKeyIndexNode) {
+ return null();
+ }
+
+ Node fieldKeyValue = handler_.newPropertyByValue(
+ fieldKeysName, fieldKeyIndexNode, wholeInitializerPos.end);
+ if (!fieldKeyValue) {
+ return null();
+ }
+
+ propAssignFieldAccess = handler_.newPropertyByValue(
+ propAssignThis, fieldKeyValue, wholeInitializerPos.end);
+ if (!propAssignFieldAccess) {
+ return null();
+ }
+ } else if (handler_.isPrivateName(propName)) {
+ // It would be nice if we could tweak this here such that only if
+ // HasHeritage::Yes we end up emitting CheckPrivateField, but otherwise we
+ // emit InitElem -- this is an optimization to minimize HasOwn checks
+ // in InitElem for classes without heritage.
+ //
+ // Further tweaking would be to ultimately only do CheckPrivateField for the
+ // -first- field in a derived class, which would suffice to match the
+ // semantic check.
+
+ NameNodeType privateNameNode = privateNameReference(propAtom);
+ if (!privateNameNode) {
+ return null();
+ }
+
+ propAssignFieldAccess = handler_.newPrivateMemberAccess(
+ propAssignThis, privateNameNode, wholeInitializerPos.end);
+ if (!propAssignFieldAccess) {
+ return null();
+ }
+ } else if (this->parserAtoms().isIndex(propAtom, &indexValue)) {
+ propAssignFieldAccess = handler_.newPropertyByValue(
+ propAssignThis, propName, wholeInitializerPos.end);
+ if (!propAssignFieldAccess) {
+ return null();
+ }
+ } else {
+ NameNodeType propAssignName =
+ handler_.newPropertyName(propAtom, wholeInitializerPos);
+ if (!propAssignName) {
+ return null();
+ }
+
+ propAssignFieldAccess =
+ handler_.newPropertyAccess(propAssignThis, propAssignName);
+ if (!propAssignFieldAccess) {
+ return null();
+ }
+ }
+
+ // Synthesize an property init.
+ BinaryNodeType initializerPropInit =
+ handler_.newInitExpr(propAssignFieldAccess, initializerExpr);
+ if (!initializerPropInit) {
+ return null();
+ }
+
+ UnaryNodeType exprStatement =
+ handler_.newExprStatement(initializerPropInit, wholeInitializerPos.end);
+ if (!exprStatement) {
+ return null();
+ }
+
+ ListNodeType statementList = handler_.newStatementList(wholeInitializerPos);
+ if (!statementList) {
+ return null();
+ }
+ handler_.addStatementToList(statementList, exprStatement);
+
+ bool canSkipLazyClosedOverBindings = handler_.reuseClosedOverBindings();
+ if (!pc_->declareFunctionThis(usedNames_, canSkipLazyClosedOverBindings)) {
+ return null();
+ }
+ if (!pc_->declareNewTarget(usedNames_, canSkipLazyClosedOverBindings)) {
+ return null();
+ }
+
+ // Set the function's body to the field assignment.
+ LexicalScopeNodeType initializerBody = finishLexicalScope(
+ pc_->varScope(), statementList, ScopeKind::FunctionLexical);
+ if (!initializerBody) {
+ return null();
+ }
+
+ handler_.setFunctionBody(funNode, initializerBody);
+
+ if (pc_->superScopeNeedsHomeObject()) {
+ funbox->setNeedsHomeObject();
+ }
+
+ if (!finishFunction()) {
+ return null();
+ }
+
+ if (!leaveInnerFunction(outerpc)) {
+ return null();
+ }
+
+ return funNode;
+}
+
+template <class ParseHandler, typename Unit>
+typename ParseHandler::FunctionNodeType
+GeneralParser<ParseHandler, Unit>::synthesizePrivateMethodInitializer(
+ TaggedParserAtomIndex propAtom, AccessorType accessorType,
+ TokenPos propNamePos) {
+ if (!abortIfSyntaxParser()) {
+ return null();
+ }
+
+ // Synthesize a name for the lexical variable that will store the
+ // accessor body.
+ StringBuffer storedMethodName(fc_);
+ if (!storedMethodName.append(this->parserAtoms(), propAtom)) {
+ return null();
+ }
+ if (!storedMethodName.append(
+ accessorType == AccessorType::Getter ? ".getter" : ".setter")) {
+ return null();
+ }
+ auto storedMethodProp =
+ storedMethodName.finishParserAtom(this->parserAtoms(), fc_);
+ if (!storedMethodProp) {
+ return null();
+ }
+ if (!noteDeclaredName(storedMethodProp, DeclarationKind::Synthetic, pos())) {
+ return null();
+ }
+
+ return privateMethodInitializer(propNamePos, propAtom, storedMethodProp);
+}
+
+#ifdef ENABLE_DECORATORS
+
+template <class ParseHandler, typename Unit>
+typename ParseHandler::Node
+GeneralParser<ParseHandler, Unit>::synthesizeAccessor(
+ Node propName, TokenPos propNamePos, TaggedParserAtomIndex propAtom,
+ TaggedParserAtomIndex privateStateNameAtom, bool isStatic,
+ FunctionSyntaxKind syntaxKind, ListNodeType decorators,
+ ClassInitializedMembers& classInitializedMembers) {
+ // Decorators Proposal
+ // https://arai-a.github.io/ecma262-compare/?pr=2417&id=sec-makeautoaccessorgetter
+ // The abstract operation MakeAutoAccessorGetter takes arguments homeObject
+ // (an Object), name (a property key or Private Name), and privateStateName (a
+ // Private Name) and returns a function object.
+ //
+ // https://arai-a.github.io/ecma262-compare/?pr=2417&id=sec-makeautoaccessorsetter
+ // The abstract operation MakeAutoAccessorSetter takes arguments homeObject
+ // (an Object), name (a property key or Private Name), and privateStateName (a
+ // Private Name) and returns a function object.
+ if (!abortIfSyntaxParser()) {
+ return null();
+ }
+
+ AccessorType accessorType = syntaxKind == FunctionSyntaxKind::Getter
+ ? AccessorType::Getter
+ : AccessorType::Setter;
+
+ mozilla::Maybe<FunctionNodeType> initializerIfPrivate = Nothing();
+ if (handler_.isPrivateName(propName)) {
+ classInitializedMembers.privateAccessors++;
+ auto initializerNode =
+ synthesizePrivateMethodInitializer(propAtom, accessorType, propNamePos);
+ if (!initializerNode) {
+ return null();
+ }
+ initializerIfPrivate = Some(initializerNode);
+ handler_.setPrivateNameKind(propName, PrivateNameKind::GetterSetter);
+ }
+
+ // https://arai-a.github.io/ecma262-compare/?pr=2417&id=sec-makeautoaccessorgetter
+ // 2. Let getter be CreateBuiltinFunction(getterClosure, 0, "get", « »).
+ //
+ // https://arai-a.github.io/ecma262-compare/?pr=2417&id=sec-makeautoaccessorsetter
+ // 2. Let setter be CreateBuiltinFunction(setterClosure, 1, "set", « »).
+ FunctionNodeType funNode =
+ synthesizeAccessorBody(propNamePos, privateStateNameAtom, syntaxKind);
+ if (!funNode) {
+ return null();
+ }
+
+ // https://arai-a.github.io/ecma262-compare/?pr=2417&id=sec-makeautoaccessorgetter
+ // 3. Perform MakeMethod(getter, homeObject).
+ // 4. Return getter.
+ //
+ // https://arai-a.github.io/ecma262-compare/?pr=2417&id=sec-makeautoaccessorsetter
+ // 3. Perform MakeMethod(setter, homeObject).
+ // 4. Return setter.
+ return handler_.newClassMethodDefinition(propName, funNode, accessorType,
+ isStatic, initializerIfPrivate,
+ decorators);
+}
+
+template <class ParseHandler, typename Unit>
+typename ParseHandler::FunctionNodeType
+GeneralParser<ParseHandler, Unit>::synthesizeAccessorBody(
+ TokenPos propNamePos, TaggedParserAtomIndex propAtom,
+ FunctionSyntaxKind syntaxKind) {
+ if (!abortIfSyntaxParser()) {
+ return null();
+ }
+
+ FunctionAsyncKind asyncKind = FunctionAsyncKind::SyncFunction;
+ GeneratorKind generatorKind = GeneratorKind::NotGenerator;
+ bool isSelfHosting = options().selfHostingMode;
+ FunctionFlags flags =
+ InitialFunctionFlags(syntaxKind, generatorKind, asyncKind, isSelfHosting);
+
+ // Create the top-level function node.
+ FunctionNodeType funNode = handler_.newFunction(syntaxKind, propNamePos);
+ if (!funNode) {
+ return null();
+ }
+
+ // Create the FunctionBox and link it to the function object.
+ Directives directives(true);
+ FunctionBox* funbox =
+ newFunctionBox(funNode, TaggedParserAtomIndex::null(), flags,
+ propNamePos.begin, directives, generatorKind, asyncKind);
+ if (!funbox) {
+ return null();
+ }
+ funbox->initWithEnclosingParseContext(pc_, syntaxKind);
+ funbox->setSyntheticFunction();
+
+ // Push a SourceParseContext on to the stack.
+ ParseContext* outerpc = pc_;
+ SourceParseContext funpc(this, funbox, /* newDirectives = */ nullptr);
+ if (!funpc.init()) {
+ return null();
+ }
+
+ pc_->functionScope().useAsVarScope(pc_);
+
+ // The function we synthesize is located at the field with the
+ // accessor.
+ setFunctionStartAtCurrentToken(funbox);
+ setFunctionEndFromCurrentToken(funbox);
+
+ // Create a ListNode for the parameters + body
+ ParamsBodyNodeType paramsbody = handler_.newParamsBody(propNamePos);
+ if (!paramsbody) {
+ return null();
+ }
+ handler_.setFunctionFormalParametersAndBody(funNode, paramsbody);
+
+ if (syntaxKind == FunctionSyntaxKind::Getter) {
+ funbox->setArgCount(0);
+ } else {
+ funbox->setArgCount(1);
+ }
+
+ // Build `this` expression to access the privateStateName for use in the
+ // operations to create the getter and setter below.
+ NameNodeType thisName = newThisName();
+ if (!thisName) {
+ return null();
+ }
+
+ ThisLiteralType propThis = handler_.newThisLiteral(propNamePos, thisName);
+ if (!propThis) {
+ return null();
+ }
+
+ NameNodeType privateNameNode = privateNameReference(propAtom);
+ if (!privateNameNode) {
+ return null();
+ }
+
+ Node propFieldAccess = handler_.newPrivateMemberAccess(
+ propThis, privateNameNode, propNamePos.end);
+ if (!propFieldAccess) {
+ return null();
+ }
+
+ Node accessorBody;
+ if (syntaxKind == FunctionSyntaxKind::Getter) {
+ // Decorators Proposal
+ // https://arai-a.github.io/ecma262-compare/?pr=2417&id=sec-makeautoaccessorgetter
+ // 1. Let getterClosure be a new Abstract Closure with no parameters that
+ // captures privateStateName and performs the following steps when called:
+ // 1.a. Let o be the this value.
+ // 1.b. Return ? PrivateGet(privateStateName, o).
+ accessorBody = handler_.newReturnStatement(propFieldAccess, propNamePos);
+ } else {
+ // Decorators Proposal
+ // https://arai-a.github.io/ecma262-compare/?pr=2417&id=sec-makeautoaccessorsetter
+ // The abstract operation MakeAutoAccessorSetter takes arguments homeObject
+ // (an Object), name (a property key or Private Name), and privateStateName
+ // (a Private Name) and returns a function object.
+ // 1. Let setterClosure be a new Abstract Closure with parameters (value)
+ // that captures privateStateName and performs the following steps when
+ // called:
+ // 1.a. Let o be the this value.
+ notePositionalFormalParameter(funNode,
+ TaggedParserAtomIndex::WellKnown::value(),
+ /* pos = */ 0, false,
+ /* duplicatedParam = */ nullptr);
+
+ Node initializerExpr = handler_.newName(
+ TaggedParserAtomIndex::WellKnown::value(), propNamePos);
+ if (!initializerExpr) {
+ return null();
+ }
+
+ // 1.b. Perform ? PrivateSet(privateStateName, o, value).
+ Node assignment = handler_.newAssignment(ParseNodeKind::AssignExpr,
+ propFieldAccess, initializerExpr);
+ if (!assignment) {
+ return null();
+ }
+
+ accessorBody = handler_.newExprStatement(assignment, propNamePos.end);
+ if (!accessorBody) {
+ return null();
+ }
+
+ // 1.c. Return undefined.
+ }
+
+ ListNodeType statementList = handler_.newStatementList(propNamePos);
+ if (!statementList) {
+ return null();
+ }
+ handler_.addStatementToList(statementList, accessorBody);
+
+ bool canSkipLazyClosedOverBindings = handler_.reuseClosedOverBindings();
+ if (!pc_->declareFunctionThis(usedNames_, canSkipLazyClosedOverBindings)) {
+ return null();
+ }
+ if (!pc_->declareNewTarget(usedNames_, canSkipLazyClosedOverBindings)) {
+ return null();
+ }
+
+ LexicalScopeNodeType initializerBody = finishLexicalScope(
+ pc_->varScope(), statementList, ScopeKind::FunctionLexical);
+ if (!initializerBody) {
+ return null();
+ }
+
+ handler_.setFunctionBody(funNode, initializerBody);
+
+ if (pc_->superScopeNeedsHomeObject()) {
+ funbox->setNeedsHomeObject();
+ }
+
+ if (!finishFunction()) {
+ return null();
+ }
+
+ if (!leaveInnerFunction(outerpc)) {
+ return null();
+ }
+
+ return funNode;
+}
+
+#endif
+
+bool ParserBase::nextTokenContinuesLetDeclaration(TokenKind next) {
+ MOZ_ASSERT(anyChars.isCurrentTokenType(TokenKind::Let));
+ MOZ_ASSERT(anyChars.nextToken().type == next);
+
+ TokenStreamShared::verifyConsistentModifier(TokenStreamShared::SlashIsDiv,
+ anyChars.nextToken());
+
+ // Destructuring continues a let declaration.
+ if (next == TokenKind::LeftBracket || next == TokenKind::LeftCurly) {
+ return true;
+ }
+
+ // A "let" edge case deserves special comment. Consider this:
+ //
+ // let // not an ASI opportunity
+ // let;
+ //
+ // Static semantics in §13.3.1.1 turn a LexicalDeclaration that binds
+ // "let" into an early error. Does this retroactively permit ASI so
+ // that we should parse this as two ExpressionStatements? No. ASI
+ // resolves during parsing. Static semantics only apply to the full
+ // parse tree with ASI applied. No backsies!
+
+ // Otherwise a let declaration must have a name.
+ return TokenKindIsPossibleIdentifier(next);
+}
+
+template <class ParseHandler, typename Unit>
+typename ParseHandler::DeclarationListNodeType
+GeneralParser<ParseHandler, Unit>::variableStatement(
+ YieldHandling yieldHandling) {
+ DeclarationListNodeType 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_);
+
+ AutoCheckRecursionLimit recursion(this->fc_);
+ if (!recursion.check(this->fc_)) {
+ return null();
+ }
+
+ TokenKind tt;
+ if (!tokenStream.getToken(&tt, TokenStream::SlashIsRegExp)) {
+ return null();
+ }
+
+ switch (tt) {
+ // BlockStatement[?Yield, ?Return]
+ case TokenKind::LeftCurly:
+ return blockStatement(yieldHandling);
+
+ // VariableStatement[?Yield]
+ case TokenKind::Var:
+ return variableStatement(yieldHandling);
+
+ // EmptyStatement
+ case TokenKind::Semi:
+ return handler_.newEmptyStatement(pos());
+
+ // ExpressionStatement[?Yield].
+
+ case TokenKind::Yield: {
+ // Don't use a ternary operator here due to obscure linker issues
+ // around using static consts in the arms of a ternary.
+ Modifier modifier;
+ if (yieldExpressionsSupported()) {
+ modifier = TokenStream::SlashIsRegExp;
+ } else {
+ modifier = TokenStream::SlashIsDiv;
+ }
+
+ TokenKind next;
+ if (!tokenStream.peekToken(&next, modifier)) {
+ return null();
+ }
+
+ if (next == TokenKind::Colon) {
+ return labeledStatement(yieldHandling);
+ }
+
+ return expressionStatement(yieldHandling);
+ }
+
+ default: {
+ // If we encounter an await in a module, and the module is not marked
+ // as async, mark the module as async.
+ if (tt == TokenKind::Await && !pc_->isAsync()) {
+ if (pc_->atModuleTopLevel()) {
+ if (!options().topLevelAwait) {
+ error(JSMSG_TOP_LEVEL_AWAIT_NOT_SUPPORTED);
+ return null();
+ }
+ pc_->sc()->asModuleContext()->setIsAsync();
+ MOZ_ASSERT(pc_->isAsync());
+ }
+ }
+
+ // Avoid getting next token with SlashIsDiv.
+ if (tt == TokenKind::Await && pc_->isAsync()) {
+ return expressionStatement(yieldHandling);
+ }
+
+ if (!TokenKindIsPossibleIdentifier(tt)) {
+ return expressionStatement(yieldHandling);
+ }
+
+ TokenKind next;
+ if (!tokenStream.peekToken(&next)) {
+ return null();
+ }
+
+ // |let| here can only be an Identifier, not a declaration. Give nicer
+ // errors for declaration-looking typos.
+ if (tt == TokenKind::Let) {
+ bool forbiddenLetDeclaration = false;
+
+ if (next == TokenKind::LeftBracket) {
+ // Enforce ExpressionStatement's 'let [' lookahead restriction.
+ forbiddenLetDeclaration = true;
+ } else if (next == TokenKind::LeftCurly ||
+ TokenKindIsPossibleIdentifier(next)) {
+ // 'let {' and 'let foo' aren't completely forbidden, if ASI
+ // causes 'let' to be the entire Statement. But if they're
+ // same-line, we can aggressively give a better error message.
+ //
+ // Note that this ignores 'yield' as TokenKind::Yield: we'll handle it
+ // correctly but with a worse error message.
+ TokenKind nextSameLine;
+ if (!tokenStream.peekTokenSameLine(&nextSameLine)) {
+ return null();
+ }
+
+ MOZ_ASSERT(TokenKindIsPossibleIdentifier(nextSameLine) ||
+ nextSameLine == TokenKind::LeftCurly ||
+ nextSameLine == TokenKind::Eol);
+
+ forbiddenLetDeclaration = nextSameLine != TokenKind::Eol;
+ }
+
+ if (forbiddenLetDeclaration) {
+ error(JSMSG_FORBIDDEN_AS_STATEMENT, "lexical declarations");
+ return null();
+ }
+ } else if (tt == TokenKind::Async) {
+ // Peek only on the same line: ExpressionStatement's lookahead
+ // restriction is phrased as
+ //
+ // [lookahead ∉ { '{',
+ // function,
+ // async [no LineTerminator here] function,
+ // class,
+ // let '[' }]
+ //
+ // meaning that code like this is valid:
+ //
+ // if (true)
+ // async // ASI opportunity
+ // function clownshoes() {}
+ TokenKind maybeFunction;
+ if (!tokenStream.peekTokenSameLine(&maybeFunction)) {
+ return null();
+ }
+
+ if (maybeFunction == TokenKind::Function) {
+ error(JSMSG_FORBIDDEN_AS_STATEMENT, "async function declarations");
+ return null();
+ }
+
+ // Otherwise this |async| begins an ExpressionStatement or is a
+ // label name.
+ }
+
+ // NOTE: It's unfortunately allowed to have a label named 'let' in
+ // non-strict code. 💯
+ if (next == TokenKind::Colon) {
+ return labeledStatement(yieldHandling);
+ }
+
+ return expressionStatement(yieldHandling);
+ }
+
+ case TokenKind::New:
+ return expressionStatement(yieldHandling, PredictInvoked);
+
+ // IfStatement[?Yield, ?Return]
+ case TokenKind::If:
+ return ifStatement(yieldHandling);
+
+ // BreakableStatement[?Yield, ?Return]
+ //
+ // BreakableStatement[Yield, Return]:
+ // IterationStatement[?Yield, ?Return]
+ // SwitchStatement[?Yield, ?Return]
+ case TokenKind::Do:
+ return doWhileStatement(yieldHandling);
+
+ case TokenKind::While:
+ return whileStatement(yieldHandling);
+
+ case TokenKind::For:
+ return forStatement(yieldHandling);
+
+ case TokenKind::Switch:
+ return switchStatement(yieldHandling);
+
+ // ContinueStatement[?Yield]
+ case TokenKind::Continue:
+ return continueStatement(yieldHandling);
+
+ // BreakStatement[?Yield]
+ case TokenKind::Break:
+ return breakStatement(yieldHandling);
+
+ // [+Return] ReturnStatement[?Yield]
+ case TokenKind::Return:
+ // The Return parameter is only used here, and the effect is easily
+ // detected this way, so don't bother passing around an extra parameter
+ // everywhere.
+ if (!pc_->allowReturn()) {
+ error(JSMSG_BAD_RETURN_OR_YIELD, js_return_str);
+ return null();
+ }
+ return returnStatement(yieldHandling);
+
+ // WithStatement[?Yield, ?Return]
+ case TokenKind::With:
+ return withStatement(yieldHandling);
+
+ // LabelledStatement[?Yield, ?Return]
+ // This is really handled by default and TokenKind::Yield cases above.
+
+ // ThrowStatement[?Yield]
+ case TokenKind::Throw:
+ return throwStatement(yieldHandling);
+
+ // TryStatement[?Yield, ?Return]
+ case TokenKind::Try:
+ return tryStatement(yieldHandling);
+
+ // DebuggerStatement
+ case TokenKind::Debugger:
+ return debuggerStatement();
+
+ // |function| is forbidden by lookahead restriction (unless as child
+ // statement of |if| or |else|, but Parser::consequentOrAlternative
+ // handles that).
+ case TokenKind::Function:
+ error(JSMSG_FORBIDDEN_AS_STATEMENT, "function declarations");
+ return null();
+
+ // |class| is also forbidden by lookahead restriction.
+ case TokenKind::Class:
+ error(JSMSG_FORBIDDEN_AS_STATEMENT, "classes");
+ return null();
+
+ // ImportDeclaration (only inside modules)
+ case TokenKind::Import:
+ return importDeclarationOrImportExpr(yieldHandling);
+
+ // ExportDeclaration (only inside modules)
+ case TokenKind::Export:
+ return exportDeclaration();
+
+ // Miscellaneous error cases arguably better caught here than elsewhere.
+
+ case TokenKind::Catch:
+ error(JSMSG_CATCH_WITHOUT_TRY);
+ return null();
+
+ case TokenKind::Finally:
+ error(JSMSG_FINALLY_WITHOUT_TRY);
+ return null();
+
+ // NOTE: default case handled in the ExpressionStatement section.
+ }
+}
+
+template <class ParseHandler, typename Unit>
+typename ParseHandler::Node
+GeneralParser<ParseHandler, Unit>::statementListItem(
+ YieldHandling yieldHandling, bool canHaveDirectives /* = false */) {
+ MOZ_ASSERT(checkOptionsCalled_);
+
+ AutoCheckRecursionLimit recursion(this->fc_);
+ if (!recursion.check(this->fc_)) {
+ return null();
+ }
+
+ TokenKind tt;
+ if (!tokenStream.getToken(&tt, TokenStream::SlashIsRegExp)) {
+ return null();
+ }
+
+ switch (tt) {
+ // BlockStatement[?Yield, ?Return]
+ case TokenKind::LeftCurly:
+ return blockStatement(yieldHandling);
+
+ // VariableStatement[?Yield]
+ case TokenKind::Var:
+ return variableStatement(yieldHandling);
+
+ // EmptyStatement
+ case TokenKind::Semi:
+ return handler_.newEmptyStatement(pos());
+
+ // ExpressionStatement[?Yield].
+ //
+ // These should probably be handled by a single ExpressionStatement
+ // function in a default, not split up this way.
+ case TokenKind::String:
+ if (!canHaveDirectives &&
+ anyChars.currentToken().atom() ==
+ TaggedParserAtomIndex::WellKnown::useAsm()) {
+ if (!warning(JSMSG_USE_ASM_DIRECTIVE_FAIL)) {
+ return null();
+ }
+ }
+ return expressionStatement(yieldHandling);
+
+ case TokenKind::Yield: {
+ // Don't use a ternary operator here due to obscure linker issues
+ // around using static consts in the arms of a ternary.
+ Modifier modifier;
+ if (yieldExpressionsSupported()) {
+ modifier = TokenStream::SlashIsRegExp;
+ } else {
+ modifier = TokenStream::SlashIsDiv;
+ }
+
+ TokenKind next;
+ if (!tokenStream.peekToken(&next, modifier)) {
+ return null();
+ }
+
+ if (next == TokenKind::Colon) {
+ return labeledStatement(yieldHandling);
+ }
+
+ return expressionStatement(yieldHandling);
+ }
+
+ default: {
+ // If we encounter an await in a module, and the module is not marked
+ // as async, mark the module as async.
+ if (tt == TokenKind::Await && !pc_->isAsync()) {
+ if (pc_->atModuleTopLevel()) {
+ if (!options().topLevelAwait) {
+ error(JSMSG_TOP_LEVEL_AWAIT_NOT_SUPPORTED);
+ return null();
+ }
+ pc_->sc()->asModuleContext()->setIsAsync();
+ MOZ_ASSERT(pc_->isAsync());
+ }
+ }
+
+ // Avoid getting next token with SlashIsDiv.
+ if (tt == TokenKind::Await && pc_->isAsync()) {
+ return expressionStatement(yieldHandling);
+ }
+
+ if (!TokenKindIsPossibleIdentifier(tt)) {
+ return expressionStatement(yieldHandling);
+ }
+
+ TokenKind next;
+ if (!tokenStream.peekToken(&next)) {
+ return null();
+ }
+
+ if (tt == TokenKind::Let && nextTokenContinuesLetDeclaration(next)) {
+ return lexicalDeclaration(yieldHandling, DeclarationKind::Let);
+ }
+
+ if (tt == TokenKind::Async) {
+ TokenKind nextSameLine = TokenKind::Eof;
+ if (!tokenStream.peekTokenSameLine(&nextSameLine)) {
+ return null();
+ }
+ if (nextSameLine == TokenKind::Function) {
+ uint32_t toStringStart = pos().begin;
+ tokenStream.consumeKnownToken(TokenKind::Function);
+ return functionStmt(toStringStart, yieldHandling, NameRequired,
+ FunctionAsyncKind::AsyncFunction);
+ }
+ }
+
+ if (next == TokenKind::Colon) {
+ return labeledStatement(yieldHandling);
+ }
+
+ return expressionStatement(yieldHandling);
+ }
+
+ case TokenKind::New:
+ return expressionStatement(yieldHandling, PredictInvoked);
+
+ // IfStatement[?Yield, ?Return]
+ case TokenKind::If:
+ return ifStatement(yieldHandling);
+
+ // BreakableStatement[?Yield, ?Return]
+ //
+ // BreakableStatement[Yield, Return]:
+ // IterationStatement[?Yield, ?Return]
+ // SwitchStatement[?Yield, ?Return]
+ case TokenKind::Do:
+ return doWhileStatement(yieldHandling);
+
+ case TokenKind::While:
+ return whileStatement(yieldHandling);
+
+ case TokenKind::For:
+ return forStatement(yieldHandling);
+
+ case TokenKind::Switch:
+ return switchStatement(yieldHandling);
+
+ // ContinueStatement[?Yield]
+ case TokenKind::Continue:
+ return continueStatement(yieldHandling);
+
+ // BreakStatement[?Yield]
+ case TokenKind::Break:
+ return breakStatement(yieldHandling);
+
+ // [+Return] ReturnStatement[?Yield]
+ case TokenKind::Return:
+ // The Return parameter is only used here, and the effect is easily
+ // detected this way, so don't bother passing around an extra parameter
+ // everywhere.
+ if (!pc_->allowReturn()) {
+ error(JSMSG_BAD_RETURN_OR_YIELD, js_return_str);
+ return null();
+ }
+ return returnStatement(yieldHandling);
+
+ // WithStatement[?Yield, ?Return]
+ case TokenKind::With:
+ return withStatement(yieldHandling);
+
+ // LabelledStatement[?Yield, ?Return]
+ // This is really handled by default and TokenKind::Yield cases above.
+
+ // ThrowStatement[?Yield]
+ case TokenKind::Throw:
+ return throwStatement(yieldHandling);
+
+ // TryStatement[?Yield, ?Return]
+ case TokenKind::Try:
+ return tryStatement(yieldHandling);
+
+ // DebuggerStatement
+ case TokenKind::Debugger:
+ return debuggerStatement();
+
+ // Declaration[Yield]:
+
+ // HoistableDeclaration[?Yield, ~Default]
+ case TokenKind::Function:
+ return functionStmt(pos().begin, yieldHandling, NameRequired);
+
+ // DecoratorList[?Yield, ?Await] opt ClassDeclaration[?Yield, ~Default]
+#ifdef ENABLE_DECORATORS
+ case TokenKind::At:
+ return classDefinition(yieldHandling, ClassExpression, NameRequired);
+#endif
+
+ case TokenKind::Class:
+ return classDefinition(yieldHandling, ClassStatement, NameRequired);
+
+ // LexicalDeclaration[In, ?Yield]
+ // LetOrConst BindingList[?In, ?Yield]
+ case TokenKind::Const:
+ // [In] is the default behavior, because for-loops specially parse
+ // their heads to handle |in| in this situation.
+ return lexicalDeclaration(yieldHandling, DeclarationKind::Const);
+
+ // ImportDeclaration (only inside modules)
+ case TokenKind::Import:
+ return importDeclarationOrImportExpr(yieldHandling);
+
+ // ExportDeclaration (only inside modules)
+ case TokenKind::Export:
+ return exportDeclaration();
+
+ // Miscellaneous error cases arguably better caught here than elsewhere.
+
+ case TokenKind::Catch:
+ error(JSMSG_CATCH_WITHOUT_TRY);
+ return null();
+
+ case TokenKind::Finally:
+ error(JSMSG_FINALLY_WITHOUT_TRY);
+ return null();
+
+ // NOTE: default case handled in the ExpressionStatement section.
+ }
+}
+
+template <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::Coalesce */
+ 2, /* ParseNodeKind::Or */
+ 3, /* ParseNodeKind::And */
+ 4, /* ParseNodeKind::BitOr */
+ 5, /* ParseNodeKind::BitXor */
+ 6, /* ParseNodeKind::BitAnd */
+ 7, /* ParseNodeKind::StrictEq */
+ 7, /* ParseNodeKind::Eq */
+ 7, /* ParseNodeKind::StrictNe */
+ 7, /* ParseNodeKind::Ne */
+ 8, /* ParseNodeKind::Lt */
+ 8, /* ParseNodeKind::Le */
+ 8, /* ParseNodeKind::Gt */
+ 8, /* ParseNodeKind::Ge */
+ 8, /* ParseNodeKind::InstanceOf */
+ 8, /* ParseNodeKind::In */
+ 8, /* ParseNodeKind::PrivateIn */
+ 9, /* ParseNodeKind::Lsh */
+ 9, /* ParseNodeKind::Rsh */
+ 9, /* ParseNodeKind::Ursh */
+ 10, /* ParseNodeKind::Add */
+ 10, /* ParseNodeKind::Sub */
+ 11, /* ParseNodeKind::Star */
+ 11, /* ParseNodeKind::Div */
+ 11, /* ParseNodeKind::Mod */
+ 12 /* ParseNodeKind::Pow */
+};
+
+static const int PRECEDENCE_CLASSES = 12;
+
+static int Precedence(ParseNodeKind pnk) {
+ // Everything binds tighter than ParseNodeKind::Limit, because we want
+ // to reduce all nodes to a single node when we reach a token that is not
+ // another binary operator.
+ if (pnk == ParseNodeKind::Limit) {
+ return 0;
+ }
+
+ MOZ_ASSERT(pnk >= ParseNodeKind::BinOpFirst);
+ MOZ_ASSERT(pnk <= ParseNodeKind::BinOpLast);
+ return PrecedenceTable[size_t(pnk) - size_t(ParseNodeKind::BinOpFirst)];
+}
+
+enum class EnforcedParentheses : uint8_t { CoalesceExpr, AndOrExpr, None };
+
+template <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,
+ PrivateNameHandling::PrivateNameAllowed);
+ if (!pn) {
+ return null();
+ }
+
+ // If a binary operator follows, consume it and compute the
+ // corresponding operator.
+ TokenKind tok;
+ if (!tokenStream.getToken(&tok)) {
+ return null();
+ }
+
+ // Ensure that if we have a private name lhs we are legally constructing a
+ // `#x in obj` expessions:
+ if (handler_.isPrivateName(pn)) {
+ if (tok != TokenKind::In || inHandling != InAllowed) {
+ error(JSMSG_ILLEGAL_PRIVATE_NAME);
+ return null();
+ }
+ }
+
+ ParseNodeKind pnk;
+ if (tok == TokenKind::In ? inHandling == InAllowed
+ : TokenKindIsBinaryOp(tok)) {
+ // We're definitely not in a destructuring context, so report any
+ // pending expression error now.
+ if (possibleError && !possibleError->checkForExpressionError()) {
+ return null();
+ }
+
+ bool isErgonomicBrandCheck = false;
+ switch (tok) {
+ // Report an error for unary expressions on the LHS of **.
+ case TokenKind::Pow:
+ if (handler_.isUnparenthesizedUnaryExpression(pn)) {
+ error(JSMSG_BAD_POW_LEFTSIDE);
+ return null();
+ }
+ break;
+
+ case TokenKind::Or:
+ case TokenKind::And:
+ // In the case that the `??` is on the left hand side of the
+ // expression: Disallow Mixing of ?? and other logical operators (||
+ // and &&) unless one expression is parenthesized
+ if (unparenthesizedExpression == EnforcedParentheses::CoalesceExpr) {
+ error(JSMSG_BAD_COALESCE_MIXING);
+ return null();
+ }
+ // If we have not detected a mixing error at this point, record that
+ // we have an unparenthesized expression, in case we have one later.
+ unparenthesizedExpression = EnforcedParentheses::AndOrExpr;
+ break;
+
+ case TokenKind::Coalesce:
+ if (unparenthesizedExpression == EnforcedParentheses::AndOrExpr) {
+ error(JSMSG_BAD_COALESCE_MIXING);
+ return null();
+ }
+ // If we have not detected a mixing error at this point, record that
+ // we have an unparenthesized expression, in case we have one later.
+ unparenthesizedExpression = EnforcedParentheses::CoalesceExpr;
+ break;
+
+ case TokenKind::In:
+ // if the LHS is a private name, and the operator is In,
+ // ensure we're construcing an ergonomic brand check of
+ // '#x in y', rather than having a higher precedence operator
+ // like + cause a different reduction, such as
+ // 1 + #x in y.
+ if (handler_.isPrivateName(pn)) {
+ if (depth > 0 && Precedence(kindStack[depth - 1]) >=
+ Precedence(ParseNodeKind::InExpr)) {
+ error(JSMSG_INVALID_PRIVATE_NAME_PRECEDENCE);
+ return null();
+ }
+
+ isErgonomicBrandCheck = true;
+ }
+ break;
+
+ default:
+ // do nothing in other cases
+ break;
+ }
+
+ if (isErgonomicBrandCheck) {
+ pnk = ParseNodeKind::PrivateInExpr;
+ } else {
+ pnk = BinaryOpTokenKindToParseNodeKind(tok);
+ }
+
+ } else {
+ tok = TokenKind::Eof;
+ pnk = ParseNodeKind::Limit;
+ }
+
+ // From this point on, destructuring defaults are definitely an error.
+ possibleError = nullptr;
+
+ // If pnk has precedence less than or equal to another operator on the
+ // stack, reduce. This combines nodes on the stack until we form the
+ // actual lhs of pnk.
+ //
+ // The >= in this condition works because it is appendOrCreateList's
+ // job to decide if the operator in question is left- or
+ // right-associative, and build the corresponding tree.
+ while (depth > 0 && Precedence(kindStack[depth - 1]) >= Precedence(pnk)) {
+ depth--;
+ ParseNodeKind combiningPnk = kindStack[depth];
+ pn = handler_.appendOrCreateList(combiningPnk, nodeStack[depth], pn, pc_);
+
+ if (!pn) {
+ return null();
+ }
+ }
+
+ if (pnk == ParseNodeKind::Limit) {
+ break;
+ }
+
+ nodeStack[depth] = pn;
+ kindStack[depth] = pnk;
+ depth++;
+ MOZ_ASSERT(depth <= PRECEDENCE_CLASSES);
+ }
+
+ anyChars.ungetToken();
+
+ // Had the next token been a Div, we would have consumed it. So there's no
+ // ambiguity if we later (after ASI) re-get this token with SlashIsRegExp.
+ anyChars.allowGettingNextTokenWithSlashIsRegExp();
+
+ MOZ_ASSERT(depth == 0);
+ return pn;
+}
+
+template <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 */) {
+ AutoCheckRecursionLimit recursion(this->fc_);
+ if (!recursion.check(this->fc_)) {
+ return null();
+ }
+
+ // It's very common at this point to have a "detectably simple" expression,
+ // i.e. a name/number/string token followed by one of the following tokens
+ // that obviously isn't part of an expression: , ; : ) ] }
+ //
+ // (In Parsemark this happens 81.4% of the time; in code with large
+ // numeric arrays, such as some Kraken benchmarks, it happens more often.)
+ //
+ // In such cases, we can avoid the full expression parsing route through
+ // assignExpr(), condExpr(), orExpr(), unaryExpr(), memberExpr(), and
+ // primaryExpr().
+
+ TokenKind firstToken;
+ if (!tokenStream.getToken(&firstToken, TokenStream::SlashIsRegExp)) {
+ return null();
+ }
+
+ TokenPos exprPos = pos();
+
+ bool endsExpr;
+
+ // This only handles identifiers that *never* have special meaning anywhere
+ // in the language. Contextual keywords, reserved words in strict mode,
+ // and other hard cases are handled outside this fast path.
+ if (firstToken == TokenKind::Name) {
+ if (!tokenStream.nextTokenEndsExpr(&endsExpr)) {
+ return null();
+ }
+ if (endsExpr) {
+ TaggedParserAtomIndex name = identifierReference(yieldHandling);
+ if (!name) {
+ return null();
+ }
+
+ return identifierReference(name);
+ }
+ }
+
+ if (firstToken == TokenKind::Number) {
+ if (!tokenStream.nextTokenEndsExpr(&endsExpr)) {
+ return null();
+ }
+ if (endsExpr) {
+ return newNumber(anyChars.currentToken());
+ }
+ }
+
+ if (firstToken == TokenKind::String) {
+ if (!tokenStream.nextTokenEndsExpr(&endsExpr)) {
+ return null();
+ }
+ if (endsExpr) {
+ return stringLiteral();
+ }
+ }
+
+ if (firstToken == TokenKind::Yield && yieldExpressionsSupported()) {
+ return yieldExpression(inHandling);
+ }
+
+ bool maybeAsyncArrow = false;
+ if (firstToken == TokenKind::Async) {
+ TokenKind nextSameLine = TokenKind::Eof;
+ if (!tokenStream.peekTokenSameLine(&nextSameLine)) {
+ return null();
+ }
+
+ if (TokenKindIsPossibleIdentifier(nextSameLine)) {
+ maybeAsyncArrow = true;
+ }
+ }
+
+ anyChars.ungetToken();
+
+ // Save the tokenizer state in case we find an arrow function and have to
+ // rewind.
+ Position start(tokenStream);
+ auto ghostToken = this->compilationState_.getPosition();
+
+ PossibleError possibleErrorInner(*this);
+ Node lhs;
+ TokenKind tokenAfterLHS;
+ bool isArrow;
+ if (maybeAsyncArrow) {
+ tokenStream.consumeKnownToken(TokenKind::Async, TokenStream::SlashIsRegExp);
+
+ TokenKind tokenAfterAsync;
+ if (!tokenStream.getToken(&tokenAfterAsync)) {
+ return null();
+ }
+ MOZ_ASSERT(TokenKindIsPossibleIdentifier(tokenAfterAsync));
+
+ // Check yield validity here.
+ TaggedParserAtomIndex name = bindingIdentifier(yieldHandling);
+ if (!name) {
+ return null();
+ }
+
+ if (!tokenStream.peekToken(&tokenAfterLHS, TokenStream::SlashIsRegExp)) {
+ return null();
+ }
+
+ isArrow = tokenAfterLHS == TokenKind::Arrow;
+
+ // |async [no LineTerminator] of| without being followed by => is only
+ // possible in for-await-of loops, e.g. |for await (async of [])|. Pretend
+ // the |async| token was parsed an identifier reference and then proceed
+ // with the rest of this function.
+ if (!isArrow) {
+ anyChars.ungetToken(); // unget the binding identifier
+
+ // The next token is guaranteed to never be a Div (, because it's an
+ // identifier), so it's okay to re-get the token with SlashIsRegExp.
+ anyChars.allowGettingNextTokenWithSlashIsRegExp();
+
+ TaggedParserAtomIndex asyncName = identifierReference(yieldHandling);
+ if (!asyncName) {
+ return null();
+ }
+
+ lhs = identifierReference(asyncName);
+ if (!lhs) {
+ return null();
+ }
+ }
+ } else {
+ lhs = condExpr(inHandling, yieldHandling, tripledotHandling,
+ &possibleErrorInner, invoked);
+ if (!lhs) {
+ return null();
+ }
+
+ // Use SlashIsRegExp here because the ConditionalExpression parsed above
+ // could be the entirety of this AssignmentExpression, and then ASI
+ // permits this token to be a regular expression.
+ if (!tokenStream.peekToken(&tokenAfterLHS, TokenStream::SlashIsRegExp)) {
+ return null();
+ }
+
+ isArrow = tokenAfterLHS == TokenKind::Arrow;
+ }
+
+ if (isArrow) {
+ // Rewind to reparse as an arrow function.
+ //
+ // Note: We do not call CompilationState::rewind here because parsing
+ // during delazification will see the same rewind and need the same sequence
+ // of inner functions to skip over.
+ // Instead, we mark inner functions as "ghost".
+ //
+ // See GHOST_FUNCTION in FunctionFlags.h for more details.
+ tokenStream.rewind(start);
+ this->compilationState_.markGhost(ghostToken);
+
+ TokenKind next;
+ if (!tokenStream.getToken(&next, TokenStream::SlashIsRegExp)) {
+ return null();
+ }
+ TokenPos startPos = pos();
+ uint32_t toStringStart = startPos.begin;
+ anyChars.ungetToken();
+
+ FunctionAsyncKind asyncKind = FunctionAsyncKind::SyncFunction;
+
+ if (next == TokenKind::Async) {
+ tokenStream.consumeKnownToken(next, TokenStream::SlashIsRegExp);
+
+ TokenKind nextSameLine = TokenKind::Eof;
+ if (!tokenStream.peekTokenSameLine(&nextSameLine)) {
+ return null();
+ }
+
+ // The AsyncArrowFunction production are
+ // async [no LineTerminator here] AsyncArrowBindingIdentifier ...
+ // async [no LineTerminator here] ArrowFormalParameters ...
+ if (TokenKindIsPossibleIdentifier(nextSameLine) ||
+ nextSameLine == TokenKind::LeftParen) {
+ asyncKind = FunctionAsyncKind::AsyncFunction;
+ } else {
+ anyChars.ungetToken();
+ }
+ }
+
+ FunctionSyntaxKind syntaxKind = FunctionSyntaxKind::Arrow;
+ FunctionNodeType funNode = handler_.newFunction(syntaxKind, startPos);
+ if (!funNode) {
+ return null();
+ }
+
+ return functionDefinition(funNode, toStringStart, inHandling, yieldHandling,
+ TaggedParserAtomIndex::null(), syntaxKind,
+ GeneratorKind::NotGenerator, asyncKind);
+ }
+
+ MOZ_ALWAYS_TRUE(
+ tokenStream.getToken(&tokenAfterLHS, TokenStream::SlashIsRegExp));
+
+ ParseNodeKind kind;
+ switch (tokenAfterLHS) {
+ case TokenKind::Assign:
+ kind = ParseNodeKind::AssignExpr;
+ break;
+ case TokenKind::AddAssign:
+ kind = ParseNodeKind::AddAssignExpr;
+ break;
+ case TokenKind::SubAssign:
+ kind = ParseNodeKind::SubAssignExpr;
+ break;
+ case TokenKind::CoalesceAssign:
+ kind = ParseNodeKind::CoalesceAssignExpr;
+ break;
+ case TokenKind::OrAssign:
+ kind = ParseNodeKind::OrAssignExpr;
+ break;
+ case TokenKind::AndAssign:
+ kind = ParseNodeKind::AndAssignExpr;
+ break;
+ case TokenKind::BitOrAssign:
+ kind = ParseNodeKind::BitOrAssignExpr;
+ break;
+ case TokenKind::BitXorAssign:
+ kind = ParseNodeKind::BitXorAssignExpr;
+ break;
+ case TokenKind::BitAndAssign:
+ kind = ParseNodeKind::BitAndAssignExpr;
+ break;
+ case TokenKind::LshAssign:
+ kind = ParseNodeKind::LshAssignExpr;
+ break;
+ case TokenKind::RshAssign:
+ kind = ParseNodeKind::RshAssignExpr;
+ break;
+ case TokenKind::UrshAssign:
+ kind = ParseNodeKind::UrshAssignExpr;
+ break;
+ case TokenKind::MulAssign:
+ kind = ParseNodeKind::MulAssignExpr;
+ break;
+ case TokenKind::DivAssign:
+ kind = ParseNodeKind::DivAssignExpr;
+ break;
+ case TokenKind::ModAssign:
+ kind = ParseNodeKind::ModAssignExpr;
+ break;
+ case TokenKind::PowAssign:
+ kind = ParseNodeKind::PowAssignExpr;
+ break;
+
+ default:
+ MOZ_ASSERT(!anyChars.isCurrentTokenAssignment());
+ if (!possibleError) {
+ if (!possibleErrorInner.checkForExpressionError()) {
+ return null();
+ }
+ } else {
+ possibleErrorInner.transferErrorsTo(possibleError);
+ }
+
+ anyChars.ungetToken();
+ return lhs;
+ }
+
+ // Verify the left-hand side expression doesn't have a forbidden form.
+ if (handler_.isUnparenthesizedDestructuringPattern(lhs)) {
+ if (kind != ParseNodeKind::AssignExpr) {
+ error(JSMSG_BAD_DESTRUCT_ASS);
+ return null();
+ }
+
+ if (!possibleErrorInner.checkForDestructuringErrorOrWarning()) {
+ return null();
+ }
+ } else if (handler_.isName(lhs)) {
+ if (const char* chars = nameIsArgumentsOrEval(lhs)) {
+ // |chars| is "arguments" or "eval" here.
+ if (!strictModeErrorAt(exprPos.begin, JSMSG_BAD_STRICT_ASSIGN, chars)) {
+ return null();
+ }
+ }
+ } else if (handler_.isPropertyOrPrivateMemberAccess(lhs)) {
+ // Permitted: no additional testing/fixup needed.
+ } else if (handler_.isFunctionCall(lhs)) {
+ // We don't have to worry about backward compatibility issues with the new
+ // compound assignment operators, so we always throw here. Also that way we
+ // don't have to worry if |f() &&= expr| should always throw an error or
+ // only if |f()| returns true.
+ if (kind == ParseNodeKind::CoalesceAssignExpr ||
+ kind == ParseNodeKind::OrAssignExpr ||
+ kind == ParseNodeKind::AndAssignExpr) {
+ errorAt(exprPos.begin, JSMSG_BAD_LEFTSIDE_OF_ASS);
+ return null();
+ }
+
+ if (!strictModeErrorAt(exprPos.begin, JSMSG_BAD_LEFTSIDE_OF_ASS)) {
+ return null();
+ }
+
+ if (possibleError) {
+ possibleError->setPendingDestructuringErrorAt(exprPos,
+ JSMSG_BAD_DESTRUCT_TARGET);
+ }
+ } else {
+ errorAt(exprPos.begin, JSMSG_BAD_LEFTSIDE_OF_ASS);
+ return null();
+ }
+
+ if (!possibleErrorInner.checkForExpressionError()) {
+ return null();
+ }
+
+ Node rhs = assignExpr(inHandling, yieldHandling, TripledotProhibited);
+ if (!rhs) {
+ return null();
+ }
+
+ return handler_.newAssignment(kind, lhs, rhs);
+}
+
+template <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)) {
+ return js_eval_str;
+ }
+ if (handler_.isArgumentsName(node)) {
+ 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_.isPropertyOrPrivateMemberAccess(operand)) {
+ // Permitted: no additional testing/fixup needed.
+ } else if (handler_.isFunctionCall(operand)) {
+ // Assignment to function calls is forbidden in ES6. We're still
+ // somewhat concerned about sites using this in dead code, so forbid it
+ // only in strict mode code.
+ if (!strictModeErrorAt(operandOffset, JSMSG_BAD_INCOP_OPERAND)) {
+ return false;
+ }
+ } else {
+ errorAt(operandOffset, JSMSG_BAD_INCOP_OPERAND);
+ return false;
+ }
+ return true;
+}
+
+template <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 */) {
+ AutoCheckRecursionLimit recursion(this->fc_);
+ if (!recursion.check(this->fc_)) {
+ return null();
+ }
+
+ uint32_t begin = pos().begin;
+
+ Node lhs = memberExpr(yieldHandling, tripledotHandling, tt,
+ /* allowCallSyntax = */ true, possibleError, invoked);
+ if (!lhs) {
+ return null();
+ }
+
+ if (!tokenStream.peekToken(&tt, TokenStream::SlashIsDiv)) {
+ return null();
+ }
+
+ if (tt != TokenKind::OptionalChain) {
+ return lhs;
+ }
+
+ while (true) {
+ if (!tokenStream.getToken(&tt)) {
+ return null();
+ }
+
+ if (tt == TokenKind::Eof) {
+ anyChars.ungetToken();
+ break;
+ }
+
+ Node nextMember;
+ if (tt == TokenKind::OptionalChain) {
+ if (!tokenStream.getToken(&tt)) {
+ return null();
+ }
+ if (TokenKindIsPossibleIdentifierName(tt)) {
+ nextMember = memberPropertyAccess(lhs, OptionalKind::Optional);
+ if (!nextMember) {
+ return null();
+ }
+ } else if (tt == TokenKind::PrivateName) {
+ nextMember = memberPrivateAccess(lhs, OptionalKind::Optional);
+ if (!nextMember) {
+ return null();
+ }
+ } else if (tt == TokenKind::LeftBracket) {
+ nextMember =
+ memberElemAccess(lhs, yieldHandling, OptionalKind::Optional);
+ if (!nextMember) {
+ return null();
+ }
+ } else if (tt == TokenKind::LeftParen) {
+ nextMember = memberCall(tt, lhs, yieldHandling, possibleError,
+ OptionalKind::Optional);
+ if (!nextMember) {
+ return null();
+ }
+ } else {
+ error(JSMSG_NAME_AFTER_DOT);
+ return null();
+ }
+ } else if (tt == TokenKind::Dot) {
+ if (!tokenStream.getToken(&tt)) {
+ return null();
+ }
+ if (TokenKindIsPossibleIdentifierName(tt)) {
+ nextMember = memberPropertyAccess(lhs);
+ if (!nextMember) {
+ return null();
+ }
+ } else if (tt == TokenKind::PrivateName) {
+ nextMember = memberPrivateAccess(lhs);
+ if (!nextMember) {
+ return null();
+ }
+ } else {
+ error(JSMSG_NAME_AFTER_DOT);
+ return null();
+ }
+ } else if (tt == TokenKind::LeftBracket) {
+ nextMember = memberElemAccess(lhs, yieldHandling);
+ if (!nextMember) {
+ return null();
+ }
+ } else if (tt == TokenKind::LeftParen) {
+ nextMember = memberCall(tt, lhs, yieldHandling, possibleError);
+ if (!nextMember) {
+ return null();
+ }
+ } else if (tt == TokenKind::TemplateHead ||
+ tt == TokenKind::NoSubsTemplate) {
+ error(JSMSG_BAD_OPTIONAL_TEMPLATE);
+ return null();
+ } else {
+ anyChars.ungetToken();
+ break;
+ }
+
+ MOZ_ASSERT(nextMember);
+ lhs = nextMember;
+ }
+
+ return handler_.newOptionalChain(begin, lhs);
+}
+
+template <class ParseHandler, typename Unit>
+typename ParseHandler::Node GeneralParser<ParseHandler, Unit>::unaryExpr(
+ YieldHandling yieldHandling, TripledotHandling tripledotHandling,
+ PossibleError* possibleError /* = nullptr */,
+ InvokedPrediction invoked /* = PredictUninvoked */,
+ PrivateNameHandling privateNameHandling /* = PrivateNameProhibited */) {
+ AutoCheckRecursionLimit recursion(this->fc_);
+ if (!recursion.check(this->fc_)) {
+ return null();
+ }
+
+ TokenKind tt;
+ if (!tokenStream.getToken(&tt, TokenStream::SlashIsRegExp)) {
+ return null();
+ }
+ uint32_t begin = pos().begin;
+ switch (tt) {
+ case TokenKind::Void:
+ return unaryOpExpr(yieldHandling, ParseNodeKind::VoidExpr, begin);
+ case TokenKind::Not:
+ return unaryOpExpr(yieldHandling, ParseNodeKind::NotExpr, begin);
+ case TokenKind::BitNot:
+ return unaryOpExpr(yieldHandling, ParseNodeKind::BitNotExpr, begin);
+ case TokenKind::Add:
+ return unaryOpExpr(yieldHandling, ParseNodeKind::PosExpr, begin);
+ case TokenKind::Sub:
+ return unaryOpExpr(yieldHandling, ParseNodeKind::NegExpr, begin);
+
+ case TokenKind::TypeOf: {
+ // The |typeof| operator is specially parsed to distinguish its
+ // application to a name, from its application to a non-name
+ // expression:
+ //
+ // // Looks up the name, doesn't find it and so evaluates to
+ // // "undefined".
+ // assertEq(typeof nonExistentName, "undefined");
+ //
+ // // Evaluates expression, triggering a runtime ReferenceError for
+ // // the undefined name.
+ // typeof (1, nonExistentName);
+ Node kid = unaryExpr(yieldHandling, TripledotProhibited);
+ if (!kid) {
+ return null();
+ }
+
+ return handler_.newTypeof(begin, kid);
+ }
+
+ case TokenKind::Inc:
+ case TokenKind::Dec: {
+ TokenKind tt2;
+ if (!tokenStream.getToken(&tt2, TokenStream::SlashIsRegExp)) {
+ return null();
+ }
+
+ uint32_t operandOffset = pos().begin;
+ Node operand = optionalExpr(yieldHandling, TripledotProhibited, tt2);
+ if (!operand || !checkIncDecOperand(operand, operandOffset)) {
+ return null();
+ }
+ ParseNodeKind pnk = (tt == TokenKind::Inc)
+ ? ParseNodeKind::PreIncrementExpr
+ : ParseNodeKind::PreDecrementExpr;
+ return handler_.newUpdate(pnk, begin, operand);
+ }
+ case TokenKind::PrivateName: {
+ if (privateNameHandling == PrivateNameHandling::PrivateNameAllowed) {
+ TaggedParserAtomIndex field = anyChars.currentName();
+ return privateNameReference(field);
+ }
+ error(JSMSG_INVALID_PRIVATE_NAME_IN_UNARY_EXPR);
+ return null();
+ }
+
+ case TokenKind::Delete: {
+ uint32_t exprOffset;
+ if (!tokenStream.peekOffset(&exprOffset, TokenStream::SlashIsRegExp)) {
+ return null();
+ }
+
+ Node expr = unaryExpr(yieldHandling, TripledotProhibited);
+ if (!expr) {
+ return null();
+ }
+
+ // Per spec, deleting most unary expressions is valid -- it simply
+ // returns true -- except for two cases:
+ // 1. `var x; ...; delete x` is a syntax error in strict mode.
+ // 2. Private fields cannot be deleted.
+ if (handler_.isName(expr)) {
+ if (!strictModeErrorAt(exprOffset, JSMSG_DEPRECATED_DELETE_OPERAND)) {
+ return null();
+ }
+
+ pc_->sc()->setBindingsAccessedDynamically();
+ }
+
+ if (handler_.isPrivateMemberAccess(expr)) {
+ errorAt(exprOffset, JSMSG_PRIVATE_DELETE);
+ return null();
+ }
+
+ return handler_.newDelete(begin, expr);
+ }
+ case TokenKind::Await: {
+ // If we encounter an await in a module, mark it as async.
+ if (!pc_->isAsync() && pc_->sc()->isModule()) {
+ if (!options().topLevelAwait) {
+ error(JSMSG_TOP_LEVEL_AWAIT_NOT_SUPPORTED);
+ return null();
+ }
+ pc_->sc()->asModuleContext()->setIsAsync();
+ MOZ_ASSERT(pc_->isAsync());
+ }
+
+ if (pc_->isAsync()) {
+ if (inParametersOfAsyncFunction()) {
+ error(JSMSG_AWAIT_IN_PARAMETER);
+ return null();
+ }
+ Node kid =
+ unaryExpr(yieldHandling, tripledotHandling, possibleError, invoked);
+ if (!kid) {
+ return null();
+ }
+ pc_->lastAwaitOffset = begin;
+ return handler_.newAwaitExpression(begin, kid);
+ }
+ }
+
+ [[fallthrough]];
+
+ default: {
+ Node expr = optionalExpr(yieldHandling, tripledotHandling, tt,
+ possibleError, invoked);
+ if (!expr) {
+ return null();
+ }
+
+ /* Don't look across a newline boundary for a postfix incop. */
+ if (!tokenStream.peekTokenSameLine(&tt)) {
+ return null();
+ }
+
+ if (tt != TokenKind::Inc && tt != TokenKind::Dec) {
+ return expr;
+ }
+
+ tokenStream.consumeKnownToken(tt);
+ if (!checkIncDecOperand(expr, begin)) {
+ return null();
+ }
+
+ ParseNodeKind pnk = (tt == TokenKind::Inc)
+ ? ParseNodeKind::PostIncrementExpr
+ : ParseNodeKind::PostDecrementExpr;
+ return handler_.newUpdate(pnk, begin, expr);
+ }
+ }
+}
+
+template <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) const {
+ 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;
+
+ AutoCheckRecursionLimit recursion(this->fc_);
+ if (!recursion.check(this->fc_)) {
+ return null();
+ }
+
+ /* Check for new expression first. */
+ if (tt == TokenKind::New) {
+ uint32_t newBegin = pos().begin;
+ // Make sure this wasn't a |new.target| in disguise.
+ NewTargetNodeType newTarget;
+ if (!tryNewTarget(&newTarget)) {
+ return null();
+ }
+ if (newTarget) {
+ lhs = newTarget;
+ } else {
+ // Gotten by tryNewTarget
+ tt = anyChars.currentToken().type;
+ Node ctorExpr = memberExpr(yieldHandling, TripledotProhibited, tt,
+ /* allowCallSyntax = */ false,
+ /* possibleError = */ nullptr, PredictInvoked);
+ if (!ctorExpr) {
+ return null();
+ }
+
+ // If we have encountered an optional chain, in the form of `new
+ // ClassName?.()` then we need to throw, as this is disallowed by the
+ // spec.
+ bool optionalToken;
+ if (!tokenStream.matchToken(&optionalToken, TokenKind::OptionalChain)) {
+ return null();
+ }
+ if (optionalToken) {
+ errorAt(newBegin, JSMSG_BAD_NEW_OPTIONAL);
+ return null();
+ }
+
+ bool matched;
+ if (!tokenStream.matchToken(&matched, TokenKind::LeftParen)) {
+ return null();
+ }
+
+ bool isSpread = false;
+ Node args;
+ if (matched) {
+ args = argumentList(yieldHandling, &isSpread);
+ } else {
+ args = handler_.newArguments(pos());
+ }
+
+ if (!args) {
+ return null();
+ }
+
+ lhs = handler_.newNewExpression(newBegin, ctorExpr, args, isSpread);
+ if (!lhs) {
+ return null();
+ }
+ }
+ } else if (tt == TokenKind::Super) {
+ NameNodeType thisName = newThisName();
+ if (!thisName) {
+ return null();
+ }
+ lhs = handler_.newSuperBase(thisName, pos());
+ if (!lhs) {
+ return null();
+ }
+ } else if (tt == TokenKind::Import) {
+ lhs = importExpr(yieldHandling, allowCallSyntax);
+ if (!lhs) {
+ return null();
+ }
+ } else {
+ lhs = primaryExpr(yieldHandling, tripledotHandling, tt, possibleError,
+ invoked);
+ if (!lhs) {
+ return null();
+ }
+ }
+
+ MOZ_ASSERT_IF(handler_.isSuperBase(lhs),
+ anyChars.isCurrentTokenType(TokenKind::Super));
+
+ while (true) {
+ if (!tokenStream.getToken(&tt)) {
+ return null();
+ }
+ if (tt == TokenKind::Eof) {
+ anyChars.ungetToken();
+ break;
+ }
+
+ Node nextMember;
+ if (tt == TokenKind::Dot) {
+ if (!tokenStream.getToken(&tt)) {
+ return null();
+ }
+
+ if (TokenKindIsPossibleIdentifierName(tt)) {
+ nextMember = memberPropertyAccess(lhs);
+ if (!nextMember) {
+ return null();
+ }
+ } else if (tt == TokenKind::PrivateName) {
+ nextMember = memberPrivateAccess(lhs);
+ if (!nextMember) {
+ return null();
+ }
+ } else {
+ error(JSMSG_NAME_AFTER_DOT);
+ return null();
+ }
+ } else if (tt == TokenKind::LeftBracket) {
+ nextMember = memberElemAccess(lhs, yieldHandling);
+ if (!nextMember) {
+ return null();
+ }
+ } else if ((allowCallSyntax && tt == TokenKind::LeftParen) ||
+ tt == TokenKind::TemplateHead ||
+ tt == TokenKind::NoSubsTemplate) {
+ if (handler_.isSuperBase(lhs)) {
+ if (!pc_->sc()->allowSuperCall()) {
+ error(JSMSG_BAD_SUPERCALL);
+ return null();
+ }
+
+ if (tt != TokenKind::LeftParen) {
+ error(JSMSG_BAD_SUPER);
+ return null();
+ }
+
+ nextMember = memberSuperCall(lhs, yieldHandling);
+ if (!nextMember) {
+ return null();
+ }
+
+ if (!noteUsedName(
+ TaggedParserAtomIndex::WellKnown::dotInitializers())) {
+ return null();
+ }
+ } else {
+ nextMember = memberCall(tt, lhs, yieldHandling, possibleError);
+ if (!nextMember) {
+ return null();
+ }
+ }
+ } else {
+ anyChars.ungetToken();
+ if (handler_.isSuperBase(lhs)) {
+ break;
+ }
+ return lhs;
+ }
+
+ lhs = nextMember;
+ }
+
+ if (handler_.isSuperBase(lhs)) {
+ error(JSMSG_BAD_SUPER);
+ return null();
+ }
+
+ return lhs;
+}
+
+template <class ParseHandler>
+inline typename ParseHandler::NameNodeType
+PerHandlerParser<ParseHandler>::newName(TaggedParserAtomIndex name) {
+ return newName(name, pos());
+}
+
+template <class ParseHandler>
+inline typename ParseHandler::NameNodeType
+PerHandlerParser<ParseHandler>::newName(TaggedParserAtomIndex name,
+ TokenPos pos) {
+ return handler_.newName(name, pos);
+}
+
+template <class ParseHandler>
+inline typename ParseHandler::NameNodeType
+PerHandlerParser<ParseHandler>::newPrivateName(TaggedParserAtomIndex 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);
+ TaggedParserAtomIndex field = anyChars.currentName();
+ if (handler_.isSuperBase(lhs) && !checkAndMarkSuperScope()) {
+ error(JSMSG_BAD_SUPERPROP, "property");
+ return null();
+ }
+
+ NameNodeType name = handler_.newPropertyName(field, pos());
+ if (!name) {
+ return null();
+ }
+
+ if (optionalKind == OptionalKind::Optional) {
+ MOZ_ASSERT(!handler_.isSuperBase(lhs));
+ return handler_.newOptionalPropertyAccess(lhs, name);
+ }
+ return handler_.newPropertyAccess(lhs, name);
+}
+
+template <class ParseHandler, typename Unit>
+typename ParseHandler::Node
+GeneralParser<ParseHandler, Unit>::memberPrivateAccess(
+ Node lhs, OptionalKind optionalKind /* = OptionalKind::NonOptional */) {
+ MOZ_ASSERT(anyChars.currentToken().type == TokenKind::PrivateName);
+
+ TaggedParserAtomIndex field = anyChars.currentName();
+ // Cannot access private fields on super.
+ if (handler_.isSuperBase(lhs)) {
+ error(JSMSG_BAD_SUPERPRIVATE);
+ return null();
+ }
+
+ NameNodeType privateName = privateNameReference(field);
+ if (!privateName) {
+ return null();
+ }
+
+ if (optionalKind == OptionalKind::Optional) {
+ MOZ_ASSERT(!handler_.isSuperBase(lhs));
+ return handler_.newOptionalPrivateMemberAccess(lhs, privateName, pos().end);
+ }
+ return handler_.newPrivateMemberAccess(lhs, privateName, pos().end);
+}
+
+template <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();
+ }
+
+ // |super()| implicitly reads |new.target|.
+ if (!noteUsedName(TaggedParserAtomIndex::WellKnown::dotNewTarget())) {
+ return null();
+ }
+
+ NameNodeType thisName = newThisName();
+ if (!thisName) {
+ return null();
+ }
+
+ return handler_.newSetThis(thisName, superCall);
+}
+
+template <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_.isPropertyOrPrivateMemberAccess(lhs) ||
+ handler_.isOptionalPropertyOrPrivateMemberAccess(lhs))) {
+ error(JSMSG_SELFHOSTED_METHOD_CALL);
+ return null();
+ }
+
+ MOZ_ASSERT(tt == TokenKind::LeftParen || tt == TokenKind::TemplateHead ||
+ tt == TokenKind::NoSubsTemplate,
+ "Unexpected token kind for member call");
+
+ JSOp op = JSOp::Call;
+ bool maybeAsyncArrow = false;
+ if (tt == TokenKind::LeftParen && optionalKind == OptionalKind::NonOptional) {
+ if (handler_.isAsyncKeyword(lhs)) {
+ // |async (| can be the start of an async arrow
+ // function, so we need to defer reporting possible
+ // errors from destructuring syntax. To give better
+ // error messages, we only allow the AsyncArrowHead
+ // part of the CoverCallExpressionAndAsyncArrowHead
+ // syntax when the initial name is "async".
+ maybeAsyncArrow = true;
+ } else if (handler_.isEvalName(lhs)) {
+ // Select the right Eval op and flag pc_ as having a
+ // direct eval.
+ op = pc_->sc()->strict() ? JSOp::StrictEval : JSOp::Eval;
+ pc_->sc()->setBindingsAccessedDynamically();
+ pc_->sc()->setHasDirectEval();
+
+ // In non-strict mode code, direct calls to eval can
+ // add variables to the call object.
+ if (pc_->isFunctionBox() && !pc_->sc()->strict()) {
+ pc_->functionBox()->setFunHasExtensibleScope();
+ }
+
+ // If we're in a method, mark the method as requiring
+ // support for 'super', since direct eval code can use
+ // it. (If we're not in a method, that's fine, so
+ // ignore the return value.)
+ checkAndMarkSuperScope();
+ }
+ }
+
+ if (tt == TokenKind::LeftParen) {
+ bool isSpread = false;
+ PossibleError* asyncPossibleError =
+ maybeAsyncArrow ? possibleError : nullptr;
+ Node args = argumentList(yieldHandling, &isSpread, asyncPossibleError);
+ if (!args) {
+ return null();
+ }
+ if (isSpread) {
+ if (op == JSOp::Eval) {
+ op = JSOp::SpreadEval;
+ } else if (op == JSOp::StrictEval) {
+ op = JSOp::StrictSpreadEval;
+ } else {
+ op = JSOp::SpreadCall;
+ }
+ }
+
+ if (optionalKind == OptionalKind::Optional) {
+ return handler_.newOptionalCall(lhs, args, op);
+ }
+ return handler_.newCall(lhs, args, op);
+ }
+
+ ListNodeType args = handler_.newArguments(pos());
+ if (!args) {
+ return null();
+ }
+
+ if (!taggedTemplate(yieldHandling, args, tt)) {
+ return null();
+ }
+
+ if (optionalKind == OptionalKind::Optional) {
+ error(JSMSG_BAD_OPTIONAL_TEMPLATE);
+ return null();
+ }
+
+ return handler_.newTaggedTemplate(lhs, args, op);
+}
+
+template <class ParseHandler, typename Unit>
+bool GeneralParser<ParseHandler, Unit>::checkLabelOrIdentifierReference(
+ TaggedParserAtomIndex ident, uint32_t offset, YieldHandling yieldHandling,
+ TokenKind hint /* = TokenKind::Limit */) {
+ TokenKind tt;
+ if (hint == TokenKind::Limit) {
+ tt = ReservedWordTokenKind(ident);
+ } else {
+ // All non-reserved word kinds are folded into TokenKind::Limit in
+ // ReservedWordTokenKind and the following code.
+ if (hint == TokenKind::Name || hint == TokenKind::PrivateName) {
+ hint = TokenKind::Limit;
+ }
+ MOZ_ASSERT(hint == ReservedWordTokenKind(ident),
+ "hint doesn't match actual token kind");
+ tt = hint;
+ }
+
+ if (!pc_->sc()->allowArguments() &&
+ ident == TaggedParserAtomIndex::WellKnown::arguments()) {
+ error(JSMSG_BAD_ARGUMENTS);
+ return false;
+ }
+
+ if (tt == TokenKind::Limit) {
+ // Either TokenKind::Name or TokenKind::PrivateName
+ return true;
+ }
+ if (TokenKindIsContextualKeyword(tt)) {
+ if (tt == TokenKind::Yield) {
+ if (yieldHandling == YieldIsKeyword) {
+ errorAt(offset, JSMSG_RESERVED_ID, "yield");
+ return false;
+ }
+ if (pc_->sc()->strict()) {
+ if (!strictModeErrorAt(offset, JSMSG_RESERVED_ID, "yield")) {
+ return false;
+ }
+ }
+ return true;
+ }
+ if (tt == TokenKind::Await) {
+ if (awaitIsKeyword() || awaitIsDisallowed()) {
+ errorAt(offset, JSMSG_RESERVED_ID, "await");
+ return false;
+ }
+ return true;
+ }
+ if (pc_->sc()->strict()) {
+ if (tt == TokenKind::Let) {
+ if (!strictModeErrorAt(offset, JSMSG_RESERVED_ID, "let")) {
+ return false;
+ }
+ return true;
+ }
+ if (tt == TokenKind::Static) {
+ if (!strictModeErrorAt(offset, JSMSG_RESERVED_ID, "static")) {
+ return false;
+ }
+ return true;
+ }
+ }
+ return true;
+ }
+ if (TokenKindIsStrictReservedWord(tt)) {
+ if (pc_->sc()->strict()) {
+ if (!strictModeErrorAt(offset, JSMSG_RESERVED_ID,
+ ReservedWordToCharZ(tt))) {
+ return false;
+ }
+ }
+ return true;
+ }
+ if (TokenKindIsKeyword(tt) || TokenKindIsReservedWordLiteral(tt)) {
+ errorAt(offset, JSMSG_INVALID_ID, ReservedWordToCharZ(tt));
+ return false;
+ }
+ if (TokenKindIsFutureReservedWord(tt)) {
+ errorAt(offset, JSMSG_RESERVED_ID, ReservedWordToCharZ(tt));
+ return false;
+ }
+ MOZ_ASSERT_UNREACHABLE("Unexpected reserved word kind.");
+ return false;
+}
+
+template <class ParseHandler, typename Unit>
+bool GeneralParser<ParseHandler, Unit>::checkBindingIdentifier(
+ TaggedParserAtomIndex ident, uint32_t offset, YieldHandling yieldHandling,
+ TokenKind hint /* = TokenKind::Limit */) {
+ if (pc_->sc()->strict()) {
+ if (ident == TaggedParserAtomIndex::WellKnown::arguments()) {
+ if (!strictModeErrorAt(offset, JSMSG_BAD_STRICT_ASSIGN, "arguments")) {
+ return false;
+ }
+ return true;
+ }
+
+ if (ident == TaggedParserAtomIndex::WellKnown::eval()) {
+ if (!strictModeErrorAt(offset, JSMSG_BAD_STRICT_ASSIGN, "eval")) {
+ return false;
+ }
+ return true;
+ }
+ }
+
+ return checkLabelOrIdentifierReference(ident, offset, yieldHandling, hint);
+}
+
+template <class ParseHandler, typename Unit>
+TaggedParserAtomIndex
+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(this->parserAtoms())
+ ? anyChars.currentToken().type
+ : TokenKind::Limit;
+ TaggedParserAtomIndex ident = anyChars.currentName();
+ if (!checkLabelOrIdentifierReference(ident, pos().begin, yieldHandling,
+ hint)) {
+ return TaggedParserAtomIndex::null();
+ }
+ return ident;
+}
+
+template <class ParseHandler, typename Unit>
+TaggedParserAtomIndex GeneralParser<ParseHandler, Unit>::bindingIdentifier(
+ YieldHandling yieldHandling) {
+ TokenKind hint = !anyChars.currentNameHasEscapes(this->parserAtoms())
+ ? anyChars.currentToken().type
+ : TokenKind::Limit;
+ TaggedParserAtomIndex ident = anyChars.currentName();
+ if (!checkBindingIdentifier(ident, pos().begin, yieldHandling, hint)) {
+ return TaggedParserAtomIndex::null();
+ }
+ return ident;
+}
+
+template <class ParseHandler>
+typename ParseHandler::NameNodeType
+PerHandlerParser<ParseHandler>::identifierReference(
+ TaggedParserAtomIndex 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(
+ TaggedParserAtomIndex 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_.reuseRegexpSyntaxParse()) {
+ // Verify that the Regexp will syntax parse when the time comes to
+ // instantiate it. If we have already done a syntax parse, we can
+ // skip this.
+ if (!irregexp::CheckPatternSyntax(this->alloc_, this->fc_->stackLimit(),
+ anyChars, range, flags, Some(line),
+ Some(column))) {
+ return nullptr;
+ }
+ }
+
+ auto atom =
+ this->parserAtoms().internChar16(fc_, chars.begin(), chars.length());
+ if (!atom) {
+ return nullptr;
+ }
+ // RegExp patterm must be atomized.
+ this->parserAtoms().markUsedByStencil(atom, ParserAtom::Atomize::Yes);
+
+ RegExpIndex index(this->compilationState_.regExpData.length());
+ if (uint32_t(index) >= TaggedScriptThingIndex::IndexLimit) {
+ ReportAllocationOverflow(fc_);
+ return nullptr;
+ }
+ if (!this->compilationState_.regExpData.emplaceBack(atom, flags)) {
+ js::ReportOutOfMemory(this->fc_);
+ return nullptr;
+ }
+
+ return handler_.newRegExp(index, pos());
+}
+
+template <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());
+ if (!irregexp::CheckPatternSyntax(this->alloc_, this->fc_->stackLimit(),
+ 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();
+ if (chars.length() > UINT32_MAX) {
+ ReportAllocationOverflow(fc_);
+ return null();
+ }
+
+ BigIntIndex index(this->compilationState_.bigIntData.length());
+ if (uint32_t(index) >= TaggedScriptThingIndex::IndexLimit) {
+ ReportAllocationOverflow(fc_);
+ return null();
+ }
+ if (!this->compilationState_.bigIntData.emplaceBack()) {
+ js::ReportOutOfMemory(this->fc_);
+ return null();
+ }
+
+ if (!this->compilationState_.bigIntData[index].init(
+ this->fc_, this->stencilAlloc(), chars)) {
+ return null();
+ }
+
+ bool isZero = this->compilationState_.bigIntData[index].isZero();
+
+ // Should the operations below fail, the buffer held by data will
+ // be cleaned up by the CompilationState destructor.
+ return handler_.newBigInt(index, isZero, pos());
+}
+
+template <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_.isPropertyOrPrivateMemberAccess(expr)) {
+ return exprPossibleError->checkForExpressionError();
+ }
+
+ // |expr| may end up as a destructuring assignment target, so we need to
+ // validate it's either a name or can be parsed as a nested destructuring
+ // pattern. Property accessors are also valid assignment targets, but
+ // those are already handled above.
+
+ exprPossibleError->transferErrorsTo(possibleError);
+
+ // Return early if a pending destructuring error is already present.
+ if (possibleError->hasPendingDestructuringError()) {
+ return true;
+ }
+
+ if (handler_.isName(expr)) {
+ checkDestructuringAssignmentName(handler_.asName(expr), exprPos,
+ possibleError);
+ return true;
+ }
+
+ if (handler_.isUnparenthesizedDestructuringPattern(expr)) {
+ if (behavior == TargetBehavior::ForbidAssignmentPattern) {
+ possibleError->setPendingDestructuringErrorAt(exprPos,
+ JSMSG_BAD_DESTRUCT_TARGET);
+ }
+ return true;
+ }
+
+ // Parentheses are forbidden around destructuring *patterns* (but allowed
+ // around names). Use our nicer error message for parenthesized, nested
+ // patterns if nested destructuring patterns are allowed.
+ if (handler_.isParenthesizedDestructuringPattern(expr) &&
+ behavior != TargetBehavior::ForbidAssignmentPattern) {
+ possibleError->setPendingDestructuringErrorAt(exprPos,
+ JSMSG_BAD_DESTRUCT_PARENS);
+ } else {
+ possibleError->setPendingDestructuringErrorAt(exprPos,
+ JSMSG_BAD_DESTRUCT_TARGET);
+ }
+
+ return true;
+}
+
+template <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)) {
+ if (pc_->sc()->strict()) {
+ possibleError->setPendingDestructuringErrorAt(
+ namePos, JSMSG_BAD_STRICT_ASSIGN_ARGUMENTS);
+ } else {
+ possibleError->setPendingDestructuringWarningAt(
+ namePos, JSMSG_BAD_STRICT_ASSIGN_ARGUMENTS);
+ }
+ return;
+ }
+
+ if (handler_.isEvalName(name)) {
+ if (pc_->sc()->strict()) {
+ possibleError->setPendingDestructuringErrorAt(
+ namePos, JSMSG_BAD_STRICT_ASSIGN_EVAL);
+ } else {
+ possibleError->setPendingDestructuringWarningAt(
+ namePos, JSMSG_BAD_STRICT_ASSIGN_EVAL);
+ }
+ return;
+ }
+ }
+}
+
+template <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,
+ TaggedParserAtomIndex* propAtomOut) {
+ // PropertyName[Yield, Await]:
+ // LiteralPropertyName
+ // ComputedPropertyName[?Yield, ?Await]
+ //
+ // LiteralPropertyName:
+ // IdentifierName
+ // StringLiteral
+ // NumericLiteral
+ TokenKind ltok = anyChars.currentToken().type;
+
+ *propAtomOut = TaggedParserAtomIndex::null();
+ switch (ltok) {
+ case TokenKind::Number: {
+ auto numAtom = NumberToParserAtom(fc_, this->parserAtoms(),
+ anyChars.currentToken().number());
+ if (!numAtom) {
+ return null();
+ }
+ *propAtomOut = numAtom;
+ return newNumber(anyChars.currentToken());
+ }
+
+ case TokenKind::BigInt: {
+ Node biNode = newBigInt();
+ if (!biNode) {
+ return null();
+ }
+ return handler_.newSyntheticComputedName(biNode, pos().begin, pos().end);
+ }
+ case TokenKind::String: {
+ auto str = anyChars.currentToken().atom();
+ *propAtomOut = str;
+ uint32_t index;
+ if (this->parserAtoms().isIndex(str, &index)) {
+ return handler_.newNumber(index, NoDecimal, pos());
+ }
+ return stringLiteral();
+ }
+
+ case TokenKind::LeftBracket:
+ return computedPropertyName(yieldHandling, maybeDecl, propertyNameContext,
+ propList);
+
+ case TokenKind::PrivateName: {
+ if (propertyNameContext != PropertyNameContext::PropertyNameInClass) {
+ error(JSMSG_ILLEGAL_PRIVATE_FIELD);
+ return null();
+ }
+
+ TaggedParserAtomIndex propName = anyChars.currentName();
+ *propAtomOut = propName;
+ return privateNameReference(propName);
+ }
+
+ default: {
+ if (!TokenKindIsPossibleIdentifierName(ltok)) {
+ error(JSMSG_UNEXPECTED_TOKEN, "property name", TokenKindToDesc(ltok));
+ return null();
+ }
+
+ TaggedParserAtomIndex name = anyChars.currentName();
+ *propAtomOut = name;
+ return handler_.newObjectLiteralPropertyName(name, pos());
+ }
+ }
+}
+
+// True if `kind` can be the first token of a PropertyName.
+static bool TokenKindCanStartPropertyName(TokenKind tt) {
+ return TokenKindIsPossibleIdentifierName(tt) || tt == TokenKind::String ||
+ tt == TokenKind::Number || tt == TokenKind::LeftBracket ||
+ tt == TokenKind::Mul || tt == TokenKind::BigInt ||
+ tt == TokenKind::PrivateName;
+}
+
+template <class ParseHandler, typename Unit>
+typename ParseHandler::Node
+GeneralParser<ParseHandler, Unit>::propertyOrMethodName(
+ YieldHandling yieldHandling, PropertyNameContext propertyNameContext,
+ const Maybe<DeclarationKind>& maybeDecl, ListNodeType propList,
+ PropertyType* propType, TaggedParserAtomIndex* propAtomOut) {
+ // We're parsing an object literal, class, or destructuring pattern;
+ // propertyNameContext tells which one. This method parses any of the
+ // following, storing the corresponding PropertyType in `*propType` to tell
+ // the caller what we parsed:
+ //
+ // async [no LineTerminator here] PropertyName
+ // ==> PropertyType::AsyncMethod
+ // async [no LineTerminator here] * PropertyName
+ // ==> PropertyType::AsyncGeneratorMethod
+ // * PropertyName ==> PropertyType::GeneratorMethod
+ // get PropertyName ==> PropertyType::Getter
+ // set PropertyName ==> PropertyType::Setter
+ // accessor PropertyName ==> PropertyType::FieldWithAccessor
+ // PropertyName : ==> PropertyType::Normal
+ // PropertyName ==> see below
+ //
+ // In the last case, where there's not a `:` token to consume, we peek at
+ // (but don't consume) the next token to decide how to set `*propType`.
+ //
+ // `,` or `}` ==> PropertyType::Shorthand
+ // `(` ==> PropertyType::Method
+ // `=`, not in a class ==> PropertyType::CoverInitializedName
+ // '=', in a class ==> PropertyType::Field
+ // any token, in a class ==> PropertyType::Field (ASI)
+ //
+ // The caller must check `*propType` and throw if whatever we parsed isn't
+ // allowed here (for example, a getter in a destructuring pattern).
+ //
+ // This method does *not* match `static` (allowed in classes) or `...`
+ // (allowed in object literals and patterns). The caller must take care of
+ // those before calling this method.
+
+ TokenKind ltok;
+ if (!tokenStream.getToken(&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` or `accessor`, indicating an accessor.
+ bool isGenerator = false;
+ bool isAsync = false;
+ bool isGetter = false;
+ bool isSetter = false;
+#ifdef ENABLE_DECORATORS
+ bool hasAccessor = false;
+#endif
+
+ if (ltok == TokenKind::Async) {
+ // `async` is also a PropertyName by itself (it's a conditional keyword),
+ // so peek at the next token to see if we're really looking at a method.
+ TokenKind tt = TokenKind::Eof;
+ if (!tokenStream.peekTokenSameLine(&tt)) {
+ return null();
+ }
+ if (TokenKindCanStartPropertyName(tt)) {
+ isAsync = true;
+ tokenStream.consumeKnownToken(tt);
+ ltok = tt;
+ }
+ }
+
+ if (ltok == TokenKind::Mul) {
+ isGenerator = true;
+ if (!tokenStream.getToken(&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);
+ }
+ }
+
+#ifdef ENABLE_DECORATORS
+ if (!isGenerator && !isAsync && propertyNameContext == PropertyNameInClass &&
+ ltok == TokenKind::Accessor) {
+ MOZ_ASSERT(!isGetter && !isSetter);
+ TokenKind tt;
+ if (!tokenStream.peekTokenSameLine(&tt)) {
+ return null();
+ }
+
+ // The target rule is `accessor [no LineTerminator here]
+ // ClassElementName[?Yield, ?Await] Initializer[+In, ?Yield, ?Await]opt`
+ if (TokenKindCanStartPropertyName(tt)) {
+ tokenStream.consumeKnownToken(tt);
+ hasAccessor = true;
+ }
+ }
+#endif
+
+ Node propName = propertyName(yieldHandling, propertyNameContext, maybeDecl,
+ propList, propAtomOut);
+ if (!propName) {
+ return null();
+ }
+
+ // Grab the next token following the property/method name.
+ // (If this isn't a colon, we're going to either put it back or throw.)
+ TokenKind tt;
+ if (!tokenStream.getToken(&tt)) {
+ return null();
+ }
+
+ if (tt == TokenKind::Colon) {
+ if (isGenerator || isAsync || isGetter || isSetter
+#ifdef ENABLE_DECORATORS
+ || hasAccessor
+#endif
+ ) {
+ error(JSMSG_BAD_PROP_ID);
+ return null();
+ }
+ *propType = PropertyType::Normal;
+ return propName;
+ }
+
+ if (propertyNameContext != PropertyNameInClass &&
+ TokenKindIsPossibleIdentifierName(ltok) &&
+ (tt == TokenKind::Comma || tt == TokenKind::RightCurly ||
+ tt == TokenKind::Assign)) {
+#ifdef ENABLE_DECORATORS
+ MOZ_ASSERT(!hasAccessor);
+#endif
+ if (isGenerator || isAsync || isGetter || isSetter) {
+ error(JSMSG_BAD_PROP_ID);
+ return null();
+ }
+
+ anyChars.ungetToken();
+ *propType = tt == TokenKind::Assign ? PropertyType::CoverInitializedName
+ : PropertyType::Shorthand;
+ return propName;
+ }
+
+ if (tt == TokenKind::LeftParen) {
+ anyChars.ungetToken();
+
+#ifdef ENABLE_RECORD_TUPLE
+ if (propertyNameContext == PropertyNameInRecord) {
+ // Record & Tuple proposal, section 7.1.1:
+ // RecordPropertyDefinition doesn't cover methods
+ error(JSMSG_BAD_PROP_ID);
+ return null();
+ }
+#endif
+
+#ifdef ENABLE_DECORATORS
+ if (hasAccessor) {
+ error(JSMSG_BAD_PROP_ID);
+ return null();
+ }
+#endif
+
+ if (isGenerator && isAsync) {
+ *propType = PropertyType::AsyncGeneratorMethod;
+ } else if (isGenerator) {
+ *propType = PropertyType::GeneratorMethod;
+ } else if (isAsync) {
+ *propType = PropertyType::AsyncMethod;
+ } else if (isGetter) {
+ *propType = PropertyType::Getter;
+ } else if (isSetter) {
+ *propType = PropertyType::Setter;
+ } else {
+ *propType = PropertyType::Method;
+ }
+ return propName;
+ }
+
+ if (propertyNameContext == PropertyNameInClass) {
+ if (isGenerator || isAsync || isGetter || isSetter) {
+ error(JSMSG_BAD_PROP_ID);
+ return null();
+ }
+ anyChars.ungetToken();
+#ifdef ENABLE_DECORATORS
+ if (!hasAccessor) {
+ *propType = PropertyType::Field;
+ } else {
+ *propType = PropertyType::FieldWithAccessor;
+ }
+#else
+ *propType = PropertyType::Field;
+#endif
+ return propName;
+ }
+
+ error(JSMSG_COLON_AFTER_ID);
+ return null();
+}
+
+template <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();
+ TaggedParserAtomIndex propAtom;
+ for (;;) {
+ TokenKind tt;
+ if (!tokenStream.peekToken(&tt)) {
+ return null();
+ }
+ if (tt == TokenKind::RightCurly) {
+ break;
+ }
+
+ if (tt == TokenKind::TripleDot) {
+ tokenStream.consumeKnownToken(TokenKind::TripleDot);
+ uint32_t begin = pos().begin;
+
+ TokenPos innerPos;
+ if (!tokenStream.peekTokenPos(&innerPos, TokenStream::SlashIsRegExp)) {
+ return null();
+ }
+
+ PossibleError possibleErrorInner(*this);
+ Node inner = assignExpr(InAllowed, yieldHandling, TripledotProhibited,
+ &possibleErrorInner);
+ if (!inner) {
+ return null();
+ }
+ if (!checkDestructuringAssignmentTarget(
+ inner, innerPos, &possibleErrorInner, possibleError,
+ TargetBehavior::ForbidAssignmentPattern)) {
+ return null();
+ }
+ if (!handler_.addSpreadProperty(literal, begin, inner)) {
+ return null();
+ }
+ } else {
+ TokenPos namePos = anyChars.nextToken().pos;
+
+ PropertyType propType;
+ Node propName =
+ propertyOrMethodName(yieldHandling, PropertyNameInLiteral, declKind,
+ literal, &propType, &propAtom);
+ if (!propName) {
+ return null();
+ }
+
+ if (propType == PropertyType::Normal) {
+ TokenPos exprPos;
+ if (!tokenStream.peekTokenPos(&exprPos, TokenStream::SlashIsRegExp)) {
+ return null();
+ }
+
+ PossibleError possibleErrorInner(*this);
+ Node propExpr = assignExpr(InAllowed, yieldHandling,
+ TripledotProhibited, &possibleErrorInner);
+ if (!propExpr) {
+ return null();
+ }
+
+ if (!checkDestructuringAssignmentElement(
+ propExpr, exprPos, &possibleErrorInner, possibleError)) {
+ return null();
+ }
+
+ if (propAtom == TaggedParserAtomIndex::WellKnown::proto()) {
+ if (seenPrototypeMutation) {
+ // Directly report the error when we're definitely not
+ // in a destructuring context.
+ if (!possibleError) {
+ errorAt(namePos.begin, JSMSG_DUPLICATE_PROTO_PROPERTY);
+ return null();
+ }
+
+ // Otherwise delay error reporting until we've
+ // determined whether or not we're destructuring.
+ possibleError->setPendingExpressionErrorAt(
+ namePos, JSMSG_DUPLICATE_PROTO_PROPERTY);
+ }
+ seenPrototypeMutation = true;
+
+ // This occurs *only* if we observe PropertyType::Normal!
+ // Only |__proto__: v| mutates [[Prototype]]. Getters,
+ // setters, method/generator definitions, computed
+ // property name versions of all of these, and shorthands
+ // do not.
+ if (!handler_.addPrototypeMutation(literal, namePos.begin,
+ propExpr)) {
+ return null();
+ }
+ } else {
+ BinaryNodeType propDef =
+ handler_.newPropertyDefinition(propName, propExpr);
+ if (!propDef) {
+ return null();
+ }
+
+ handler_.addPropertyDefinition(literal, propDef);
+ }
+ } else if (propType == PropertyType::Shorthand) {
+ /*
+ * Support, e.g., |({x, y} = o)| as destructuring shorthand
+ * for |({x: x, y: y} = o)|, and |var o = {x, y}| as
+ * initializer shorthand for |var o = {x: x, y: y}|.
+ */
+ TaggedParserAtomIndex name = identifierReference(yieldHandling);
+ if (!name) {
+ return null();
+ }
+
+ NameNodeType nameExpr = identifierReference(name);
+ if (!nameExpr) {
+ return null();
+ }
+
+ if (possibleError) {
+ checkDestructuringAssignmentName(nameExpr, namePos, possibleError);
+ }
+
+ if (!handler_.addShorthand(literal, handler_.asName(propName),
+ nameExpr)) {
+ return null();
+ }
+ } else if (propType == PropertyType::CoverInitializedName) {
+ /*
+ * Support, e.g., |({x=1, y=2} = o)| as destructuring
+ * shorthand with default values, as per ES6 12.14.5
+ */
+ TaggedParserAtomIndex name = identifierReference(yieldHandling);
+ if (!name) {
+ return null();
+ }
+
+ Node lhs = identifierReference(name);
+ if (!lhs) {
+ return null();
+ }
+
+ tokenStream.consumeKnownToken(TokenKind::Assign);
+
+ if (!seenCoverInitializedName) {
+ // "shorthand default" or "CoverInitializedName" syntax is
+ // only valid in the case of destructuring.
+ seenCoverInitializedName = true;
+
+ if (!possibleError) {
+ // Destructuring defaults are definitely not allowed
+ // in this object literal, because of something the
+ // caller knows about the preceding code. For example,
+ // maybe the preceding token is an operator:
+ // |x + {y=z}|.
+ error(JSMSG_COLON_AFTER_ID);
+ return null();
+ }
+
+ // Here we set a pending error so that later in the parse,
+ // once we've determined whether or not we're
+ // destructuring, the error can be reported or ignored
+ // appropriately.
+ possibleError->setPendingExpressionErrorAt(pos(),
+ JSMSG_COLON_AFTER_ID);
+ }
+
+ if (const char* chars = nameIsArgumentsOrEval(lhs)) {
+ // |chars| is "arguments" or "eval" here.
+ if (!strictModeErrorAt(namePos.begin, JSMSG_BAD_STRICT_ASSIGN,
+ chars)) {
+ return null();
+ }
+ }
+
+ Node rhs = assignExpr(InAllowed, yieldHandling, TripledotProhibited);
+ if (!rhs) {
+ return null();
+ }
+
+ BinaryNodeType propExpr =
+ handler_.newAssignment(ParseNodeKind::AssignExpr, lhs, rhs);
+ if (!propExpr) {
+ return null();
+ }
+
+ if (!handler_.addPropertyDefinition(literal, propName, propExpr)) {
+ return null();
+ }
+ } else {
+ TaggedParserAtomIndex funName;
+ bool hasStaticName =
+ !anyChars.isCurrentTokenType(TokenKind::RightBracket) && propAtom;
+ if (hasStaticName) {
+ funName = propAtom;
+
+ if (propType == PropertyType::Getter ||
+ propType == PropertyType::Setter) {
+ funName = prefixAccessorName(propType, propAtom);
+ if (!funName) {
+ return null();
+ }
+ }
+ }
+
+ FunctionNodeType funNode =
+ methodDefinition(namePos.begin, propType, funName);
+ if (!funNode) {
+ return null();
+ }
+
+ AccessorType atype = ToAccessorType(propType);
+ if (!handler_.addObjectMethodDefinition(literal, propName, funNode,
+ atype)) {
+ return null();
+ }
+
+ if (possibleError) {
+ possibleError->setPendingDestructuringErrorAt(
+ namePos, JSMSG_BAD_DESTRUCT_TARGET);
+ }
+ }
+ }
+
+ bool matched;
+ if (!tokenStream.matchToken(&matched, TokenKind::Comma,
+ TokenStream::SlashIsInvalid)) {
+ return null();
+ }
+ if (!matched) {
+ break;
+ }
+ if (tt == TokenKind::TripleDot && possibleError) {
+ possibleError->setPendingDestructuringErrorAt(pos(),
+ JSMSG_REST_WITH_COMMA);
+ }
+ }
+
+ if (!mustMatchToken(
+ TokenKind::RightCurly, [this, openedPos](TokenKind actual) {
+ this->reportMissingClosing(JSMSG_CURLY_AFTER_LIST,
+ JSMSG_CURLY_OPENED, openedPos);
+ })) {
+ return null();
+ }
+
+ handler_.setEndPosition(literal, pos().end);
+ return literal;
+}
+
+#ifdef ENABLE_RECORD_TUPLE
+template <class ParseHandler, typename Unit>
+typename ParseHandler::ListNodeType
+GeneralParser<ParseHandler, Unit>::recordLiteral(YieldHandling yieldHandling) {
+ MOZ_ASSERT(anyChars.isCurrentTokenType(TokenKind::HashCurly));
+
+ uint32_t openedPos = pos().begin;
+
+ ListNodeType literal = handler_.newRecordLiteral(pos().begin);
+ if (!literal) {
+ return null();
+ }
+
+ TaggedParserAtomIndex propAtom;
+ for (;;) {
+ TokenKind tt;
+ if (!tokenStream.peekToken(&tt)) {
+ return null();
+ }
+ if (tt == TokenKind::RightCurly) {
+ break;
+ }
+
+ if (tt == TokenKind::TripleDot) {
+ tokenStream.consumeKnownToken(TokenKind::TripleDot);
+ uint32_t begin = pos().begin;
+
+ TokenPos innerPos;
+ if (!tokenStream.peekTokenPos(&innerPos, TokenStream::SlashIsRegExp)) {
+ return null();
+ }
+
+ Node inner = assignExpr(InAllowed, yieldHandling, TripledotProhibited);
+ if (!inner) {
+ return null();
+ }
+
+ if (!handler_.addSpreadProperty(literal, begin, inner)) {
+ return null();
+ }
+ } else {
+ TokenPos namePos = anyChars.nextToken().pos;
+
+ PropertyType propType;
+ Node propName = propertyOrMethodName(yieldHandling, PropertyNameInRecord,
+ /* maybeDecl */ Nothing(), literal,
+ &propType, &propAtom);
+ if (!propName) {
+ return null();
+ }
+
+ if (propType == PropertyType::Normal) {
+ TokenPos exprPos;
+ if (!tokenStream.peekTokenPos(&exprPos, TokenStream::SlashIsRegExp)) {
+ return null();
+ }
+
+ Node propExpr =
+ assignExpr(InAllowed, yieldHandling, TripledotProhibited);
+ if (!propExpr) {
+ return null();
+ }
+
+ if (propAtom == TaggedParserAtomIndex::WellKnown::proto()) {
+ errorAt(namePos.begin, JSMSG_RECORD_NO_PROTO);
+ return null();
+ }
+
+ BinaryNodeType propDef =
+ handler_.newPropertyDefinition(propName, propExpr);
+ if (!propDef) {
+ return null();
+ }
+
+ handler_.addPropertyDefinition(literal, propDef);
+ } else if (propType == PropertyType::Shorthand) {
+ /*
+ * Support |var o = #{x, y}| as initializer shorthand for
+ * |var o = #{x: x, y: y}|.
+ */
+ TaggedParserAtomIndex name = identifierReference(yieldHandling);
+ if (!name) {
+ return null();
+ }
+
+ NameNodeType nameExpr = identifierReference(name);
+ if (!nameExpr) {
+ return null();
+ }
+
+ if (!handler_.addShorthand(literal, handler_.asName(propName),
+ nameExpr)) {
+ return null();
+ }
+ } else {
+ error(JSMSG_BAD_PROP_ID);
+ return null();
+ }
+ }
+
+ bool matched;
+ if (!tokenStream.matchToken(&matched, TokenKind::Comma,
+ TokenStream::SlashIsInvalid)) {
+ return null();
+ }
+ if (!matched) {
+ break;
+ }
+ }
+
+ if (!mustMatchToken(
+ TokenKind::RightCurly, [this, openedPos](TokenKind actual) {
+ this->reportMissingClosing(JSMSG_CURLY_AFTER_LIST,
+ JSMSG_CURLY_OPENED, openedPos);
+ })) {
+ return null();
+ }
+
+ handler_.setEndPosition(literal, pos().end);
+ return literal;
+}
+
+template <class ParseHandler, typename Unit>
+typename ParseHandler::ListNodeType
+GeneralParser<ParseHandler, Unit>::tupleLiteral(YieldHandling yieldHandling) {
+ MOZ_ASSERT(anyChars.isCurrentTokenType(TokenKind::HashBracket));
+
+ uint32_t begin = pos().begin;
+ ListNodeType literal = handler_.newTupleLiteral(begin);
+ if (!literal) {
+ return null();
+ }
+
+ for (uint32_t index = 0;; index++) {
+ if (index >= NativeObject::MAX_DENSE_ELEMENTS_COUNT) {
+ error(JSMSG_ARRAY_INIT_TOO_BIG);
+ return null();
+ }
+
+ TokenKind tt;
+ if (!tokenStream.peekToken(&tt, TokenStream::SlashIsRegExp)) {
+ return null();
+ }
+ if (tt == TokenKind::RightBracket) {
+ break;
+ }
+
+ if (tt == TokenKind::TripleDot) {
+ tokenStream.consumeKnownToken(TokenKind::TripleDot,
+ TokenStream::SlashIsRegExp);
+ uint32_t begin = pos().begin;
+
+ TokenPos innerPos;
+ if (!tokenStream.peekTokenPos(&innerPos, TokenStream::SlashIsRegExp)) {
+ return null();
+ }
+
+ Node inner = assignExpr(InAllowed, yieldHandling, TripledotProhibited);
+ if (!inner) {
+ return null();
+ }
+
+ if (!handler_.addSpreadElement(literal, begin, inner)) {
+ return null();
+ }
+ } else {
+ TokenPos elementPos;
+ if (!tokenStream.peekTokenPos(&elementPos, TokenStream::SlashIsRegExp)) {
+ return null();
+ }
+
+ Node element = assignExpr(InAllowed, yieldHandling, TripledotProhibited);
+ if (!element) {
+ return null();
+ }
+ handler_.addArrayElement(literal, element);
+ }
+
+ bool matched;
+ if (!tokenStream.matchToken(&matched, TokenKind::Comma,
+ TokenStream::SlashIsRegExp)) {
+ return null();
+ }
+ if (!matched) {
+ break;
+ }
+ }
+
+ if (!mustMatchToken(TokenKind::RightBracket, [this, begin](TokenKind actual) {
+ this->reportMissingClosing(JSMSG_BRACKET_AFTER_LIST,
+ JSMSG_BRACKET_OPENED, begin);
+ })) {
+ return null();
+ }
+
+ handler_.setEndPosition(literal, pos().end);
+ return literal;
+}
+#endif
+
+template <class ParseHandler, typename Unit>
+typename ParseHandler::FunctionNodeType
+GeneralParser<ParseHandler, Unit>::methodDefinition(
+ uint32_t toStringStart, PropertyType propType,
+ TaggedParserAtomIndex funName) {
+ FunctionSyntaxKind syntaxKind;
+ switch (propType) {
+ case PropertyType::Getter:
+ syntaxKind = FunctionSyntaxKind::Getter;
+ break;
+
+ case PropertyType::Setter:
+ syntaxKind = FunctionSyntaxKind::Setter;
+ break;
+
+ case PropertyType::Method:
+ case PropertyType::GeneratorMethod:
+ case PropertyType::AsyncMethod:
+ case PropertyType::AsyncGeneratorMethod:
+ syntaxKind = FunctionSyntaxKind::Method;
+ break;
+
+ case PropertyType::Constructor:
+ syntaxKind = FunctionSyntaxKind::ClassConstructor;
+ break;
+
+ case PropertyType::DerivedConstructor:
+ syntaxKind = FunctionSyntaxKind::DerivedClassConstructor;
+ break;
+
+ default:
+ MOZ_CRASH("unexpected property type");
+ }
+
+ GeneratorKind generatorKind = (propType == PropertyType::GeneratorMethod ||
+ propType == PropertyType::AsyncGeneratorMethod)
+ ? GeneratorKind::Generator
+ : GeneratorKind::NotGenerator;
+
+ FunctionAsyncKind asyncKind = (propType == PropertyType::AsyncMethod ||
+ propType == PropertyType::AsyncGeneratorMethod)
+ ? FunctionAsyncKind::AsyncFunction
+ : FunctionAsyncKind::SyncFunction;
+
+ YieldHandling yieldHandling = GetYieldHandling(generatorKind);
+
+ FunctionNodeType funNode = handler_.newFunction(syntaxKind, pos());
+ if (!funNode) {
+ return null();
+ }
+
+ return functionDefinition(funNode, toStringStart, InAllowed, yieldHandling,
+ funName, syntaxKind, generatorKind, asyncKind);
+}
+
+template <class ParseHandler, typename Unit>
+bool GeneralParser<ParseHandler, Unit>::tryNewTarget(
+ NewTargetNodeType* newTarget) {
+ MOZ_ASSERT(anyChars.isCurrentTokenType(TokenKind::New));
+
+ *newTarget = null();
+
+ NullaryNodeType newHolder = handler_.newPosHolder(pos());
+ if (!newHolder) {
+ return false;
+ }
+
+ uint32_t begin = pos().begin;
+
+ // |new| expects to look for an operand, so we will honor that.
+ TokenKind next;
+ if (!tokenStream.getToken(&next, TokenStream::SlashIsRegExp)) {
+ return false;
+ }
+
+ // Don't unget the token, since lookahead cannot handle someone calling
+ // getToken() with a different modifier. Callers should inspect
+ // currentToken().
+ if (next != TokenKind::Dot) {
+ return true;
+ }
+
+ if (!tokenStream.getToken(&next)) {
+ return false;
+ }
+ if (next != TokenKind::Target) {
+ error(JSMSG_UNEXPECTED_TOKEN, "target", TokenKindToDesc(next));
+ return false;
+ }
+
+ if (!pc_->sc()->allowNewTarget()) {
+ errorAt(begin, JSMSG_BAD_NEWTARGET);
+ return false;
+ }
+
+ NullaryNodeType targetHolder = handler_.newPosHolder(pos());
+ if (!targetHolder) {
+ return false;
+ }
+
+ NameNodeType newTargetName = newNewTargetName();
+ if (!newTargetName) {
+ return false;
+ }
+
+ *newTarget = handler_.newNewTarget(newHolder, targetHolder, newTargetName);
+ return !!*newTarget;
+}
+
+template <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 (!tokenStream.peekToken(&next, TokenStream::SlashIsRegExp)) {
+ return null();
+ }
+
+ Node optionalArg;
+ if (options().importAssertions) {
+ if (next == TokenKind::Comma) {
+ tokenStream.consumeKnownToken(TokenKind::Comma,
+ TokenStream::SlashIsRegExp);
+
+ if (!tokenStream.peekToken(&next, TokenStream::SlashIsRegExp)) {
+ return null();
+ }
+
+ if (next != TokenKind::RightParen) {
+ optionalArg =
+ assignExpr(InAllowed, yieldHandling, TripledotProhibited);
+ if (!optionalArg) {
+ return null();
+ }
+
+ if (!tokenStream.peekToken(&next, TokenStream::SlashIsRegExp)) {
+ return null();
+ }
+
+ if (next == TokenKind::Comma) {
+ tokenStream.consumeKnownToken(TokenKind::Comma,
+ TokenStream::SlashIsRegExp);
+ }
+ } else {
+ optionalArg = handler_.newPosHolder(TokenPos(pos().end, pos().end));
+ if (!optionalArg) {
+ return null();
+ }
+ }
+ } else {
+ optionalArg = handler_.newPosHolder(TokenPos(pos().end, pos().end));
+ if (!optionalArg) {
+ return null();
+ }
+ }
+ } else {
+ optionalArg = handler_.newPosHolder(TokenPos(pos().end, pos().end));
+ if (!optionalArg) {
+ return null();
+ }
+ }
+
+ if (!mustMatchToken(TokenKind::RightParen, JSMSG_PAREN_AFTER_ARGS)) {
+ return null();
+ }
+
+ Node spec = handler_.newCallImportSpec(arg, optionalArg);
+ if (!spec) {
+ return null();
+ }
+
+ return handler_.newCallImport(importHolder, spec);
+ } else {
+ error(JSMSG_UNEXPECTED_TOKEN_NO_EXPECT, TokenKindToDesc(next));
+ return null();
+ }
+}
+
+template <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));
+ AutoCheckRecursionLimit recursion(this->fc_);
+ if (!recursion.check(this->fc_)) {
+ return null();
+ }
+
+ switch (tt) {
+ case TokenKind::Function:
+ return functionExpr(pos().begin, invoked,
+ FunctionAsyncKind::SyncFunction);
+
+ case TokenKind::Class:
+ return classDefinition(yieldHandling, ClassExpression, NameRequired);
+
+ case TokenKind::LeftBracket:
+ return arrayInitializer(yieldHandling, possibleError);
+
+ case TokenKind::LeftCurly:
+ return objectLiteral(yieldHandling, possibleError);
+
+#ifdef ENABLE_RECORD_TUPLE
+ case TokenKind::HashCurly:
+ return recordLiteral(yieldHandling);
+
+ case TokenKind::HashBracket:
+ return tupleLiteral(yieldHandling);
+#endif
+
+#ifdef ENABLE_DECORATORS
+ case TokenKind::At:
+ return classDefinition(yieldHandling, ClassExpression, NameRequired);
+#endif
+
+ case TokenKind::LeftParen: {
+ TokenKind next;
+ if (!tokenStream.peekToken(&next, TokenStream::SlashIsRegExp)) {
+ return null();
+ }
+
+ if (next == TokenKind::RightParen) {
+ // Not valid expression syntax, but this is valid in an arrow function
+ // with no params: `() => body`.
+ tokenStream.consumeKnownToken(TokenKind::RightParen,
+ TokenStream::SlashIsRegExp);
+
+ if (!tokenStream.peekToken(&next)) {
+ return null();
+ }
+ if (next != TokenKind::Arrow) {
+ error(JSMSG_UNEXPECTED_TOKEN, "expression",
+ TokenKindToDesc(TokenKind::RightParen));
+ return null();
+ }
+
+ // Now just return something that will allow parsing to continue.
+ // It doesn't matter what; when we reach the =>, we will rewind and
+ // reparse the whole arrow function. See Parser::assignExpr.
+ return handler_.newNullLiteral(pos());
+ }
+
+ // Pass |possibleError| to support destructuring in arrow parameters.
+ Node expr = exprInParens(InAllowed, yieldHandling, TripledotAllowed,
+ possibleError);
+ if (!expr) {
+ return null();
+ }
+ if (!mustMatchToken(TokenKind::RightParen, JSMSG_PAREN_IN_PAREN)) {
+ return null();
+ }
+ return handler_.parenthesize(expr);
+ }
+
+ case TokenKind::TemplateHead:
+ return templateLiteral(yieldHandling);
+
+ case TokenKind::NoSubsTemplate:
+ return noSubstitutionUntaggedTemplate();
+
+ case TokenKind::String:
+ return stringLiteral();
+
+ default: {
+ if (!TokenKindIsPossibleIdentifier(tt)) {
+ error(JSMSG_UNEXPECTED_TOKEN, "expression", TokenKindToDesc(tt));
+ return null();
+ }
+
+ if (tt == TokenKind::Async) {
+ TokenKind nextSameLine = TokenKind::Eof;
+ if (!tokenStream.peekTokenSameLine(&nextSameLine)) {
+ return null();
+ }
+
+ if (nextSameLine == TokenKind::Function) {
+ uint32_t toStringStart = pos().begin;
+ tokenStream.consumeKnownToken(TokenKind::Function);
+ return functionExpr(toStringStart, PredictUninvoked,
+ FunctionAsyncKind::AsyncFunction);
+ }
+ }
+
+ TaggedParserAtomIndex name = identifierReference(yieldHandling);
+ if (!name) {
+ return null();
+ }
+
+ return identifierReference(name);
+ }
+
+ case TokenKind::RegExp:
+ return newRegExp();
+
+ case TokenKind::Number:
+ return newNumber(anyChars.currentToken());
+
+ case TokenKind::BigInt:
+ return newBigInt();
+
+ case TokenKind::True:
+ return handler_.newBooleanLiteral(true, pos());
+ case TokenKind::False:
+ return handler_.newBooleanLiteral(false, pos());
+ case TokenKind::This: {
+ NameNodeType thisName = null();
+ if (pc_->sc()->hasFunctionThisBinding()) {
+ thisName = newThisName();
+ if (!thisName) {
+ return null();
+ }
+ }
+ return handler_.newThisLiteral(pos(), thisName);
+ }
+ case TokenKind::Null:
+ return handler_.newNullLiteral(pos());
+
+ case TokenKind::TripleDot: {
+ // This isn't valid expression syntax, but it's valid in an arrow
+ // function as a trailing rest param: `(a, b, ...rest) => body`. Check
+ // if it's directly under
+ // CoverParenthesizedExpressionAndArrowParameterList, and check for a
+ // name, closing parenthesis, and arrow, and allow it only if all are
+ // present.
+ if (tripledotHandling != TripledotAllowed) {
+ error(JSMSG_UNEXPECTED_TOKEN, "expression", TokenKindToDesc(tt));
+ return null();
+ }
+
+ TokenKind next;
+ if (!tokenStream.getToken(&next)) {
+ return null();
+ }
+
+ if (next == TokenKind::LeftBracket || next == TokenKind::LeftCurly) {
+ // Validate, but don't store the pattern right now. The whole arrow
+ // function is reparsed in functionFormalParametersAndBody().
+ if (!destructuringDeclaration(DeclarationKind::CoverArrowParameter,
+ yieldHandling, next)) {
+ return null();
+ }
+ } else {
+ // This doesn't check that the provided name is allowed, e.g. if
+ // the enclosing code is strict mode code, any of "let", "yield",
+ // or "arguments" should be prohibited. Argument-parsing code
+ // handles that.
+ if (!TokenKindIsPossibleIdentifier(next)) {
+ error(JSMSG_UNEXPECTED_TOKEN, "rest argument name",
+ TokenKindToDesc(next));
+ return null();
+ }
+ }
+
+ if (!tokenStream.getToken(&next)) {
+ return null();
+ }
+ if (next != TokenKind::RightParen) {
+ error(JSMSG_UNEXPECTED_TOKEN, "closing parenthesis",
+ TokenKindToDesc(next));
+ return null();
+ }
+
+ if (!tokenStream.peekToken(&next)) {
+ return null();
+ }
+ if (next != TokenKind::Arrow) {
+ // Advance the scanner for proper error location reporting.
+ tokenStream.consumeKnownToken(next);
+ error(JSMSG_UNEXPECTED_TOKEN, "'=>' after argument list",
+ TokenKindToDesc(next));
+ return null();
+ }
+
+ anyChars.ungetToken(); // put back right paren
+
+ // Return an arbitrary expression node. See case TokenKind::RightParen
+ // above.
+ return handler_.newNullLiteral(pos());
+ }
+ }
+}
+
+template <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>;
+
+} // namespace js::frontend
diff --git a/js/src/frontend/Parser.h b/js/src/frontend/Parser.h
new file mode 100644
index 0000000000..5f14a76227
--- /dev/null
+++ b/js/src/frontend/Parser.h
@@ -0,0 +1,1955 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* JS parser. */
+
+#ifndef frontend_Parser_h
+#define frontend_Parser_h
+
+/*
+ * [SMDOC] JS Parser
+ *
+ * JS parsers capable of generating ASTs from source text.
+ *
+ * A parser embeds token stream information, then gets and matches tokens to
+ * generate a syntax tree that, if desired, BytecodeEmitter will use to compile
+ * bytecode.
+ *
+ * Like token streams (see the comment near the top of TokenStream.h), parser
+ * classes are heavily templatized -- along the token stream's character-type
+ * axis, and also along a full-parse/syntax-parse axis. Certain limitations of
+ * C++ (primarily the inability to partially specialize function templates),
+ * plus the desire to minimize compiled code size in duplicate function
+ * template instantiations wherever possible, mean that Parser exhibits much of
+ * the same unholy template/inheritance complexity as token streams.
+ *
+ * == ParserSharedBase ==
+ *
+ * ParserSharedBase is the base class for both regular JS and BinAST parsing.
+ * This class contains common fields and methods between both parsers. There is
+ * currently no BinAST parser here so this can potentially be merged into the
+ * ParserBase type below.
+ *
+ * == ParserBase → ParserSharedBase, ErrorReportMixin ==
+ *
+ * ParserBase is the base class for regular JS parser, shared by all regular JS
+ * parsers of all character types and parse-handling behavior. It stores
+ * everything character- and handler-agnostic.
+ *
+ * ParserBase's most important field is the parser's token stream's
+ * |TokenStreamAnyChars| component, for all tokenizing aspects that are
+ * character-type-agnostic. The character-type-sensitive components residing
+ * in |TokenStreamSpecific| (see the comment near the top of TokenStream.h)
+ * live elsewhere in this hierarchy. These separate locations are the reason
+ * for the |AnyCharsAccess| template parameter to |TokenStreamChars| and
+ * |TokenStreamSpecific|.
+ *
+ * == PerHandlerParser<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/Maybe.h"
+
+#include <type_traits>
+#include <utility>
+
+#include "frontend/CompilationStencil.h" // CompilationState
+#include "frontend/ErrorReporter.h"
+#include "frontend/FullParseHandler.h"
+#include "frontend/FunctionSyntaxKind.h" // FunctionSyntaxKind
+#include "frontend/IteratorKind.h"
+#include "frontend/NameAnalysisTypes.h"
+#include "frontend/ParseContext.h"
+#include "frontend/ParserAtom.h" // ParserAtomsTable, TaggedParserAtomIndex
+#include "frontend/SharedContext.h"
+#include "frontend/SyntaxParseHandler.h"
+#include "frontend/TokenStream.h"
+#include "js/friend/ErrorMessages.h" // JSErrNum, JSMSG_*
+#include "vm/GeneratorAndAsyncKind.h" // js::GeneratorKind, js::FunctionAsyncKind
+
+namespace js {
+
+class FrontendContext;
+struct ErrorMetadata;
+
+namespace frontend {
+
+template <class 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->fc_, 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,
+ FieldWithAccessor,
+};
+
+enum AwaitHandling : uint8_t {
+ AwaitIsName,
+ AwaitIsKeyword,
+ AwaitIsModuleKeyword,
+ AwaitIsDisallowed
+};
+
+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(FrontendContext* fc, CompilationState& compilationState,
+ Kind kind);
+ ~ParserSharedBase();
+
+ public:
+ FrontendContext* fc_;
+
+ LifoAlloc& alloc_;
+
+ CompilationState& compilationState_;
+
+ // innermost parse context (stack-allocated)
+ ParseContext* pc_;
+
+ // For tracking used names in this parsing session.
+ UsedNameTracker& usedNames_;
+
+ public:
+ CompilationState& getCompilationState() { return compilationState_; }
+
+ ParserAtomsTable& parserAtoms() { return compilationState_.parserAtoms; }
+ const ParserAtomsTable& parserAtoms() const {
+ return compilationState_.parserAtoms;
+ }
+
+ LifoAlloc& stencilAlloc() { return compilationState_.alloc; }
+
+#if defined(DEBUG) || defined(JS_JITSPEW)
+ void dumpAtom(TaggedParserAtomIndex index) const;
+#endif
+};
+
+class MOZ_STACK_CLASS ParserBase : public ParserSharedBase,
+ public ErrorReportMixin {
+ using Base = ErrorReportMixin;
+
+ public:
+ TokenStreamAnyChars anyChars;
+
+ ScriptSource* ss;
+
+ // Perform constant-folding; must be true when interfacing with the emitter.
+ const bool foldConstants_ : 1;
+
+ protected:
+#if DEBUG
+ /* Our fallible 'checkOptions' member function has been called. */
+ bool checkOptionsCalled_ : 1;
+#endif
+
+ /* Unexpected end of input, i.e. Eof not at top-level. */
+ bool isUnexpectedEOF_ : 1;
+
+ /* AwaitHandling */ uint8_t awaitHandling_ : 2;
+
+ bool inParametersOfAsyncFunction_ : 1;
+
+ public:
+ JSAtom* liftParserAtomToJSAtom(TaggedParserAtomIndex index);
+
+ bool awaitIsKeyword() const {
+ return awaitHandling_ == AwaitIsKeyword ||
+ awaitHandling_ == AwaitIsModuleKeyword;
+ }
+ bool awaitIsDisallowed() const { return awaitHandling_ == AwaitIsDisallowed; }
+
+ bool inParametersOfAsyncFunction() const {
+ return inParametersOfAsyncFunction_;
+ }
+
+ ParseGoal parseGoal() const {
+ return pc_->sc()->hasModuleGoal() ? ParseGoal::Module : ParseGoal::Script;
+ }
+
+ template <class, typename>
+ friend class AutoAwaitIsKeyword;
+ template <class, typename>
+ friend class AutoInParametersOfAsyncFunction;
+
+ ParserBase(FrontendContext* fc, const JS::ReadOnlyCompileOptions& options,
+ bool foldConstants, CompilationState& compilationState);
+ ~ParserBase();
+
+ bool checkOptions();
+
+ const char* getFilename() const { return anyChars.getFilename(); }
+ TokenPos pos() const { return anyChars.currentToken().pos; }
+
+ // Determine whether |yield| is a valid name in the current context.
+ bool yieldExpressionsSupported() const { return pc_->isGenerator(); }
+
+ bool setLocalStrictMode(bool strict) {
+ MOZ_ASSERT(anyChars.debugHasNoLookahead());
+ return pc_->sc()->setLocalStrictMode(strict);
+ }
+
+ public:
+ // Implement ErrorReportMixin.
+
+ FrontendContext* getContext() const override { return fc_; }
+
+ bool strictMode() const override { return pc_->sc()->strict(); }
+
+ const JS::ReadOnlyCompileOptions& options() const override {
+ return anyChars.options();
+ }
+
+ using Base::error;
+ using Base::errorAt;
+ using Base::errorNoOffset;
+ using Base::errorWithNotes;
+ using Base::errorWithNotesAt;
+ using Base::errorWithNotesNoOffset;
+ using Base::strictModeError;
+ using Base::strictModeErrorAt;
+ using Base::strictModeErrorNoOffset;
+ using Base::strictModeErrorWithNotes;
+ using Base::strictModeErrorWithNotesAt;
+ using Base::strictModeErrorWithNotesNoOffset;
+ using Base::warning;
+ using Base::warningAt;
+ using Base::warningNoOffset;
+
+ public:
+ bool isUnexpectedEOF() const { return isUnexpectedEOF_; }
+
+ bool isValidStrictBinding(TaggedParserAtomIndex name);
+
+ bool hasValidSimpleStrictParameterNames();
+
+ // A Parser::Mark is the extension of the LifoAlloc::Mark to the entire
+ // Parser's state. Note: clients must still take care that any ParseContext
+ // that points into released ParseNodes is destroyed.
+ class Mark {
+ friend class ParserBase;
+ LifoAlloc::Mark mark;
+ CompilationState::CompilationStatePosition pos;
+ };
+ Mark mark() const {
+ Mark m;
+ m.mark = alloc_.mark();
+ m.pos = compilationState_.getPosition();
+ return m;
+ }
+ void release(Mark m) {
+ alloc_.release(m.mark);
+ compilationState_.rewind(m.pos);
+ }
+
+ public:
+ mozilla::Maybe<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);
+ mozilla::Maybe<ClassBodyScope::ParserData*> newClassBodyScopeData(
+ ParseContext::Scope& scope);
+
+ protected:
+ enum InvokedPrediction { PredictUninvoked = false, PredictInvoked = true };
+ enum ForInitLocation { InForInit, NotInForInit };
+
+ // While on a |let| Name token, examine |next| (which must already be
+ // gotten). Indicate whether |next|, the next token already gotten with
+ // modifier TokenStream::SlashIsDiv, continues a LexicalDeclaration.
+ bool nextTokenContinuesLetDeclaration(TokenKind next);
+
+ bool noteUsedNameInternal(TaggedParserAtomIndex name,
+ NameVisibility visibility,
+ mozilla::Maybe<TokenPos> tokenPosition);
+
+ bool checkAndMarkSuperScope();
+
+ bool leaveInnerFunction(ParseContext* outerpc);
+
+ TaggedParserAtomIndex prefixAccessorName(PropertyType propType,
+ TaggedParserAtomIndex propAtom);
+
+ [[nodiscard]] 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(FrontendContext* fc,
+ const JS::ReadOnlyCompileOptions& options,
+ bool foldConstants, CompilationState& compilationState,
+ void* internalSyntaxParser);
+
+ protected:
+ template <typename Unit>
+ PerHandlerParser(FrontendContext* fc,
+ const JS::ReadOnlyCompileOptions& options,
+ bool foldConstants, CompilationState& compilationState,
+ GeneralParser<SyntaxParseHandler, Unit>* syntaxParser)
+ : PerHandlerParser(fc, options, foldConstants, compilationState,
+ 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(
+ TaggedParserAtomIndex 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_.reuseClosedOverBindings()) {
+ return true;
+ }
+
+ return ParserBase::noteUsedNameInternal(name, visibility, tokenPosition);
+ }
+
+ // Required on Scope exit.
+ bool propagateFreeNamesAndMarkClosedOverBindings(ParseContext::Scope& scope);
+
+ bool checkForUndefinedPrivateFields(EvalSharedContext* evalSc = nullptr);
+
+ bool finishFunctionScopes(bool isStandaloneFunction);
+ LexicalScopeNodeType finishLexicalScope(ParseContext::Scope& scope, Node body,
+ ScopeKind kind = ScopeKind::Lexical);
+ ClassBodyScopeNodeType finishClassBodyScope(ParseContext::Scope& scope,
+ ListNodeType body);
+ bool finishFunction(bool isStandaloneFunction = false);
+
+ inline NameNodeType newName(TaggedParserAtomIndex name);
+ inline NameNodeType newName(TaggedParserAtomIndex name, TokenPos pos);
+
+ inline NameNodeType newPrivateName(TaggedParserAtomIndex name);
+
+ NameNodeType newInternalDotName(TaggedParserAtomIndex name);
+ NameNodeType newThisName();
+ NameNodeType newNewTargetName();
+ NameNodeType newDotGeneratorName();
+
+ NameNodeType identifierReference(TaggedParserAtomIndex name);
+ NameNodeType privateNameReference(TaggedParserAtomIndex name);
+
+ Node noSubstitutionTaggedTemplate();
+
+ inline bool processExport(Node node);
+ inline bool processExportFrom(BinaryNodeType node);
+ inline bool processImport(BinaryNodeType node);
+
+ // If ParseHandler is SyntaxParseHandler:
+ // Do nothing.
+ // If ParseHandler is FullParseHandler:
+ // Disable syntax parsing of all future inner functions during this
+ // full-parse.
+ inline void disableSyntaxParser();
+
+ // If ParseHandler is SyntaxParseHandler:
+ // Flag the current syntax parse as aborted due to unsupported language
+ // constructs and return false. Aborting the current syntax parse does
+ // not disable attempts to syntax-parse future inner functions.
+ // If ParseHandler is FullParseHandler:
+ // Disable syntax parsing of all future inner functions and return true.
+ inline bool abortIfSyntaxParser();
+
+ // If ParseHandler is SyntaxParseHandler:
+ // Return whether the last syntax parse was aborted due to unsupported
+ // language constructs.
+ // If ParseHandler is FullParseHandler:
+ // Return false.
+ inline bool hadAbortedSyntaxParse();
+
+ // If ParseHandler is SyntaxParseHandler:
+ // Clear whether the last syntax parse was aborted.
+ // If ParseHandler is FullParseHandler:
+ // Do nothing.
+ inline void clearAbortedSyntaxParse();
+
+ public:
+ NameNodeType newPropertyName(TaggedParserAtomIndex key, const TokenPos& pos) {
+ return handler_.newPropertyName(key, pos);
+ }
+
+ PropertyAccessType newPropertyAccess(Node expr, NameNodeType key) {
+ return handler_.newPropertyAccess(expr, key);
+ }
+
+ FunctionBox* newFunctionBox(FunctionNodeType funNode,
+ TaggedParserAtomIndex explicitName,
+ FunctionFlags flags, uint32_t toStringStart,
+ Directives directives,
+ GeneratorKind generatorKind,
+ FunctionAsyncKind asyncKind);
+
+ FunctionBox* newFunctionBox(FunctionNodeType funNode,
+ const ScriptStencil& cachedScriptData,
+ const ScriptStencilExtra& cachedScriptExtra);
+
+ public:
+ // ErrorReportMixin.
+
+ using Base::error;
+ using Base::errorAt;
+ using Base::errorNoOffset;
+ using Base::errorWithNotes;
+ using Base::errorWithNotesAt;
+ using Base::errorWithNotesNoOffset;
+ using Base::strictModeError;
+ using Base::strictModeErrorAt;
+ using Base::strictModeErrorNoOffset;
+ using Base::strictModeErrorWithNotes;
+ using Base::strictModeErrorWithNotesAt;
+ using Base::strictModeErrorWithNotesNoOffset;
+ using Base::warning;
+ using Base::warningAt;
+ using Base::warningNoOffset;
+};
+
+#define ABORTED_SYNTAX_PARSE_SENTINEL reinterpret_cast<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 almost exactly equivalent to checking
+// whether we're in a function box -- easier and simpler than passing an extra
+// parameter everywhere.
+enum YieldHandling { YieldIsName, YieldIsKeyword };
+enum InHandling { InAllowed, InProhibited };
+enum DefaultHandling { NameRequired, AllowDefaultName };
+enum TripledotHandling { TripledotAllowed, TripledotProhibited };
+
+// For Ergonomic brand checks.
+enum PrivateNameHandling { PrivateNameProhibited, PrivateNameAllowed };
+
+template <class 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::awaitIsDisallowed;
+ using Base::awaitIsKeyword;
+ using Base::inParametersOfAsyncFunction;
+ using Base::parseGoal;
+#if DEBUG
+ using Base::checkOptionsCalled_;
+#endif
+ using Base::checkForUndefinedPrivateFields;
+ using Base::finishClassBodyScope;
+ using Base::finishFunctionScopes;
+ using Base::finishLexicalScope;
+ using Base::foldConstants_;
+ using Base::getFilename;
+ using Base::hasValidSimpleStrictParameterNames;
+ using Base::isUnexpectedEOF_;
+ using Base::nameIsArgumentsOrEval;
+ using Base::newDotGeneratorName;
+ using Base::newFunctionBox;
+ using Base::newName;
+ using Base::null;
+ using Base::options;
+ using Base::pos;
+ using Base::propagateFreeNamesAndMarkClosedOverBindings;
+ using Base::setLocalStrictMode;
+ using Base::stringLiteral;
+ using Base::yieldExpressionsSupported;
+
+ using Base::abortIfSyntaxParser;
+ using Base::clearAbortedSyntaxParse;
+ using Base::disableSyntaxParser;
+ using Base::hadAbortedSyntaxParse;
+
+ public:
+ // Implement ErrorReportMixin.
+
+ [[nodiscard]] bool computeErrorMetadata(
+ ErrorMetadata* err,
+ const ErrorReportMixin::ErrorOffset& offset) const override;
+
+ using Base::error;
+ using Base::errorAt;
+ using Base::errorNoOffset;
+ using Base::errorWithNotes;
+ using Base::errorWithNotesAt;
+ using Base::errorWithNotesNoOffset;
+ using Base::strictModeError;
+ using Base::strictModeErrorAt;
+ using Base::strictModeErrorNoOffset;
+ using Base::strictModeErrorWithNotes;
+ using Base::strictModeErrorWithNotesAt;
+ using Base::strictModeErrorWithNotesNoOffset;
+ using Base::warning;
+ using Base::warningAt;
+ using Base::warningNoOffset;
+
+ public:
+ using Base::anyChars;
+ using Base::fc_;
+ using Base::handler_;
+ using Base::noteUsedName;
+ using Base::pc_;
+ using Base::usedNames_;
+
+ private:
+ using Base::checkAndMarkSuperScope;
+ using Base::finishFunction;
+ using Base::identifierReference;
+ using Base::leaveInnerFunction;
+ using Base::newInternalDotName;
+ using Base::newNewTargetName;
+ using Base::newThisName;
+ using Base::nextTokenContinuesLetDeclaration;
+ using Base::noSubstitutionTaggedTemplate;
+ using Base::noteDestructuredPositionalFormalParameter;
+ using Base::prefixAccessorName;
+ using Base::privateNameReference;
+ using Base::processExport;
+ using Base::processExportFrom;
+ using Base::processImport;
+ using Base::setFunctionEndFromCurrentToken;
+
+ private:
+ inline FinalParser* asFinalParser();
+ inline const FinalParser* asFinalParser() const;
+
+ /*
+ * A class for temporarily stashing errors while parsing continues.
+ *
+ * The ability to stash an error is useful for handling situations where we
+ * aren't able to verify that an error has occurred until later in the parse.
+ * For instance | ({x=1}) | is always parsed as an object literal with
+ * a SyntaxError, however, in the case where it is followed by '=>' we rewind
+ * and reparse it as a valid arrow function. Here a PossibleError would be
+ * set to 'pending' when the initial SyntaxError was encountered then
+ * 'resolved' just before rewinding the parser.
+ *
+ * There are currently two kinds of PossibleErrors: Expression and
+ * Destructuring errors. Expression errors are used to mark a possible
+ * syntax error when a grammar production is used in an expression context.
+ * For example in |{x = 1}|, we mark the CoverInitializedName |x = 1| as a
+ * possible expression error, because CoverInitializedName productions
+ * are disallowed when an actual ObjectLiteral is expected.
+ * Destructuring errors are used to record possible syntax errors in
+ * destructuring contexts. For example in |[...rest, ] = []|, we initially
+ * mark the trailing comma after the spread expression as a possible
+ * destructuring error, because the ArrayAssignmentPattern grammar
+ * production doesn't allow a trailing comma after the rest element.
+ *
+ * When using PossibleError one should set a pending error at the location
+ * where an error occurs. From that point, the error may be resolved
+ * (invalidated) or left until the PossibleError is checked.
+ *
+ * Ex:
+ * PossibleError possibleError(*this);
+ * possibleError.setPendingExpressionErrorAt(pos, JSMSG_BAD_PROP_ID);
+ * // A JSMSG_BAD_PROP_ID ParseError is reported, returns false.
+ * if (!possibleError.checkForExpressionError()) {
+ * return false; // we reach this point with a pending exception
+ * }
+ *
+ * PossibleError possibleError(*this);
+ * possibleError.setPendingExpressionErrorAt(pos, JSMSG_BAD_PROP_ID);
+ * // Returns true, no error is reported.
+ * if (!possibleError.checkForDestructuringError()) {
+ * return false; // not reached, no pending exception
+ * }
+ *
+ * PossibleError possibleError(*this);
+ * // Returns true, no error is reported.
+ * if (!possibleError.checkForExpressionError()) {
+ * return false; // not reached, no pending exception
+ * }
+ */
+ class MOZ_STACK_CLASS PossibleError {
+ private:
+ enum class ErrorKind { Expression, Destructuring, DestructuringWarning };
+
+ enum class ErrorState { None, Pending };
+
+ struct Error {
+ ErrorState state_ = ErrorState::None;
+
+ // Error reporting fields.
+ uint32_t offset_;
+ unsigned errorNumber_;
+ };
+
+ GeneralParser<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.
+ [[nodiscard]] 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.
+ [[nodiscard]] bool checkForDestructuringErrorOrWarning();
+
+ // If there is a pending expression error, report it and return false,
+ // otherwise return true. Clears any pending destructuring error or
+ // warning.
+ [[nodiscard]] bool checkForExpressionError();
+
+ // Pass pending errors between possible error instances. This is useful
+ // for extending the lifetime of a pending error beyond the scope of
+ // the PossibleError where it was initially set (keeping in mind that
+ // PossibleError is a MOZ_STACK_CLASS).
+ void transferErrorsTo(PossibleError* other);
+ };
+
+ protected:
+ SyntaxParser* getSyntaxParser() const {
+ return reinterpret_cast<SyntaxParser*>(Base::internalSyntaxParser_);
+ }
+
+ public:
+ TokenStream tokenStream;
+
+ public:
+ GeneralParser(FrontendContext* fc, const JS::ReadOnlyCompileOptions& options,
+ const Unit* units, size_t length, bool foldConstants,
+ CompilationState& compilationState, SyntaxParser* syntaxParser);
+
+ inline void setAwaitHandling(AwaitHandling awaitHandling);
+ inline void setInParametersOfAsyncFunction(bool inParameters);
+
+ /*
+ * Parse a top-level JS script.
+ */
+ ListNodeType parse();
+
+ private:
+ /*
+ * Gets the next token and checks if it matches to the given `condition`.
+ * If it matches, returns true.
+ * If it doesn't match, calls `errorReport` to report the error, and
+ * returns false.
+ * If other error happens, it returns false but `errorReport` may not be
+ * called and other error will be thrown in that case.
+ *
+ * In any case, the already gotten token is not ungotten.
+ *
+ * The signature of `condition` is [...](TokenKind actual) -> bool, and
+ * the signature of `errorReport` is [...](TokenKind actual).
+ */
+ template <typename ConditionT, typename ErrorReportT>
+ [[nodiscard]] bool mustMatchTokenInternal(ConditionT condition,
+ ErrorReportT errorReport);
+
+ public:
+ /*
+ * The following mustMatchToken variants follow the behavior and parameter
+ * types of mustMatchTokenInternal above.
+ *
+ * If modifier is omitted, `SlashIsDiv` is used.
+ * If TokenKind is passed instead of `condition`, it checks if the next
+ * token is the passed token.
+ * If error number is passed instead of `errorReport`, it reports an
+ * error with the passed errorNumber.
+ */
+ [[nodiscard]] bool mustMatchToken(TokenKind expected, JSErrNum errorNumber) {
+ return mustMatchTokenInternal(
+ [expected](TokenKind actual) { return actual == expected; },
+ [this, errorNumber](TokenKind) { this->error(errorNumber); });
+ }
+
+ template <typename ConditionT>
+ [[nodiscard]] bool mustMatchToken(ConditionT condition,
+ JSErrNum errorNumber) {
+ return mustMatchTokenInternal(condition, [this, errorNumber](TokenKind) {
+ this->error(errorNumber);
+ });
+ }
+
+ template <typename ErrorReportT>
+ [[nodiscard]] bool mustMatchToken(TokenKind expected,
+ ErrorReportT errorReport) {
+ return mustMatchTokenInternal(
+ [expected](TokenKind actual) { return actual == expected; },
+ errorReport);
+ }
+
+ private:
+ NameNodeType noSubstitutionUntaggedTemplate();
+ ListNodeType templateLiteral(YieldHandling yieldHandling);
+ bool taggedTemplate(YieldHandling yieldHandling, ListNodeType tagArgsList,
+ TokenKind tt);
+ bool appendToCallSiteObj(CallSiteNodeType callSiteObj);
+ bool addExprAndGetNextTemplStrToken(YieldHandling yieldHandling,
+ ListNodeType nodeList, TokenKind* ttp);
+
+ inline bool trySyntaxParseInnerFunction(
+ FunctionNodeType* funNode, TaggedParserAtomIndex explicitName,
+ FunctionFlags flags, uint32_t toStringStart, InHandling inHandling,
+ YieldHandling yieldHandling, FunctionSyntaxKind kind,
+ GeneratorKind generatorKind, FunctionAsyncKind asyncKind, bool tryAnnexB,
+ Directives inheritedDirectives, Directives* newDirectives);
+
+ inline bool skipLazyInnerFunction(FunctionNodeType funNode,
+ uint32_t toStringStart, bool tryAnnexB);
+
+ void setFunctionStartAtPosition(FunctionBox* funbox, TokenPos pos) const;
+ void setFunctionStartAtCurrentToken(FunctionBox* funbox) const;
+
+ public:
+ /* Public entry points for parsing. */
+ Node statementListItem(YieldHandling yieldHandling,
+ bool canHaveDirectives = false);
+
+ // Parse an inner function given an enclosing ParseContext and a
+ // FunctionBox for the inner function.
+ [[nodiscard]] FunctionNodeType innerFunctionForFunctionBox(
+ FunctionNodeType funNode, ParseContext* outerpc, FunctionBox* funbox,
+ InHandling inHandling, YieldHandling yieldHandling,
+ FunctionSyntaxKind kind, Directives* newDirectives);
+
+ // Parse a function's formal parameters and its body assuming its function
+ // ParseContext is already on the stack.
+ bool functionFormalParametersAndBody(
+ InHandling inHandling, YieldHandling yieldHandling,
+ FunctionNodeType* funNode, FunctionSyntaxKind kind,
+ const mozilla::Maybe<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();
+
+ DeclarationListNodeType variableStatement(YieldHandling yieldHandling);
+
+ LabeledStatementType labeledStatement(YieldHandling yieldHandling);
+ Node labeledItem(YieldHandling yieldHandling);
+
+ TernaryNodeType ifStatement(YieldHandling yieldHandling);
+ Node consequentOrAlternative(YieldHandling yieldHandling);
+
+ DeclarationListNodeType lexicalDeclaration(YieldHandling yieldHandling,
+ DeclarationKind kind);
+
+ NameNodeType moduleExportName();
+
+ bool assertClause(ListNodeType assertionsSet);
+
+ BinaryNodeType importDeclaration();
+ Node importDeclarationOrImportExpr(YieldHandling yieldHandling);
+ bool namedImports(ListNodeType importSpecSet);
+ bool namespaceImport(ListNodeType importSpecSet);
+
+ TaggedParserAtomIndex importedBinding() {
+ return bindingIdentifier(YieldIsName);
+ }
+
+ BinaryNodeType exportFrom(uint32_t begin, Node specList);
+ BinaryNodeType exportBatch(uint32_t begin);
+ inline bool checkLocalExportNames(ListNodeType node);
+ Node exportClause(uint32_t begin);
+ UnaryNodeType exportFunctionDeclaration(
+ uint32_t begin, uint32_t toStringStart,
+ FunctionAsyncKind asyncKind = FunctionAsyncKind::SyncFunction);
+ UnaryNodeType exportVariableStatement(uint32_t begin);
+ UnaryNodeType exportClassDeclaration(uint32_t begin);
+ UnaryNodeType exportLexicalDeclaration(uint32_t begin, DeclarationKind kind);
+ BinaryNodeType exportDefaultFunctionDeclaration(
+ uint32_t begin, uint32_t toStringStart,
+ FunctionAsyncKind asyncKind = FunctionAsyncKind::SyncFunction);
+ BinaryNodeType exportDefaultClassDeclaration(uint32_t begin);
+ BinaryNodeType exportDefaultAssignExpr(uint32_t begin);
+ BinaryNodeType exportDefault(uint32_t begin);
+ Node exportDeclaration();
+
+ UnaryNodeType expressionStatement(
+ YieldHandling yieldHandling,
+ InvokedPrediction invoked = PredictUninvoked);
+
+ // Declaration parsing. The main entrypoint is Parser::declarationList,
+ // with sub-functionality split out into the remaining methods.
+
+ // |blockScope| may be non-null only when |kind| corresponds to a lexical
+ // declaration (that is, PNK_LET or PNK_CONST).
+ //
+ // The for* parameters, for normal declarations, should be null/ignored.
+ // They should be non-null only when Parser::forHeadStart parses a
+ // declaration at the start of a for-loop head.
+ //
+ // In this case, on success |*forHeadKind| is PNK_FORHEAD, PNK_FORIN, or
+ // PNK_FOROF, corresponding to the three for-loop kinds. The precise value
+ // indicates what was parsed.
+ //
+ // If parsing recognized a for(;;) loop, the next token is the ';' within
+ // the loop-head that separates the init/test parts.
+ //
+ // Otherwise, for for-in/of loops, the next token is the ')' ending the
+ // loop-head. Additionally, the expression that the loop iterates over was
+ // parsed into |*forInOrOfExpression|.
+ DeclarationListNodeType declarationList(YieldHandling yieldHandling,
+ ParseNodeKind kind,
+ ParseNodeKind* forHeadKind = nullptr,
+ Node* forInOrOfExpression = nullptr);
+
+ // The items in a declaration list are either patterns or names, with or
+ // without initializers. These two methods parse a single pattern/name and
+ // any associated initializer -- and if parsing an |initialDeclaration|
+ // will, if parsing in a for-loop head (as specified by |forHeadKind| being
+ // non-null), consume additional tokens up to the closing ')' in a
+ // for-in/of loop head, returning the iterated expression in
+ // |*forInOrOfExpression|. (An "initial declaration" is the first
+ // declaration in a declaration list: |a| but not |b| in |var a, b|, |{c}|
+ // but not |d| in |let {c} = 3, d|.)
+ Node declarationPattern(DeclarationKind declKind, TokenKind tt,
+ bool initialDeclaration, YieldHandling yieldHandling,
+ ParseNodeKind* forHeadKind,
+ Node* forInOrOfExpression);
+ Node declarationName(DeclarationKind declKind, TokenKind tt,
+ bool initialDeclaration, YieldHandling yieldHandling,
+ ParseNodeKind* forHeadKind, Node* forInOrOfExpression);
+
+ // Having parsed a name (not found in a destructuring pattern) declared by
+ // a declaration, with the current token being the '=' separating the name
+ // from its initializer, parse and bind that initializer -- and possibly
+ // consume trailing in/of and subsequent expression, if so directed by
+ // |forHeadKind|.
+ AssignmentNodeType initializerInNameDeclaration(NameNodeType binding,
+ DeclarationKind declKind,
+ bool initialDeclaration,
+ YieldHandling yieldHandling,
+ ParseNodeKind* forHeadKind,
+ Node* forInOrOfExpression);
+
+ Node expr(InHandling inHandling, YieldHandling yieldHandling,
+ TripledotHandling tripledotHandling,
+ PossibleError* possibleError = nullptr,
+ InvokedPrediction invoked = PredictUninvoked);
+ Node assignExpr(InHandling inHandling, YieldHandling yieldHandling,
+ TripledotHandling tripledotHandling,
+ PossibleError* possibleError = nullptr,
+ InvokedPrediction invoked = PredictUninvoked);
+ Node assignExprWithoutYieldOrAwait(YieldHandling yieldHandling);
+ UnaryNodeType yieldExpression(InHandling inHandling);
+ Node condExpr(InHandling inHandling, YieldHandling yieldHandling,
+ TripledotHandling tripledotHandling,
+ PossibleError* possibleError, InvokedPrediction invoked);
+ Node orExpr(InHandling inHandling, YieldHandling yieldHandling,
+ TripledotHandling tripledotHandling, PossibleError* possibleError,
+ InvokedPrediction invoked);
+ Node unaryExpr(YieldHandling yieldHandling,
+ TripledotHandling tripledotHandling,
+ PossibleError* possibleError = nullptr,
+ InvokedPrediction invoked = PredictUninvoked,
+ PrivateNameHandling privateNameHandling =
+ PrivateNameHandling::PrivateNameProhibited);
+ Node optionalExpr(YieldHandling yieldHandling,
+ TripledotHandling tripledotHandling, TokenKind tt,
+ PossibleError* possibleError = nullptr,
+ InvokedPrediction invoked = PredictUninvoked);
+ Node memberExpr(YieldHandling yieldHandling,
+ TripledotHandling tripledotHandling, TokenKind tt,
+ bool allowCallSyntax, PossibleError* possibleError,
+ InvokedPrediction invoked);
+ Node primaryExpr(YieldHandling yieldHandling,
+ TripledotHandling tripledotHandling, TokenKind tt,
+ PossibleError* possibleError, InvokedPrediction invoked);
+ Node exprInParens(InHandling inHandling, YieldHandling yieldHandling,
+ TripledotHandling tripledotHandling,
+ PossibleError* possibleError = nullptr);
+
+ bool tryNewTarget(NewTargetNodeType* newTarget);
+
+ BinaryNodeType importExpr(YieldHandling yieldHandling, bool allowCallSyntax);
+
+ FunctionNodeType methodDefinition(uint32_t toStringStart,
+ PropertyType propType,
+ TaggedParserAtomIndex funName);
+
+ /*
+ * Additional JS parsers.
+ */
+ bool functionArguments(YieldHandling yieldHandling, FunctionSyntaxKind kind,
+ FunctionNodeType funNode);
+
+ FunctionNodeType functionDefinition(
+ FunctionNodeType funNode, uint32_t toStringStart, InHandling inHandling,
+ YieldHandling yieldHandling, TaggedParserAtomIndex name,
+ FunctionSyntaxKind kind, GeneratorKind generatorKind,
+ FunctionAsyncKind asyncKind, bool tryAnnexB = false);
+
+ // Parse a function body. Pass StatementListBody if the body is a list of
+ // statements; pass ExpressionBody if the body is a single expression.
+ //
+ // Don't include opening LeftCurly token when invoking.
+ enum FunctionBodyType { StatementListBody, ExpressionBody };
+ LexicalScopeNodeType functionBody(InHandling inHandling,
+ YieldHandling yieldHandling,
+ FunctionSyntaxKind kind,
+ FunctionBodyType type);
+
+ UnaryNodeType unaryOpExpr(YieldHandling yieldHandling, ParseNodeKind kind,
+ uint32_t begin);
+
+ Node condition(InHandling inHandling, YieldHandling yieldHandling);
+
+ ListNodeType argumentList(YieldHandling yieldHandling, bool* isSpread,
+ PossibleError* possibleError = nullptr);
+ Node destructuringDeclaration(DeclarationKind kind,
+ YieldHandling yieldHandling, TokenKind tt);
+ Node destructuringDeclarationWithoutYieldOrAwait(DeclarationKind kind,
+ YieldHandling yieldHandling,
+ TokenKind tt);
+
+ inline bool checkExportedName(TaggedParserAtomIndex exportName);
+ inline bool checkExportedNamesForArrayBinding(ListNodeType array);
+ inline bool checkExportedNamesForObjectBinding(ListNodeType obj);
+ inline bool checkExportedNamesForDeclaration(Node node);
+ inline bool checkExportedNamesForDeclarationList(
+ DeclarationListNodeType node);
+ inline bool checkExportedNameForFunction(FunctionNodeType funNode);
+ inline bool checkExportedNameForClass(ClassNodeType classNode);
+ inline bool checkExportedNameForClause(NameNodeType nameNode);
+
+ enum ClassContext { ClassStatement, ClassExpression };
+ ClassNodeType classDefinition(YieldHandling yieldHandling,
+ ClassContext classContext,
+ DefaultHandling defaultHandling);
+
+ struct ClassInitializedMembers {
+ // The number of instance class fields.
+ size_t instanceFields = 0;
+
+ // The number of instance class fields with computed property names.
+ size_t instanceFieldKeys = 0;
+
+ // The number of static class fields.
+ size_t staticFields = 0;
+
+ // The number of static blocks
+ size_t staticBlocks = 0;
+
+ // The number of static class fields with computed property names.
+ size_t staticFieldKeys = 0;
+
+ // The number of instance class private methods.
+ size_t privateMethods = 0;
+
+ // The number of instance class private accessors.
+ size_t privateAccessors = 0;
+
+ bool hasPrivateBrand() const {
+ return privateMethods > 0 || privateAccessors > 0;
+ }
+ };
+#ifdef ENABLE_DECORATORS
+ ListNodeType decoratorList(YieldHandling yieldHandling);
+#endif
+ [[nodiscard]] bool classMember(
+ YieldHandling yieldHandling,
+ const ParseContext::ClassStatement& classStmt,
+ TaggedParserAtomIndex className, uint32_t classStartOffset,
+ HasHeritage hasHeritage, ClassInitializedMembers& classInitializedMembers,
+ ListNodeType& classMembers, bool* done);
+ [[nodiscard]] bool finishClassConstructor(
+ const ParseContext::ClassStatement& classStmt,
+ TaggedParserAtomIndex className, HasHeritage hasHeritage,
+ uint32_t classStartOffset, uint32_t classEndOffset,
+ const ClassInitializedMembers& classInitializedMembers,
+ ListNodeType& classMembers);
+
+ FunctionNodeType privateMethodInitializer(
+ TokenPos propNamePos, TaggedParserAtomIndex propAtom,
+ TaggedParserAtomIndex storedMethodAtom);
+ FunctionNodeType fieldInitializerOpt(
+ TokenPos propNamePos, Node name, TaggedParserAtomIndex atom,
+ ClassInitializedMembers& classInitializedMembers, bool isStatic,
+ HasHeritage hasHeritage);
+
+ FunctionNodeType synthesizePrivateMethodInitializer(
+ TaggedParserAtomIndex propAtom, AccessorType accessorType,
+ TokenPos propNamePos);
+
+#ifdef ENABLE_DECORATORS
+ Node synthesizeAccessor(Node propName, TokenPos propNamePos,
+ TaggedParserAtomIndex propAtom,
+ TaggedParserAtomIndex privateStateNameAtom,
+ bool isStatic, FunctionSyntaxKind syntaxKind,
+ ListNodeType decorators,
+ ClassInitializedMembers& classInitializedMembers);
+
+ FunctionNodeType synthesizeAccessorBody(TokenPos propNamePos,
+ TaggedParserAtomIndex atom,
+ FunctionSyntaxKind syntaxKind);
+#endif
+
+ FunctionNodeType staticClassBlock(
+ ClassInitializedMembers& classInitializedMembers);
+
+ FunctionNodeType synthesizeConstructor(TaggedParserAtomIndex className,
+ TokenPos synthesizedBodyPos,
+ HasHeritage hasHeritage);
+
+ protected:
+ FunctionNodeType synthesizeConstructorBody(TokenPos synthesizedBodyPos,
+ HasHeritage hasHeritage,
+ FunctionNodeType funNode,
+ FunctionBox* funbox);
+
+ private:
+ bool checkBindingIdentifier(TaggedParserAtomIndex ident, uint32_t offset,
+ YieldHandling yieldHandling,
+ TokenKind hint = TokenKind::Limit);
+
+ TaggedParserAtomIndex labelOrIdentifierReference(YieldHandling yieldHandling);
+
+ TaggedParserAtomIndex labelIdentifier(YieldHandling yieldHandling) {
+ return labelOrIdentifierReference(yieldHandling);
+ }
+
+ TaggedParserAtomIndex identifierReference(YieldHandling yieldHandling) {
+ return labelOrIdentifierReference(yieldHandling);
+ }
+
+ bool matchLabel(YieldHandling yieldHandling, TaggedParserAtomIndex* labelOut);
+
+ // Indicate if the next token (tokenized with SlashIsRegExp) is |in| or |of|.
+ // If so, consume it.
+ bool matchInOrOf(bool* isForInp, bool* isForOfp);
+
+ private:
+ bool checkIncDecOperand(Node operand, uint32_t operandOffset);
+ bool checkStrictAssignment(Node lhs);
+
+ void reportMissingClosing(unsigned errorNumber, unsigned noteNumber,
+ uint32_t openedPos);
+
+ void reportRedeclaration(TaggedParserAtomIndex name, DeclarationKind prevKind,
+ TokenPos pos, uint32_t prevPos);
+ bool notePositionalFormalParameter(FunctionNodeType funNode,
+ TaggedParserAtomIndex name,
+ uint32_t beginPos,
+ bool disallowDuplicateParams,
+ bool* duplicatedParam);
+
+ enum PropertyNameContext {
+ PropertyNameInLiteral,
+ PropertyNameInPattern,
+ PropertyNameInClass,
+#ifdef ENABLE_RECORD_TUPLE
+ PropertyNameInRecord
+#endif
+ };
+ Node propertyName(YieldHandling yieldHandling,
+ PropertyNameContext propertyNameContext,
+ const mozilla::Maybe<DeclarationKind>& maybeDecl,
+ ListNodeType propList, TaggedParserAtomIndex* propAtomOut);
+ Node propertyOrMethodName(YieldHandling yieldHandling,
+ PropertyNameContext propertyNameContext,
+ const mozilla::Maybe<DeclarationKind>& maybeDecl,
+ ListNodeType propList, PropertyType* propType,
+ TaggedParserAtomIndex* 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);
+
+#ifdef ENABLE_RECORD_TUPLE
+ ListNodeType recordLiteral(YieldHandling yieldHandling);
+ ListNodeType tupleLiteral(YieldHandling yieldHandling);
+#endif
+
+ BinaryNodeType bindingInitializer(Node lhs, DeclarationKind kind,
+ YieldHandling yieldHandling);
+ NameNodeType bindingIdentifier(DeclarationKind kind,
+ YieldHandling yieldHandling);
+ Node bindingIdentifierOrPattern(DeclarationKind kind,
+ YieldHandling yieldHandling, TokenKind tt);
+ ListNodeType objectBindingPattern(DeclarationKind kind,
+ YieldHandling yieldHandling);
+ ListNodeType arrayBindingPattern(DeclarationKind kind,
+ YieldHandling yieldHandling);
+
+ enum class TargetBehavior {
+ PermitAssignmentPattern,
+ ForbidAssignmentPattern
+ };
+ bool checkDestructuringAssignmentTarget(
+ Node expr, TokenPos exprPos, PossibleError* exprPossibleError,
+ PossibleError* possibleError,
+ TargetBehavior behavior = TargetBehavior::PermitAssignmentPattern);
+ void checkDestructuringAssignmentName(NameNodeType name, TokenPos namePos,
+ PossibleError* possibleError);
+ bool checkDestructuringAssignmentElement(Node expr, TokenPos exprPos,
+ PossibleError* exprPossibleError,
+ PossibleError* possibleError);
+
+ NumericLiteralType newNumber(const Token& tok) {
+ return handler_.newNumber(tok.number(), tok.decimalPoint(), tok.pos);
+ }
+
+ inline BigIntLiteralType newBigInt();
+
+ enum class OptionalKind {
+ NonOptional = 0,
+ Optional,
+ };
+ Node memberPropertyAccess(
+ Node lhs, OptionalKind optionalKind = OptionalKind::NonOptional);
+ Node memberPrivateAccess(
+ Node lhs, OptionalKind optionalKind = OptionalKind::NonOptional);
+ Node memberElemAccess(Node lhs, YieldHandling yieldHandling,
+ OptionalKind optionalKind = OptionalKind::NonOptional);
+ Node memberSuperCall(Node lhs, YieldHandling yieldHandling);
+ Node memberCall(TokenKind tt, Node lhs, YieldHandling yieldHandling,
+ PossibleError* possibleError,
+ OptionalKind optionalKind = OptionalKind::NonOptional);
+
+ protected:
+ // Match the current token against the BindingIdentifier production with
+ // the given Yield parameter. If there is no match, report a syntax
+ // error.
+ TaggedParserAtomIndex bindingIdentifier(YieldHandling yieldHandling);
+
+ bool checkLabelOrIdentifierReference(TaggedParserAtomIndex ident,
+ uint32_t offset,
+ YieldHandling yieldHandling,
+ TokenKind hint = TokenKind::Limit);
+
+ ListNodeType statementList(YieldHandling yieldHandling);
+
+ [[nodiscard]] FunctionNodeType innerFunction(
+ FunctionNodeType funNode, ParseContext* outerpc,
+ TaggedParserAtomIndex explicitName, FunctionFlags flags,
+ uint32_t toStringStart, InHandling inHandling,
+ YieldHandling yieldHandling, FunctionSyntaxKind kind,
+ GeneratorKind generatorKind, FunctionAsyncKind asyncKind, bool tryAnnexB,
+ Directives inheritedDirectives, Directives* newDirectives);
+
+ // Implements Automatic Semicolon Insertion.
+ //
+ // Use this to match `;` in contexts where ASI is allowed. Call this after
+ // ruling out all other possibilities except `;`, by peeking ahead if
+ // necessary.
+ //
+ // Unlike most optional Modifiers, this method's `modifier` argument defaults
+ // to SlashIsRegExp, since that's by far the most common case: usually an
+ // optional semicolon is at the end of a statement or declaration, and the
+ // next token could be a RegExp literal beginning a new ExpressionStatement.
+ bool matchOrInsertSemicolon(Modifier modifier = TokenStream::SlashIsRegExp);
+
+ bool noteDeclaredName(TaggedParserAtomIndex name, DeclarationKind kind,
+ TokenPos pos, ClosedOver isClosedOver = ClosedOver::No);
+
+ bool noteDeclaredPrivateName(Node nameNode, TaggedParserAtomIndex name,
+ PropertyType propType, FieldPlacement placement,
+ TokenPos pos);
+
+ private:
+ inline bool asmJS(ListNodeType list);
+};
+
+template <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::hadAbortedSyntaxParse;
+ using Base::innerFunctionForFunctionBox;
+ using Base::tokenStream;
+
+ public:
+ // ErrorReportMixin.
+
+ using Base::error;
+ using Base::errorAt;
+ using Base::errorNoOffset;
+ using Base::errorWithNotes;
+ using Base::errorWithNotesAt;
+ using Base::errorWithNotesNoOffset;
+ using Base::strictModeError;
+ using Base::strictModeErrorAt;
+ using Base::strictModeErrorNoOffset;
+ using Base::strictModeErrorWithNotes;
+ using Base::strictModeErrorWithNotesAt;
+ using Base::strictModeErrorWithNotesNoOffset;
+ using Base::warning;
+ using Base::warningAt;
+ using Base::warningNoOffset;
+
+ private:
+ using Base::alloc_;
+#if DEBUG
+ using Base::checkOptionsCalled_;
+#endif
+ using Base::checkForUndefinedPrivateFields;
+ using Base::finishFunctionScopes;
+ using Base::functionFormalParametersAndBody;
+ using Base::handler_;
+ using Base::innerFunction;
+ using Base::matchOrInsertSemicolon;
+ using Base::mustMatchToken;
+ using Base::newFunctionBox;
+ using Base::newLexicalScopeData;
+ using Base::newModuleScopeData;
+ using Base::newName;
+ using Base::noteDeclaredName;
+ using Base::null;
+ using Base::options;
+ using Base::pc_;
+ using Base::pos;
+ using Base::propagateFreeNamesAndMarkClosedOverBindings;
+ using Base::ss;
+ using Base::statementList;
+ using Base::stringLiteral;
+ using Base::usedNames_;
+
+ private:
+ using Base::abortIfSyntaxParser;
+ using Base::disableSyntaxParser;
+
+ public:
+ // Functions with multiple overloads of different visibility. We can't
+ // |using| the whole thing into existence because of the visibility
+ // distinction, so we instead must manually delegate the required overload.
+
+ TaggedParserAtomIndex bindingIdentifier(YieldHandling yieldHandling) {
+ return Base::bindingIdentifier(yieldHandling);
+ }
+
+ // Functions present in both Parser<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 bool checkLocalExportNames(ListNodeType node);
+ inline bool checkExportedName(TaggedParserAtomIndex exportName);
+ inline bool checkExportedNamesForArrayBinding(ListNodeType array);
+ inline bool checkExportedNamesForObjectBinding(ListNodeType obj);
+ inline bool checkExportedNamesForDeclaration(Node node);
+ inline bool checkExportedNamesForDeclarationList(
+ DeclarationListNodeType node);
+ inline bool checkExportedNameForFunction(FunctionNodeType funNode);
+ inline bool checkExportedNameForClass(ClassNodeType classNode);
+ inline bool checkExportedNameForClause(NameNodeType nameNode);
+
+ bool trySyntaxParseInnerFunction(
+ FunctionNodeType* funNode, TaggedParserAtomIndex explicitName,
+ FunctionFlags flags, uint32_t toStringStart, InHandling inHandling,
+ YieldHandling yieldHandling, FunctionSyntaxKind kind,
+ GeneratorKind generatorKind, FunctionAsyncKind asyncKind, bool tryAnnexB,
+ Directives inheritedDirectives, Directives* newDirectives);
+
+ bool skipLazyInnerFunction(FunctionNodeType funNode, uint32_t toStringStart,
+ bool tryAnnexB);
+
+ bool asmJS(ListNodeType list);
+
+ // Functions present only in Parser<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::fc_;
+ using Base::finishClassBodyScope;
+ using Base::finishFunctionScopes;
+ using Base::finishLexicalScope;
+ using Base::innerFunction;
+ using Base::innerFunctionForFunctionBox;
+ using Base::matchOrInsertSemicolon;
+ using Base::mustMatchToken;
+ using Base::newEvalScopeData;
+ using Base::newFunctionScopeData;
+ using Base::newGlobalScopeData;
+ using Base::newLexicalScopeData;
+ using Base::newModuleScopeData;
+ using Base::newName;
+ using Base::newVarScopeData;
+ using Base::noteDeclaredName;
+ using Base::noteUsedName;
+ using Base::null;
+ using Base::propagateFreeNamesAndMarkClosedOverBindings;
+ using Base::statementList;
+ using Base::stringLiteral;
+ using Base::usedNames_;
+
+ using Base::abortIfSyntaxParser;
+ using Base::disableSyntaxParser;
+ using Base::getSyntaxParser;
+
+ public:
+ // Functions with multiple overloads of different visibility. We can't
+ // |using| the whole thing into existence because of the visibility
+ // distinction, so we instead must manually delegate the required overload.
+
+ TaggedParserAtomIndex bindingIdentifier(YieldHandling yieldHandling) {
+ return Base::bindingIdentifier(yieldHandling);
+ }
+
+ // Functions present in both Parser<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);
+
+ bool checkLocalExportNames(ListNodeType node);
+ bool checkExportedName(TaggedParserAtomIndex exportName);
+ bool checkExportedNamesForArrayBinding(ListNodeType array);
+ bool checkExportedNamesForObjectBinding(ListNodeType obj);
+ bool checkExportedNamesForDeclaration(Node node);
+ bool checkExportedNamesForDeclarationList(DeclarationListNodeType node);
+ bool checkExportedNameForFunction(FunctionNodeType funNode);
+ bool checkExportedNameForClass(ClassNodeType classNode);
+ inline bool checkExportedNameForClause(NameNodeType nameNode);
+
+ bool trySyntaxParseInnerFunction(
+ FunctionNodeType* funNode, TaggedParserAtomIndex explicitName,
+ FunctionFlags flags, uint32_t toStringStart, InHandling inHandling,
+ YieldHandling yieldHandling, FunctionSyntaxKind kind,
+ GeneratorKind generatorKind, FunctionAsyncKind asyncKind, bool tryAnnexB,
+ Directives inheritedDirectives, Directives* newDirectives);
+
+ [[nodiscard]] bool advancePastSyntaxParsedFunction(
+ SyntaxParser* syntaxParser);
+
+ bool skipLazyInnerFunction(FunctionNodeType funNode, uint32_t toStringStart,
+ bool tryAnnexB);
+
+ // Functions present only in Parser<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(CompilationInput& input,
+ uint32_t toStringStart, bool strict,
+ GeneratorKind generatorKind,
+ FunctionAsyncKind asyncKind);
+
+ // Parse a function, used for the Function, GeneratorFunction, and
+ // AsyncFunction constructors.
+ FunctionNodeType standaloneFunction(
+ const mozilla::Maybe<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 checkLocalExportName(TaggedParserAtomIndex 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(FrontendContext* fc,
+ LifoAlloc& alloc,
+ uint32_t numBindings);
+
+VarScope::ParserData* NewEmptyVarScopeData(FrontendContext* fc,
+ LifoAlloc& alloc,
+ uint32_t numBindings);
+
+LexicalScope::ParserData* NewEmptyLexicalScopeData(FrontendContext* fc,
+ LifoAlloc& alloc,
+ uint32_t numBindings);
+
+FunctionScope::ParserData* NewEmptyFunctionScopeData(FrontendContext* fc,
+ LifoAlloc& alloc,
+ uint32_t numBindings);
+
+mozilla::Maybe<GlobalScope::ParserData*> NewGlobalScopeData(
+ JSContext* cx, FrontendContext* fc, ParseContext::Scope& scope,
+ LifoAlloc& alloc, ParseContext* pc);
+
+mozilla::Maybe<EvalScope::ParserData*> NewEvalScopeData(
+ JSContext* cx, FrontendContext* fc, ParseContext::Scope& scope,
+ LifoAlloc& alloc, ParseContext* pc);
+
+mozilla::Maybe<FunctionScope::ParserData*> NewFunctionScopeData(
+ JSContext* cx, FrontendContext* fc, ParseContext::Scope& scope,
+ bool hasParameterExprs, LifoAlloc& alloc, ParseContext* pc);
+
+mozilla::Maybe<VarScope::ParserData*> NewVarScopeData(
+ JSContext* cx, FrontendContext* fc, ParseContext::Scope& scope,
+ LifoAlloc& alloc, ParseContext* pc);
+
+mozilla::Maybe<LexicalScope::ParserData*> NewLexicalScopeData(
+ JSContext* cx, FrontendContext* fc, ParseContext::Scope& scope,
+ LifoAlloc& alloc, ParseContext* pc);
+
+bool FunctionScopeHasClosedOverBindings(ParseContext* pc);
+bool LexicalScopeHasClosedOverBindings(ParseContext* pc,
+ ParseContext::Scope& scope);
+
+} /* namespace frontend */
+} /* namespace js */
+
+#endif /* frontend_Parser_h */
diff --git a/js/src/frontend/ParserAtom.cpp b/js/src/frontend/ParserAtom.cpp
new file mode 100644
index 0000000000..2141a7189b
--- /dev/null
+++ b/js/src/frontend/ParserAtom.cpp
@@ -0,0 +1,1316 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "frontend/ParserAtom.h"
+
+#include "mozilla/TextUtils.h" // mozilla::IsAscii
+
+#include <memory> // std::uninitialized_fill_n
+
+#include "jsnum.h" // CharsToNumber
+
+#include "frontend/BytecodeCompiler.h" // IsIdentifier
+#include "frontend/CompilationStencil.h"
+#include "js/GCAPI.h" // JS::AutoSuppressGCAnalysis
+#include "js/Printer.h" // Sprinter, QuoteString
+#include "util/StringBuffer.h" // StringBuffer
+#include "util/Text.h" // AsciiDigitToNumber
+#include "util/Unicode.h"
+#include "vm/JSContext.h"
+#include "vm/Runtime.h"
+#include "vm/SelfHosting.h" // ExtendedUnclonedSelfHostedFunctionNamePrefix
+#include "vm/StaticStrings.h"
+#include "vm/StringType.h"
+
+using namespace js;
+using namespace js::frontend;
+
+namespace js {
+namespace frontend {
+
+JSAtom* GetWellKnownAtom(JSContext* cx, WellKnownAtomId atomId) {
+#define ASSERT_OFFSET_(_, NAME, _2) \
+ static_assert(offsetof(JSAtomState, NAME) == \
+ int32_t(WellKnownAtomId::NAME) * \
+ sizeof(js::ImmutableTenuredPtr<PropertyName*>));
+ FOR_EACH_COMMON_PROPERTYNAME(ASSERT_OFFSET_);
+#undef ASSERT_OFFSET_
+
+#define ASSERT_OFFSET_(NAME, _) \
+ static_assert(offsetof(JSAtomState, NAME) == \
+ int32_t(WellKnownAtomId::NAME) * \
+ sizeof(js::ImmutableTenuredPtr<PropertyName*>));
+ JS_FOR_EACH_PROTOTYPE(ASSERT_OFFSET_);
+#undef ASSERT_OFFSET_
+
+#define ASSERT_OFFSET_(NAME) \
+ static_assert(offsetof(JSAtomState, NAME) == \
+ int32_t(WellKnownAtomId::NAME) * \
+ sizeof(js::ImmutableTenuredPtr<PropertyName*>));
+ JS_FOR_EACH_WELL_KNOWN_SYMBOL(ASSERT_OFFSET_);
+#undef ASSERT_OFFSET_
+
+ static_assert(int32_t(WellKnownAtomId::abort) == 0,
+ "Unexpected order of WellKnownAtom");
+
+ return (&cx->names().abort)[int32_t(atomId)];
+}
+
+#ifdef DEBUG
+void TaggedParserAtomIndex::validateRaw() {
+ if (isParserAtomIndex()) {
+ MOZ_ASSERT(toParserAtomIndex().index < IndexLimit);
+ } else if (isWellKnownAtomId()) {
+ MOZ_ASSERT(uint32_t(toWellKnownAtomId()) <
+ uint32_t(WellKnownAtomId::Limit));
+ } else if (isLength1StaticParserString()) {
+ // always valid
+ } else if (isLength2StaticParserString()) {
+ MOZ_ASSERT(size_t(toLength2StaticParserString()) < Length2StaticLimit);
+ } else if (isLength3StaticParserString()) {
+ // always valid
+ } else {
+ MOZ_ASSERT(isNull());
+ }
+}
+#endif
+
+HashNumber TaggedParserAtomIndex::staticOrWellKnownHash() const {
+ MOZ_ASSERT(!isParserAtomIndex());
+
+ if (isWellKnownAtomId()) {
+ const auto& info = GetWellKnownAtomInfo(toWellKnownAtomId());
+ return info.hash;
+ }
+
+ if (isLength1StaticParserString()) {
+ Latin1Char content[1];
+ ParserAtomsTable::getLength1Content(toLength1StaticParserString(), content);
+ return mozilla::HashString(content, 1);
+ }
+
+ if (isLength2StaticParserString()) {
+ char content[2];
+ ParserAtomsTable::getLength2Content(toLength2StaticParserString(), content);
+ return mozilla::HashString(reinterpret_cast<const Latin1Char*>(content), 2);
+ }
+
+ MOZ_ASSERT(isLength3StaticParserString());
+ char content[3];
+ ParserAtomsTable::getLength3Content(toLength3StaticParserString(), content);
+ return mozilla::HashString(reinterpret_cast<const Latin1Char*>(content), 3);
+}
+
+template <typename CharT, typename SeqCharT>
+/* static */ ParserAtom* ParserAtom::allocate(
+ FrontendContext* fc, LifoAlloc& alloc, InflatedChar16Sequence<SeqCharT> seq,
+ uint32_t length, HashNumber hash) {
+ constexpr size_t HeaderSize = sizeof(ParserAtom);
+ void* raw = alloc.alloc(HeaderSize + (sizeof(CharT) * length));
+ if (!raw) {
+ js::ReportOutOfMemory(fc);
+ return nullptr;
+ }
+
+ constexpr bool hasTwoByteChars = (sizeof(CharT) == 2);
+ static_assert(sizeof(CharT) == 1 || sizeof(CharT) == 2,
+ "CharT should be 1 or 2 byte type");
+ ParserAtom* entry = new (raw) ParserAtom(length, hash, hasTwoByteChars);
+ CharT* entryBuf = entry->chars<CharT>();
+ drainChar16Seq(entryBuf, seq, length);
+ return entry;
+}
+
+bool ParserAtom::isInstantiatedAsJSAtom() const {
+ if (isMarkedAtomize()) {
+ return true;
+ }
+
+ // Always use JSAtom for short strings.
+ if (length() < MinimumLengthForNonAtom) {
+ return true;
+ }
+
+ return false;
+}
+
+JSString* ParserAtom::instantiateString(JSContext* cx, FrontendContext* fc,
+ ParserAtomIndex index,
+ CompilationAtomCache& atomCache) const {
+ MOZ_ASSERT(!isInstantiatedAsJSAtom());
+
+ JSString* str;
+ if (hasLatin1Chars()) {
+ str = NewStringCopyNDontDeflateNonStaticValidLength<CanGC>(
+ cx, latin1Chars(), length(), gc::Heap::Tenured);
+ } else {
+ str = NewStringCopyNDontDeflateNonStaticValidLength<CanGC>(
+ cx, twoByteChars(), length(), gc::Heap::Tenured);
+ }
+ if (!str) {
+ return nullptr;
+ }
+ if (!atomCache.setAtomAt(fc, index, str)) {
+ return nullptr;
+ }
+
+ return str;
+}
+
+JSAtom* ParserAtom::instantiateAtom(JSContext* cx, FrontendContext* fc,
+ ParserAtomIndex index,
+ CompilationAtomCache& atomCache) const {
+ MOZ_ASSERT(isInstantiatedAsJSAtom());
+
+ JSAtom* atom;
+ if (hasLatin1Chars()) {
+ atom =
+ AtomizeCharsNonStaticValidLength(cx, hash(), latin1Chars(), length());
+ } else {
+ atom =
+ AtomizeCharsNonStaticValidLength(cx, hash(), twoByteChars(), length());
+ }
+ if (!atom) {
+ return nullptr;
+ }
+ if (!atomCache.setAtomAt(fc, index, atom)) {
+ return nullptr;
+ }
+ return atom;
+}
+
+JSAtom* ParserAtom::instantiatePermanentAtom(
+ JSContext* cx, FrontendContext* fc, AtomSet& atomSet, ParserAtomIndex index,
+ CompilationAtomCache& atomCache) const {
+ MOZ_ASSERT(!cx->zone());
+
+ MOZ_ASSERT(hasLatin1Chars());
+ MOZ_ASSERT(length() <= JSString::MAX_LENGTH);
+ JSAtom* atom = PermanentlyAtomizeCharsNonStaticValidLength(
+ cx, atomSet, hash(), latin1Chars(), length());
+ if (!atom) {
+ return nullptr;
+ }
+ if (!atomCache.setAtomAt(fc, index, atom)) {
+ return nullptr;
+ }
+ return atom;
+}
+
+#if defined(DEBUG) || defined(JS_JITSPEW)
+void ParserAtom::dump() const {
+ js::Fprinter out(stderr);
+ out.put("\"");
+ dumpCharsNoQuote(out);
+ out.put("\"\n");
+}
+
+void ParserAtom::dumpCharsNoQuote(js::GenericPrinter& out) const {
+ if (hasLatin1Chars()) {
+ JSString::dumpCharsNoQuote<Latin1Char>(latin1Chars(), length(), out);
+ } else {
+ JSString::dumpCharsNoQuote<char16_t>(twoByteChars(), length(), out);
+ }
+}
+
+void ParserAtomsTable::dump(TaggedParserAtomIndex index) const {
+ if (index.isParserAtomIndex()) {
+ getParserAtom(index.toParserAtomIndex())->dump();
+ return;
+ }
+
+ if (index.isWellKnownAtomId()) {
+ const auto& info = GetWellKnownAtomInfo(index.toWellKnownAtomId());
+ js::Fprinter out(stderr);
+ out.put("\"");
+ out.put(info.content, info.length);
+ out.put("\"");
+ return;
+ }
+
+ if (index.isLength1StaticParserString()) {
+ js::Fprinter out(stderr);
+ out.put("\"");
+ dumpCharsNoQuote(out, index.toLength1StaticParserString());
+ out.put("\"\n");
+ return;
+ }
+
+ if (index.isLength2StaticParserString()) {
+ js::Fprinter out(stderr);
+ out.put("\"");
+ dumpCharsNoQuote(out, index.toLength2StaticParserString());
+ out.put("\"\n");
+ return;
+ }
+
+ if (index.isLength3StaticParserString()) {
+ js::Fprinter out(stderr);
+ out.put("\"");
+ dumpCharsNoQuote(out, index.toLength3StaticParserString());
+ out.put("\"\n");
+ return;
+ }
+
+ MOZ_ASSERT(index.isNull());
+ js::Fprinter out(stderr);
+ out.put("#<null>");
+}
+
+void ParserAtomsTable::dumpCharsNoQuote(js::GenericPrinter& out,
+ TaggedParserAtomIndex index) const {
+ if (index.isParserAtomIndex()) {
+ getParserAtom(index.toParserAtomIndex())->dumpCharsNoQuote(out);
+ return;
+ }
+
+ if (index.isWellKnownAtomId()) {
+ dumpCharsNoQuote(out, index.toWellKnownAtomId());
+ return;
+ }
+
+ if (index.isLength1StaticParserString()) {
+ dumpCharsNoQuote(out, index.toLength1StaticParserString());
+ return;
+ }
+
+ if (index.isLength2StaticParserString()) {
+ dumpCharsNoQuote(out, index.toLength2StaticParserString());
+ return;
+ }
+
+ if (index.isLength3StaticParserString()) {
+ dumpCharsNoQuote(out, index.toLength3StaticParserString());
+ return;
+ }
+
+ MOZ_ASSERT(index.isNull());
+ out.put("#<null>");
+}
+
+/* static */
+void ParserAtomsTable::dumpCharsNoQuote(js::GenericPrinter& out,
+ WellKnownAtomId id) {
+ const auto& info = GetWellKnownAtomInfo(id);
+ out.put(info.content, info.length);
+}
+
+/* static */
+void ParserAtomsTable::dumpCharsNoQuote(js::GenericPrinter& out,
+ Length1StaticParserString index) {
+ Latin1Char content[1];
+ getLength1Content(index, content);
+ out.putChar(content[0]);
+}
+
+/* static */
+void ParserAtomsTable::dumpCharsNoQuote(js::GenericPrinter& out,
+ Length2StaticParserString index) {
+ char content[2];
+ getLength2Content(index, content);
+ out.putChar(content[0]);
+ out.putChar(content[1]);
+}
+
+/* static */
+void ParserAtomsTable::dumpCharsNoQuote(js::GenericPrinter& out,
+ Length3StaticParserString index) {
+ char content[3];
+ getLength3Content(index, content);
+ out.putChar(content[0]);
+ out.putChar(content[1]);
+ out.putChar(content[2]);
+}
+#endif
+
+ParserAtomsTable::ParserAtomsTable(LifoAlloc& alloc) : alloc_(&alloc) {}
+
+TaggedParserAtomIndex ParserAtomsTable::addEntry(FrontendContext* fc,
+ EntryMap::AddPtr& addPtr,
+ ParserAtom* entry) {
+ MOZ_ASSERT(!addPtr);
+ ParserAtomIndex index = ParserAtomIndex(entries_.length());
+ if (size_t(index) >= TaggedParserAtomIndex::IndexLimit) {
+ ReportAllocationOverflow(fc);
+ return TaggedParserAtomIndex::null();
+ }
+ if (!entries_.append(entry)) {
+ js::ReportOutOfMemory(fc);
+ return TaggedParserAtomIndex::null();
+ }
+ auto taggedIndex = TaggedParserAtomIndex(index);
+ if (!entryMap_.add(addPtr, entry, taggedIndex)) {
+ js::ReportOutOfMemory(fc);
+ return TaggedParserAtomIndex::null();
+ }
+ return taggedIndex;
+}
+
+template <typename AtomCharT, typename SeqCharT>
+TaggedParserAtomIndex ParserAtomsTable::internChar16Seq(
+ FrontendContext* fc, EntryMap::AddPtr& addPtr, HashNumber hash,
+ InflatedChar16Sequence<SeqCharT> seq, uint32_t length) {
+ MOZ_ASSERT(!addPtr);
+
+ ParserAtom* entry =
+ ParserAtom::allocate<AtomCharT>(fc, *alloc_, seq, length, hash);
+ if (!entry) {
+ return TaggedParserAtomIndex::null();
+ }
+ return addEntry(fc, addPtr, entry);
+}
+
+static const uint16_t MAX_LATIN1_CHAR = 0xff;
+
+TaggedParserAtomIndex ParserAtomsTable::internAscii(FrontendContext* fc,
+ const char* asciiPtr,
+ uint32_t length) {
+ // ASCII strings are strict subsets of Latin1 strings.
+ const Latin1Char* latin1Ptr = reinterpret_cast<const Latin1Char*>(asciiPtr);
+ return internLatin1(fc, latin1Ptr, length);
+}
+
+TaggedParserAtomIndex ParserAtomsTable::internLatin1(
+ FrontendContext* fc, const Latin1Char* latin1Ptr, uint32_t length) {
+ // Check for tiny strings which are abundant in minified code.
+ if (auto tiny = WellKnownParserAtoms::getSingleton().lookupTinyIndex(
+ latin1Ptr, length)) {
+ return tiny;
+ }
+
+ // Check for well-known atom.
+ InflatedChar16Sequence<Latin1Char> seq(latin1Ptr, length);
+ SpecificParserAtomLookup<Latin1Char> lookup(seq);
+ if (auto wk = WellKnownParserAtoms::getSingleton().lookupChar16Seq(lookup)) {
+ return wk;
+ }
+
+ // Check for existing atom.
+ auto addPtr = entryMap_.lookupForAdd(lookup);
+ if (addPtr) {
+ return addPtr->value();
+ }
+
+ return internChar16Seq<Latin1Char>(fc, addPtr, lookup.hash(), seq, length);
+}
+
+bool IsWide(const InflatedChar16Sequence<char16_t>& seq) {
+ InflatedChar16Sequence<char16_t> seqCopy = seq;
+ while (seqCopy.hasMore()) {
+ char16_t ch = seqCopy.next();
+ if (ch > MAX_LATIN1_CHAR) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+template <typename AtomCharT>
+TaggedParserAtomIndex ParserAtomsTable::internExternalParserAtomImpl(
+ FrontendContext* fc, const ParserAtom* atom) {
+ InflatedChar16Sequence<AtomCharT> seq(atom->chars<AtomCharT>(),
+ atom->length());
+ SpecificParserAtomLookup<AtomCharT> lookup(seq, atom->hash());
+
+ // Check for existing atom.
+ auto addPtr = entryMap_.lookupForAdd(lookup);
+ if (addPtr) {
+ auto index = addPtr->value();
+
+ // Copy UsedByStencilFlag and AtomizeFlag.
+ MOZ_ASSERT(entries_[index.toParserAtomIndex()]->hasTwoByteChars() ==
+ atom->hasTwoByteChars());
+ entries_[index.toParserAtomIndex()]->flags_ |= atom->flags_;
+ return index;
+ }
+
+ auto index =
+ internChar16Seq<AtomCharT>(fc, addPtr, atom->hash(), seq, atom->length());
+ if (!index) {
+ return TaggedParserAtomIndex::null();
+ }
+
+ // Copy UsedByStencilFlag and AtomizeFlag.
+ MOZ_ASSERT(entries_[index.toParserAtomIndex()]->hasTwoByteChars() ==
+ atom->hasTwoByteChars());
+ entries_[index.toParserAtomIndex()]->flags_ |= atom->flags_;
+ return index;
+}
+
+TaggedParserAtomIndex ParserAtomsTable::internExternalParserAtom(
+ FrontendContext* fc, const ParserAtom* atom) {
+ if (atom->hasLatin1Chars()) {
+ return internExternalParserAtomImpl<JS::Latin1Char>(fc, atom);
+ }
+ return internExternalParserAtomImpl<char16_t>(fc, atom);
+}
+
+bool ParserAtomsTable::addPlaceholder(FrontendContext* fc) {
+ ParserAtomIndex index = ParserAtomIndex(entries_.length());
+ if (size_t(index) >= TaggedParserAtomIndex::IndexLimit) {
+ ReportAllocationOverflow(fc);
+ return false;
+ }
+ if (!entries_.append(nullptr)) {
+ js::ReportOutOfMemory(fc);
+ return false;
+ }
+ return true;
+}
+
+TaggedParserAtomIndex ParserAtomsTable::internExternalParserAtomIndex(
+ FrontendContext* fc, const CompilationStencil& context,
+ TaggedParserAtomIndex atom) {
+ // When the atom is not a parser atom index, the value represent the atom
+ // without the need for a ParserAtom, and thus we can skip interning it.
+ if (!atom.isParserAtomIndex()) {
+ return atom;
+ }
+ auto index = atom.toParserAtomIndex();
+ return internExternalParserAtom(fc, context.parserAtomData[index]);
+}
+
+bool ParserAtomsTable::isEqualToExternalParserAtomIndex(
+ TaggedParserAtomIndex internal, const CompilationStencil& context,
+ TaggedParserAtomIndex external) const {
+ // If one is null, well-known or static, then testing the equality of the bits
+ // of the TaggedParserAtomIndex is sufficient.
+ if (!internal.isParserAtomIndex() || !external.isParserAtomIndex()) {
+ return internal == external;
+ }
+
+ // Otherwise we have to compare 2 atom-indexes from different ParserAtomTable.
+ ParserAtom* internalAtom = getParserAtom(internal.toParserAtomIndex());
+ ParserAtom* externalAtom =
+ context.parserAtomData[external.toParserAtomIndex()];
+
+ if (internalAtom->hash() != externalAtom->hash()) {
+ return false;
+ }
+
+ HashNumber hash = internalAtom->hash();
+ size_t length = internalAtom->length();
+ if (internalAtom->hasLatin1Chars()) {
+ const Latin1Char* chars = internalAtom->latin1Chars();
+ InflatedChar16Sequence<Latin1Char> seq(chars, length);
+ return externalAtom->equalsSeq(hash, seq);
+ }
+
+ const char16_t* chars = internalAtom->twoByteChars();
+ InflatedChar16Sequence<char16_t> seq(chars, length);
+ return externalAtom->equalsSeq(hash, seq);
+}
+
+bool ParserAtomSpanBuilder::allocate(FrontendContext* fc, LifoAlloc& alloc,
+ size_t count) {
+ if (count >= TaggedParserAtomIndex::IndexLimit) {
+ ReportAllocationOverflow(fc);
+ return false;
+ }
+
+ auto* p = alloc.newArrayUninitialized<ParserAtom*>(count);
+ if (!p) {
+ js::ReportOutOfMemory(fc);
+ return false;
+ }
+ std::uninitialized_fill_n(p, count, nullptr);
+
+ entries_ = mozilla::Span(p, count);
+ return true;
+}
+
+static inline bool IsLatin1(mozilla::Utf8Unit c1, mozilla::Utf8Unit c2) {
+ auto u1 = c1.toUint8();
+ auto u2 = c2.toUint8();
+
+ // 0x80-0xBF
+ if (u1 == 0xC2 && 0x80 <= u2 && u2 <= 0xBF) {
+ return true;
+ }
+
+ // 0xC0-0xFF
+ if (u1 == 0xC3 && 0x80 <= u2 && u2 <= 0xBF) {
+ return true;
+ }
+
+ return false;
+}
+
+TaggedParserAtomIndex ParserAtomsTable::internUtf8(
+ FrontendContext* fc, const mozilla::Utf8Unit* utf8Ptr, uint32_t nbyte) {
+ if (auto tiny = WellKnownParserAtoms::getSingleton().lookupTinyIndexUTF8(
+ utf8Ptr, nbyte)) {
+ return tiny;
+ }
+
+ // If source text is ASCII, then the length of the target char buffer
+ // is the same as the length of the UTF8 input. Convert it to a Latin1
+ // encoded string on the heap.
+ JS::UTF8Chars utf8(utf8Ptr, nbyte);
+ JS::SmallestEncoding minEncoding = FindSmallestEncoding(utf8);
+ if (minEncoding == JS::SmallestEncoding::ASCII) {
+ // As ascii strings are a subset of Latin1 strings, and each encoding
+ // unit is the same size, we can reliably cast this `Utf8Unit*`
+ // to a `Latin1Char*`.
+ const Latin1Char* latin1Ptr = reinterpret_cast<const Latin1Char*>(utf8Ptr);
+ return internLatin1(fc, 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(!WellKnownParserAtoms::getSingleton().lookupChar16Seq(lookup));
+ EntryMap::AddPtr addPtr = entryMap_.lookupForAdd(lookup);
+ if (addPtr) {
+ return addPtr->value();
+ }
+
+ // Compute length in code-points.
+ uint32_t length = 0;
+ InflatedChar16Sequence<mozilla::Utf8Unit> seqCopy = seq;
+ while (seqCopy.hasMore()) {
+ (void)seqCopy.next();
+ length += 1;
+ }
+
+ // Otherwise, add new entry.
+ bool wide = (minEncoding == JS::SmallestEncoding::UTF16);
+ return wide
+ ? internChar16Seq<char16_t>(fc, addPtr, lookup.hash(), seq, length)
+ : internChar16Seq<Latin1Char>(fc, addPtr, lookup.hash(), seq,
+ length);
+}
+
+TaggedParserAtomIndex ParserAtomsTable::internChar16(FrontendContext* fc,
+ const char16_t* char16Ptr,
+ uint32_t length) {
+ // Check for tiny strings which are abundant in minified code.
+ if (auto tiny = WellKnownParserAtoms::getSingleton().lookupTinyIndex(
+ char16Ptr, length)) {
+ return tiny;
+ }
+
+ // Check against well-known.
+ InflatedChar16Sequence<char16_t> seq(char16Ptr, length);
+ SpecificParserAtomLookup<char16_t> lookup(seq);
+ if (auto wk = WellKnownParserAtoms::getSingleton().lookupChar16Seq(lookup)) {
+ return wk;
+ }
+
+ // Check for existing atom.
+ EntryMap::AddPtr addPtr = entryMap_.lookupForAdd(lookup);
+ if (addPtr) {
+ return addPtr->value();
+ }
+
+ // Otherwise, add new entry.
+ return IsWide(seq)
+ ? internChar16Seq<char16_t>(fc, addPtr, lookup.hash(), seq, length)
+ : internChar16Seq<Latin1Char>(fc, addPtr, lookup.hash(), seq,
+ length);
+}
+
+TaggedParserAtomIndex ParserAtomsTable::internJSAtom(
+ FrontendContext* fc, CompilationAtomCache& atomCache, JSAtom* atom) {
+ TaggedParserAtomIndex parserAtom;
+ {
+ JS::AutoCheckCannotGC nogc;
+
+ parserAtom =
+ atom->hasLatin1Chars()
+ ? internLatin1(fc, atom->latin1Chars(nogc), atom->length())
+ : internChar16(fc, atom->twoByteChars(nogc), atom->length());
+ if (!parserAtom) {
+ return TaggedParserAtomIndex::null();
+ }
+ }
+
+ if (parserAtom.isParserAtomIndex()) {
+ ParserAtomIndex index = parserAtom.toParserAtomIndex();
+ if (!atomCache.hasAtomAt(index)) {
+ if (!atomCache.setAtomAt(fc, index, atom)) {
+ return TaggedParserAtomIndex::null();
+ }
+ }
+ }
+
+ // We should (infallibly) map back to the same JSAtom.
+#ifdef DEBUG
+ if (JSContext* cx = fc->maybeCurrentJSContext()) {
+ JS::AutoSuppressGCAnalysis suppress(cx);
+ MOZ_ASSERT(toJSAtom(cx, fc, parserAtom, atomCache) == atom);
+ }
+#endif
+
+ return parserAtom;
+}
+
+ParserAtom* ParserAtomsTable::getParserAtom(ParserAtomIndex index) const {
+ return entries_[index];
+}
+
+void ParserAtomsTable::markUsedByStencil(TaggedParserAtomIndex index,
+ ParserAtom::Atomize atomize) const {
+ if (!index.isParserAtomIndex()) {
+ return;
+ }
+
+ getParserAtom(index.toParserAtomIndex())->markUsedByStencil(atomize);
+}
+
+void ParserAtomsTable::markAtomize(TaggedParserAtomIndex index,
+ ParserAtom::Atomize atomize) const {
+ if (!index.isParserAtomIndex()) {
+ return;
+ }
+
+ getParserAtom(index.toParserAtomIndex())->markAtomize(atomize);
+}
+
+bool ParserAtomsTable::isIdentifier(TaggedParserAtomIndex index) const {
+ if (index.isParserAtomIndex()) {
+ const auto* atom = getParserAtom(index.toParserAtomIndex());
+ return atom->hasLatin1Chars()
+ ? IsIdentifier(atom->latin1Chars(), atom->length())
+ : IsIdentifier(atom->twoByteChars(), atom->length());
+ }
+
+ if (index.isWellKnownAtomId()) {
+ const auto& info = GetWellKnownAtomInfo(index.toWellKnownAtomId());
+ return IsIdentifier(reinterpret_cast<const Latin1Char*>(info.content),
+ info.length);
+ }
+
+ if (index.isLength1StaticParserString()) {
+ Latin1Char content[1];
+ getLength1Content(index.toLength1StaticParserString(), content);
+ if (MOZ_UNLIKELY(content[0] > 127)) {
+ return IsIdentifier(content, 1);
+ }
+ return IsIdentifierASCII(char(content[0]));
+ }
+
+ if (index.isLength2StaticParserString()) {
+ char content[2];
+ getLength2Content(index.toLength2StaticParserString(), content);
+ return IsIdentifierASCII(content[0], content[1]);
+ }
+
+ MOZ_ASSERT(index.isLength3StaticParserString());
+#ifdef DEBUG
+ char content[3];
+ getLength3Content(index.toLength3StaticParserString(), content);
+ MOZ_ASSERT(!reinterpret_cast<const Latin1Char*>(
+ IsIdentifier(reinterpret_cast<const Latin1Char*>(content), 3)));
+#endif
+ return false;
+}
+
+bool ParserAtomsTable::isPrivateName(TaggedParserAtomIndex index) const {
+ if (!index.isParserAtomIndex()) {
+ return false;
+ }
+
+ const auto* atom = getParserAtom(index.toParserAtomIndex());
+ return atom->isPrivateName();
+}
+
+bool ParserAtomsTable::isExtendedUnclonedSelfHostedFunctionName(
+ TaggedParserAtomIndex index) const {
+ if (index.isParserAtomIndex()) {
+ const auto* atom = getParserAtom(index.toParserAtomIndex());
+ if (atom->length() < 2) {
+ return false;
+ }
+
+ return atom->charAt(0) == ExtendedUnclonedSelfHostedFunctionNamePrefix;
+ }
+
+ if (index.isWellKnownAtomId()) {
+ switch (index.toWellKnownAtomId()) {
+ case WellKnownAtomId::ArrayBufferSpecies:
+ case WellKnownAtomId::ArraySpecies:
+ case WellKnownAtomId::ArrayValues:
+ case WellKnownAtomId::RegExpFlagsGetter:
+ case WellKnownAtomId::RegExpToString: {
+#ifdef DEBUG
+ const auto& info = GetWellKnownAtomInfo(index.toWellKnownAtomId());
+ MOZ_ASSERT(info.content[0] ==
+ ExtendedUnclonedSelfHostedFunctionNamePrefix);
+#endif
+ return true;
+ }
+ default: {
+#ifdef DEBUG
+ const auto& info = GetWellKnownAtomInfo(index.toWellKnownAtomId());
+ MOZ_ASSERT(info.length == 0 ||
+ info.content[0] !=
+ ExtendedUnclonedSelfHostedFunctionNamePrefix);
+#endif
+ break;
+ }
+ }
+ return false;
+ }
+
+ // Length-1/2/3 shouldn't be used for extented uncloned self-hosted
+ // function name, and this query shouldn't be used for them.
+#ifdef DEBUG
+ if (index.isLength1StaticParserString()) {
+ Latin1Char content[1];
+ getLength1Content(index.toLength1StaticParserString(), content);
+ MOZ_ASSERT(content[0] != ExtendedUnclonedSelfHostedFunctionNamePrefix);
+ } else if (index.isLength2StaticParserString()) {
+ char content[2];
+ getLength2Content(index.toLength2StaticParserString(), content);
+ MOZ_ASSERT(content[0] != ExtendedUnclonedSelfHostedFunctionNamePrefix);
+ } else {
+ MOZ_ASSERT(index.isLength3StaticParserString());
+ char content[3];
+ getLength3Content(index.toLength3StaticParserString(), content);
+ MOZ_ASSERT(content[0] != ExtendedUnclonedSelfHostedFunctionNamePrefix);
+ }
+#endif
+ return false;
+}
+
+static bool HasUnpairedSurrogate(mozilla::Range<const char16_t> chars) {
+ for (auto ptr = chars.begin(); ptr < chars.end();) {
+ char16_t ch = *ptr++;
+ if (unicode::IsLeadSurrogate(ch)) {
+ if (ptr == chars.end() || !unicode::IsTrailSurrogate(*ptr++)) {
+ return true;
+ }
+ } else if (unicode::IsTrailSurrogate(ch)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+bool ParserAtomsTable::isModuleExportName(TaggedParserAtomIndex index) const {
+ if (index.isParserAtomIndex()) {
+ const ParserAtom* name = getParserAtom(index.toParserAtomIndex());
+ return name->hasLatin1Chars() ||
+ !HasUnpairedSurrogate(name->twoByteRange());
+ }
+
+ // Well-known/length-2 are ASCII.
+ // length-1 are Latin1.
+ return true;
+}
+
+bool ParserAtomsTable::isIndex(TaggedParserAtomIndex index,
+ uint32_t* indexp) const {
+ if (index.isParserAtomIndex()) {
+ const auto* atom = getParserAtom(index.toParserAtomIndex());
+ size_t len = atom->length();
+ if (len == 0 || len > UINT32_CHAR_BUFFER_LENGTH) {
+ return false;
+ }
+ if (atom->hasLatin1Chars()) {
+ return mozilla::IsAsciiDigit(*atom->latin1Chars()) &&
+ js::CheckStringIsIndex(atom->latin1Chars(), len, indexp);
+ }
+ return mozilla::IsAsciiDigit(*atom->twoByteChars()) &&
+ js::CheckStringIsIndex(atom->twoByteChars(), len, indexp);
+ }
+
+ if (index.isWellKnownAtomId()) {
+#ifdef DEBUG
+ // Well-known atom shouldn't start with digit.
+ const auto& info = GetWellKnownAtomInfo(index.toWellKnownAtomId());
+ MOZ_ASSERT(info.length == 0 || !mozilla::IsAsciiDigit(info.content[0]));
+#endif
+ return false;
+ }
+
+ if (index.isLength1StaticParserString()) {
+ Latin1Char content[1];
+ getLength1Content(index.toLength1StaticParserString(), content);
+ if (mozilla::IsAsciiDigit(content[0])) {
+ *indexp = AsciiDigitToNumber(content[0]);
+ return true;
+ }
+ return false;
+ }
+
+ if (index.isLength2StaticParserString()) {
+ char content[2];
+ getLength2Content(index.toLength2StaticParserString(), content);
+ // Leading '0' isn't allowed.
+ // See CheckStringIsIndex comment.
+ if (content[0] != '0' && mozilla::IsAsciiDigit(content[0]) &&
+ mozilla::IsAsciiDigit(content[1])) {
+ *indexp =
+ AsciiDigitToNumber(content[0]) * 10 + AsciiDigitToNumber(content[1]);
+ return true;
+ }
+ return false;
+ }
+
+ MOZ_ASSERT(index.isLength3StaticParserString());
+ *indexp = uint32_t(index.toLength3StaticParserString());
+#ifdef DEBUG
+ char content[3];
+ getLength3Content(index.toLength3StaticParserString(), content);
+ MOZ_ASSERT(uint32_t(AsciiDigitToNumber(content[0])) * 100 +
+ uint32_t(AsciiDigitToNumber(content[1])) * 10 +
+ uint32_t(AsciiDigitToNumber(content[2])) ==
+ *indexp);
+ MOZ_ASSERT(100 <= *indexp);
+#endif
+ return true;
+}
+
+bool ParserAtomsTable::isInstantiatedAsJSAtom(
+ TaggedParserAtomIndex index) const {
+ if (index.isParserAtomIndex()) {
+ const auto* atom = getParserAtom(index.toParserAtomIndex());
+ return atom->isInstantiatedAsJSAtom();
+ }
+
+ // Everything else are always JSAtom.
+ return true;
+}
+
+uint32_t ParserAtomsTable::length(TaggedParserAtomIndex index) const {
+ if (index.isParserAtomIndex()) {
+ return getParserAtom(index.toParserAtomIndex())->length();
+ }
+
+ if (index.isWellKnownAtomId()) {
+ const auto& info = GetWellKnownAtomInfo(index.toWellKnownAtomId());
+ return info.length;
+ }
+
+ if (index.isLength1StaticParserString()) {
+ return 1;
+ }
+
+ if (index.isLength2StaticParserString()) {
+ return 2;
+ }
+
+ MOZ_ASSERT(index.isLength3StaticParserString());
+ return 3;
+}
+
+HashNumber ParserAtomsTable::hash(TaggedParserAtomIndex index) const {
+ if (index.isParserAtomIndex()) {
+ return getParserAtom(index.toParserAtomIndex())->hash();
+ }
+
+ return index.staticOrWellKnownHash();
+}
+
+double ParserAtomsTable::toNumber(TaggedParserAtomIndex index) const {
+ if (index.isParserAtomIndex()) {
+ const auto* atom = getParserAtom(index.toParserAtomIndex());
+ size_t len = atom->length();
+ return atom->hasLatin1Chars() ? CharsToNumber(atom->latin1Chars(), len)
+ : CharsToNumber(atom->twoByteChars(), len);
+ }
+
+ if (index.isWellKnownAtomId()) {
+ const auto& info = GetWellKnownAtomInfo(index.toWellKnownAtomId());
+ return CharsToNumber(reinterpret_cast<const Latin1Char*>(info.content),
+ info.length);
+ }
+
+ if (index.isLength1StaticParserString()) {
+ Latin1Char content[1];
+ getLength1Content(index.toLength1StaticParserString(), content);
+ return CharsToNumber(content, 1);
+ }
+
+ if (index.isLength2StaticParserString()) {
+ char content[2];
+ getLength2Content(index.toLength2StaticParserString(), content);
+ return CharsToNumber(reinterpret_cast<const Latin1Char*>(content), 2);
+ }
+
+ MOZ_ASSERT(index.isLength3StaticParserString());
+ double result = double(index.toLength3StaticParserString());
+#ifdef DEBUG
+ char content[3];
+ getLength3Content(index.toLength3StaticParserString(), content);
+ double tmp = CharsToNumber(reinterpret_cast<const Latin1Char*>(content), 3);
+ MOZ_ASSERT(tmp == result);
+#endif
+ return result;
+}
+
+UniqueChars ParserAtomsTable::toNewUTF8CharsZ(
+ FrontendContext* fc, TaggedParserAtomIndex index) const {
+ auto* alloc = fc->getAllocator();
+
+ if (index.isParserAtomIndex()) {
+ const auto* atom = getParserAtom(index.toParserAtomIndex());
+ return UniqueChars(
+ atom->hasLatin1Chars()
+ ? JS::CharsToNewUTF8CharsZ(alloc, atom->latin1Range()).c_str()
+ : JS::CharsToNewUTF8CharsZ(alloc, atom->twoByteRange()).c_str());
+ }
+
+ if (index.isWellKnownAtomId()) {
+ const auto& info = GetWellKnownAtomInfo(index.toWellKnownAtomId());
+ return UniqueChars(
+ JS::CharsToNewUTF8CharsZ(
+ alloc,
+ mozilla::Range(reinterpret_cast<const Latin1Char*>(info.content),
+ info.length))
+ .c_str());
+ }
+
+ if (index.isLength1StaticParserString()) {
+ Latin1Char content[1];
+ getLength1Content(index.toLength1StaticParserString(), content);
+ return UniqueChars(
+ JS::CharsToNewUTF8CharsZ(alloc, mozilla::Range(content, 1)).c_str());
+ }
+
+ if (index.isLength2StaticParserString()) {
+ char content[2];
+ getLength2Content(index.toLength2StaticParserString(), content);
+ return UniqueChars(
+ JS::CharsToNewUTF8CharsZ(
+ alloc,
+ mozilla::Range(reinterpret_cast<const Latin1Char*>(content), 2))
+ .c_str());
+ }
+
+ MOZ_ASSERT(index.isLength3StaticParserString());
+ char content[3];
+ getLength3Content(index.toLength3StaticParserString(), content);
+ return UniqueChars(
+ JS::CharsToNewUTF8CharsZ(
+ alloc,
+ mozilla::Range(reinterpret_cast<const Latin1Char*>(content), 3))
+ .c_str());
+}
+
+template <typename CharT>
+UniqueChars ToPrintableStringImpl(mozilla::Range<CharT> str,
+ char quote = '\0') {
+ // Pass nullptr as JSContext, given we don't use JSString.
+ // OOM should be handled by caller.
+ Sprinter sprinter(nullptr);
+ if (!sprinter.init()) {
+ return nullptr;
+ }
+ if (!QuoteString<QuoteTarget::String>(&sprinter, str, quote)) {
+ return nullptr;
+ }
+ return sprinter.release();
+}
+
+UniqueChars ParserAtomsTable::toPrintableString(
+ TaggedParserAtomIndex index) const {
+ if (index.isParserAtomIndex()) {
+ const auto* atom = getParserAtom(index.toParserAtomIndex());
+ return atom->hasLatin1Chars() ? ToPrintableStringImpl(atom->latin1Range())
+ : ToPrintableStringImpl(atom->twoByteRange());
+ }
+
+ if (index.isWellKnownAtomId()) {
+ const auto& info = GetWellKnownAtomInfo(index.toWellKnownAtomId());
+ return ToPrintableStringImpl(mozilla::Range(
+ reinterpret_cast<const Latin1Char*>(info.content), info.length));
+ }
+
+ if (index.isLength1StaticParserString()) {
+ Latin1Char content[1];
+ getLength1Content(index.toLength1StaticParserString(), content);
+ return ToPrintableStringImpl(mozilla::Range<const Latin1Char>(content, 1));
+ }
+
+ if (index.isLength2StaticParserString()) {
+ char content[2];
+ getLength2Content(index.toLength2StaticParserString(), content);
+ return ToPrintableStringImpl(
+ mozilla::Range(reinterpret_cast<const Latin1Char*>(content), 2));
+ }
+
+ MOZ_ASSERT(index.isLength3StaticParserString());
+ char content[3];
+ getLength3Content(index.toLength3StaticParserString(), content);
+ return ToPrintableStringImpl(
+ mozilla::Range(reinterpret_cast<const Latin1Char*>(content), 3));
+}
+
+UniqueChars ParserAtomsTable::toQuotedString(
+ TaggedParserAtomIndex index) const {
+ if (index.isParserAtomIndex()) {
+ const auto* atom = getParserAtom(index.toParserAtomIndex());
+ return atom->hasLatin1Chars()
+ ? ToPrintableStringImpl(atom->latin1Range(), '\"')
+ : ToPrintableStringImpl(atom->twoByteRange(), '\"');
+ }
+
+ if (index.isWellKnownAtomId()) {
+ const auto& info = GetWellKnownAtomInfo(index.toWellKnownAtomId());
+ return ToPrintableStringImpl(
+ mozilla::Range(reinterpret_cast<const Latin1Char*>(info.content),
+ info.length),
+ '\"');
+ }
+
+ if (index.isLength1StaticParserString()) {
+ Latin1Char content[1];
+ getLength1Content(index.toLength1StaticParserString(), content);
+ return ToPrintableStringImpl(mozilla::Range<const Latin1Char>(content, 1),
+ '\"');
+ }
+
+ if (index.isLength2StaticParserString()) {
+ char content[2];
+ getLength2Content(index.toLength2StaticParserString(), content);
+ return ToPrintableStringImpl(
+ mozilla::Range(reinterpret_cast<const Latin1Char*>(content), 2), '\"');
+ }
+
+ MOZ_ASSERT(index.isLength3StaticParserString());
+ char content[3];
+ getLength3Content(index.toLength3StaticParserString(), content);
+ return ToPrintableStringImpl(
+ mozilla::Range(reinterpret_cast<const Latin1Char*>(content), 3), '\"');
+}
+
+JSAtom* ParserAtomsTable::toJSAtom(JSContext* cx, FrontendContext* fc,
+ TaggedParserAtomIndex index,
+ CompilationAtomCache& atomCache) const {
+ // This function can be called before we instantiate atoms based on
+ // AtomizeFlag.
+
+ if (index.isParserAtomIndex()) {
+ auto atomIndex = index.toParserAtomIndex();
+
+ // If we already instantiated this parser atom, it should always be JSAtom.
+ // `asAtom()` called in getAtomAt asserts that.
+ JSAtom* atom = atomCache.getAtomAt(atomIndex);
+ if (atom) {
+ return atom;
+ }
+
+ // For consistency, mark atomize.
+ ParserAtom* parserAtom = getParserAtom(atomIndex);
+ parserAtom->markAtomize(ParserAtom::Atomize::Yes);
+ return parserAtom->instantiateAtom(cx, fc, atomIndex, atomCache);
+ }
+
+ if (index.isWellKnownAtomId()) {
+ return GetWellKnownAtom(cx, index.toWellKnownAtomId());
+ }
+
+ if (index.isLength1StaticParserString()) {
+ char16_t ch = static_cast<char16_t>(index.toLength1StaticParserString());
+ return cx->staticStrings().getUnit(ch);
+ }
+
+ if (index.isLength2StaticParserString()) {
+ size_t s = static_cast<size_t>(index.toLength2StaticParserString());
+ return cx->staticStrings().getLength2FromIndex(s);
+ }
+
+ MOZ_ASSERT(index.isLength3StaticParserString());
+ uint32_t s = uint32_t(index.toLength3StaticParserString());
+ return cx->staticStrings().getUint(s);
+}
+
+bool ParserAtomsTable::appendTo(StringBuffer& buffer,
+ TaggedParserAtomIndex index) const {
+ if (index.isParserAtomIndex()) {
+ const auto* atom = getParserAtom(index.toParserAtomIndex());
+ size_t length = atom->length();
+ return atom->hasLatin1Chars() ? buffer.append(atom->latin1Chars(), length)
+ : buffer.append(atom->twoByteChars(), length);
+ }
+
+ if (index.isWellKnownAtomId()) {
+ const auto& info = GetWellKnownAtomInfo(index.toWellKnownAtomId());
+ return buffer.append(info.content, info.length);
+ }
+
+ if (index.isLength1StaticParserString()) {
+ Latin1Char content[1];
+ getLength1Content(index.toLength1StaticParserString(), content);
+ return buffer.append(content[0]);
+ }
+
+ if (index.isLength2StaticParserString()) {
+ char content[2];
+ getLength2Content(index.toLength2StaticParserString(), content);
+ return buffer.append(content, 2);
+ }
+
+ MOZ_ASSERT(index.isLength3StaticParserString());
+ char content[3];
+ getLength3Content(index.toLength3StaticParserString(), content);
+ return buffer.append(content, 3);
+}
+
+bool InstantiateMarkedAtoms(JSContext* cx, FrontendContext* fc,
+ const ParserAtomSpan& entries,
+ CompilationAtomCache& atomCache) {
+ MOZ_ASSERT(cx->zone());
+
+ for (size_t i = 0; i < entries.size(); i++) {
+ const auto& entry = entries[i];
+ if (!entry) {
+ continue;
+ }
+ if (!entry->isUsedByStencil()) {
+ continue;
+ }
+
+ auto index = ParserAtomIndex(i);
+ if (atomCache.hasAtomAt(index)) {
+ continue;
+ }
+
+ if (!entry->isInstantiatedAsJSAtom()) {
+ if (!entry->instantiateString(cx, fc, index, atomCache)) {
+ return false;
+ }
+ } else {
+ if (!entry->instantiateAtom(cx, fc, index, atomCache)) {
+ return false;
+ }
+ }
+ }
+ return true;
+}
+
+bool InstantiateMarkedAtomsAsPermanent(JSContext* cx, FrontendContext* fc,
+ AtomSet& atomSet,
+ const ParserAtomSpan& entries,
+ CompilationAtomCache& atomCache) {
+ MOZ_ASSERT(!cx->zone());
+
+ for (size_t i = 0; i < entries.size(); i++) {
+ const auto& entry = entries[i];
+ if (!entry) {
+ continue;
+ }
+ if (!entry->isUsedByStencil()) {
+ continue;
+ }
+
+ auto index = ParserAtomIndex(i);
+ if (atomCache.hasAtomAt(index)) {
+ MOZ_ASSERT(atomCache.getAtomAt(index)->isPermanentAtom());
+ continue;
+ }
+
+ if (!entry->instantiatePermanentAtom(cx, fc, atomSet, index, atomCache)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+/* static */
+WellKnownParserAtoms WellKnownParserAtoms::singleton_;
+
+template <typename CharT>
+TaggedParserAtomIndex WellKnownParserAtoms::lookupChar16Seq(
+ const SpecificParserAtomLookup<CharT>& lookup) const {
+ EntryMap::Ptr ptr = wellKnownMap_.readonlyThreadsafeLookup(lookup);
+ if (ptr) {
+ return ptr->value();
+ }
+ return TaggedParserAtomIndex::null();
+}
+
+TaggedParserAtomIndex WellKnownParserAtoms::lookupTinyIndexUTF8(
+ const mozilla::Utf8Unit* utf8Ptr, size_t nbyte) const {
+ // Check for tiny strings which are abundant in minified code.
+ if (nbyte == 2 && IsLatin1(utf8Ptr[0], utf8Ptr[1])) {
+ // Special case the length-1 non-ASCII range.
+ InflatedChar16Sequence<mozilla::Utf8Unit> seq(utf8Ptr, 2);
+ char16_t u = seq.next();
+ const Latin1Char c = u;
+ MOZ_ASSERT(!seq.hasMore());
+ auto tiny = lookupTinyIndex(&c, 1);
+ MOZ_ASSERT(tiny);
+ return tiny;
+ }
+
+ // NOTE: Other than length-1 non-ASCII range, the tiny atoms are all
+ // ASCII-only so we can directly look at the UTF-8 data without
+ // worrying about surrogates.
+ return lookupTinyIndex(reinterpret_cast<const Latin1Char*>(utf8Ptr), nbyte);
+}
+
+bool WellKnownParserAtoms::initSingle(const WellKnownAtomInfo& info,
+ TaggedParserAtomIndex index) {
+ unsigned int len = info.length;
+ const Latin1Char* str = reinterpret_cast<const Latin1Char*>(info.content);
+
+ // Well-known atoms are all currently ASCII with length <= MaxWellKnownLength.
+ MOZ_ASSERT(len <= MaxWellKnownLength);
+ MOZ_ASSERT(mozilla::IsAscii(mozilla::Span(info.content, len)));
+
+ // Strings matched by lookupTinyIndex are stored in static table and aliases
+ // should be initialized directly in WellKnownParserAtoms::init.
+ MOZ_ASSERT(lookupTinyIndex(str, len) == TaggedParserAtomIndex::null(),
+ "Well-known atom matches a tiny StaticString. Did you add it to "
+ "the wrong CommonPropertyNames.h list?");
+
+ InflatedChar16Sequence<Latin1Char> seq(str, len);
+ SpecificParserAtomLookup<Latin1Char> lookup(seq, info.hash);
+
+ // Save name for returning after moving entry into set.
+ if (!wellKnownMap_.putNew(lookup, &info, index)) {
+ return false;
+ }
+
+ return true;
+}
+
+bool WellKnownParserAtoms::init() {
+ MOZ_ASSERT(wellKnownMap_.empty());
+
+ // Add well-known strings to the HashMap. The HashMap is used for dynamic
+ // lookups later and does not change once this init method is complete.
+#define COMMON_NAME_INIT_(_, NAME, _2) \
+ if (!initSingle(GetWellKnownAtomInfo(WellKnownAtomId::NAME), \
+ TaggedParserAtomIndex::WellKnown::NAME())) { \
+ return false; \
+ }
+ FOR_EACH_NONTINY_COMMON_PROPERTYNAME(COMMON_NAME_INIT_)
+#undef COMMON_NAME_INIT_
+#define COMMON_NAME_INIT_(NAME, _) \
+ if (!initSingle(GetWellKnownAtomInfo(WellKnownAtomId::NAME), \
+ TaggedParserAtomIndex::WellKnown::NAME())) { \
+ return false; \
+ }
+ JS_FOR_EACH_PROTOTYPE(COMMON_NAME_INIT_)
+#undef COMMON_NAME_INIT_
+#define COMMON_NAME_INIT_(NAME) \
+ if (!initSingle(GetWellKnownAtomInfo(WellKnownAtomId::NAME), \
+ TaggedParserAtomIndex::WellKnown::NAME())) { \
+ return false; \
+ }
+ JS_FOR_EACH_WELL_KNOWN_SYMBOL(COMMON_NAME_INIT_)
+#undef COMMON_NAME_INIT_
+
+ return true;
+}
+
+void WellKnownParserAtoms::free() { wellKnownMap_.clear(); }
+
+/* static */ bool WellKnownParserAtoms::initSingleton() {
+ return singleton_.init();
+}
+
+/* static */ void WellKnownParserAtoms::freeSingleton() { singleton_.free(); }
+
+} /* namespace frontend */
+} /* namespace js */
diff --git a/js/src/frontend/ParserAtom.h b/js/src/frontend/ParserAtom.h
new file mode 100644
index 0000000000..86a033af2c
--- /dev/null
+++ b/js/src/frontend/ParserAtom.h
@@ -0,0 +1,905 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef frontend_ParserAtom_h
+#define frontend_ParserAtom_h
+
+#include "mozilla/MemoryReporting.h" // mozilla::MallocSizeOf
+#include "mozilla/Range.h" // mozilla::Range
+#include "mozilla/Span.h" // mozilla::Span
+#include "mozilla/TextUtils.h"
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include "jstypes.h"
+#include "NamespaceImports.h"
+
+#include "frontend/TypedIndex.h" // TypedIndex
+#include "js/HashTable.h" // HashMap
+#include "js/ProtoKey.h" // JS_FOR_EACH_PROTOTYPE
+#include "js/Symbol.h" // JS_FOR_EACH_WELL_KNOWN_SYMBOL
+#include "js/TypeDecls.h" // Latin1Char
+#include "js/Utility.h" // UniqueChars
+#include "js/Vector.h" // Vector
+#include "threading/Mutex.h" // Mutex
+#include "util/Text.h" // InflatedChar16Sequence
+#include "vm/CommonPropertyNames.h"
+#include "vm/StaticStrings.h"
+#include "vm/WellKnownAtom.h" // WellKnownAtomId, WellKnownAtomInfo
+
+struct JS_PUBLIC_API JSContext;
+
+class JSAtom;
+class JSString;
+
+namespace mozilla {
+union Utf8Unit;
+}
+
+namespace js {
+
+class AtomSet;
+class JS_PUBLIC_API GenericPrinter;
+class LifoAlloc;
+class StringBuffer;
+
+namespace frontend {
+
+struct CompilationAtomCache;
+struct CompilationStencil;
+
+template <typename CharT>
+class SpecificParserAtomLookup;
+
+// These types correspond into indices in the StaticStrings arrays.
+enum class Length1StaticParserString : uint8_t;
+enum class Length2StaticParserString : uint16_t;
+enum class Length3StaticParserString : uint8_t;
+
+class ParserAtom;
+using ParserAtomIndex = TypedIndex<ParserAtom>;
+
+// ParserAtomIndex, WellKnownAtomId, Length1StaticParserString,
+// Length2StaticParserString, Length3StaticParserString, or null.
+//
+// 0x0000_0000 Null atom
+//
+// 0x1YYY_YYYY 28-bit ParserAtom
+//
+// 0x2000_YYYY Well-known atom ID
+// 0x2001_YYYY Static length-1 atom : whole Latin1 range
+// 0x2002_YYYY Static length-2 atom : `[A-Za-z0-9$_]{2}`
+// 0x2003_YYYY Static length-3 atom : decimal "100" to "255"
+class TaggedParserAtomIndex {
+ uint32_t data_;
+
+ public:
+ static constexpr size_t IndexBit = 28;
+ static constexpr size_t IndexMask = BitMask(IndexBit);
+
+ static constexpr size_t TagShift = IndexBit;
+ static constexpr size_t TagBit = 4;
+ static constexpr size_t TagMask = BitMask(TagBit) << TagShift;
+
+ enum class Kind : uint32_t {
+ Null = 0,
+ ParserAtomIndex,
+ WellKnown,
+ };
+
+ private:
+ static constexpr size_t SmallIndexBit = 16;
+ static constexpr size_t SmallIndexMask = BitMask(SmallIndexBit);
+
+ static constexpr size_t SubTagShift = SmallIndexBit;
+ static constexpr size_t SubTagBit = 2;
+ static constexpr size_t SubTagMask = BitMask(SubTagBit) << SubTagShift;
+
+ public:
+ static constexpr uint32_t NullTag = uint32_t(Kind::Null) << TagShift;
+ static constexpr uint32_t ParserAtomIndexTag = uint32_t(Kind::ParserAtomIndex)
+ << TagShift;
+ static constexpr uint32_t WellKnownTag = uint32_t(Kind::WellKnown)
+ << TagShift;
+
+ private:
+ static constexpr uint32_t WellKnownSubTag = 0 << SubTagShift;
+ static constexpr uint32_t Length1StaticSubTag = 1 << SubTagShift;
+ static constexpr uint32_t Length2StaticSubTag = 2 << SubTagShift;
+ static constexpr uint32_t Length3StaticSubTag = 3 << SubTagShift;
+
+ public:
+ static constexpr uint32_t IndexLimit = Bit(IndexBit);
+ static constexpr uint32_t SmallIndexLimit = Bit(SmallIndexBit);
+
+ static constexpr size_t Length1StaticLimit = 256U;
+ static constexpr size_t Length2StaticLimit =
+ StaticStrings::NUM_LENGTH2_ENTRIES;
+ static constexpr size_t Length3StaticLimit = 256U;
+
+ private:
+ explicit TaggedParserAtomIndex(uint32_t data) : data_(data) {}
+
+ public:
+ constexpr TaggedParserAtomIndex() : data_(NullTag) {}
+
+ explicit constexpr TaggedParserAtomIndex(ParserAtomIndex index)
+ : data_(index.index | ParserAtomIndexTag) {
+ MOZ_ASSERT(index.index < IndexLimit);
+ }
+ explicit constexpr TaggedParserAtomIndex(WellKnownAtomId index)
+ : data_(uint32_t(index) | WellKnownTag | WellKnownSubTag) {
+ MOZ_ASSERT(uint32_t(index) < SmallIndexLimit);
+
+ // Length1Static/Length2Static string shouldn't use WellKnownAtomId.
+#define CHECK_(_, NAME, _2) MOZ_ASSERT(index != WellKnownAtomId::NAME);
+ FOR_EACH_NON_EMPTY_TINY_PROPERTYNAME(CHECK_)
+#undef CHECK_
+ }
+ explicit constexpr TaggedParserAtomIndex(Length1StaticParserString index)
+ : data_(uint32_t(index) | WellKnownTag | Length1StaticSubTag) {}
+ explicit constexpr TaggedParserAtomIndex(Length2StaticParserString index)
+ : data_(uint32_t(index) | WellKnownTag | Length2StaticSubTag) {}
+ explicit constexpr TaggedParserAtomIndex(Length3StaticParserString index)
+ : data_(uint32_t(index) | WellKnownTag | Length3StaticSubTag) {}
+
+ class WellKnown {
+ public:
+#define METHOD_(_, NAME, _2) \
+ static constexpr TaggedParserAtomIndex NAME() { \
+ return TaggedParserAtomIndex(WellKnownAtomId::NAME); \
+ }
+ FOR_EACH_NONTINY_COMMON_PROPERTYNAME(METHOD_)
+#undef METHOD_
+
+#define METHOD_(NAME, _) \
+ static constexpr TaggedParserAtomIndex NAME() { \
+ return TaggedParserAtomIndex(WellKnownAtomId::NAME); \
+ }
+ JS_FOR_EACH_PROTOTYPE(METHOD_)
+#undef METHOD_
+
+#define METHOD_(NAME) \
+ static constexpr TaggedParserAtomIndex NAME() { \
+ return TaggedParserAtomIndex(WellKnownAtomId::NAME); \
+ }
+ JS_FOR_EACH_WELL_KNOWN_SYMBOL(METHOD_)
+#undef METHOD_
+
+#define METHOD_(_, NAME, STR) \
+ static constexpr TaggedParserAtomIndex NAME() { \
+ return TaggedParserAtomIndex(Length1StaticParserString((STR)[0])); \
+ }
+ FOR_EACH_LENGTH1_PROPERTYNAME(METHOD_)
+#undef METHOD_
+
+#define METHOD_(_, NAME, STR) \
+ static constexpr TaggedParserAtomIndex NAME() { \
+ return TaggedParserAtomIndex(Length2StaticParserString( \
+ (StaticStrings::getLength2IndexStatic((STR)[0], (STR)[1])))); \
+ }
+ FOR_EACH_LENGTH2_PROPERTYNAME(METHOD_)
+#undef METHOD_
+
+ static constexpr TaggedParserAtomIndex empty() {
+ return TaggedParserAtomIndex(WellKnownAtomId::empty);
+ }
+ };
+
+ // The value of rawData() for WellKnown TaggedParserAtomIndex.
+ // For using in switch-case.
+ class WellKnownRawData {
+ public:
+#define METHOD_(_, NAME, _2) \
+ static constexpr uint32_t NAME() { \
+ return uint32_t(WellKnownAtomId::NAME) | WellKnownTag | WellKnownSubTag; \
+ }
+ FOR_EACH_NONTINY_COMMON_PROPERTYNAME(METHOD_)
+#undef METHOD_
+
+#define METHOD_(NAME, _) \
+ static constexpr uint32_t NAME() { \
+ return uint32_t(WellKnownAtomId::NAME) | WellKnownTag | WellKnownSubTag; \
+ }
+ JS_FOR_EACH_PROTOTYPE(METHOD_)
+#undef METHOD_
+
+#define METHOD_(NAME) \
+ static constexpr uint32_t NAME() { \
+ return uint32_t(WellKnownAtomId::NAME) | WellKnownTag | WellKnownSubTag; \
+ }
+ JS_FOR_EACH_WELL_KNOWN_SYMBOL(METHOD_)
+#undef METHOD_
+
+#define METHOD_(_, NAME, STR) \
+ static constexpr uint32_t NAME() { \
+ return uint32_t((STR)[0]) | WellKnownTag | Length1StaticSubTag; \
+ }
+ FOR_EACH_LENGTH1_PROPERTYNAME(METHOD_)
+#undef METHOD_
+
+#define METHOD_(_, NAME, STR) \
+ static constexpr uint32_t NAME() { \
+ return uint32_t( \
+ StaticStrings::getLength2IndexStatic((STR)[0], (STR)[1])) | \
+ WellKnownTag | Length2StaticSubTag; \
+ }
+ FOR_EACH_LENGTH2_PROPERTYNAME(METHOD_)
+#undef METHOD_
+
+ static constexpr uint32_t empty() {
+ return uint32_t(WellKnownAtomId::empty) | WellKnownTag | WellKnownSubTag;
+ }
+ };
+
+ // NOTE: this is not well-known "null".
+ static TaggedParserAtomIndex null() { return TaggedParserAtomIndex(); }
+
+#ifdef DEBUG
+ void validateRaw();
+#endif
+
+ static TaggedParserAtomIndex fromRaw(uint32_t data) {
+ auto result = TaggedParserAtomIndex(data);
+#ifdef DEBUG
+ result.validateRaw();
+#endif
+ return result;
+ }
+
+ bool isParserAtomIndex() const {
+ return (data_ & TagMask) == ParserAtomIndexTag;
+ }
+ bool isWellKnownAtomId() const {
+ return (data_ & (TagMask | SubTagMask)) == (WellKnownTag | WellKnownSubTag);
+ }
+ bool isLength1StaticParserString() const {
+ return (data_ & (TagMask | SubTagMask)) ==
+ (WellKnownTag | Length1StaticSubTag);
+ }
+ bool isLength2StaticParserString() const {
+ return (data_ & (TagMask | SubTagMask)) ==
+ (WellKnownTag | Length2StaticSubTag);
+ }
+ bool isLength3StaticParserString() const {
+ return (data_ & (TagMask | SubTagMask)) ==
+ (WellKnownTag | Length3StaticSubTag);
+ }
+ bool isNull() const {
+ bool result = !data_;
+ MOZ_ASSERT_IF(result, (data_ & TagMask) == NullTag);
+ return result;
+ }
+ HashNumber staticOrWellKnownHash() const;
+
+ ParserAtomIndex toParserAtomIndex() const {
+ MOZ_ASSERT(isParserAtomIndex());
+ return ParserAtomIndex(data_ & IndexMask);
+ }
+ WellKnownAtomId toWellKnownAtomId() const {
+ MOZ_ASSERT(isWellKnownAtomId());
+ return WellKnownAtomId(data_ & SmallIndexMask);
+ }
+ Length1StaticParserString toLength1StaticParserString() const {
+ MOZ_ASSERT(isLength1StaticParserString());
+ return Length1StaticParserString(data_ & SmallIndexMask);
+ }
+ Length2StaticParserString toLength2StaticParserString() const {
+ MOZ_ASSERT(isLength2StaticParserString());
+ return Length2StaticParserString(data_ & SmallIndexMask);
+ }
+ Length3StaticParserString toLength3StaticParserString() const {
+ MOZ_ASSERT(isLength3StaticParserString());
+ return Length3StaticParserString(data_ & SmallIndexMask);
+ }
+
+ uint32_t* rawDataRef() { return &data_; }
+ uint32_t rawData() const { return data_; }
+
+ bool operator==(const TaggedParserAtomIndex& rhs) const {
+ return data_ == rhs.data_;
+ }
+ bool operator!=(const TaggedParserAtomIndex& rhs) const {
+ return data_ != rhs.data_;
+ }
+
+ explicit operator bool() const { return !isNull(); }
+};
+
+// Trivial variant of TaggedParserAtomIndex, to use in collection that requires
+// trivial type.
+// Provides minimal set of methods to use in collection.
+class TrivialTaggedParserAtomIndex {
+ uint32_t data_;
+
+ public:
+ static TrivialTaggedParserAtomIndex from(TaggedParserAtomIndex index) {
+ TrivialTaggedParserAtomIndex result;
+ result.data_ = index.rawData();
+ return result;
+ }
+
+ operator TaggedParserAtomIndex() const {
+ return TaggedParserAtomIndex::fromRaw(data_);
+ }
+
+ static TrivialTaggedParserAtomIndex null() {
+ TrivialTaggedParserAtomIndex result;
+ result.data_ = 0;
+ return result;
+ }
+
+ bool isNull() const {
+ static_assert(TaggedParserAtomIndex::NullTag == 0);
+ return data_ == 0;
+ }
+
+ uint32_t rawData() const { return data_; }
+
+ bool operator==(const TrivialTaggedParserAtomIndex& rhs) const {
+ return data_ == rhs.data_;
+ }
+ bool operator!=(const TrivialTaggedParserAtomIndex& rhs) const {
+ return data_ != rhs.data_;
+ }
+
+ explicit operator bool() const { return !isNull(); }
+};
+
+/**
+ * A ParserAtom is an in-parser representation of an interned atomic
+ * string. It mostly mirrors the information carried by a JSAtom*.
+ *
+ * The atom contents are stored in one of two locations:
+ * 1. Inline Latin1Char storage (immediately after the ParserAtom memory).
+ * 2. Inline char16_t storage (immediately after the ParserAtom memory).
+ */
+class alignas(alignof(uint32_t)) ParserAtom {
+ friend class ParserAtomsTable;
+ friend class WellKnownParserAtoms;
+
+ static const uint16_t MAX_LATIN1_CHAR = 0xff;
+
+ // Bit flags inside flags_.
+ static constexpr uint32_t HasTwoByteCharsFlag = 1 << 0;
+ static constexpr uint32_t UsedByStencilFlag = 1 << 1;
+ static constexpr uint32_t AtomizeFlag = 1 << 2;
+
+ public:
+ // Whether to atomize the ParserAtom during instantiation.
+ //
+ // If this ParserAtom is used by opcode with JOF_ATOM, or used as a binding
+ // in scope, it needs to be instantiated as JSAtom.
+ // Otherwise, it needs to be instantiated as LinearString, to reduce the
+ // cost of atomization.
+ enum class Atomize : uint32_t {
+ No = 0,
+ Yes = AtomizeFlag,
+ };
+
+ private:
+ // Helper routine to read some sequence of two-byte chars, and write them
+ // into a target buffer of a particular character width.
+ //
+ // The characters in the sequence must have been verified prior
+ template <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;
+
+ uint32_t flags_ = 0;
+
+ // End of fields.
+
+ ParserAtom(uint32_t length, HashNumber hash, bool hasTwoByteChars)
+ : hash_(hash),
+ length_(length),
+ flags_(hasTwoByteChars ? HasTwoByteCharsFlag : 0) {}
+
+ public:
+ // The constexpr constructor is used by XDR
+ constexpr ParserAtom() = default;
+
+ // ParserAtoms may own their content buffers in variant_, and thus
+ // cannot be copy-constructed - as a new chars would need to be allocated.
+ ParserAtom(const ParserAtom&) = delete;
+ ParserAtom(ParserAtom&& other) = delete;
+
+ template <typename CharT, typename SeqCharT>
+ static ParserAtom* allocate(FrontendContext* fc, LifoAlloc& alloc,
+ InflatedChar16Sequence<SeqCharT> seq,
+ uint32_t length, HashNumber hash);
+
+ bool hasLatin1Chars() const { return !(flags_ & HasTwoByteCharsFlag); }
+ bool hasTwoByteChars() const { return flags_ & HasTwoByteCharsFlag; }
+
+ bool isAscii() const {
+ if (hasTwoByteChars()) {
+ return false;
+ }
+ for (Latin1Char ch : latin1Range()) {
+ if (!mozilla::IsAscii(ch)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ bool isPrivateName() const {
+ if (length() < 2) {
+ return false;
+ }
+
+ return charAt(0) == '#';
+ }
+
+ HashNumber hash() const { return hash_; }
+ uint32_t length() const { return length_; }
+
+ bool isUsedByStencil() const { return flags_ & UsedByStencilFlag; }
+
+ private:
+ bool isMarkedAtomize() const { return flags_ & AtomizeFlag; }
+
+ static constexpr uint32_t MinimumLengthForNonAtom = 8;
+
+ public:
+ bool isInstantiatedAsJSAtom() const;
+
+ template <typename CharT>
+ bool equalsSeq(HashNumber hash, InflatedChar16Sequence<CharT> seq) const;
+
+ // Convert NotInstantiated and usedByStencil entry to a js-atom.
+ JSString* instantiateString(JSContext* cx, FrontendContext* fc,
+ ParserAtomIndex index,
+ CompilationAtomCache& atomCache) const;
+ JSAtom* instantiateAtom(JSContext* cx, FrontendContext* fc,
+ ParserAtomIndex index,
+ CompilationAtomCache& atomCache) const;
+ JSAtom* instantiatePermanentAtom(JSContext* cx, FrontendContext* fc,
+ AtomSet& atomSet, ParserAtomIndex index,
+ CompilationAtomCache& atomCache) const;
+
+ private:
+ void markUsedByStencil(Atomize atomize) {
+ flags_ |= UsedByStencilFlag | uint32_t(atomize);
+ }
+ void markAtomize(Atomize atomize) { flags_ |= uint32_t(atomize); }
+
+ template <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_);
+ }
+
+ // Returns index-th char.
+ // Boundary check isn't performed.
+ char16_t charAt(size_t index) const {
+ MOZ_ASSERT(index < length());
+ if (hasLatin1Chars()) {
+ return latin1Chars()[index];
+ }
+ return twoByteChars()[index];
+ }
+
+ public:
+#if defined(DEBUG) || defined(JS_JITSPEW)
+ void dump() const;
+ void dumpCharsNoQuote(js::GenericPrinter& out) const;
+#endif
+};
+
+/**
+ * A lookup structure that allows for querying ParserAtoms in
+ * a hashtable using a flexible input type that supports string
+ * representations of various forms.
+ */
+class ParserAtomLookup {
+ protected:
+ HashNumber hash_;
+
+ ParserAtomLookup(HashNumber hash) : hash_(hash) {}
+
+ public:
+ HashNumber hash() const { return hash_; }
+
+ virtual bool equalsEntry(const ParserAtom* entry) const = 0;
+ virtual bool equalsEntry(const WellKnownAtomInfo* info) const = 0;
+};
+
+struct ParserAtomLookupHasher {
+ using Lookup = ParserAtomLookup;
+
+ static inline HashNumber hash(const Lookup& l) { return l.hash(); }
+ static inline bool match(const ParserAtom* entry, const Lookup& l) {
+ return l.equalsEntry(entry);
+ }
+};
+
+struct WellKnownAtomInfoHasher {
+ using Lookup = ParserAtomLookup;
+
+ static inline HashNumber hash(const Lookup& l) { return l.hash(); }
+ static inline bool match(const WellKnownAtomInfo* info, const Lookup& l) {
+ return l.equalsEntry(info);
+ }
+};
+
+using ParserAtomVector = Vector<ParserAtom*, 0, js::SystemAllocPolicy>;
+using ParserAtomSpan = mozilla::Span<ParserAtom*>;
+
+/**
+ * WellKnownParserAtoms allows the parser to lookup up specific atoms in
+ * constant time.
+ */
+class WellKnownParserAtoms {
+ static WellKnownParserAtoms singleton_;
+
+ // Common property and prototype names are tracked in a hash table. This table
+ // does not key for any items already in a direct-indexing tiny atom table.
+ using EntryMap = HashMap<const WellKnownAtomInfo*, TaggedParserAtomIndex,
+ WellKnownAtomInfoHasher, js::SystemAllocPolicy>;
+ EntryMap wellKnownMap_;
+
+ bool initSingle(const WellKnownAtomInfo& info, TaggedParserAtomIndex index);
+
+ bool init();
+ void free();
+
+ public:
+ static bool initSingleton();
+ static void freeSingleton();
+
+ static WellKnownParserAtoms& getSingleton() {
+ MOZ_ASSERT(!singleton_.wellKnownMap_.empty());
+ return singleton_;
+ }
+
+ // Maximum length of any well known atoms. This can be increased if needed.
+ static constexpr size_t MaxWellKnownLength = 32;
+
+ template <typename CharT>
+ TaggedParserAtomIndex lookupChar16Seq(
+ const SpecificParserAtomLookup<CharT>& lookup) const;
+
+ template <typename CharsT>
+ TaggedParserAtomIndex lookupTinyIndex(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*>,
+ "This assert mostly explicitly documents the calling types, "
+ "and forces that to be updated if new types show up.");
+ switch (length) {
+ case 0:
+ return TaggedParserAtomIndex::WellKnown::empty();
+
+ case 1: {
+ if (char16_t(chars[0]) < TaggedParserAtomIndex::Length1StaticLimit) {
+ return TaggedParserAtomIndex(Length1StaticParserString(chars[0]));
+ }
+ break;
+ }
+
+ case 2:
+ if (StaticStrings::fitsInSmallChar(chars[0]) &&
+ StaticStrings::fitsInSmallChar(chars[1])) {
+ return TaggedParserAtomIndex(Length2StaticParserString(
+ StaticStrings::getLength2Index(chars[0], chars[1])));
+ }
+ break;
+
+ case 3: {
+ int i;
+ if (StaticStrings::fitsInLength3Static(chars[0], chars[1], chars[2],
+ &i)) {
+ return TaggedParserAtomIndex(Length3StaticParserString(i));
+ }
+ break;
+ }
+ }
+
+ // No match on tiny Atoms
+ return TaggedParserAtomIndex::null();
+ }
+
+ TaggedParserAtomIndex lookupTinyIndexUTF8(const mozilla::Utf8Unit* utf8Ptr,
+ size_t nbyte) const;
+
+ size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const {
+ return wellKnownMap_.shallowSizeOfExcludingThis(mallocSizeOf);
+ }
+};
+
+bool InstantiateMarkedAtoms(JSContext* cx, FrontendContext* fc,
+ const ParserAtomSpan& entries,
+ CompilationAtomCache& atomCache);
+
+bool InstantiateMarkedAtomsAsPermanent(JSContext* cx, FrontendContext* fc,
+ AtomSet& atomSet,
+ const ParserAtomSpan& entries,
+ CompilationAtomCache& atomCache);
+
+/**
+ * A ParserAtomsTable owns and manages the vector of ParserAtom entries
+ * associated with a given compile session.
+ */
+class ParserAtomsTable {
+ friend struct CompilationStencil;
+
+ private:
+ LifoAlloc* alloc_;
+
+ // The ParserAtom are owned by the LifoAlloc.
+ using EntryMap = HashMap<const ParserAtom*, TaggedParserAtomIndex,
+ ParserAtomLookupHasher, js::SystemAllocPolicy>;
+ EntryMap entryMap_;
+ ParserAtomVector entries_;
+
+ public:
+ explicit ParserAtomsTable(LifoAlloc& alloc);
+ ParserAtomsTable(ParserAtomsTable&&) = default;
+ ParserAtomsTable& operator=(ParserAtomsTable&& other) noexcept {
+ entryMap_ = std::move(other.entryMap_);
+ entries_ = std::move(other.entries_);
+ return *this;
+ }
+
+ void fixupAlloc(LifoAlloc& alloc) { alloc_ = &alloc; }
+
+ size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const {
+ return entryMap_.shallowSizeOfExcludingThis(mallocSizeOf) +
+ entries_.sizeOfExcludingThis(mallocSizeOf);
+ }
+ size_t sizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const {
+ return mallocSizeOf(this) + sizeOfExcludingThis(mallocSizeOf);
+ }
+
+ private:
+ // Internal APIs for interning to the table after well-known atoms cases have
+ // been tested.
+ TaggedParserAtomIndex addEntry(FrontendContext* fc, EntryMap::AddPtr& addPtr,
+ ParserAtom* entry);
+ template <typename AtomCharT, typename SeqCharT>
+ TaggedParserAtomIndex internChar16Seq(FrontendContext* fc,
+ EntryMap::AddPtr& addPtr,
+ HashNumber hash,
+ InflatedChar16Sequence<SeqCharT> seq,
+ uint32_t length);
+
+ template <typename AtomCharT>
+ TaggedParserAtomIndex internExternalParserAtomImpl(FrontendContext* fc,
+ const ParserAtom* atom);
+
+ public:
+ TaggedParserAtomIndex internAscii(FrontendContext* fc, const char* asciiPtr,
+ uint32_t length);
+
+ TaggedParserAtomIndex internLatin1(FrontendContext* fc,
+ const JS::Latin1Char* latin1Ptr,
+ uint32_t length);
+
+ TaggedParserAtomIndex internUtf8(FrontendContext* fc,
+ const mozilla::Utf8Unit* utf8Ptr,
+ uint32_t nbyte);
+
+ TaggedParserAtomIndex internChar16(FrontendContext* fc,
+ const char16_t* char16Ptr,
+ uint32_t length);
+
+ TaggedParserAtomIndex internJSAtom(FrontendContext* fc,
+ CompilationAtomCache& atomCache,
+ JSAtom* atom);
+
+ // Intern ParserAtom data from other ParserAtomTable.
+ // This copies flags as well.
+ TaggedParserAtomIndex internExternalParserAtom(FrontendContext* fc,
+ const ParserAtom* atom);
+
+ // The atomIndex given as argument is in relation with the context Stencil.
+ // The atomIndex might be a well-known or static, in which case this function
+ // is a no-op.
+ TaggedParserAtomIndex internExternalParserAtomIndex(
+ FrontendContext* fc, const CompilationStencil& context,
+ TaggedParserAtomIndex atomIndex);
+
+ // Compare an internal atom index with an external atom index coming from the
+ // stencil given as argument.
+ bool isEqualToExternalParserAtomIndex(TaggedParserAtomIndex internal,
+ const CompilationStencil& context,
+ TaggedParserAtomIndex external) const;
+
+ bool addPlaceholder(FrontendContext* fc);
+
+ private:
+ const ParserAtom* getWellKnown(WellKnownAtomId atomId) const;
+ ParserAtom* getParserAtom(ParserAtomIndex index) const;
+
+ public:
+ const ParserAtomVector& entries() const { return entries_; }
+
+ // Accessors for querying atom properties.
+ bool isIdentifier(TaggedParserAtomIndex index) const;
+ bool isPrivateName(TaggedParserAtomIndex index) const;
+ bool isExtendedUnclonedSelfHostedFunctionName(
+ TaggedParserAtomIndex index) const;
+ bool isModuleExportName(TaggedParserAtomIndex index) const;
+ bool isIndex(TaggedParserAtomIndex index, uint32_t* indexp) const;
+ bool isInstantiatedAsJSAtom(TaggedParserAtomIndex index) const;
+ uint32_t length(TaggedParserAtomIndex index) const;
+ HashNumber hash(TaggedParserAtomIndex index) const;
+
+ // Methods for atom.
+ void markUsedByStencil(TaggedParserAtomIndex index,
+ ParserAtom::Atomize atomize) const;
+ void markAtomize(TaggedParserAtomIndex index,
+ ParserAtom::Atomize atomize) const;
+ double toNumber(TaggedParserAtomIndex index) const;
+ UniqueChars toNewUTF8CharsZ(FrontendContext* fc,
+ TaggedParserAtomIndex index) const;
+ UniqueChars toPrintableString(TaggedParserAtomIndex index) const;
+ UniqueChars toQuotedString(TaggedParserAtomIndex index) const;
+ JSAtom* toJSAtom(JSContext* cx, FrontendContext* fc,
+ TaggedParserAtomIndex index,
+ CompilationAtomCache& atomCache) const;
+
+ private:
+ JSAtom* toWellKnownJSAtom(JSContext* cx, TaggedParserAtomIndex index) const;
+
+ public:
+ bool appendTo(StringBuffer& buffer, TaggedParserAtomIndex index) const;
+
+ public:
+#if defined(DEBUG) || defined(JS_JITSPEW)
+ void dump(TaggedParserAtomIndex index) const;
+ void dumpCharsNoQuote(js::GenericPrinter& out,
+ TaggedParserAtomIndex index) const;
+
+ static void dumpCharsNoQuote(js::GenericPrinter& out, WellKnownAtomId id);
+ static void dumpCharsNoQuote(js::GenericPrinter& out,
+ Length1StaticParserString index);
+ static void dumpCharsNoQuote(js::GenericPrinter& out,
+ Length2StaticParserString index);
+ static void dumpCharsNoQuote(js::GenericPrinter& out,
+ Length3StaticParserString index);
+#endif
+
+ static void getLength1Content(Length1StaticParserString s,
+ Latin1Char contents[1]) {
+ contents[0] = Latin1Char(s);
+ }
+
+ static void getLength2Content(Length2StaticParserString s, char contents[2]) {
+ contents[0] = StaticStrings::firstCharOfLength2(size_t(s));
+ contents[1] = StaticStrings::secondCharOfLength2(size_t(s));
+ }
+
+ static void getLength3Content(Length3StaticParserString s, char contents[3]) {
+ contents[0] = StaticStrings::firstCharOfLength3(int32_t(s));
+ contents[1] = StaticStrings::secondCharOfLength3(int32_t(s));
+ contents[2] = StaticStrings::thirdCharOfLength3(int32_t(s));
+ }
+};
+
+// Lightweight version of ParserAtomsTable.
+// This doesn't support deduplication.
+// Used while decoding XDR.
+class ParserAtomSpanBuilder {
+ ParserAtomSpan& entries_;
+
+ public:
+ explicit ParserAtomSpanBuilder(ParserAtomSpan& entries) : entries_(entries) {}
+
+ bool allocate(FrontendContext* fc, LifoAlloc& alloc, size_t count);
+
+ void set(ParserAtomIndex index, const ParserAtom* atom) {
+ entries_[index] = const_cast<ParserAtom*>(atom);
+ }
+};
+
+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 ParserAtom* entry) const override {
+ return entry->equalsSeq<CharT>(hash_, seq_);
+ }
+
+ virtual bool equalsEntry(const WellKnownAtomInfo* info) const override {
+ // Compare hashes first.
+ if (info->hash != hash_) {
+ return false;
+ }
+
+ InflatedChar16Sequence<CharT> seq = seq_;
+ for (uint32_t i = 0; i < info->length; i++) {
+ if (!seq.hasMore() || char16_t(info->content[i]) != seq.next()) {
+ return false;
+ }
+ }
+ return !seq.hasMore();
+ }
+};
+
+template <typename CharT>
+inline bool ParserAtom::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/PrivateOpEmitter.cpp b/js/src/frontend/PrivateOpEmitter.cpp
new file mode 100644
index 0000000000..9e617b209e
--- /dev/null
+++ b/js/src/frontend/PrivateOpEmitter.cpp
@@ -0,0 +1,331 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "frontend/PrivateOpEmitter.h"
+
+#include "frontend/BytecodeEmitter.h"
+#include "frontend/NameOpEmitter.h"
+#include "vm/Opcodes.h"
+#include "vm/ThrowMsgKind.h" // ThrowMsgKind
+
+using namespace js;
+using namespace js::frontend;
+
+PrivateOpEmitter::PrivateOpEmitter(BytecodeEmitter* bce, Kind kind,
+ TaggedParserAtomIndex name)
+ : bce_(bce), kind_(kind), name_(name) {
+ MOZ_ASSERT(kind_ != Kind::Delete);
+}
+
+bool PrivateOpEmitter::init() {
+ // Static analysis needs us to initialise this to something, so use Dynamic()
+ NameLocation loc = NameLocation::Dynamic();
+ bce_->lookupPrivate(name_, loc, brandLoc_);
+ loc_ = mozilla::Some(loc);
+ return true;
+}
+
+bool PrivateOpEmitter::emitLoad(TaggedParserAtomIndex name,
+ const NameLocation& loc) {
+ NameOpEmitter noe(bce_, name, loc, NameOpEmitter::Kind::Get);
+ return noe.emitGet();
+}
+
+bool PrivateOpEmitter::emitLoadPrivateBrand() {
+ return emitLoad(TaggedParserAtomIndex::WellKnown::dotPrivateBrand(),
+ *brandLoc_);
+}
+
+bool PrivateOpEmitter::emitBrandCheck() {
+ MOZ_ASSERT(state_ == State::Reference);
+
+ if (isBrandCheck()) {
+ // Emit a CheckPrivateField CheckRhs; note: The message is irrelvant here,
+ // it will never be thrown, so DoubleInit was chosen arbitrarily.
+ if (!bce_->emitCheckPrivateField(ThrowCondition::OnlyCheckRhs,
+ ThrowMsgKind::PrivateDoubleInit)) {
+ // [stack] OBJ KEY BBOOL
+ return false;
+ }
+
+ return true;
+ }
+
+ // [stack] OBJ KEY
+ if (isFieldInit()) {
+ if (!bce_->emitCheckPrivateField(ThrowCondition::ThrowHas,
+ ThrowMsgKind::PrivateDoubleInit)) {
+ // [stack] OBJ KEY false
+ return false;
+ }
+ } else {
+ bool assigning =
+ isSimpleAssignment() || isCompoundAssignment() || isIncDec();
+ if (!bce_->emitCheckPrivateField(ThrowCondition::ThrowHasNot,
+ assigning
+ ? ThrowMsgKind::MissingPrivateOnSet
+ : ThrowMsgKind::MissingPrivateOnGet)) {
+ // [stack] OBJ KEY true
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool PrivateOpEmitter::emitReference() {
+ MOZ_ASSERT(state_ == State::Start);
+
+ if (!init()) {
+ return false;
+ }
+
+ if (brandLoc_) {
+ if (!emitLoadPrivateBrand()) {
+ // [stack] OBJ BRAND
+ return false;
+ }
+ } else {
+ if (!emitLoad(name_, loc_.ref())) {
+ // [stack] OBJ NAME
+ return false;
+ }
+ }
+#ifdef DEBUG
+ state_ = State::Reference;
+#endif
+ return true;
+}
+
+bool PrivateOpEmitter::skipReference() {
+ MOZ_ASSERT(state_ == State::Start);
+
+ if (!init()) {
+ return false;
+ }
+
+#ifdef DEBUG
+ state_ = State::Reference;
+#endif
+ return true;
+}
+
+bool PrivateOpEmitter::emitGet() {
+ MOZ_ASSERT(state_ == State::Reference);
+
+ // [stack] OBJ NAME
+
+ if (brandLoc_) {
+ // Note that the decision of what we leave on the stack depends on kind_,
+ // not loc_->bindingKind(). We can't emit code for a call just because this
+ // private member is a method. `obj.#method` is allowed without a call,
+ // just fetching the function object (it's useful in code like
+ // `obj.#method.bind(...)`). Even if the user says `obj.#method += 7`, we
+ // emit honest bytecode for the brand check, method load, and addition, and
+ // throw the error later. This preserves stack nuses/ndefs balance.
+ if (!emitBrandCheck()) {
+ // [stack] OBJ BRAND true
+ return false;
+ }
+
+ if (isCompoundAssignment()) {
+ if (!bce_->emit1(JSOp::Pop)) {
+ // [stack] OBJ BRAND
+ return false;
+ }
+ } else if (isCall()) {
+ if (!bce_->emitPopN(2)) {
+ // [stack] OBJ
+ return false;
+ }
+ } else {
+ if (!bce_->emitPopN(3)) {
+ // [stack]
+ return false;
+ }
+ }
+
+ if (!emitLoad(name_, loc_.ref())) {
+ // [stack] OBJ BRAND METHOD # if isCompoundAssignment
+ // [stack] OBJ METHOD # if call
+ // [stack] METHOD # otherwise
+ return false;
+ }
+ } else {
+ if (isCall()) {
+ if (!bce_->emitDupAt(1)) {
+ // [stack] OBJ NAME OBJ
+ return false;
+ }
+ if (!bce_->emit1(JSOp::Swap)) {
+ // [stack] OBJ OBJ NAME
+ return false;
+ }
+ }
+ // [stack] OBJ? OBJ NAME
+ if (!emitBrandCheck()) {
+ // [stack] OBJ? OBJ NAME true
+ return false;
+ }
+ if (!bce_->emit1(JSOp::Pop)) {
+ // [stack] OBJ? OBJ NAME
+ return false;
+ }
+
+ if (isCompoundAssignment()) {
+ if (!bce_->emit1(JSOp::Dup2)) {
+ // [stack] OBJ NAME OBJ NAME
+ return false;
+ }
+ }
+
+ if (!bce_->emitElemOpBase(JSOp::GetElem)) {
+ // [stack] OBJ NAME VALUE # if isCompoundAssignment
+ // [stack] OBJ METHOD # if Call
+ // [stack] VALUE # otherwise
+ return false;
+ }
+ }
+
+ if (isCall()) {
+ if (!bce_->emit1(JSOp::Swap)) {
+ // [stack] METHOD OBJ
+ return false;
+ }
+ }
+
+ // [stack] OBJ NAME VALUE # if isCompoundAssignment
+ // [stack] METHOD OBJ # if call
+ // [stack] VALUE # otherwise
+
+#ifdef DEBUG
+ state_ = State::Get;
+#endif
+ return true;
+}
+
+bool PrivateOpEmitter::emitGetForCallOrNew() { return emitGet(); }
+
+bool PrivateOpEmitter::emitAssignment() {
+ MOZ_ASSERT(isSimpleAssignment() || isFieldInit() || isCompoundAssignment());
+ MOZ_ASSERT_IF(!isCompoundAssignment(), state_ == State::Reference);
+ MOZ_ASSERT_IF(isCompoundAssignment(), state_ == State::Get);
+
+ // [stack] OBJ KEY RHS
+
+ if (brandLoc_) {
+ if (!bce_->emit2(JSOp::ThrowMsg,
+ uint8_t(ThrowMsgKind::AssignToPrivateMethod))) {
+ return false;
+ }
+
+ // Balance the expression stack.
+ if (!bce_->emitPopN(2)) {
+ // [stack] OBJ
+ return false;
+ }
+ } else {
+ // Emit a brand check. If this is compound assignment, emitGet() already
+ // emitted a check for this object and key. There's no point checking
+ // again--a private field can't be removed from an object.
+ if (!isCompoundAssignment()) {
+ if (!bce_->emitUnpickN(2)) {
+ // [stack] RHS OBJ KEY
+ return false;
+ }
+ if (!emitBrandCheck()) {
+ // [stack] RHS OBJ KEY BOOL
+ return false;
+ }
+ if (!bce_->emit1(JSOp::Pop)) {
+ // [stack] RHS OBJ KEY
+ return false;
+ }
+ if (!bce_->emitPickN(2)) {
+ // [stack] OBJ KEY RHS
+ return false;
+ }
+ }
+
+ JSOp setOp = isFieldInit() ? JSOp::InitElem : JSOp::StrictSetElem;
+ if (!bce_->emitElemOpBase(setOp)) {
+ // [stack] RHS
+ return false;
+ }
+ }
+
+#ifdef DEBUG
+ state_ = State::Assignment;
+#endif
+ return true;
+}
+
+bool PrivateOpEmitter::emitIncDec(ValueUsage valueUsage) {
+ MOZ_ASSERT(state_ == State::Reference);
+ MOZ_ASSERT(isIncDec());
+ // [stack] OBJ NAME
+
+ if (!bce_->emitDupAt(1, 2)) {
+ // [stack] OBJ NAME OBJ NAME
+ return false;
+ }
+
+ if (!emitGet()) {
+ // [stack] OBJ NAME VALUE
+ return false;
+ }
+
+ MOZ_ASSERT(state_ == State::Get);
+
+ JSOp incOp = isInc() ? JSOp::Inc : JSOp::Dec;
+
+ if (!bce_->emit1(JSOp::ToNumeric)) {
+ // [stack] OBJ NAME N
+ return false;
+ }
+ if (isPostIncDec() && valueUsage == ValueUsage::WantValue) {
+ // [stack] OBJ NAME N
+ if (!bce_->emit1(JSOp::Dup)) {
+ // [stack] OBJ NAME N N
+ return false;
+ }
+ if (!bce_->emit2(JSOp::Unpick, 3)) {
+ // [stack] N OBJ NAME N
+ return false;
+ }
+ }
+ if (!bce_->emit1(incOp)) {
+ // [stack] N? OBJ NAME N+1
+ return false;
+ }
+
+ if (brandLoc_) {
+ if (!bce_->emit2(JSOp::ThrowMsg,
+ uint8_t(ThrowMsgKind::AssignToPrivateMethod))) {
+ return false;
+ }
+
+ // Balance the expression stack.
+ if (!bce_->emitPopN(2)) {
+ // [stack] N? N+1
+ return false;
+ }
+ } else {
+ if (!bce_->emitElemOpBase(JSOp::StrictSetElem)) {
+ // [stack] N? N+1
+ return false;
+ }
+ }
+
+ if (isPostIncDec() && valueUsage == ValueUsage::WantValue) {
+ if (!bce_->emit1(JSOp::Pop)) {
+ // [stack] N
+ return false;
+ }
+ }
+
+ return true;
+}
diff --git a/js/src/frontend/PrivateOpEmitter.h b/js/src/frontend/PrivateOpEmitter.h
new file mode 100644
index 0000000000..558541b05c
--- /dev/null
+++ b/js/src/frontend/PrivateOpEmitter.h
@@ -0,0 +1,231 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef frontend_PrivateOpEmitter_h
+#define frontend_PrivateOpEmitter_h
+
+#include "mozilla/Attributes.h"
+#include "mozilla/Maybe.h"
+
+#include <stddef.h>
+
+#include "frontend/NameAnalysisTypes.h" // NameLocation
+#include "frontend/ParserAtom.h" // TaggedParserAtomIndex
+
+namespace js {
+namespace frontend {
+
+struct BytecodeEmitter;
+enum class ValueUsage;
+
+// Class for emitting bytecode for operations on private members of objects.
+//
+// Usage is similar to PropOpEmitter, but the name of the private member must
+// be passed to the constructor; prepare*() methods aren't necessary; and
+// `delete obj.#member` and `super.#member` aren't supported because both are
+// SyntaxErrors.
+//
+// Usage: (error checking is omitted for simplicity)
+//
+// `obj.#member;`
+// PrivateOpEmitter xoe(this,
+// privateName,
+// PrivateOpEmitter::Kind::Get);
+// emit(obj);
+// xoe.emitReference();
+// xoe.emitGet();
+//
+// `obj.#member();`
+// PrivateOpEmitter xoe(this,
+// privateName,
+// PrivateOpEmitter::Kind::Call);
+// emit(obj);
+// xoe.emitReference();
+// xoe.emitGet();
+// emit_call_here();
+//
+// `new obj.#member();`
+// The same, but use PrivateOpEmitter::Kind::Get.
+//
+// `obj.#field++;`
+// PrivateOpEmitter xoe(this,
+// privateName,
+// PrivateOpEmitter::Kind::PostIncrement);
+// emit(obj);
+// xoe.emitReference();
+// xoe.emitIncDec();
+//
+// `obj.#field = value;`
+// PrivateOpEmitter xoe(this,
+// privateName,
+// PrivateOpEmitter::Kind::SimpleAssignment);
+// emit(obj);
+// xoe.emitReference();
+// emit(value);
+// xoe.emitAssignment();
+//
+// `obj.#field += value;`
+// PrivateOpEmitter xoe(this,
+// privateName,
+// PrivateOpEmitter::Kind::CompoundAssignment);
+// emit(obj);
+// xoe.emitReference();
+// emit(JSOp::Dup2);
+// xoe.emitGet();
+// emit(value);
+// emit_add_op_here();
+// xoe.emitAssignment();
+//
+class MOZ_STACK_CLASS PrivateOpEmitter {
+ public:
+ enum class Kind {
+ Get,
+ Call,
+ Delete,
+ PostIncrement,
+ PreIncrement,
+ PostDecrement,
+ PreDecrement,
+ SimpleAssignment,
+ PropInit,
+ CompoundAssignment,
+ ErgonomicBrandCheck,
+ };
+
+ private:
+ BytecodeEmitter* bce_;
+
+ Kind kind_;
+
+ // Name of the private member, e.g. "#field".
+ TaggedParserAtomIndex name_;
+
+ // Location of the slot containing the private name symbol; or, for a
+ // non-static private method, the slot containing the method.
+ mozilla::Maybe<NameLocation> loc_;
+
+ // For non-static private method accesses, the location of the relevant
+ // `.privateBrand` binding. Otherwise, `Nothing`.
+ mozilla::Maybe<NameLocation> brandLoc_{};
+
+#ifdef DEBUG
+ // The state of this emitter.
+ //
+ // emitReference
+ // +-------+ skipReference +-----------+
+ // | Start |---------------->| Reference |
+ // +-------+ +-----+-----+
+ // |
+ // +---------------------------+
+ // |
+ // |
+ // | [Get]
+ // | emitGet
+ // | [Call] [Get] [CompoundAssignment]
+ // | emitGetForCallOrNew +-----+ emitAssignment
+ // +---------------------->| Get |-------------------+
+ // | +-----+ |
+ // | [PostIncrement] |
+ // | [PreIncrement] |
+ // | [PostDecrement] |
+ // | [PreDecrement] |
+ // | emitIncDec |
+ // +------------------------------------------------>+
+ // | |
+ // | [SimpleAssignment] |
+ // | [PropInit] V
+ // | emitAssignment +------------+
+ // +------------------------------------------>| Assignment |
+ // +------------+
+ enum class State {
+ // The initial state.
+ Start,
+
+ // After calling emitReference or skipReference.
+ Reference,
+
+ // After calling emitGet.
+ Get,
+
+ // After calling emitAssignment or emitIncDec.
+ Assignment,
+ };
+ State state_ = State::Start;
+#endif
+
+ public:
+ PrivateOpEmitter(BytecodeEmitter* bce, Kind kind, TaggedParserAtomIndex name);
+
+ private:
+ [[nodiscard]] bool isCall() const { return kind_ == Kind::Call; }
+
+ [[nodiscard]] bool isSimpleAssignment() const {
+ return kind_ == Kind::SimpleAssignment;
+ }
+
+ [[nodiscard]] bool isFieldInit() const { return kind_ == Kind::PropInit; }
+
+ [[nodiscard]] bool isBrandCheck() const {
+ return kind_ == Kind::ErgonomicBrandCheck;
+ }
+
+ [[nodiscard]] bool isCompoundAssignment() const {
+ return kind_ == Kind::CompoundAssignment;
+ }
+
+ [[nodiscard]] bool isIncDec() const {
+ return isPostIncDec() || isPreIncDec();
+ }
+
+ [[nodiscard]] bool isPostIncDec() const {
+ return kind_ == Kind::PostIncrement || kind_ == Kind::PostDecrement;
+ }
+
+ [[nodiscard]] bool isPreIncDec() const {
+ return kind_ == Kind::PreIncrement || kind_ == Kind::PreDecrement;
+ }
+
+ [[nodiscard]] bool isInc() const {
+ return kind_ == Kind::PostIncrement || kind_ == Kind::PreIncrement;
+ }
+
+ [[nodiscard]] bool init();
+
+ // Emit a GetAliasedLexical or similar instruction.
+ [[nodiscard]] bool emitLoad(TaggedParserAtomIndex name,
+ const NameLocation& loc);
+
+ [[nodiscard]] bool emitLoadPrivateBrand();
+
+ public:
+ // Emit bytecode to check for the presence/absence of a private field/brand.
+ //
+ // Given OBJ KEY on the stack, where KEY is a private name symbol, the
+ // emitted code will throw if OBJ does not have the given KEY.
+ //
+ // If `isFieldInit()`, the check is reversed: the code will throw if OBJ
+ // already has the KEY.
+ //
+ // If `isBrandCheck()`, the check verifies RHS is an object (throwing if not).
+ //
+ // The bytecode leaves OBJ KEY BOOL on the stack. Caller is responsible for
+ // consuming or popping it.
+ [[nodiscard]] bool emitBrandCheck();
+
+ [[nodiscard]] bool emitReference();
+ [[nodiscard]] bool skipReference();
+ [[nodiscard]] bool emitGet();
+ [[nodiscard]] bool emitGetForCallOrNew();
+ [[nodiscard]] bool emitAssignment();
+ [[nodiscard]] bool emitIncDec(ValueUsage valueUsage);
+
+ [[nodiscard]] size_t numReferenceSlots() { return 2; }
+};
+
+} /* namespace frontend */
+} /* namespace js */
+
+#endif /* frontend_PrivateOpEmitter_h */
diff --git a/js/src/frontend/PropOpEmitter.cpp b/js/src/frontend/PropOpEmitter.cpp
new file mode 100644
index 0000000000..ff08bd8580
--- /dev/null
+++ b/js/src/frontend/PropOpEmitter.cpp
@@ -0,0 +1,244 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "frontend/PropOpEmitter.h"
+
+#include "frontend/BytecodeEmitter.h"
+#include "frontend/ParserAtom.h" // ParserAtom
+#include "frontend/SharedContext.h"
+#include "vm/Opcodes.h"
+#include "vm/ThrowMsgKind.h" // ThrowMsgKind
+
+using namespace js;
+using namespace js::frontend;
+
+PropOpEmitter::PropOpEmitter(BytecodeEmitter* bce, Kind kind, ObjKind objKind)
+ : bce_(bce), kind_(kind), objKind_(objKind) {}
+
+bool PropOpEmitter::prepareAtomIndex(TaggedParserAtomIndex prop) {
+ return bce_->makeAtomIndex(prop, ParserAtom::Atomize::Yes, &propAtomIndex_);
+}
+
+bool PropOpEmitter::prepareForObj() {
+ MOZ_ASSERT(state_ == State::Start);
+
+#ifdef DEBUG
+ state_ = State::Obj;
+#endif
+ return true;
+}
+
+bool PropOpEmitter::emitGet(TaggedParserAtomIndex prop) {
+ MOZ_ASSERT(state_ == State::Obj);
+
+ if (!prepareAtomIndex(prop)) {
+ return false;
+ }
+ if (isCall()) {
+ if (!bce_->emit1(JSOp::Dup)) {
+ // [stack] # if Super
+ // [stack] THIS THIS
+ // [stack] # otherwise
+ // [stack] OBJ OBJ
+ return false;
+ }
+ }
+ if (isSuper()) {
+ if (!bce_->emitSuperBase()) {
+ // [stack] THIS? THIS SUPERBASE
+ return false;
+ }
+ }
+ if (isIncDec() || isCompoundAssignment()) {
+ if (isSuper()) {
+ if (!bce_->emit1(JSOp::Dup2)) {
+ // [stack] THIS SUPERBASE THIS SUPERBASE
+ return false;
+ }
+ } else {
+ if (!bce_->emit1(JSOp::Dup)) {
+ // [stack] OBJ OBJ
+ return false;
+ }
+ }
+ }
+
+ JSOp op = isSuper() ? JSOp::GetPropSuper : JSOp::GetProp;
+ if (!bce_->emitAtomOp(op, propAtomIndex_)) {
+ // [stack] # if Get
+ // [stack] PROP
+ // [stack] # if Call
+ // [stack] THIS PROP
+ // [stack] # if Inc/Dec/Compound, Super]
+ // [stack] THIS SUPERBASE PROP
+ // [stack] # if Inc/Dec/Compound, other
+ // [stack] OBJ PROP
+ return false;
+ }
+ if (isCall()) {
+ if (!bce_->emit1(JSOp::Swap)) {
+ // [stack] PROP THIS
+ return false;
+ }
+ }
+
+#ifdef DEBUG
+ state_ = State::Get;
+#endif
+ return true;
+}
+
+bool PropOpEmitter::prepareForRhs() {
+ MOZ_ASSERT(isSimpleAssignment() || isPropInit() || isCompoundAssignment());
+ MOZ_ASSERT_IF(isSimpleAssignment() || isPropInit(), state_ == State::Obj);
+ MOZ_ASSERT_IF(isCompoundAssignment(), state_ == State::Get);
+
+ if (isSimpleAssignment() || isPropInit()) {
+ // For CompoundAssignment, SuperBase is already emitted by emitGet.
+ if (isSuper()) {
+ if (!bce_->emitSuperBase()) {
+ // [stack] THIS SUPERBASE
+ return false;
+ }
+ }
+ }
+
+#ifdef DEBUG
+ state_ = State::Rhs;
+#endif
+ return true;
+}
+
+bool PropOpEmitter::skipObjAndRhs() {
+ MOZ_ASSERT(state_ == State::Start);
+ MOZ_ASSERT(isSimpleAssignment() || isPropInit());
+
+#ifdef DEBUG
+ state_ = State::Rhs;
+#endif
+ return true;
+}
+
+bool PropOpEmitter::emitDelete(TaggedParserAtomIndex prop) {
+ MOZ_ASSERT_IF(!isSuper(), state_ == State::Obj);
+ MOZ_ASSERT_IF(isSuper(), state_ == State::Start);
+ MOZ_ASSERT(isDelete());
+
+ if (!prepareAtomIndex(prop)) {
+ return false;
+ }
+ if (isSuper()) {
+ if (!bce_->emitSuperBase()) {
+ // [stack] THIS SUPERBASE
+ return false;
+ }
+
+ // Unconditionally throw when attempting to delete a super-reference.
+ if (!bce_->emit2(JSOp::ThrowMsg, uint8_t(ThrowMsgKind::CantDeleteSuper))) {
+ // [stack] THIS SUPERBASE
+ return false;
+ }
+
+ // Another wrinkle: Balance the stack from the emitter's point of view.
+ // Execution will not reach here, as the last bytecode threw.
+ if (!bce_->emit1(JSOp::Pop)) {
+ // [stack] THIS
+ return false;
+ }
+ } else {
+ JSOp op = bce_->sc->strict() ? JSOp::StrictDelProp : JSOp::DelProp;
+ if (!bce_->emitAtomOp(op, propAtomIndex_)) {
+ // [stack] SUCCEEDED
+ return false;
+ }
+ }
+
+#ifdef DEBUG
+ state_ = State::Delete;
+#endif
+ return true;
+}
+
+bool PropOpEmitter::emitAssignment(TaggedParserAtomIndex prop) {
+ MOZ_ASSERT(isSimpleAssignment() || isPropInit() || isCompoundAssignment());
+ MOZ_ASSERT(state_ == State::Rhs);
+
+ if (isSimpleAssignment() || isPropInit()) {
+ if (!prepareAtomIndex(prop)) {
+ return false;
+ }
+ }
+
+ MOZ_ASSERT_IF(isPropInit(), !isSuper());
+ JSOp setOp = isPropInit() ? JSOp::InitProp
+ : isSuper() ? bce_->sc->strict() ? JSOp::StrictSetPropSuper
+ : JSOp::SetPropSuper
+ : bce_->sc->strict() ? JSOp::StrictSetProp
+ : JSOp::SetProp;
+ if (!bce_->emitAtomOp(setOp, propAtomIndex_)) {
+ // [stack] VAL
+ return false;
+ }
+
+#ifdef DEBUG
+ state_ = State::Assignment;
+#endif
+ return true;
+}
+
+bool PropOpEmitter::emitIncDec(TaggedParserAtomIndex prop,
+ ValueUsage valueUsage) {
+ MOZ_ASSERT(state_ == State::Obj);
+ MOZ_ASSERT(isIncDec());
+
+ if (!emitGet(prop)) {
+ return false;
+ }
+
+ MOZ_ASSERT(state_ == State::Get);
+
+ JSOp incOp = isInc() ? JSOp::Inc : JSOp::Dec;
+
+ if (!bce_->emit1(JSOp::ToNumeric)) {
+ // [stack] ... N
+ return false;
+ }
+ if (isPostIncDec() && valueUsage == ValueUsage::WantValue) {
+ // [stack] OBJ SUPERBASE? N
+ if (!bce_->emit1(JSOp::Dup)) {
+ // [stack] .. N N
+ return false;
+ }
+ if (!bce_->emit2(JSOp::Unpick, 2 + isSuper())) {
+ // [stack] N OBJ SUPERBASE? N
+ return false;
+ }
+ }
+ if (!bce_->emit1(incOp)) {
+ // [stack] ... N+1
+ return false;
+ }
+
+ JSOp setOp = isSuper() ? bce_->sc->strict() ? JSOp::StrictSetPropSuper
+ : JSOp::SetPropSuper
+ : bce_->sc->strict() ? JSOp::StrictSetProp
+ : JSOp::SetProp;
+ if (!bce_->emitAtomOp(setOp, propAtomIndex_)) {
+ // [stack] N? N+1
+ return false;
+ }
+ if (isPostIncDec() && valueUsage == ValueUsage::WantValue) {
+ if (!bce_->emit1(JSOp::Pop)) {
+ // [stack] N
+ return false;
+ }
+ }
+
+#ifdef DEBUG
+ state_ = State::IncDec;
+#endif
+ return true;
+}
diff --git a/js/src/frontend/PropOpEmitter.h b/js/src/frontend/PropOpEmitter.h
new file mode 100644
index 0000000000..76402025ea
--- /dev/null
+++ b/js/src/frontend/PropOpEmitter.h
@@ -0,0 +1,254 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef frontend_PropOpEmitter_h
+#define frontend_PropOpEmitter_h
+
+#include "mozilla/Attributes.h"
+
+#include "vm/SharedStencil.h" // GCThingIndex
+
+namespace js {
+namespace frontend {
+
+struct BytecodeEmitter;
+class TaggedParserAtomIndex;
+enum class ValueUsage;
+
+// Class for emitting bytecode for property operation.
+//
+// Usage: (check for the return value is omitted for simplicity)
+//
+// `obj.prop;`
+// PropOpEmitter poe(this,
+// PropOpEmitter::Kind::Get,
+// PropOpEmitter::ObjKind::Other);
+// poe.prepareForObj();
+// emit(obj);
+// poe.emitGet(atom_of_prop);
+//
+// `super.prop;`
+// PropOpEmitter poe(this,
+// PropOpEmitter::Kind::Get,
+// PropOpEmitter::ObjKind::Super);
+// poe.prepareForObj();
+// emit(obj);
+// poe.emitGet(atom_of_prop);
+//
+// `obj.prop();`
+// PropOpEmitter poe(this,
+// PropOpEmitter::Kind::Call,
+// PropOpEmitter::ObjKind::Other);
+// poe.prepareForObj();
+// emit(obj);
+// poe.emitGet(atom_of_prop);
+// emit_call_here();
+//
+// `new obj.prop();`
+// PropOpEmitter poe(this,
+// PropOpEmitter::Kind::Call,
+// PropOpEmitter::ObjKind::Other);
+// poe.prepareForObj();
+// emit(obj);
+// poe.emitGet(atom_of_prop);
+// emit_call_here();
+//
+// `delete obj.prop;`
+// PropOpEmitter poe(this,
+// PropOpEmitter::Kind::Delete,
+// PropOpEmitter::ObjKind::Other);
+// poe.prepareForObj();
+// emit(obj);
+// poe.emitDelete(atom_of_prop);
+//
+// `delete super.prop;`
+// PropOpEmitter poe(this,
+// PropOpEmitter::Kind::Delete,
+// PropOpEmitter::ObjKind::Other);
+// poe.emitDelete(atom_of_prop);
+//
+// `obj.prop++;`
+// PropOpEmitter poe(this,
+// PropOpEmitter::Kind::PostIncrement,
+// PropOpEmitter::ObjKind::Other);
+// poe.prepareForObj();
+// emit(obj);
+// poe.emitIncDec(atom_of_prop);
+//
+// `obj.prop = value;`
+// PropOpEmitter poe(this,
+// PropOpEmitter::Kind::SimpleAssignment,
+// PropOpEmitter::ObjKind::Other);
+// poe.prepareForObj();
+// emit(obj);
+// poe.prepareForRhs();
+// emit(value);
+// poe.emitAssignment(atom_of_prop);
+//
+// `obj.prop += value;`
+// PropOpEmitter poe(this,
+// PropOpEmitter::Kind::CompoundAssignment,
+// PropOpEmitter::ObjKind::Other);
+// poe.prepareForObj();
+// emit(obj);
+// poe.emitGet(atom_of_prop);
+// poe.prepareForRhs();
+// emit(value);
+// emit_add_op_here();
+// poe.emitAssignment(nullptr); // nullptr for CompoundAssignment
+//
+class MOZ_STACK_CLASS PropOpEmitter {
+ public:
+ enum class Kind {
+ Get,
+ Call,
+ Delete,
+ PostIncrement,
+ PreIncrement,
+ PostDecrement,
+ PreDecrement,
+ SimpleAssignment,
+ PropInit,
+ CompoundAssignment
+ };
+ enum class ObjKind { Super, Other };
+
+ private:
+ BytecodeEmitter* bce_;
+
+ Kind kind_;
+ ObjKind objKind_;
+
+ // The index for the property name's atom.
+ GCThingIndex propAtomIndex_;
+
+#ifdef DEBUG
+ // The state of this emitter.
+ //
+ // skipObjAndRhs
+ // +----------------------------+
+ // | |
+ // +-------+ | prepareForObj +-----+ |
+ // | Start |-+-------------->| Obj |-+ |
+ // +-------+ +-----+ | |
+ // | |
+ // +---------------------------------+ |
+ // | |
+ // | |
+ // | [Get] |
+ // | [Call] |
+ // | emitGet +-----+ |
+ // +---------->| Get | |
+ // | +-----+ |
+ // | |
+ // | [Delete] |
+ // | emitDelete +--------+ |
+ // +------------->| Delete | |
+ // | +--------+ |
+ // | |
+ // | [PostIncrement] |
+ // | [PreIncrement] |
+ // | [PostDecrement] |
+ // | [PreDecrement] |
+ // | emitIncDec +--------+ |
+ // +------------->| IncDec | |
+ // | +--------+ |
+ // | |
+ // | [SimpleAssignment] |
+ // | [PropInit] |
+ // | prepareForRhs | +-----+
+ // +--------------------->+-------------->+->| Rhs |-+
+ // | ^ +-----+ |
+ // | | |
+ // | | +---------+
+ // | [CompoundAssignment] | |
+ // | emitGet +-----+ | | emitAssignment +------------+
+ // +---------->| Get |----+ + -------------->| Assignment |
+ // +-----+ +------------+
+ enum class State {
+ // The initial state.
+ Start,
+
+ // After calling prepareForObj.
+ Obj,
+
+ // After calling emitGet.
+ Get,
+
+ // After calling emitDelete.
+ Delete,
+
+ // After calling emitIncDec.
+ IncDec,
+
+ // After calling prepareForRhs or skipObjAndRhs.
+ Rhs,
+
+ // After calling emitAssignment.
+ Assignment,
+ };
+ State state_ = State::Start;
+#endif
+
+ public:
+ PropOpEmitter(BytecodeEmitter* bce, Kind kind, ObjKind objKind);
+
+ private:
+ [[nodiscard]] bool isCall() const { return kind_ == Kind::Call; }
+
+ [[nodiscard]] bool isSuper() const { return objKind_ == ObjKind::Super; }
+
+ [[nodiscard]] bool isSimpleAssignment() const {
+ return kind_ == Kind::SimpleAssignment;
+ }
+
+ [[nodiscard]] bool isPropInit() const { return kind_ == Kind::PropInit; }
+
+ [[nodiscard]] bool isDelete() const { return kind_ == Kind::Delete; }
+
+ [[nodiscard]] bool isCompoundAssignment() const {
+ return kind_ == Kind::CompoundAssignment;
+ }
+
+ [[nodiscard]] bool isIncDec() const {
+ return isPostIncDec() || isPreIncDec();
+ }
+
+ [[nodiscard]] bool isPostIncDec() const {
+ return kind_ == Kind::PostIncrement || kind_ == Kind::PostDecrement;
+ }
+
+ [[nodiscard]] bool isPreIncDec() const {
+ return kind_ == Kind::PreIncrement || kind_ == Kind::PreDecrement;
+ }
+
+ [[nodiscard]] bool isInc() const {
+ return kind_ == Kind::PostIncrement || kind_ == Kind::PreIncrement;
+ }
+
+ [[nodiscard]] bool prepareAtomIndex(TaggedParserAtomIndex prop);
+
+ public:
+ [[nodiscard]] bool prepareForObj();
+
+ [[nodiscard]] bool emitGet(TaggedParserAtomIndex prop);
+
+ [[nodiscard]] bool prepareForRhs();
+ [[nodiscard]] bool skipObjAndRhs();
+
+ [[nodiscard]] bool emitDelete(TaggedParserAtomIndex prop);
+
+ // `prop` can be nullptr for CompoundAssignment.
+ [[nodiscard]] bool emitAssignment(TaggedParserAtomIndex prop);
+
+ [[nodiscard]] bool emitIncDec(TaggedParserAtomIndex prop,
+ ValueUsage valueUsage);
+};
+
+} /* namespace frontend */
+} /* namespace js */
+
+#endif /* frontend_PropOpEmitter_h */
diff --git a/js/src/frontend/ReservedWords.h b/js/src/frontend/ReservedWords.h
new file mode 100644
index 0000000000..723f310ecf
--- /dev/null
+++ b/js/src/frontend/ReservedWords.h
@@ -0,0 +1,78 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* A higher-order macro for enumerating reserved word tokens. */
+
+#ifndef vm_ReservedWords_h
+#define vm_ReservedWords_h
+
+#define FOR_EACH_JAVASCRIPT_RESERVED_WORD(MACRO) \
+ MACRO(false, false_, TokenKind::False) \
+ MACRO(true, true_, TokenKind::True) \
+ MACRO(null, null, TokenKind::Null) \
+ \
+ /* Keywords. */ \
+ MACRO(break, break_, TokenKind::Break) \
+ MACRO(case, case_, TokenKind::Case) \
+ MACRO(catch, catch_, TokenKind::Catch) \
+ MACRO(const, const_, TokenKind::Const) \
+ MACRO(continue, continue_, TokenKind::Continue) \
+ MACRO(debugger, debugger, TokenKind::Debugger) \
+ MACRO(default, default_, TokenKind::Default) \
+ MACRO(delete, delete_, TokenKind::Delete) \
+ MACRO(do, do_, TokenKind::Do) \
+ MACRO(else, else_, TokenKind::Else) \
+ MACRO(finally, finally_, TokenKind::Finally) \
+ MACRO(for, for_, TokenKind::For) \
+ MACRO(function, function, TokenKind::Function) \
+ MACRO(if, if_, TokenKind::If) \
+ MACRO(in, in, TokenKind::In) \
+ MACRO(instanceof, instanceof, TokenKind::InstanceOf) \
+ MACRO(new, new_, TokenKind::New) \
+ MACRO(return, return_, TokenKind::Return) \
+ MACRO(switch, switch_, TokenKind::Switch) \
+ MACRO(this, this_, TokenKind::This) \
+ MACRO(throw, throw_, TokenKind::Throw) \
+ MACRO(try, try_, TokenKind::Try) \
+ MACRO(typeof, typeof_, TokenKind::TypeOf) \
+ MACRO(var, var, TokenKind::Var) \
+ MACRO(void, void_, TokenKind::Void) \
+ MACRO(while, while_, TokenKind::While) \
+ MACRO(with, with, TokenKind::With) \
+ MACRO(import, import, TokenKind::Import) \
+ MACRO(export, export_, TokenKind::Export) \
+ MACRO(class, class_, TokenKind::Class) \
+ MACRO(extends, extends, TokenKind::Extends) \
+ IF_DECORATORS(MACRO(accessor, accessor, TokenKind::Accessor)) \
+ MACRO(super, super, TokenKind::Super) \
+ \
+ /* Future reserved words. */ \
+ MACRO(enum, enum_, TokenKind::Enum) \
+ \
+ /* Future reserved words, but only in strict mode. */ \
+ MACRO(implements, implements, TokenKind::Implements) \
+ MACRO(interface, interface, TokenKind::Interface) \
+ MACRO(package, package, TokenKind::Package) \
+ MACRO(private, private_, TokenKind::Private) \
+ MACRO(protected, protected_, TokenKind::Protected) \
+ MACRO(public, public_, TokenKind::Public) \
+ \
+ /* Contextual keywords. */ \
+ MACRO(as, as, TokenKind::As) \
+ MACRO(assert, assert_, TokenKind::Assert) \
+ MACRO(async, async, TokenKind::Async) \
+ MACRO(await, await, TokenKind::Await) \
+ MACRO(from, from, TokenKind::From) \
+ MACRO(get, get, TokenKind::Get) \
+ MACRO(let, let, TokenKind::Let) \
+ MACRO(meta, meta, TokenKind::Meta) \
+ MACRO(of, of, TokenKind::Of) \
+ MACRO(set, set, TokenKind::Set) \
+ MACRO(static, static_, TokenKind::Static) \
+ MACRO(target, target, TokenKind::Target) \
+ MACRO(yield, yield, TokenKind::Yield)
+
+#endif /* vm_ReservedWords_h */
diff --git a/js/src/frontend/ScopeBindingCache.h b/js/src/frontend/ScopeBindingCache.h
new file mode 100644
index 0000000000..ee3dc35e33
--- /dev/null
+++ b/js/src/frontend/ScopeBindingCache.h
@@ -0,0 +1,281 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef frontend_ScopeBindingCache_h
+#define frontend_ScopeBindingCache_h
+
+#include "mozilla/Attributes.h" // MOZ_STACK_CLASS
+#include "mozilla/HashTable.h" // mozilla::HashMap
+
+#include "jstypes.h" // JS_PUBLIC_API
+
+#include "frontend/NameAnalysisTypes.h" // NameLocation
+#include "frontend/ParserAtom.h" // TaggedPArserAtomIndex, ParserAtomsTable
+
+#include "js/Utility.h" // AutoEnterOOMUnsafeRegion
+
+#include "vm/Scope.h" // AbstractBaseScopeData
+#include "vm/StringType.h" // JSAtom
+
+namespace js {
+namespace frontend {
+
+struct CompilationAtomCache;
+struct CompilationStencil;
+struct ScopeStencilRef;
+struct CompilationStencilMerger;
+
+// Generic atom wrapper which provides a way to interpret any Atom given
+// contextual information. Thus, this structure offers the ability to compare
+// Atom from different domains.
+//
+// This structure provides a `hash` field which is universal across all Atom
+// representations. Thus, Atoms from different contexes can be registered in a
+// hash table and looked up with a different Atom kind.
+struct GenericAtom {
+ // Emitter names are TaggedParserAtomIndex which are registered in the
+ // ParserAtomsTable of an extensible compilation stencil, frequently related
+ // to bytecode emitter, which lookup names in the scope chain to replace names
+ // by variable locations.
+ struct EmitterName {
+ FrontendContext* fc;
+ ParserAtomsTable& parserAtoms;
+ CompilationAtomCache& atomCache;
+ TaggedParserAtomIndex index;
+
+ EmitterName(FrontendContext* fc, ParserAtomsTable& parserAtoms,
+ CompilationAtomCache& atomCache, TaggedParserAtomIndex index)
+ : fc(fc),
+ parserAtoms(parserAtoms),
+ atomCache(atomCache),
+ index(index) {}
+ };
+
+ // Stencil names are TaggedParserAtomIndex which are registered in a
+ // ParserAtomVector of a compilation stencil, frequently related to the result
+ // of a compilation. It can be seen while manipulating names of a scope chain
+ // while delazifying functions using a stencil for context.
+ struct StencilName {
+ const CompilationStencil& stencil;
+ TaggedParserAtomIndex index;
+ };
+
+ // Any names are references to different Atom representation, including some
+ // which are interpretable given some contexts such as EmitterName and
+ // StencilName.
+ using AnyName = mozilla::Variant<EmitterName, StencilName, JSAtom*>;
+
+ HashNumber hash;
+ AnyName ref;
+
+ // Constructor for atoms managed by an ExtensibleCompilationState, while
+ // compiling a script.
+ GenericAtom(FrontendContext* fc, ParserAtomsTable& parserAtoms,
+ CompilationAtomCache& atomCache, TaggedParserAtomIndex index);
+
+ // Constructors for atoms managed by a CompilationStencil or a
+ // BorrowingCompilationStencil, which provide contextual information from an
+ // already compiled script.
+ GenericAtom(const CompilationStencil& context, TaggedParserAtomIndex index);
+ GenericAtom(ScopeStencilRef& scope, TaggedParserAtomIndex index);
+
+ // Constructor for atoms managed by the Garbage Collector, while providing
+ // contextual scope information when delazifying functions on the main thread.
+ GenericAtom(const Scope*, JSAtom* ptr) : GenericAtom(ptr) {}
+ explicit GenericAtom(JSAtom* ptr) : ref(ptr) { hash = ptr->hash(); }
+
+ bool operator==(const GenericAtom& other) const;
+};
+
+template <typename NameT>
+struct BindingHasher;
+
+template <>
+struct BindingHasher<TaggedParserAtomIndex> {
+ // This is a GenericAtom::StencilName stripped from its context which is the
+ // same for every key.
+ using Key = TaggedParserAtomIndex;
+ struct Lookup {
+ // When building a BindingMap, we assume that the TaggedParserAtomIndex is
+ // coming from an existing Stencil, and is not an EmitterName.
+ const CompilationStencil& keyStencil;
+ GenericAtom other;
+
+ Lookup(ScopeStencilRef& scope_ref, const GenericAtom& other);
+ };
+
+ static HashNumber hash(const Lookup& aLookup) { return aLookup.other.hash; }
+
+ static bool match(const Key& aKey, const Lookup& aLookup) {
+ GenericAtom key(aLookup.keyStencil, aKey);
+ return key == aLookup.other;
+ }
+};
+
+template <>
+struct BindingHasher<JSAtom*> {
+ using Key = JSAtom*;
+ struct Lookup {
+ GenericAtom other;
+
+ template <typename Any>
+ Lookup(const Any&, const GenericAtom& other) : other(other) {}
+ };
+
+ static HashNumber hash(const Lookup& aLookup) { return aLookup.other.hash; }
+
+ static bool match(const Key& aKey, const Lookup& aLookup) {
+ GenericAtom key(aKey);
+ return key == aLookup.other;
+ }
+};
+
+// Map the bound names to their respective name location. This is used to avoid
+// doing a linear lookup over the list of bindings each time we are looking for
+// a single name.
+//
+// The names given used as a key are either JSAtom in the case of a on-demand
+// delazification, or a TaggedParserAtomIndex in case of a
+// concurrent-delazification. In both case Lookup arguments are not trivially
+// created out of a key, as in the case of a TaggedParserAtomIndex, the
+// CompilationStencil should be provided to interpret the TaggedParserAtomIndex
+// which are stored in this hash table.
+template <typename NameT>
+struct BindingMap {
+ using Lookup = typename BindingHasher<NameT>::Lookup;
+ using Map =
+ HashMap<NameT, NameLocation, BindingHasher<NameT>, js::SystemAllocPolicy>;
+
+ Map hashMap;
+ mozilla::Maybe<NameLocation> catchAll;
+};
+
+// For each list of bound names, map the list of bound names to the hash table
+// which is used to reduce the time needed per lookup.
+//
+// The name parameter are either JSAtom in the case of a on-demand
+// delazification, or a TaggedParserAtomIndex in case of a
+// concurrent-delazification.
+template <typename NameT, typename ScopeT = NameT>
+using ScopeBindingMap =
+ HashMap<AbstractBaseScopeData<ScopeT>*, BindingMap<NameT>,
+ DefaultHasher<AbstractBaseScopeData<ScopeT>*>,
+ js::SystemAllocPolicy>;
+
+// Common interface for a cache holding the mapping of Scope to a hash table
+// which mirror the binding mapping stored in the scope.
+class ScopeBindingCache {
+ public:
+ using CacheGeneration = size_t;
+
+ virtual CacheGeneration getCurrentGeneration() const = 0;
+
+ // Check whether the cache provided as argument is capable of storing the type
+ // of scope given as arguments.
+ virtual bool canCacheFor(Scope* ptr);
+ virtual bool canCacheFor(ScopeStencilRef ref);
+
+ // Create a new BindingMap cache for a given scope. This cache should then be
+ // filled with all names which might be looked up.
+ virtual BindingMap<JSAtom*>* createCacheFor(Scope* ptr);
+ virtual BindingMap<TaggedParserAtomIndex>* createCacheFor(
+ ScopeStencilRef ref);
+
+ // Return the BindingMap created for the associated scope, unless the
+ // generation value does not match the one stored internally, in which case a
+ // null pointer is always returned.
+ virtual BindingMap<JSAtom*>* lookupScope(Scope* ptr, CacheGeneration gen);
+ virtual BindingMap<TaggedParserAtomIndex>* lookupScope(ScopeStencilRef ref,
+ CacheGeneration gen);
+};
+
+// NoScopeBindingCache is a no-op which does not implement a ScopeBindingCache.
+//
+// This is useful when compiling a global script or module, where we are not
+// interested in looking up anything from the enclosing scope chain.
+class NoScopeBindingCache final : public ScopeBindingCache {
+ public:
+ CacheGeneration getCurrentGeneration() const override { return 1; };
+
+ bool canCacheFor(Scope* ptr) override;
+ bool canCacheFor(ScopeStencilRef ref) override;
+};
+
+// StencilScopeBindingCache provides an interface to cache the bindings provided
+// by a CompilationStencilMerger.
+//
+// This cache lives on the stack and its content would be invalidated once going
+// out of scope. The constructor expects a reference to a
+// CompilationStencilMerger, that is expected to:
+// - out-live this class.
+// - contain the enclosing scope which are manipulated by this class.
+// - be the receiver of delazified functions.
+class MOZ_STACK_CLASS StencilScopeBindingCache final
+ : public ScopeBindingCache {
+ ScopeBindingMap<TaggedParserAtomIndex> scopeMap;
+#ifdef DEBUG
+ const CompilationStencilMerger& merger_;
+#endif
+
+ public:
+ explicit StencilScopeBindingCache(const CompilationStencilMerger& merger)
+#ifdef DEBUG
+ : merger_(merger)
+#endif
+ {
+ }
+
+ // The cache content is always valid as long as it does not out-live the
+ // CompilationStencilMerger. No need for a generation number.
+ CacheGeneration getCurrentGeneration() const override { return 1; }
+
+ bool canCacheFor(ScopeStencilRef ref) override;
+ BindingMap<TaggedParserAtomIndex>* createCacheFor(
+ ScopeStencilRef ref) override;
+ BindingMap<TaggedParserAtomIndex>* lookupScope(ScopeStencilRef ref,
+ CacheGeneration gen) override;
+};
+
+// RuntimeScopeBindingCache is used to hold the binding map for each scope which
+// is hold by a Scope managed by the garbage collector.
+//
+// This cache is not thread safe.
+//
+// The generation number is used to assert the validity of the cached content.
+// During a GC, the cached content is thrown away and getCurrentGeneration
+// returns a different number. When the generation number differs from the
+// initialization of the cached content, the cache content might be renewed or
+// ignored.
+class RuntimeScopeBindingCache final : public ScopeBindingCache {
+ ScopeBindingMap<JSAtom*, JSAtom> scopeMap;
+
+ // This value is initialized to 1, such that we can differentiate it from the
+ // typical 0-init of size_t values, when non-initialized.
+ size_t cacheGeneration = 1;
+
+ public:
+ CacheGeneration getCurrentGeneration() const override {
+ return cacheGeneration;
+ }
+
+ bool canCacheFor(Scope* ptr) override;
+ BindingMap<JSAtom*>* createCacheFor(Scope* ptr) override;
+ BindingMap<JSAtom*>* lookupScope(Scope* ptr, CacheGeneration gen) override;
+
+ // The ScopeBindingCache is not instrumented for tracing weakly the keys used
+ // for mapping to the NameLocation. Instead, we always purge during compaction
+ // or collection, and increment the cacheGeneration to notify all consumers
+ // that the cache can no longer be used without being re-populated.
+ void purge() {
+ cacheGeneration++;
+ scopeMap.clearAndCompact();
+ }
+};
+
+} // namespace frontend
+} // namespace js
+
+#endif // frontend_ScopeBindingCache_h
diff --git a/js/src/frontend/ScopeIndex.h b/js/src/frontend/ScopeIndex.h
new file mode 100644
index 0000000000..de8087211c
--- /dev/null
+++ b/js/src/frontend/ScopeIndex.h
@@ -0,0 +1,32 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef frontend_ScopeIndex_h
+#define frontend_ScopeIndex_h
+
+#include <cstdint> // uint32_t, UINT32_MAX
+
+#include "frontend/TypedIndex.h" // TypedIndex
+
+namespace js {
+
+class Scope;
+
+class ScopeIndex : public frontend::TypedIndex<Scope> {
+ // Delegate constructors;
+ using Base = frontend::TypedIndex<Scope>;
+ using Base::Base;
+
+ static constexpr uint32_t InvalidIndex = UINT32_MAX;
+
+ public:
+ static constexpr ScopeIndex invalid() { return ScopeIndex(InvalidIndex); }
+ bool isValid() const { return index != InvalidIndex; }
+};
+
+} /* namespace js */
+
+#endif /* frontend_ScopeIndex_h */
diff --git a/js/src/frontend/ScriptIndex.h b/js/src/frontend/ScriptIndex.h
new file mode 100644
index 0000000000..ed16b6be57
--- /dev/null
+++ b/js/src/frontend/ScriptIndex.h
@@ -0,0 +1,42 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef frontend_ScriptIndex_h
+#define frontend_ScriptIndex_h
+
+#include <utility>
+
+#include "frontend/TypedIndex.h" // TypedIndex
+#include "js/GCPolicyAPI.h" // JS::GCPolicy, JS::IgnoreGCPolicy
+
+namespace js {
+namespace frontend {
+
+class ScriptStencil;
+
+using ScriptIndex = TypedIndex<ScriptStencil>;
+
+// Represents a range for scripts [start, limit).
+struct ScriptIndexRange {
+ ScriptIndex start;
+ ScriptIndex limit;
+
+ ScriptIndexRange(ScriptIndex start, ScriptIndex limit)
+ : start(start), limit(limit) {
+ MOZ_ASSERT(start < limit);
+ }
+};
+
+} /* namespace frontend */
+} /* namespace js */
+
+namespace JS {
+template <>
+struct GCPolicy<js::frontend::ScriptIndexRange>
+ : public IgnoreGCPolicy<js::frontend::ScriptIndexRange> {};
+} // namespace JS
+
+#endif /* frontend_ScriptIndex_h */
diff --git a/js/src/frontend/SelfHostedIter.h b/js/src/frontend/SelfHostedIter.h
new file mode 100644
index 0000000000..f2c454716a
--- /dev/null
+++ b/js/src/frontend/SelfHostedIter.h
@@ -0,0 +1,28 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef frontend_SelfHostedIter_h
+#define frontend_SelfHostedIter_h
+
+namespace js::frontend {
+
+// `for-of`, `for-await-of`, and spread operations are allowed on
+// self-hosted JS code only when the operand is explicitly marked with
+// `allowContentIter()`.
+//
+// This value is effectful only when emitting self-hosted JS code.
+enum class SelfHostedIter {
+ // The operand is not marked.
+ // Also means "don't care" for non-self-hosted JS case.
+ Deny,
+
+ // The operand is marked.
+ Allow,
+};
+
+} /* namespace js::frontend */
+
+#endif /* frontend_SelfHostedIter_h */
diff --git a/js/src/frontend/SharedContext-inl.h b/js/src/frontend/SharedContext-inl.h
new file mode 100644
index 0000000000..9175fcc0e2
--- /dev/null
+++ b/js/src/frontend/SharedContext-inl.h
@@ -0,0 +1,23 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef frontend_SharedContext_inl_h
+#define frontend_SharedContext_inl_h
+
+#include "frontend/SharedContext.h"
+#include "frontend/ParseContext.h"
+
+namespace js {
+namespace frontend {
+
+inline Directives::Directives(ParseContext* parent)
+ : strict_(parent->sc()->strict()), asmJS_(parent->useAsmOrInsideUseAsm()) {}
+
+} // namespace frontend
+
+} // namespace js
+
+#endif // frontend_SharedContext_inl_h
diff --git a/js/src/frontend/SharedContext.cpp b/js/src/frontend/SharedContext.cpp
new file mode 100644
index 0000000000..7fa3b724fb
--- /dev/null
+++ b/js/src/frontend/SharedContext.cpp
@@ -0,0 +1,409 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "frontend/SharedContext.h"
+
+#include "mozilla/RefPtr.h"
+
+#include "frontend/CompilationStencil.h"
+#include "frontend/FunctionSyntaxKind.h" // FunctionSyntaxKind
+#include "frontend/ModuleSharedContext.h"
+#include "frontend/ParseContext.h"
+#include "frontend/ParseNode.h"
+#include "frontend/ParserAtom.h"
+#include "frontend/ScopeIndex.h"
+#include "frontend/ScriptIndex.h"
+#include "frontend/Stencil.h"
+#include "js/CompileOptions.h"
+#include "js/Vector.h"
+#include "vm/FunctionFlags.h" // js::FunctionFlags
+#include "vm/GeneratorAndAsyncKind.h" // js::GeneratorKind, js::FunctionAsyncKind
+#include "vm/JSScript.h" // js::FillImmutableFlagsFromCompileOptionsForTopLevel, js::FillImmutableFlagsFromCompileOptionsForFunction
+#include "vm/StencilEnums.h" // ImmutableScriptFlagsEnum
+
+#include "frontend/ParseContext-inl.h"
+
+namespace js {
+
+class ModuleBuilder;
+
+namespace frontend {
+
+SharedContext::SharedContext(FrontendContext* fc, Kind kind,
+ const JS::ReadOnlyCompileOptions& options,
+ Directives directives, SourceExtent extent)
+ : fc_(fc),
+ extent_(extent),
+ allowNewTarget_(false),
+ allowSuperProperty_(false),
+ allowSuperCall_(false),
+ allowArguments_(true),
+ inWith_(false),
+ inClass_(false),
+ localStrict(false),
+ hasExplicitUseStrict_(false),
+ isScriptExtraFieldCopiedToStencil(false) {
+ // Compute the script kind "input" flags.
+ if (kind == Kind::FunctionBox) {
+ setFlag(ImmutableFlags::IsFunction);
+ } else if (kind == Kind::Module) {
+ MOZ_ASSERT(!options.nonSyntacticScope);
+ setFlag(ImmutableFlags::IsModule);
+ } else if (kind == Kind::Eval) {
+ setFlag(ImmutableFlags::IsForEval);
+ } else {
+ MOZ_ASSERT(kind == Kind::Global);
+ }
+
+ // Initialize the transitive "input" flags. These are applied to all
+ // SharedContext in this compilation and generally cannot be determined from
+ // the source text alone.
+ if (isTopLevelContext()) {
+ js::FillImmutableFlagsFromCompileOptionsForTopLevel(options,
+ immutableFlags_);
+ } else {
+ js::FillImmutableFlagsFromCompileOptionsForFunction(options,
+ immutableFlags_);
+ }
+
+ // Initialize the strict flag. This may be updated by the parser as we observe
+ // further directives in the body.
+ setFlag(ImmutableFlags::Strict, directives.strict());
+}
+
+GlobalSharedContext::GlobalSharedContext(
+ FrontendContext* fc, ScopeKind scopeKind,
+ const JS::ReadOnlyCompileOptions& options, Directives directives,
+ SourceExtent extent)
+ : SharedContext(fc, Kind::Global, options, directives, extent),
+ scopeKind_(scopeKind),
+ bindings(nullptr) {
+ MOZ_ASSERT(scopeKind == ScopeKind::Global ||
+ scopeKind == ScopeKind::NonSyntactic);
+ MOZ_ASSERT(thisBinding_ == ThisBinding::Global);
+}
+
+EvalSharedContext::EvalSharedContext(FrontendContext* fc,
+ CompilationState& compilationState,
+ SourceExtent extent)
+ : SharedContext(fc, Kind::Eval, compilationState.input.options,
+ compilationState.directives, extent),
+ bindings(nullptr) {
+ // Eval inherits syntax and binding rules from enclosing environment.
+ allowNewTarget_ = compilationState.scopeContext.allowNewTarget;
+ allowSuperProperty_ = compilationState.scopeContext.allowSuperProperty;
+ allowSuperCall_ = compilationState.scopeContext.allowSuperCall;
+ allowArguments_ = compilationState.scopeContext.allowArguments;
+ thisBinding_ = compilationState.scopeContext.thisBinding;
+ inWith_ = compilationState.scopeContext.inWith;
+}
+
+SuspendableContext::SuspendableContext(
+ FrontendContext* fc, Kind kind, const JS::ReadOnlyCompileOptions& options,
+ Directives directives, SourceExtent extent, bool isGenerator, bool isAsync)
+ : SharedContext(fc, kind, options, directives, extent) {
+ setFlag(ImmutableFlags::IsGenerator, isGenerator);
+ setFlag(ImmutableFlags::IsAsync, isAsync);
+}
+
+FunctionBox::FunctionBox(FrontendContext* fc, SourceExtent extent,
+ CompilationState& compilationState,
+ Directives directives, GeneratorKind generatorKind,
+ FunctionAsyncKind asyncKind, bool isInitialCompilation,
+ TaggedParserAtomIndex atom, FunctionFlags flags,
+ ScriptIndex index)
+ : SuspendableContext(fc, Kind::FunctionBox, compilationState.input.options,
+ directives, extent,
+ generatorKind == GeneratorKind::Generator,
+ asyncKind == FunctionAsyncKind::AsyncFunction),
+ compilationState_(compilationState),
+ atom_(atom),
+ funcDataIndex_(index),
+ flags_(FunctionFlags::clearMutableflags(flags)),
+ emitBytecode(false),
+ wasEmittedByEnclosingScript_(false),
+ isAnnexB(false),
+ useAsm(false),
+ hasParameterExprs(false),
+ hasDestructuringArgs(false),
+ hasDuplicateParameters(false),
+ hasExprBody_(false),
+ allowReturn_(true),
+ isFunctionFieldCopiedToStencil(false),
+ isInitialCompilation(isInitialCompilation),
+ isStandalone(false) {}
+
+void FunctionBox::initFromLazyFunction(const ScriptStencilExtra& extra,
+ ScopeContext& scopeContext,
+ FunctionSyntaxKind kind) {
+ initFromScriptStencilExtra(extra);
+ initStandaloneOrLazy(scopeContext, kind);
+}
+
+void FunctionBox::initFromScriptStencilExtra(const ScriptStencilExtra& extra) {
+ immutableFlags_ = extra.immutableFlags;
+ extent_ = extra.extent;
+}
+
+void FunctionBox::initWithEnclosingParseContext(ParseContext* enclosing,
+ FunctionSyntaxKind kind) {
+ SharedContext* sc = enclosing->sc();
+
+ // HasModuleGoal and useAsm are inherited from enclosing context.
+ useAsm = sc->isFunctionBox() && sc->asFunctionBox()->useAsmOrInsideUseAsm();
+ setHasModuleGoal(sc->hasModuleGoal());
+
+ // Arrow functions don't have their own `this` binding.
+ if (flags_.isArrow()) {
+ allowNewTarget_ = sc->allowNewTarget();
+ allowSuperProperty_ = sc->allowSuperProperty();
+ allowSuperCall_ = sc->allowSuperCall();
+ allowArguments_ = sc->allowArguments();
+ thisBinding_ = sc->thisBinding();
+ } else {
+ if (IsConstructorKind(kind)) {
+ // Record this function into the enclosing class statement so that
+ // finishClassConstructor can final processing. Due to aborted syntax
+ // parses (eg, because of asm.js), this may have already been set with an
+ // early FunctionBox. In that case, the FunctionNode should still match.
+ auto classStmt =
+ enclosing->findInnermostStatement<ParseContext::ClassStatement>();
+ MOZ_ASSERT(classStmt);
+ MOZ_ASSERT(classStmt->constructorBox == nullptr ||
+ classStmt->constructorBox->functionNode == this->functionNode);
+ classStmt->constructorBox = this;
+ }
+
+ allowNewTarget_ = true;
+ allowSuperProperty_ = flags_.allowSuperProperty();
+
+ if (kind == FunctionSyntaxKind::DerivedClassConstructor) {
+ setDerivedClassConstructor();
+ allowSuperCall_ = true;
+ thisBinding_ = ThisBinding::DerivedConstructor;
+ } else {
+ thisBinding_ = ThisBinding::Function;
+ }
+
+ if (kind == FunctionSyntaxKind::FieldInitializer ||
+ kind == FunctionSyntaxKind::StaticClassBlock) {
+ setSyntheticFunction();
+ allowArguments_ = false;
+ if (kind == FunctionSyntaxKind::StaticClassBlock) {
+ allowSuperCall_ = false;
+ allowReturn_ = false;
+ }
+ }
+ }
+
+ if (sc->inWith()) {
+ inWith_ = true;
+ } else {
+ auto isWith = [](ParseContext::Statement* stmt) {
+ return stmt->kind() == StatementKind::With;
+ };
+
+ inWith_ = enclosing->findInnermostStatement(isWith);
+ }
+
+ if (sc->inClass()) {
+ inClass_ = true;
+ } else {
+ auto isClass = [](ParseContext::Statement* stmt) {
+ return stmt->kind() == StatementKind::Class;
+ };
+
+ inClass_ = enclosing->findInnermostStatement(isClass);
+ }
+}
+
+void FunctionBox::initStandalone(ScopeContext& scopeContext,
+ FunctionSyntaxKind kind) {
+ initStandaloneOrLazy(scopeContext, kind);
+
+ isStandalone = true;
+}
+
+void FunctionBox::initStandaloneOrLazy(ScopeContext& scopeContext,
+ FunctionSyntaxKind kind) {
+ if (flags_.isArrow()) {
+ allowNewTarget_ = scopeContext.allowNewTarget;
+ allowSuperProperty_ = scopeContext.allowSuperProperty;
+ allowSuperCall_ = scopeContext.allowSuperCall;
+ allowArguments_ = scopeContext.allowArguments;
+ thisBinding_ = scopeContext.thisBinding;
+ } else {
+ allowNewTarget_ = true;
+ allowSuperProperty_ = flags_.allowSuperProperty();
+
+ if (kind == FunctionSyntaxKind::DerivedClassConstructor) {
+ setDerivedClassConstructor();
+ allowSuperCall_ = true;
+ thisBinding_ = ThisBinding::DerivedConstructor;
+ } else {
+ thisBinding_ = ThisBinding::Function;
+ }
+
+ if (kind == FunctionSyntaxKind::FieldInitializer) {
+ setSyntheticFunction();
+ allowArguments_ = false;
+ }
+ }
+
+ inWith_ = scopeContext.inWith;
+ inClass_ = scopeContext.inClass;
+}
+
+void FunctionBox::setEnclosingScopeForInnerLazyFunction(ScopeIndex scopeIndex) {
+ // For lazy functions inside a function which is being compiled, we cache
+ // the incomplete scope object while compiling, and store it to the
+ // BaseScript once the enclosing script successfully finishes compilation
+ // in FunctionBox::finish.
+ MOZ_ASSERT(enclosingScopeIndex_.isNothing());
+ enclosingScopeIndex_ = mozilla::Some(scopeIndex);
+ if (isFunctionFieldCopiedToStencil) {
+ copyUpdatedEnclosingScopeIndex();
+ }
+}
+
+bool FunctionBox::setAsmJSModule(const JS::WasmModule* module) {
+ MOZ_ASSERT(!isFunctionFieldCopiedToStencil);
+
+ MOZ_ASSERT(flags_.kind() == FunctionFlags::NormalFunction);
+
+ // Update flags we will use to allocate the JSFunction.
+ flags_.clearBaseScript();
+ flags_.setIsExtended();
+ flags_.setKind(FunctionFlags::AsmJS);
+
+ if (!compilationState_.asmJS) {
+ compilationState_.asmJS =
+ fc_->getAllocator()->new_<StencilAsmJSContainer>();
+ if (!compilationState_.asmJS) {
+ return false;
+ }
+ }
+
+ if (!compilationState_.asmJS->moduleMap.putNew(index(), module)) {
+ js::ReportOutOfMemory(fc_);
+ return false;
+ }
+ return true;
+}
+
+ModuleSharedContext::ModuleSharedContext(
+ FrontendContext* fc, const JS::ReadOnlyCompileOptions& options,
+ ModuleBuilder& builder, SourceExtent extent)
+ : SuspendableContext(fc, Kind::Module, options, Directives(true), extent,
+ /* isGenerator = */ false,
+ /* isAsync = */ false),
+ bindings(nullptr),
+ builder(builder) {
+ thisBinding_ = ThisBinding::Module;
+ setFlag(ImmutableFlags::HasModuleGoal);
+}
+
+ScriptStencil& FunctionBox::functionStencil() const {
+ return compilationState_.scriptData[funcDataIndex_];
+}
+
+ScriptStencilExtra& FunctionBox::functionExtraStencil() const {
+ return compilationState_.scriptExtra[funcDataIndex_];
+}
+
+void SharedContext::copyScriptExtraFields(ScriptStencilExtra& scriptExtra) {
+ MOZ_ASSERT(!isScriptExtraFieldCopiedToStencil);
+
+ scriptExtra.immutableFlags = immutableFlags_;
+ scriptExtra.extent = extent_;
+
+ isScriptExtraFieldCopiedToStencil = true;
+}
+
+void FunctionBox::finishScriptFlags() {
+ MOZ_ASSERT(!isScriptExtraFieldCopiedToStencil);
+
+ using ImmutableFlags = ImmutableScriptFlagsEnum;
+ immutableFlags_.setFlag(ImmutableFlags::HasMappedArgsObj, hasMappedArgsObj());
+}
+
+void FunctionBox::copyFunctionFields(ScriptStencil& script) {
+ MOZ_ASSERT(&script == &functionStencil());
+ MOZ_ASSERT(!isFunctionFieldCopiedToStencil);
+
+ if (atom_) {
+ compilationState_.parserAtoms.markUsedByStencil(atom_,
+ ParserAtom::Atomize::Yes);
+ script.functionAtom = atom_;
+ }
+ script.functionFlags = flags_;
+ if (enclosingScopeIndex_) {
+ script.setLazyFunctionEnclosingScopeIndex(*enclosingScopeIndex_);
+ }
+ if (wasEmittedByEnclosingScript_) {
+ script.setWasEmittedByEnclosingScript();
+ }
+
+ isFunctionFieldCopiedToStencil = true;
+}
+
+void FunctionBox::copyFunctionExtraFields(ScriptStencilExtra& scriptExtra) {
+ if (useMemberInitializers()) {
+ scriptExtra.setMemberInitializers(memberInitializers());
+ }
+
+ scriptExtra.nargs = nargs_;
+}
+
+void FunctionBox::copyUpdatedImmutableFlags() {
+ if (isInitialCompilation) {
+ ScriptStencilExtra& scriptExtra = functionExtraStencil();
+ scriptExtra.immutableFlags = immutableFlags_;
+ }
+}
+
+void FunctionBox::copyUpdatedExtent() {
+ ScriptStencilExtra& scriptExtra = functionExtraStencil();
+ scriptExtra.extent = extent_;
+}
+
+void FunctionBox::copyUpdatedMemberInitializers() {
+ MOZ_ASSERT(useMemberInitializers());
+ if (isInitialCompilation) {
+ ScriptStencilExtra& scriptExtra = functionExtraStencil();
+ scriptExtra.setMemberInitializers(memberInitializers());
+ } else {
+ // We are delazifying and the original PrivateScriptData has the member
+ // initializer information already. See: JSScript::fullyInitFromStencil.
+ }
+}
+
+void FunctionBox::copyUpdatedEnclosingScopeIndex() {
+ ScriptStencil& script = functionStencil();
+ if (enclosingScopeIndex_) {
+ script.setLazyFunctionEnclosingScopeIndex(*enclosingScopeIndex_);
+ }
+}
+
+void FunctionBox::copyUpdatedAtomAndFlags() {
+ ScriptStencil& script = functionStencil();
+ if (atom_) {
+ compilationState_.parserAtoms.markUsedByStencil(atom_,
+ ParserAtom::Atomize::Yes);
+ script.functionAtom = atom_;
+ }
+ script.functionFlags = flags_;
+}
+
+void FunctionBox::copyUpdatedWasEmitted() {
+ ScriptStencil& script = functionStencil();
+ if (wasEmittedByEnclosingScript_) {
+ script.setWasEmittedByEnclosingScript();
+ }
+}
+
+} // namespace frontend
+} // namespace js
diff --git a/js/src/frontend/SharedContext.h b/js/src/frontend/SharedContext.h
new file mode 100644
index 0000000000..ded2e0439f
--- /dev/null
+++ b/js/src/frontend/SharedContext.h
@@ -0,0 +1,747 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef frontend_SharedContext_h
+#define frontend_SharedContext_h
+
+#include "mozilla/Assertions.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/Maybe.h"
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include "jstypes.h"
+
+#include "frontend/FunctionSyntaxKind.h" // FunctionSyntaxKind
+#include "frontend/ParserAtom.h" // TaggedParserAtomIndex
+#include "frontend/ScopeIndex.h" // ScopeIndex
+#include "frontend/ScriptIndex.h" // ScriptIndex
+#include "vm/FunctionFlags.h" // js::FunctionFlags
+#include "vm/GeneratorAndAsyncKind.h" // js::GeneratorKind, js::FunctionAsyncKind
+#include "vm/Scope.h"
+#include "vm/ScopeKind.h"
+#include "vm/SharedStencil.h"
+#include "vm/StencilEnums.h"
+
+namespace JS {
+class JS_PUBLIC_API ReadOnlyCompileOptions;
+struct WasmModule;
+} // namespace JS
+
+namespace js {
+
+class FrontendContext;
+
+namespace frontend {
+
+struct CompilationState;
+class FunctionBox;
+class FunctionNode;
+class ParseContext;
+class ScriptStencil;
+class ScriptStencilExtra;
+struct ScopeContext;
+
+enum class StatementKind : uint8_t {
+ Label,
+ Block,
+ If,
+ Switch,
+ With,
+ Catch,
+ Try,
+ Finally,
+ ForLoopLexicalHead,
+ ForLoop,
+ ForInLoop,
+ ForOfLoop,
+ DoLoop,
+ WhileLoop,
+ Class,
+
+ // Used only by BytecodeEmitter.
+ Spread,
+ YieldStar,
+};
+
+static inline bool StatementKindIsLoop(StatementKind kind) {
+ return kind == StatementKind::ForLoop || kind == StatementKind::ForInLoop ||
+ kind == StatementKind::ForOfLoop || kind == StatementKind::DoLoop ||
+ kind == StatementKind::WhileLoop || kind == StatementKind::Spread ||
+ kind == StatementKind::YieldStar;
+}
+
+static inline bool StatementKindIsUnlabeledBreakTarget(StatementKind kind) {
+ return StatementKindIsLoop(kind) || kind == StatementKind::Switch;
+}
+
+// List of directives that may be encountered in a Directive Prologue
+// (ES5 15.1).
+class Directives {
+ bool strict_;
+ bool asmJS_;
+
+ public:
+ explicit Directives(bool strict) : strict_(strict), asmJS_(false) {}
+ explicit Directives(ParseContext* parent);
+
+ void setStrict() { strict_ = true; }
+ bool strict() const { return strict_; }
+
+ void setAsmJS() { asmJS_ = true; }
+ bool asmJS() const { return asmJS_; }
+
+ Directives& operator=(Directives rhs) {
+ strict_ = rhs.strict_;
+ asmJS_ = rhs.asmJS_;
+ return *this;
+ }
+ bool operator==(const Directives& rhs) const {
+ return strict_ == rhs.strict_ && asmJS_ == rhs.asmJS_;
+ }
+ bool operator!=(const Directives& rhs) const { return !(*this == rhs); }
+};
+
+// The kind of this-binding for the current scope. Note that arrow functions
+// have a lexical this-binding so their ThisBinding is the same as the
+// ThisBinding of their enclosing scope and can be any value. Derived
+// constructors require TDZ checks when accessing the binding.
+enum class ThisBinding : uint8_t {
+ Global,
+ Module,
+ Function,
+ DerivedConstructor
+};
+
+// If Yes, the script inherits it's "this" environment and binding from the
+// enclosing script. This is true for arrow-functions and eval scripts.
+enum class InheritThis { No, Yes };
+
+class GlobalSharedContext;
+class EvalSharedContext;
+class ModuleSharedContext;
+class SuspendableContext;
+
+#define IMMUTABLE_FLAG_GETTER_SETTER(lowerName, name) \
+ GENERIC_FLAG_GETTER_SETTER(ImmutableFlags, lowerName, name)
+
+#define IMMUTABLE_FLAG_GETTER(lowerName, name) \
+ GENERIC_FLAG_GETTER(ImmutableFlags, lowerName, name)
+
+/*
+ * The struct SharedContext is part of the current parser context (see
+ * ParseContext). It stores information that is reused between the parser and
+ * the bytecode emitter.
+ */
+class SharedContext {
+ public:
+ FrontendContext* const fc_;
+
+ protected:
+ // See: BaseScript::immutableFlags_
+ ImmutableScriptFlags immutableFlags_ = {};
+
+ // The location of this script in the source. Note that the value here differs
+ // from the final BaseScript for the case of standalone functions.
+ // This field is copied to ScriptStencil, and shouldn't be modified after the
+ // copy.
+ SourceExtent extent_ = {};
+
+ protected:
+ // See: ThisBinding
+ ThisBinding thisBinding_ = ThisBinding::Global;
+
+ // These flags do not have corresponding script flags and may be inherited
+ // from the scope chain in the case of eval and arrows.
+ bool allowNewTarget_ : 1;
+ bool allowSuperProperty_ : 1;
+ bool allowSuperCall_ : 1;
+ bool allowArguments_ : 1;
+ bool inWith_ : 1;
+ bool inClass_ : 1;
+
+ // See `strict()` below.
+ bool localStrict : 1;
+
+ // True if "use strict"; appears in the body instead of being inherited.
+ bool hasExplicitUseStrict_ : 1;
+
+ // Tracks if script-related fields are already copied to ScriptStencilExtra.
+ //
+ // If this field is true, those fileds shouldn't be modified.
+ //
+ // For FunctionBox, some fields are allowed to be modified, but the
+ // modification should be synced with ScriptStencilExtra by
+ // FunctionBox::copyUpdated* methods.
+ bool isScriptExtraFieldCopiedToStencil : 1;
+
+ // End of fields.
+
+ enum class Kind : uint8_t { FunctionBox, Global, Eval, Module };
+
+ // Alias enum into SharedContext
+ using ImmutableFlags = ImmutableScriptFlagsEnum;
+
+ [[nodiscard]] bool hasFlag(ImmutableFlags flag) const {
+ return immutableFlags_.hasFlag(flag);
+ }
+ void setFlag(ImmutableFlags flag, bool b = true) {
+ MOZ_ASSERT(!isScriptExtraFieldCopiedToStencil);
+ immutableFlags_.setFlag(flag, b);
+ }
+ void clearFlag(ImmutableFlags flag) {
+ MOZ_ASSERT(!isScriptExtraFieldCopiedToStencil);
+ immutableFlags_.clearFlag(flag);
+ }
+
+ public:
+ SharedContext(FrontendContext* fc, Kind kind,
+ const JS::ReadOnlyCompileOptions& options,
+ Directives directives, SourceExtent extent);
+
+ IMMUTABLE_FLAG_GETTER_SETTER(isForEval, IsForEval)
+ IMMUTABLE_FLAG_GETTER_SETTER(isModule, IsModule)
+ IMMUTABLE_FLAG_GETTER_SETTER(isFunction, IsFunction)
+ IMMUTABLE_FLAG_GETTER_SETTER(selfHosted, SelfHosted)
+ IMMUTABLE_FLAG_GETTER_SETTER(forceStrict, ForceStrict)
+ IMMUTABLE_FLAG_GETTER_SETTER(hasNonSyntacticScope, HasNonSyntacticScope)
+ IMMUTABLE_FLAG_GETTER_SETTER(noScriptRval, NoScriptRval)
+ IMMUTABLE_FLAG_GETTER(treatAsRunOnce, TreatAsRunOnce)
+ // Strict: custom logic below
+ IMMUTABLE_FLAG_GETTER_SETTER(hasModuleGoal, HasModuleGoal)
+ IMMUTABLE_FLAG_GETTER_SETTER(hasInnerFunctions, HasInnerFunctions)
+ IMMUTABLE_FLAG_GETTER_SETTER(hasDirectEval, HasDirectEval)
+ IMMUTABLE_FLAG_GETTER_SETTER(bindingsAccessedDynamically,
+ BindingsAccessedDynamically)
+ IMMUTABLE_FLAG_GETTER_SETTER(hasCallSiteObj, HasCallSiteObj)
+
+ const SourceExtent& extent() const { return extent_; }
+
+ bool isFunctionBox() const { return isFunction(); }
+ inline FunctionBox* asFunctionBox();
+ bool isModuleContext() const { return isModule(); }
+ inline ModuleSharedContext* asModuleContext();
+ bool isSuspendableContext() const { return isFunction() || isModule(); }
+ inline SuspendableContext* asSuspendableContext();
+ bool isGlobalContext() const {
+ return !(isFunction() || isModule() || isForEval());
+ }
+ inline GlobalSharedContext* asGlobalContext();
+ bool isEvalContext() const { return isForEval(); }
+ inline EvalSharedContext* asEvalContext();
+
+ bool isTopLevelContext() const { return !isFunction(); }
+
+ ThisBinding thisBinding() const { return thisBinding_; }
+ bool hasFunctionThisBinding() const {
+ return thisBinding() == ThisBinding::Function ||
+ thisBinding() == ThisBinding::DerivedConstructor;
+ }
+ bool needsThisTDZChecks() const {
+ return thisBinding() == ThisBinding::DerivedConstructor;
+ }
+
+ bool isSelfHosted() const { return selfHosted(); }
+ bool allowNewTarget() const { return allowNewTarget_; }
+ bool allowSuperProperty() const { return allowSuperProperty_; }
+ bool allowSuperCall() const { return allowSuperCall_; }
+ bool allowArguments() const { return allowArguments_; }
+ bool inWith() const { return inWith_; }
+ bool inClass() const { return inClass_; }
+
+ bool hasExplicitUseStrict() const { return hasExplicitUseStrict_; }
+ void setExplicitUseStrict() { hasExplicitUseStrict_ = true; }
+
+ ImmutableScriptFlags immutableFlags() { return immutableFlags_; }
+
+ bool allBindingsClosedOver() { return bindingsAccessedDynamically(); }
+
+ // The ImmutableFlag tracks if the entire script is strict, while the
+ // localStrict flag indicates the current region (such as class body) should
+ // be treated as strict. The localStrict flag will always be reset to false
+ // before the end of the script.
+ bool strict() const { return hasFlag(ImmutableFlags::Strict) || localStrict; }
+ void setStrictScript() { setFlag(ImmutableFlags::Strict); }
+ bool setLocalStrictMode(bool strict) {
+ bool retVal = localStrict;
+ localStrict = strict;
+ return retVal;
+ }
+
+ void copyScriptExtraFields(ScriptStencilExtra& scriptExtra);
+};
+
+class MOZ_STACK_CLASS GlobalSharedContext : public SharedContext {
+ ScopeKind scopeKind_;
+
+ public:
+ GlobalScope::ParserData* bindings;
+
+ GlobalSharedContext(FrontendContext* fc, ScopeKind scopeKind,
+ const JS::ReadOnlyCompileOptions& options,
+ Directives directives, SourceExtent extent);
+
+ ScopeKind scopeKind() const { return scopeKind_; }
+};
+
+inline GlobalSharedContext* SharedContext::asGlobalContext() {
+ MOZ_ASSERT(isGlobalContext());
+ return static_cast<GlobalSharedContext*>(this);
+}
+
+class MOZ_STACK_CLASS EvalSharedContext : public SharedContext {
+ public:
+ EvalScope::ParserData* bindings;
+
+ EvalSharedContext(FrontendContext* fc, CompilationState& compilationState,
+ SourceExtent extent);
+};
+
+inline EvalSharedContext* SharedContext::asEvalContext() {
+ MOZ_ASSERT(isEvalContext());
+ return static_cast<EvalSharedContext*>(this);
+}
+
+enum class HasHeritage { No, Yes };
+
+class SuspendableContext : public SharedContext {
+ public:
+ SuspendableContext(FrontendContext* fc, Kind kind,
+ const JS::ReadOnlyCompileOptions& options,
+ Directives directives, SourceExtent extent,
+ bool isGenerator, bool isAsync);
+
+ IMMUTABLE_FLAG_GETTER_SETTER(isAsync, IsAsync)
+ IMMUTABLE_FLAG_GETTER_SETTER(isGenerator, IsGenerator)
+
+ bool needsFinalYield() const { return isGenerator() || isAsync(); }
+ bool needsDotGeneratorName() const { return isGenerator() || isAsync(); }
+ bool needsClearSlotsOnExit() const { return isGenerator() || isAsync(); }
+ bool needsIteratorResult() const { return isGenerator() && !isAsync(); }
+ bool needsPromiseResult() const { return isAsync() && !isGenerator(); }
+};
+
+class FunctionBox : public SuspendableContext {
+ friend struct GCThingList;
+
+ CompilationState& compilationState_;
+
+ // If this FunctionBox refers to a lazy child of the function being
+ // compiled, this field holds the child's immediately enclosing scope's index.
+ // Once compilation succeeds, we will store the scope pointed by this in the
+ // child's BaseScript. (Debugger may become confused if lazy scripts refer to
+ // partially initialized enclosing scopes, so we must avoid storing the
+ // scope in the BaseScript until compilation has completed
+ // successfully.)
+ // This is copied to ScriptStencil.
+ // Any update after the copy should be synced to the ScriptStencil.
+ mozilla::Maybe<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.
+ TaggedParserAtomIndex atom_;
+
+ // Index into CompilationStencil::scriptData.
+ ScriptIndex funcDataIndex_ = ScriptIndex(-1);
+
+ // See: FunctionFlags
+ // This is copied to ScriptStencil.
+ // Any update after the copy should be synced to the ScriptStencil.
+ FunctionFlags flags_ = {};
+
+ // See: ImmutableScriptData::funLength
+ uint16_t length_ = 0;
+
+ // JSFunction::nargs_
+ // This field is copied to ScriptStencil, and shouldn't be modified after the
+ // copy.
+ uint16_t nargs_ = 0;
+
+ // See: PrivateScriptData::memberInitializers_
+ // This field is copied to ScriptStencil, and shouldn't be modified after the
+ // copy.
+ MemberInitializers memberInitializers_ = MemberInitializers::Invalid();
+
+ public:
+ // Back pointer used by asm.js for error messages.
+ FunctionNode* functionNode = nullptr;
+
+ // True if bytecode will be emitted for this function in the current
+ // compilation.
+ bool emitBytecode : 1;
+
+ // This is set by the BytecodeEmitter of the enclosing script when a reference
+ // to this function is generated. This is also used to determine a hoisted
+ // function already is referenced by the bytecode.
+ bool wasEmittedByEnclosingScript_ : 1;
+
+ // Need to emit a synthesized Annex B assignment
+ bool isAnnexB : 1;
+
+ // Track if we saw "use asm".
+ // If we successfully validated it, `flags_` is seto to `AsmJS` kind.
+ bool useAsm : 1;
+
+ // Analysis of parameter list
+ bool hasParameterExprs : 1;
+ bool hasDestructuringArgs : 1;
+ bool hasDuplicateParameters : 1;
+
+ // Arrow function with expression body like: `() => 1`.
+ bool hasExprBody_ : 1;
+
+ // Used to issue an early error in static class blocks.
+ bool allowReturn_ : 1;
+
+ // Tracks if function-related fields are already copied to ScriptStencil.
+ // If this field is true, modification to those fields should be synced with
+ // ScriptStencil by copyUpdated* methods.
+ bool isFunctionFieldCopiedToStencil : 1;
+
+ // True if this is part of initial compilation.
+ // False if this is part of delazification.
+ bool isInitialCompilation : 1;
+
+ // True if this is standalone function
+ // (new Function() including generator/async, or event handler).
+ bool isStandalone : 1;
+
+ // End of fields.
+
+ FunctionBox(FrontendContext* fc, SourceExtent extent,
+ CompilationState& compilationState, Directives directives,
+ GeneratorKind generatorKind, FunctionAsyncKind asyncKind,
+ bool isInitialCompilation, TaggedParserAtomIndex atom,
+ FunctionFlags flags, ScriptIndex index);
+
+ ScriptStencil& functionStencil() const;
+ ScriptStencilExtra& functionExtraStencil() const;
+
+ LexicalScope::ParserData* namedLambdaBindings() {
+ return namedLambdaBindings_;
+ }
+ void setNamedLambdaBindings(LexicalScope::ParserData* bindings) {
+ namedLambdaBindings_ = bindings;
+ }
+
+ FunctionScope::ParserData* functionScopeBindings() {
+ return functionScopeBindings_;
+ }
+ void setFunctionScopeBindings(FunctionScope::ParserData* bindings) {
+ functionScopeBindings_ = bindings;
+ }
+
+ VarScope::ParserData* extraVarScopeBindings() {
+ return extraVarScopeBindings_;
+ }
+ void setExtraVarScopeBindings(VarScope::ParserData* bindings) {
+ extraVarScopeBindings_ = bindings;
+ }
+
+ void initFromLazyFunction(const ScriptStencilExtra& extra,
+ ScopeContext& scopeContext,
+ FunctionSyntaxKind kind);
+ void initFromScriptStencilExtra(const ScriptStencilExtra& extra);
+ void initStandalone(ScopeContext& scopeContext, FunctionSyntaxKind kind);
+
+ private:
+ void initStandaloneOrLazy(ScopeContext& scopeContext,
+ FunctionSyntaxKind kind);
+
+ public:
+ void initWithEnclosingParseContext(ParseContext* enclosing,
+ FunctionSyntaxKind kind);
+
+ void setEnclosingScopeForInnerLazyFunction(ScopeIndex scopeIndex);
+
+ bool wasEmittedByEnclosingScript() const {
+ return wasEmittedByEnclosingScript_;
+ }
+ void setWasEmittedByEnclosingScript(bool wasEmitted) {
+ wasEmittedByEnclosingScript_ = wasEmitted;
+ if (isFunctionFieldCopiedToStencil) {
+ copyUpdatedWasEmitted();
+ }
+ }
+
+ [[nodiscard]] bool setAsmJSModule(const JS::WasmModule* module);
+ bool isAsmJSModule() const { return flags_.isAsmJSNative(); }
+
+ bool hasEnclosingScopeIndex() const { return enclosingScopeIndex_.isSome(); }
+ ScopeIndex getEnclosingScopeIndex() const { return *enclosingScopeIndex_; }
+
+ IMMUTABLE_FLAG_GETTER_SETTER(isAsync, IsAsync)
+ IMMUTABLE_FLAG_GETTER_SETTER(isGenerator, IsGenerator)
+ IMMUTABLE_FLAG_GETTER_SETTER(funHasExtensibleScope, FunHasExtensibleScope)
+ IMMUTABLE_FLAG_GETTER_SETTER(functionHasThisBinding, FunctionHasThisBinding)
+ IMMUTABLE_FLAG_GETTER_SETTER(functionHasNewTargetBinding,
+ FunctionHasNewTargetBinding)
+ // NeedsHomeObject: custom logic below.
+ // IsDerivedClassConstructor: custom logic below.
+ // IsFieldInitializer: custom logic below.
+ IMMUTABLE_FLAG_GETTER(useMemberInitializers, UseMemberInitializers)
+ IMMUTABLE_FLAG_GETTER_SETTER(hasRest, HasRest)
+ IMMUTABLE_FLAG_GETTER_SETTER(needsFunctionEnvironmentObjects,
+ NeedsFunctionEnvironmentObjects)
+ IMMUTABLE_FLAG_GETTER_SETTER(functionHasExtraBodyVarScope,
+ FunctionHasExtraBodyVarScope)
+ IMMUTABLE_FLAG_GETTER_SETTER(shouldDeclareArguments, ShouldDeclareArguments)
+ IMMUTABLE_FLAG_GETTER_SETTER(needsArgsObj, NeedsArgsObj)
+ // HasMappedArgsObj: custom logic below.
+
+ bool needsCallObjectRegardlessOfBindings() const {
+ // Always create a CallObject if:
+ // - The scope is extensible at runtime due to sloppy eval.
+ // - The function is a generator or async function. (The debugger reads the
+ // generator object directly from the frame.)
+
+ return funHasExtensibleScope() || isGenerator() || isAsync();
+ }
+
+ bool needsExtraBodyVarEnvironmentRegardlessOfBindings() const {
+ MOZ_ASSERT(hasParameterExprs);
+ return funHasExtensibleScope();
+ }
+
+ GeneratorKind generatorKind() const {
+ return isGenerator() ? GeneratorKind::Generator
+ : GeneratorKind::NotGenerator;
+ }
+
+ FunctionAsyncKind asyncKind() const {
+ return isAsync() ? FunctionAsyncKind::AsyncFunction
+ : FunctionAsyncKind::SyncFunction;
+ }
+
+ bool needsFinalYield() const { return isGenerator() || isAsync(); }
+ bool needsDotGeneratorName() const { return isGenerator() || isAsync(); }
+ bool needsClearSlotsOnExit() const { return isGenerator() || isAsync(); }
+ bool needsIteratorResult() const { return isGenerator() && !isAsync(); }
+ bool needsPromiseResult() const { return isAsync() && !isGenerator(); }
+
+ bool isArrow() const { return flags_.isArrow(); }
+ bool isLambda() const { return flags_.isLambda(); }
+
+ bool hasExprBody() const { return hasExprBody_; }
+ void setHasExprBody() {
+ MOZ_ASSERT(isArrow());
+ hasExprBody_ = true;
+ }
+
+ bool allowReturn() const { return allowReturn_; }
+
+ bool isNamedLambda() const { return flags_.isNamedLambda(!!explicitName()); }
+ bool isGetter() const { return flags_.isGetter(); }
+ bool isSetter() const { return flags_.isSetter(); }
+ bool isMethod() const { return flags_.isMethod(); }
+ bool isClassConstructor() const { return flags_.isClassConstructor(); }
+
+ bool isInterpreted() const { return flags_.hasBaseScript(); }
+
+ FunctionFlags::FunctionKind kind() { return flags_.kind(); }
+
+ bool hasInferredName() const { return flags_.hasInferredName(); }
+ bool hasGuessedAtom() const { return flags_.hasGuessedAtom(); }
+
+ TaggedParserAtomIndex displayAtom() const { return atom_; }
+ TaggedParserAtomIndex explicitName() const {
+ return (hasInferredName() || hasGuessedAtom())
+ ? TaggedParserAtomIndex::null()
+ : atom_;
+ }
+
+ // NOTE: We propagate to any existing functions for now. This handles both the
+ // delazification case where functions already exist, and also handles
+ // code-coverage which is not yet deferred.
+ void setInferredName(TaggedParserAtomIndex atom) {
+ atom_ = atom;
+ flags_.setInferredName();
+ if (isFunctionFieldCopiedToStencil) {
+ copyUpdatedAtomAndFlags();
+ }
+ }
+ void setGuessedAtom(TaggedParserAtomIndex atom) {
+ atom_ = atom;
+ flags_.setGuessedAtom();
+ if (isFunctionFieldCopiedToStencil) {
+ copyUpdatedAtomAndFlags();
+ }
+ }
+
+ bool needsHomeObject() const {
+ return hasFlag(ImmutableFlags::NeedsHomeObject);
+ }
+ void setNeedsHomeObject() {
+ MOZ_ASSERT(flags_.allowSuperProperty());
+ setFlag(ImmutableFlags::NeedsHomeObject);
+ flags_.setIsExtended();
+ }
+
+ bool isDerivedClassConstructor() const {
+ return hasFlag(ImmutableFlags::IsDerivedClassConstructor);
+ }
+ void setDerivedClassConstructor() {
+ MOZ_ASSERT(flags_.isClassConstructor());
+ setFlag(ImmutableFlags::IsDerivedClassConstructor);
+ }
+
+ bool isSyntheticFunction() const {
+ return hasFlag(ImmutableFlags::IsSyntheticFunction);
+ }
+ void setSyntheticFunction() {
+ // Field initializer, class constructor or getter or setter
+ // synthesized from accessor keyword.
+ MOZ_ASSERT(flags_.isMethod() || flags_.isGetter() || flags_.isSetter());
+ setFlag(ImmutableFlags::IsSyntheticFunction);
+ }
+
+ bool hasSimpleParameterList() const {
+ return !hasRest() && !hasParameterExprs && !hasDestructuringArgs;
+ }
+
+ bool hasMappedArgsObj() const {
+ return !strict() && hasSimpleParameterList();
+ }
+
+ // Return whether this or an enclosing function is being parsed and
+ // validated as asm.js. Note: if asm.js validation fails, this will be false
+ // while the function is being reparsed. This flag can be used to disable
+ // certain parsing features that are necessary in general, but unnecessary
+ // for validated asm.js.
+ bool useAsmOrInsideUseAsm() const { return useAsm; }
+
+ void setStart(uint32_t offset, uint32_t line, uint32_t column) {
+ MOZ_ASSERT(!isScriptExtraFieldCopiedToStencil);
+ extent_.sourceStart = offset;
+ extent_.lineno = line;
+ extent_.column = column;
+ }
+
+ void setEnd(uint32_t end) {
+ MOZ_ASSERT(!isScriptExtraFieldCopiedToStencil);
+ // For all functions except class constructors, the buffer and
+ // toString ending positions are the same. Class constructors override
+ // the toString ending position with the end of the class definition.
+ extent_.sourceEnd = end;
+ extent_.toStringEnd = end;
+ }
+
+ void setCtorToStringEnd(uint32_t end) {
+ extent_.toStringEnd = end;
+ if (isScriptExtraFieldCopiedToStencil) {
+ copyUpdatedExtent();
+ }
+ }
+
+ void setCtorFunctionHasThisBinding() {
+ immutableFlags_.setFlag(ImmutableFlags::FunctionHasThisBinding, true);
+ if (isScriptExtraFieldCopiedToStencil) {
+ copyUpdatedImmutableFlags();
+ }
+ }
+
+ void setIsInlinableLargeFunction() {
+ immutableFlags_.setFlag(ImmutableFlags::IsInlinableLargeFunction, true);
+ if (isScriptExtraFieldCopiedToStencil) {
+ copyUpdatedImmutableFlags();
+ }
+ }
+
+ void setUsesArgumentsIntrinsics() {
+ immutableFlags_.setFlag(ImmutableFlags::UsesArgumentsIntrinsics, true);
+ if (isScriptExtraFieldCopiedToStencil) {
+ copyUpdatedImmutableFlags();
+ }
+ }
+
+ uint16_t length() { return length_; }
+ void setLength(uint16_t length) { length_ = length; }
+
+ void setArgCount(uint16_t args) {
+ MOZ_ASSERT(!isFunctionFieldCopiedToStencil);
+ nargs_ = args;
+ }
+
+ size_t nargs() { return nargs_; }
+
+ const MemberInitializers& memberInitializers() const {
+ MOZ_ASSERT(useMemberInitializers());
+ return memberInitializers_;
+ }
+ void setMemberInitializers(MemberInitializers memberInitializers) {
+ immutableFlags_.setFlag(ImmutableFlags::UseMemberInitializers, true);
+ memberInitializers_ = memberInitializers;
+ if (isScriptExtraFieldCopiedToStencil) {
+ copyUpdatedImmutableFlags();
+ copyUpdatedMemberInitializers();
+ }
+ }
+
+ ScriptIndex index() { return funcDataIndex_; }
+
+ void finishScriptFlags();
+ void copyFunctionFields(ScriptStencil& script);
+ void copyFunctionExtraFields(ScriptStencilExtra& scriptExtra);
+
+ // * setCtorFunctionHasThisBinding can be called to a class constructor
+ // with a lazy function, while parsing enclosing class
+ // * setIsInlinableLargeFunction can be called by BCE to update flags of the
+ // previous top-level function, but only in self-hosted mode.
+ void copyUpdatedImmutableFlags();
+
+ // * setCtorToStringEnd bcan be called to a class constructor with a lazy
+ // function, while parsing enclosing class
+ void copyUpdatedExtent();
+
+ // * setMemberInitializers can be called to a class constructor with a lazy
+ // function, while emitting enclosing script
+ void copyUpdatedMemberInitializers();
+
+ // * setEnclosingScopeForInnerLazyFunction can be called to a lazy function,
+ // while emitting enclosing script
+ void copyUpdatedEnclosingScopeIndex();
+
+ // * setInferredName can be called to a lazy function, while emitting
+ // enclosing script
+ // * setGuessedAtom can be called to both lazy/non-lazy functions,
+ // while running NameFunctions
+ void copyUpdatedAtomAndFlags();
+
+ // * setWasEmitted can be called to a lazy function, while emitting
+ // enclosing script
+ void copyUpdatedWasEmitted();
+};
+
+#undef FLAG_GETTER_SETTER
+#undef IMMUTABLE_FLAG_GETTER_SETTER
+
+inline FunctionBox* SharedContext::asFunctionBox() {
+ MOZ_ASSERT(isFunctionBox());
+ return static_cast<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..a42ab238df
--- /dev/null
+++ b/js/src/frontend/Stencil.cpp
@@ -0,0 +1,5363 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "frontend/Stencil.h"
+
+#include "mozilla/AlreadyAddRefed.h" // already_AddRefed
+#include "mozilla/Assertions.h" // MOZ_RELEASE_ASSERT
+#include "mozilla/Maybe.h" // mozilla::Maybe
+#include "mozilla/OperatorNewExtensions.h" // mozilla::KnownNotNull
+#include "mozilla/PodOperations.h" // mozilla::PodCopy
+#include "mozilla/RefPtr.h" // RefPtr
+#include "mozilla/ScopeExit.h" // mozilla::ScopeExit
+#include "mozilla/Sprintf.h" // SprintfLiteral
+
+#include "ds/LifoAlloc.h" // LifoAlloc
+#include "frontend/AbstractScopePtr.h" // ScopeIndex
+#include "frontend/BytecodeCompilation.h" // CanLazilyParse, CompileGlobalScriptToStencil
+#include "frontend/BytecodeCompiler.h" // ParseModuleToStencil
+#include "frontend/BytecodeSection.h" // EmitScriptThingsVector
+#include "frontend/CompilationStencil.h" // CompilationStencil, CompilationState, ExtensibleCompilationStencil, CompilationGCOutput, CompilationStencilMerger
+#include "frontend/FrontendContext.h"
+#include "frontend/NameAnalysisTypes.h" // EnvironmentCoordinate
+#include "frontend/ScopeBindingCache.h" // ScopeBindingCache
+#include "frontend/SharedContext.h"
+#include "frontend/StencilXdr.h" // XDRStencilEncoder, XDRStencilDecoder
+#include "gc/AllocKind.h" // gc::AllocKind
+#include "gc/Tracer.h" // TraceNullableRoot
+#include "js/CallArgs.h" // JSNative
+#include "js/CompileOptions.h" // JS::DecodeOptions
+#include "js/experimental/JSStencil.h" // JS::Stencil
+#include "js/GCAPI.h" // JS::AutoCheckCannotGC
+#include "js/Printer.h" // js::Fprinter
+#include "js/RootingAPI.h" // Rooted
+#include "js/Transcoding.h" // JS::TranscodeBuffer
+#include "js/Value.h" // ObjectValue
+#include "js/WasmModule.h" // JS::WasmModule
+#include "vm/BigIntType.h" // ParseBigIntLiteral, BigIntLiteralIsZero
+#include "vm/BindingKind.h" // BindingKind
+#include "vm/EnvironmentObject.h"
+#include "vm/GeneratorAndAsyncKind.h" // GeneratorKind, FunctionAsyncKind
+#include "vm/JSContext.h" // JSContext
+#include "vm/JSFunction.h" // JSFunction, GetFunctionPrototype, NewFunctionWithProto
+#include "vm/JSObject.h" // JSObject, TenuredObject
+#include "vm/JSONPrinter.h" // js::JSONPrinter
+#include "vm/JSScript.h" // BaseScript, JSScript
+#include "vm/RegExpObject.h" // js::RegExpObject
+#include "vm/Scope.h" // Scope, *Scope, ScopeKind::*, ScopeKindString, ScopeIter, ScopeKindIsCatch, BindingIter, GetScopeDataTrailingNames, SizeOfParserScopeData
+#include "vm/ScopeKind.h" // ScopeKind
+#include "vm/SelfHosting.h" // SetClonedSelfHostedFunctionName
+#include "vm/StaticStrings.h"
+#include "vm/StencilEnums.h" // ImmutableScriptFlagsEnum
+#include "vm/StringType.h" // JSAtom, js::CopyChars
+#include "wasm/AsmJS.h" // InstantiateAsmJS
+
+#include "vm/EnvironmentObject-inl.h" // JSObject::enclosingEnvironment
+#include "vm/JSFunction-inl.h" // JSFunction::create
+
+using namespace js;
+using namespace js::frontend;
+
+// These 2 functions are used to write the same code with lambda using auto
+// arguments. The auto argument type is set by the Variant.match function of the
+// InputScope variant. Thus dispatching to either a Scope* or to a
+// ScopeStencilRef. This function can then be used as a way to specialize the
+// code within the lambda without duplicating the code.
+//
+// Identically, an InputName is constructed using the scope type and the
+// matching binding name type. This way, functions which are called by this
+// lambda can manipulate an InputName and do not have to be duplicated.
+//
+// for (InputScopeIter si(...); si; si++) {
+// si.scope().match([](auto& scope) {
+// for (auto bi = InputBindingIter(scope); bi; bi++) {
+// InputName name(scope, bi.name());
+// }
+// });
+// }
+static js::BindingIter InputBindingIter(Scope* ptr) {
+ return js::BindingIter(ptr);
+}
+
+static ParserBindingIter InputBindingIter(const ScopeStencilRef& ref) {
+ return ParserBindingIter(ref);
+}
+
+InputName InputScript::displayAtom() const {
+ return script_.match(
+ [](BaseScript* ptr) {
+ return InputName(ptr, ptr->function()->displayAtom());
+ },
+ [](const ScriptStencilRef& ref) {
+ return InputName(ref, ref.scriptData().functionAtom);
+ });
+}
+
+TaggedParserAtomIndex InputName::internInto(FrontendContext* fc,
+ ParserAtomsTable& parserAtoms,
+ CompilationAtomCache& atomCache) {
+ return variant_.match(
+ [&](JSAtom* ptr) -> TaggedParserAtomIndex {
+ return parserAtoms.internJSAtom(fc, atomCache, ptr);
+ },
+ [&](NameStencilRef& ref) -> TaggedParserAtomIndex {
+ return parserAtoms.internExternalParserAtomIndex(fc, ref.context_,
+ ref.atomIndex_);
+ });
+}
+
+bool InputName::isEqualTo(FrontendContext* fc, ParserAtomsTable& parserAtoms,
+ CompilationAtomCache& atomCache,
+ TaggedParserAtomIndex other,
+ JSAtom** otherCached) const {
+ return variant_.match(
+ [&](const JSAtom* ptr) -> bool {
+ if (ptr->hash() != parserAtoms.hash(other)) {
+ return false;
+ }
+
+ // JSAtom variant is used only on the main thread delazification,
+ // where JSContext is always available.
+ JSContext* cx = fc->maybeCurrentJSContext();
+ MOZ_ASSERT(cx);
+
+ if (!*otherCached) {
+ // TODO-Stencil:
+ // Here, we convert our name into a JSAtom*, and hard-crash on failure
+ // to allocate. This conversion should not be required as we should be
+ // able to iterate up snapshotted scope chains that use parser atoms.
+ //
+ // This will be fixed when the enclosing scopes are snapshotted.
+ //
+ // See bug 1690277.
+ AutoEnterOOMUnsafeRegion oomUnsafe;
+ *otherCached = parserAtoms.toJSAtom(cx, fc, other, atomCache);
+ if (!*otherCached) {
+ oomUnsafe.crash("InputName::isEqualTo");
+ }
+ } else {
+ MOZ_ASSERT(atomCache.getExistingAtomAt(cx, other) == *otherCached);
+ }
+ return ptr == *otherCached;
+ },
+ [&](const NameStencilRef& ref) -> bool {
+ return parserAtoms.isEqualToExternalParserAtomIndex(other, ref.context_,
+ ref.atomIndex_);
+ });
+}
+
+GenericAtom::GenericAtom(FrontendContext* fc, ParserAtomsTable& parserAtoms,
+ CompilationAtomCache& atomCache,
+ TaggedParserAtomIndex index)
+ : ref(EmitterName(fc, parserAtoms, atomCache, index)) {
+ hash = parserAtoms.hash(index);
+}
+
+GenericAtom::GenericAtom(const CompilationStencil& context,
+ TaggedParserAtomIndex index)
+ : ref(StencilName{context, index}) {
+ if (index.isParserAtomIndex()) {
+ ParserAtom* atom = context.parserAtomData[index.toParserAtomIndex()];
+ hash = atom->hash();
+ } else {
+ hash = index.staticOrWellKnownHash();
+ }
+}
+
+GenericAtom::GenericAtom(ScopeStencilRef& scope, TaggedParserAtomIndex index)
+ : GenericAtom(scope.context_, index) {}
+
+BindingHasher<TaggedParserAtomIndex>::Lookup::Lookup(ScopeStencilRef& scope_ref,
+ const GenericAtom& other)
+ : keyStencil(scope_ref.context_), other(other) {}
+
+bool GenericAtom::operator==(const GenericAtom& other) const {
+ return ref.match(
+ [&other](const EmitterName& name) -> bool {
+ return other.ref.match(
+ [&name](const EmitterName& other) -> bool {
+ // We never have multiple Emitter context at the same time.
+ MOZ_ASSERT(name.fc == other.fc);
+ MOZ_ASSERT(&name.parserAtoms == &other.parserAtoms);
+ MOZ_ASSERT(&name.atomCache == &other.atomCache);
+ return name.index == other.index;
+ },
+ [&name](const StencilName& other) -> bool {
+ return name.parserAtoms.isEqualToExternalParserAtomIndex(
+ name.index, other.stencil, other.index);
+ },
+ [&name](JSAtom* other) -> bool {
+ // JSAtom variant is used only on the main thread delazification,
+ // where JSContext is always available.
+ JSContext* cx = name.fc->maybeCurrentJSContext();
+ MOZ_ASSERT(cx);
+ AutoEnterOOMUnsafeRegion oomUnsafe;
+ JSAtom* namePtr = name.parserAtoms.toJSAtom(
+ cx, name.fc, name.index, name.atomCache);
+ if (!namePtr) {
+ oomUnsafe.crash("GenericAtom(EmitterName == JSAtom*)");
+ }
+ return namePtr == other;
+ });
+ },
+ [&other](const StencilName& name) -> bool {
+ return other.ref.match(
+ [&name](const EmitterName& other) -> bool {
+ return other.parserAtoms.isEqualToExternalParserAtomIndex(
+ other.index, name.stencil, name.index);
+ },
+ [&name](const StencilName& other) -> bool {
+ // Technically it is possible to have multiple stencils, but in
+ // this particular case let's assume we never encounter a case
+ // where we are comparing names from different stencils.
+ //
+ // The reason this assumption is safe today is that we are only
+ // using this in the context of a stencil-delazification, where
+ // the only StencilNames are coming from the CompilationStencil
+ // provided to CompilationInput::initFromStencil.
+ MOZ_ASSERT(&name.stencil == &other.stencil);
+ return name.index == other.index;
+ },
+ [](JSAtom* other) -> bool {
+ MOZ_CRASH("Never used.");
+ return false;
+ });
+ },
+ [&other](JSAtom* name) -> bool {
+ return other.ref.match(
+ [&name](const EmitterName& other) -> bool {
+ // JSAtom variant is used only on the main thread delazification,
+ // where JSContext is always available.
+ JSContext* cx = other.fc->maybeCurrentJSContext();
+ MOZ_ASSERT(cx);
+ AutoEnterOOMUnsafeRegion oomUnsafe;
+ JSAtom* otherPtr = other.parserAtoms.toJSAtom(
+ cx, other.fc, other.index, other.atomCache);
+ if (!otherPtr) {
+ oomUnsafe.crash("GenericAtom(JSAtom* == EmitterName)");
+ }
+ return name == otherPtr;
+ },
+ [](const StencilName& other) -> bool {
+ MOZ_CRASH("Never used.");
+ return false;
+ },
+ [&name](JSAtom* other) -> bool { return name == other; });
+ });
+}
+
+#ifdef DEBUG
+template <typename SpanT, typename VecT>
+void AssertBorrowingSpan(const SpanT& span, const VecT& vec) {
+ MOZ_ASSERT(span.size() == vec.length());
+ MOZ_ASSERT(span.data() == vec.begin());
+}
+#endif
+
+bool ScopeBindingCache::canCacheFor(Scope* ptr) {
+ MOZ_CRASH("Unexpected scope chain type: Scope*");
+}
+
+bool ScopeBindingCache::canCacheFor(ScopeStencilRef ref) {
+ MOZ_CRASH("Unexpected scope chain type: ScopeStencilRef");
+}
+
+BindingMap<JSAtom*>* ScopeBindingCache::createCacheFor(Scope* ptr) {
+ MOZ_CRASH("Unexpected scope chain type: Scope*");
+}
+
+BindingMap<JSAtom*>* ScopeBindingCache::lookupScope(Scope* ptr,
+ CacheGeneration gen) {
+ MOZ_CRASH("Unexpected scope chain type: Scope*");
+}
+
+BindingMap<TaggedParserAtomIndex>* ScopeBindingCache::createCacheFor(
+ ScopeStencilRef ref) {
+ MOZ_CRASH("Unexpected scope chain type: ScopeStencilRef");
+}
+
+BindingMap<TaggedParserAtomIndex>* ScopeBindingCache::lookupScope(
+ ScopeStencilRef ref, CacheGeneration gen) {
+ MOZ_CRASH("Unexpected scope chain type: ScopeStencilRef");
+}
+
+bool NoScopeBindingCache::canCacheFor(Scope* ptr) { return false; }
+
+bool NoScopeBindingCache::canCacheFor(ScopeStencilRef ref) { return false; }
+
+bool RuntimeScopeBindingCache::canCacheFor(Scope* ptr) { return true; }
+
+BindingMap<JSAtom*>* RuntimeScopeBindingCache::createCacheFor(Scope* ptr) {
+ BaseScopeData* dataPtr = ptr->rawData();
+ BindingMap<JSAtom*> bindingCache;
+ if (!scopeMap.putNew(dataPtr, std::move(bindingCache))) {
+ return nullptr;
+ }
+
+ return lookupScope(ptr, cacheGeneration);
+}
+
+BindingMap<JSAtom*>* RuntimeScopeBindingCache::lookupScope(
+ Scope* ptr, CacheGeneration gen) {
+ MOZ_ASSERT(gen == cacheGeneration);
+ BaseScopeData* dataPtr = ptr->rawData();
+ auto valuePtr = scopeMap.lookup(dataPtr);
+ if (!valuePtr) {
+ return nullptr;
+ }
+ return &valuePtr->value();
+}
+
+bool StencilScopeBindingCache::canCacheFor(ScopeStencilRef ref) { return true; }
+
+BindingMap<TaggedParserAtomIndex>* StencilScopeBindingCache::createCacheFor(
+ ScopeStencilRef ref) {
+#ifdef DEBUG
+ AssertBorrowingSpan(ref.context_.scopeNames, merger_.getResult().scopeNames);
+#endif
+ auto* dataPtr = ref.context_.scopeNames[ref.scopeIndex_];
+ BindingMap<TaggedParserAtomIndex> bindingCache;
+ if (!scopeMap.putNew(dataPtr, std::move(bindingCache))) {
+ return nullptr;
+ }
+
+ return lookupScope(ref, 1);
+}
+
+BindingMap<TaggedParserAtomIndex>* StencilScopeBindingCache::lookupScope(
+ ScopeStencilRef ref, CacheGeneration gen) {
+#ifdef DEBUG
+ AssertBorrowingSpan(ref.context_.scopeNames, merger_.getResult().scopeNames);
+#endif
+ auto* dataPtr = ref.context_.scopeNames[ref.scopeIndex_];
+ auto ptr = scopeMap.lookup(dataPtr);
+ if (!ptr) {
+ return nullptr;
+ }
+ return &ptr->value();
+}
+
+bool ScopeContext::init(FrontendContext* fc, CompilationInput& input,
+ ParserAtomsTable& parserAtoms,
+ ScopeBindingCache* scopeCache, InheritThis inheritThis,
+ JSObject* enclosingEnv) {
+ // Record the scopeCache to be used while looking up NameLocation bindings.
+ this->scopeCache = scopeCache;
+ scopeCacheGen = scopeCache->getCurrentGeneration();
+
+ InputScope maybeNonDefaultEnclosingScope(
+ input.maybeNonDefaultEnclosingScope());
+
+ // If this eval is in response to Debugger.Frame.eval, we may have an
+ // incomplete scope chain. In order to provide a better debugging experience,
+ // we inspect the (optional) environment chain to determine it's enclosing
+ // FunctionScope if there is one. If there is no such scope, we use the
+ // orignal scope provided.
+ //
+ // NOTE: This is used to compute the ThisBinding kind and to allow access to
+ // private fields and methods, while other contextual information only
+ // uses the actual scope passed to the compile.
+ auto effectiveScope =
+ determineEffectiveScope(maybeNonDefaultEnclosingScope, enclosingEnv);
+
+ if (inheritThis == InheritThis::Yes) {
+ computeThisBinding(effectiveScope);
+ computeThisEnvironment(maybeNonDefaultEnclosingScope);
+ }
+ computeInScope(maybeNonDefaultEnclosingScope);
+
+ cacheEnclosingScope(input.enclosingScope);
+
+ if (input.target == CompilationInput::CompilationTarget::Eval) {
+ if (!cacheEnclosingScopeBindingForEval(fc, input, parserAtoms)) {
+ return false;
+ }
+ if (!cachePrivateFieldsForEval(fc, input, enclosingEnv, effectiveScope,
+ parserAtoms)) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+void ScopeContext::computeThisEnvironment(const InputScope& enclosingScope) {
+ uint32_t envCount = 0;
+ for (InputScopeIter si(enclosingScope); si; si++) {
+ if (si.kind() == ScopeKind::Function) {
+ // Arrow function inherit the "this" environment of the enclosing script,
+ // so continue ignore them.
+ if (!si.scope().isArrow()) {
+ allowNewTarget = true;
+
+ if (si.scope().allowSuperProperty()) {
+ allowSuperProperty = true;
+ enclosingThisEnvironmentHops = envCount;
+ }
+
+ if (si.scope().isClassConstructor()) {
+ memberInitializers =
+ si.scope().useMemberInitializers()
+ ? mozilla::Some(si.scope().getMemberInitializers())
+ : mozilla::Some(MemberInitializers::Empty());
+ MOZ_ASSERT(memberInitializers->valid);
+ } else {
+ if (si.scope().isSyntheticFunction()) {
+ allowArguments = false;
+ }
+ }
+
+ if (si.scope().isDerivedClassConstructor()) {
+ allowSuperCall = true;
+ }
+
+ // Found the effective "this" environment, so stop.
+ return;
+ }
+ }
+
+ if (si.scope().hasEnvironment()) {
+ envCount++;
+ }
+ }
+}
+
+void ScopeContext::computeThisBinding(const InputScope& scope) {
+ // Inspect the scope-chain.
+ for (InputScopeIter si(scope); si; si++) {
+ if (si.kind() == ScopeKind::Module) {
+ thisBinding = ThisBinding::Module;
+ return;
+ }
+
+ if (si.kind() == ScopeKind::Function) {
+ // Arrow functions don't have their own `this` binding.
+ if (si.scope().isArrow()) {
+ continue;
+ }
+
+ // Derived class constructors (and their nested arrow functions and evals)
+ // use ThisBinding::DerivedConstructor, which ensures TDZ checks happen
+ // when accessing |this|.
+ if (si.scope().isDerivedClassConstructor()) {
+ thisBinding = ThisBinding::DerivedConstructor;
+ } else {
+ thisBinding = ThisBinding::Function;
+ }
+
+ return;
+ }
+ }
+
+ thisBinding = ThisBinding::Global;
+}
+
+void ScopeContext::computeInScope(const InputScope& enclosingScope) {
+ for (InputScopeIter si(enclosingScope); si; si++) {
+ if (si.kind() == ScopeKind::ClassBody) {
+ inClass = true;
+ }
+
+ if (si.kind() == ScopeKind::With) {
+ inWith = true;
+ }
+ }
+}
+
+void ScopeContext::cacheEnclosingScope(const InputScope& enclosingScope) {
+ if (enclosingScope.isNull()) {
+ return;
+ }
+
+ enclosingScopeEnvironmentChainLength =
+ enclosingScope.environmentChainLength();
+ enclosingScopeKind = enclosingScope.kind();
+
+ if (enclosingScopeKind == ScopeKind::Function) {
+ enclosingScopeIsArrow = enclosingScope.isArrow();
+ }
+
+ enclosingScopeHasEnvironment = enclosingScope.hasEnvironment();
+
+#ifdef DEBUG
+ hasNonSyntacticScopeOnChain =
+ enclosingScope.hasOnChain(ScopeKind::NonSyntactic);
+
+ // This computes a general answer for the query "does the enclosing scope
+ // have a function scope that needs a home object?", but it's only asserted
+ // if the parser parses eval body that contains `super` that needs a home
+ // object.
+ for (InputScopeIter si(enclosingScope); si; si++) {
+ if (si.kind() == ScopeKind::Function) {
+ if (si.scope().isArrow()) {
+ continue;
+ }
+ if (si.scope().allowSuperProperty() && si.scope().needsHomeObject()) {
+ hasFunctionNeedsHomeObjectOnChain = true;
+ }
+ break;
+ }
+ }
+#endif
+
+ // Pre-fill the scope cache by iterating over all the names. Stop iterating
+ // as soon as we find a scope which already has a filled scope cache.
+ AutoEnterOOMUnsafeRegion oomUnsafe;
+ for (InputScopeIter si(enclosingScope); si; si++) {
+ // If the current scope already exists, then there is no need to go deeper
+ // as the scope which are encoded after this one should already be present
+ // in the cache.
+ bool hasScopeCache = si.scope().match([&](auto& scope_ref) -> bool {
+ MOZ_ASSERT(scopeCache->canCacheFor(scope_ref));
+ return scopeCache->lookupScope(scope_ref, scopeCacheGen);
+ });
+ if (hasScopeCache) {
+ return;
+ }
+
+ bool hasEnv = si.hasSyntacticEnvironment();
+ auto setCacthAll = [&](NameLocation loc) {
+ return si.scope().match([&](auto& scope_ref) {
+ using BindingMapPtr = decltype(scopeCache->createCacheFor(scope_ref));
+ BindingMapPtr bindingMapPtr = scopeCache->createCacheFor(scope_ref);
+ if (!bindingMapPtr) {
+ oomUnsafe.crash(
+ "ScopeContext::cacheEnclosingScope: scopeCache->createCacheFor");
+ return;
+ }
+
+ bindingMapPtr->catchAll.emplace(loc);
+ });
+ };
+ auto createEmpty = [&]() {
+ return si.scope().match([&](auto& scope_ref) {
+ using BindingMapPtr = decltype(scopeCache->createCacheFor(scope_ref));
+ BindingMapPtr bindingMapPtr = scopeCache->createCacheFor(scope_ref);
+ if (!bindingMapPtr) {
+ oomUnsafe.crash(
+ "ScopeContext::cacheEnclosingScope: scopeCache->createCacheFor");
+ return;
+ }
+ });
+ };
+
+ switch (si.kind()) {
+ case ScopeKind::Function:
+ if (hasEnv) {
+ if (si.scope().funHasExtensibleScope()) {
+ setCacthAll(NameLocation::Dynamic());
+ return;
+ }
+
+ si.scope().match([&](auto& scope_ref) {
+ using BindingMapPtr =
+ decltype(scopeCache->createCacheFor(scope_ref));
+ using Lookup =
+ typename std::remove_pointer_t<BindingMapPtr>::Lookup;
+ BindingMapPtr bindingMapPtr = scopeCache->createCacheFor(scope_ref);
+ if (!bindingMapPtr) {
+ oomUnsafe.crash(
+ "ScopeContext::cacheEnclosingScope: "
+ "scopeCache->createCacheFor");
+ return;
+ }
+
+ for (auto bi = InputBindingIter(scope_ref); bi; bi++) {
+ NameLocation loc = bi.nameLocation();
+ if (loc.kind() != NameLocation::Kind::EnvironmentCoordinate) {
+ continue;
+ }
+ auto ctxFreeKey = bi.name();
+ GenericAtom ctxKey(scope_ref, ctxFreeKey);
+ Lookup ctxLookup(scope_ref, ctxKey);
+ if (!bindingMapPtr->hashMap.put(ctxLookup, ctxFreeKey, loc)) {
+ oomUnsafe.crash(
+ "ScopeContext::cacheEnclosingScope: bindingMapPtr->put");
+ return;
+ }
+ }
+ });
+ } else {
+ createEmpty();
+ }
+ break;
+
+ case ScopeKind::StrictEval:
+ case ScopeKind::FunctionBodyVar:
+ case ScopeKind::Lexical:
+ case ScopeKind::NamedLambda:
+ case ScopeKind::StrictNamedLambda:
+ case ScopeKind::SimpleCatch:
+ case ScopeKind::Catch:
+ case ScopeKind::FunctionLexical:
+ case ScopeKind::ClassBody:
+ if (hasEnv) {
+ si.scope().match([&](auto& scope_ref) {
+ using BindingMapPtr =
+ decltype(scopeCache->createCacheFor(scope_ref));
+ using Lookup =
+ typename std::remove_pointer_t<BindingMapPtr>::Lookup;
+ BindingMapPtr bindingMapPtr = scopeCache->createCacheFor(scope_ref);
+ if (!bindingMapPtr) {
+ oomUnsafe.crash(
+ "ScopeContext::cacheEnclosingScope: "
+ "scopeCache->createCacheFor");
+ return;
+ }
+
+ for (auto bi = InputBindingIter(scope_ref); bi; bi++) {
+ NameLocation loc = bi.nameLocation();
+ if (loc.kind() != NameLocation::Kind::EnvironmentCoordinate) {
+ continue;
+ }
+ auto ctxFreeKey = bi.name();
+ GenericAtom ctxKey(scope_ref, ctxFreeKey);
+ Lookup ctxLookup(scope_ref, ctxKey);
+ if (!bindingMapPtr->hashMap.putNew(ctxLookup, ctxFreeKey, loc)) {
+ oomUnsafe.crash(
+ "ScopeContext::cacheEnclosingScope: bindingMapPtr->put");
+ return;
+ }
+ }
+ });
+ } else {
+ createEmpty();
+ }
+ break;
+
+ case ScopeKind::Module:
+ // This case is used only when delazifying a function inside
+ // module.
+ // Initial compilation of module doesn't have enlcosing scope.
+ if (hasEnv) {
+ si.scope().match([&](auto& scope_ref) {
+ using BindingMapPtr =
+ decltype(scopeCache->createCacheFor(scope_ref));
+ using Lookup =
+ typename std::remove_pointer_t<BindingMapPtr>::Lookup;
+ BindingMapPtr bindingMapPtr = scopeCache->createCacheFor(scope_ref);
+ if (!bindingMapPtr) {
+ oomUnsafe.crash(
+ "ScopeContext::cacheEnclosingScope: "
+ "scopeCache->createCacheFor");
+ return;
+ }
+
+ for (auto bi = InputBindingIter(scope_ref); bi; bi++) {
+ // Imports are on the environment but are indirect
+ // bindings and must be accessed dynamically instead of
+ // using an EnvironmentCoordinate.
+ NameLocation loc = bi.nameLocation();
+ if (loc.kind() != NameLocation::Kind::EnvironmentCoordinate &&
+ loc.kind() != NameLocation::Kind::Import) {
+ continue;
+ }
+ auto ctxFreeKey = bi.name();
+ GenericAtom ctxKey(scope_ref, ctxFreeKey);
+ Lookup ctxLookup(scope_ref, ctxKey);
+ if (!bindingMapPtr->hashMap.putNew(ctxLookup, ctxFreeKey, loc)) {
+ oomUnsafe.crash(
+ "ScopeContext::cacheEnclosingScope: bindingMapPtr->put");
+ return;
+ }
+ }
+ });
+ } else {
+ createEmpty();
+ }
+ break;
+
+ case ScopeKind::Eval:
+ // As an optimization, if the eval doesn't have its own var
+ // environment and its immediate enclosing scope is a global
+ // scope, all accesses are global.
+ if (!hasEnv) {
+ ScopeKind kind = si.scope().enclosing().kind();
+ if (kind == ScopeKind::Global || kind == ScopeKind::NonSyntactic) {
+ setCacthAll(NameLocation::Global(BindingKind::Var));
+ return;
+ }
+ }
+
+ setCacthAll(NameLocation::Dynamic());
+ return;
+
+ case ScopeKind::Global:
+ setCacthAll(NameLocation::Global(BindingKind::Var));
+ return;
+
+ case ScopeKind::With:
+ case ScopeKind::NonSyntactic:
+ setCacthAll(NameLocation::Dynamic());
+ return;
+
+ case ScopeKind::WasmInstance:
+ case ScopeKind::WasmFunction:
+ MOZ_CRASH("No direct eval inside wasm functions");
+ }
+ }
+
+ MOZ_CRASH("Malformed scope chain");
+}
+
+InputScope ScopeContext::determineEffectiveScope(InputScope& scope,
+ JSObject* environment) {
+ MOZ_ASSERT(effectiveScopeHops == 0);
+ // If the scope-chain is non-syntactic, we may still determine a more precise
+ // effective-scope to use instead.
+ if (environment && scope.hasOnChain(ScopeKind::NonSyntactic)) {
+ JSObject* env = environment;
+ while (env) {
+ // Look at target of any DebugEnvironmentProxy, but be sure to use
+ // enclosingEnvironment() of the proxy itself.
+ JSObject* unwrapped = env;
+ if (env->is<DebugEnvironmentProxy>()) {
+ unwrapped = &env->as<DebugEnvironmentProxy>().environment();
+#ifdef DEBUG
+ enclosingEnvironmentIsDebugProxy_ = true;
+#endif
+ }
+
+ if (unwrapped->is<CallObject>()) {
+ JSFunction* callee = &unwrapped->as<CallObject>().callee();
+ return InputScope(callee->nonLazyScript()->bodyScope());
+ }
+
+ env = env->enclosingEnvironment();
+ effectiveScopeHops++;
+ }
+ }
+
+ return scope;
+}
+
+static uint32_t DepthOfNearestVarScopeForDirectEval(const InputScope& scope) {
+ uint32_t depth = 0;
+ if (scope.isNull()) {
+ return depth;
+ }
+ for (InputScopeIter si(scope); si; si++) {
+ depth++;
+ switch (si.scope().kind()) {
+ case ScopeKind::Function:
+ case ScopeKind::FunctionBodyVar:
+ case ScopeKind::Global:
+ case ScopeKind::NonSyntactic:
+ return depth;
+ default:
+ break;
+ }
+ }
+ return depth;
+}
+
+bool ScopeContext::cacheEnclosingScopeBindingForEval(
+ FrontendContext* fc, CompilationInput& input,
+ ParserAtomsTable& parserAtoms) {
+ enclosingLexicalBindingCache_.emplace();
+
+ uint32_t varScopeDepth =
+ DepthOfNearestVarScopeForDirectEval(input.enclosingScope);
+ uint32_t depth = 0;
+ for (InputScopeIter si(input.enclosingScope); si; si++) {
+ bool success = si.scope().match([&](auto& scope_ref) {
+ for (auto bi = InputBindingIter(scope_ref); bi; bi++) {
+ switch (bi.kind()) {
+ case BindingKind::Let: {
+ // Annex B.3.5 allows redeclaring simple (non-destructured)
+ // catch parameters with var declarations.
+ bool annexB35Allowance = si.kind() == ScopeKind::SimpleCatch;
+ if (!annexB35Allowance) {
+ auto kind = ScopeKindIsCatch(si.kind())
+ ? EnclosingLexicalBindingKind::CatchParameter
+ : EnclosingLexicalBindingKind::Let;
+ InputName binding(scope_ref, bi.name());
+ if (!addToEnclosingLexicalBindingCache(
+ fc, parserAtoms, input.atomCache, binding, kind)) {
+ return false;
+ }
+ }
+ break;
+ }
+
+ case BindingKind::Const: {
+ InputName binding(scope_ref, bi.name());
+ if (!addToEnclosingLexicalBindingCache(
+ fc, parserAtoms, input.atomCache, binding,
+ EnclosingLexicalBindingKind::Const)) {
+ return false;
+ }
+ break;
+ }
+
+ case BindingKind::Synthetic: {
+ InputName binding(scope_ref, bi.name());
+ if (!addToEnclosingLexicalBindingCache(
+ fc, parserAtoms, input.atomCache, binding,
+ EnclosingLexicalBindingKind::Synthetic)) {
+ return false;
+ }
+ break;
+ }
+
+ case BindingKind::PrivateMethod: {
+ InputName binding(scope_ref, bi.name());
+ if (!addToEnclosingLexicalBindingCache(
+ fc, parserAtoms, input.atomCache, binding,
+ EnclosingLexicalBindingKind::PrivateMethod)) {
+ return false;
+ }
+ break;
+ }
+
+ case BindingKind::Import:
+ case BindingKind::FormalParameter:
+ case BindingKind::Var:
+ case BindingKind::NamedLambdaCallee:
+ break;
+ }
+ }
+ return true;
+ });
+ if (!success) {
+ return false;
+ }
+
+ if (++depth == varScopeDepth) {
+ break;
+ }
+ }
+
+ return true;
+}
+
+bool ScopeContext::addToEnclosingLexicalBindingCache(
+ FrontendContext* fc, ParserAtomsTable& parserAtoms,
+ CompilationAtomCache& atomCache, InputName& name,
+ EnclosingLexicalBindingKind kind) {
+ TaggedParserAtomIndex parserName =
+ name.internInto(fc, parserAtoms, atomCache);
+ if (!parserName) {
+ return false;
+ }
+
+ // Same lexical binding can appear multiple times across scopes.
+ //
+ // enclosingLexicalBindingCache_ map is used for detecting conflicting
+ // `var` binding, and inner binding should be reported in the error.
+ //
+ // cacheEnclosingScopeBindingForEval iterates from inner scope, and
+ // inner-most binding is added to the map first.
+ //
+ // Do not overwrite the value with outer bindings.
+ auto p = enclosingLexicalBindingCache_->lookupForAdd(parserName);
+ if (!p) {
+ if (!enclosingLexicalBindingCache_->add(p, parserName, kind)) {
+ ReportOutOfMemory(fc);
+ return false;
+ }
+ }
+
+ return true;
+}
+
+static bool IsPrivateField(Scope*, JSAtom* atom) {
+ MOZ_ASSERT(atom->length() > 0);
+
+ JS::AutoCheckCannotGC nogc;
+ if (atom->hasLatin1Chars()) {
+ return atom->latin1Chars(nogc)[0] == '#';
+ }
+
+ return atom->twoByteChars(nogc)[0] == '#';
+}
+
+static bool IsPrivateField(ScopeStencilRef& scope, TaggedParserAtomIndex atom) {
+ if (atom.isParserAtomIndex()) {
+ const CompilationStencil& context = scope.context_;
+ ParserAtom* parserAtom = context.parserAtomData[atom.toParserAtomIndex()];
+ return parserAtom->isPrivateName();
+ }
+
+#ifdef DEBUG
+ if (atom.isWellKnownAtomId()) {
+ const auto& info = GetWellKnownAtomInfo(atom.toWellKnownAtomId());
+ // #constructor is a well-known term, but it is invalid private name.
+ MOZ_ASSERT(!(info.length > 1 && info.content[0] == '#'));
+ } else if (atom.isLength2StaticParserString()) {
+ char content[2];
+ ParserAtomsTable::getLength2Content(atom.toLength2StaticParserString(),
+ content);
+ // # character is not part of the allowed character of static strings.
+ MOZ_ASSERT(content[0] != '#');
+ }
+#endif
+
+ return false;
+}
+
+bool ScopeContext::cachePrivateFieldsForEval(FrontendContext* fc,
+ CompilationInput& input,
+ JSObject* enclosingEnvironment,
+ const InputScope& effectiveScope,
+ ParserAtomsTable& parserAtoms) {
+ effectiveScopePrivateFieldCache_.emplace();
+
+ // We compute an environment coordinate relative to the effective scope
+ // environment. In order to safely consume these environment coordinates,
+ // we re-map them to include the hops to get the to the effective scope:
+ // see EmitterScope::lookupPrivate
+ uint32_t hops = effectiveScopeHops;
+ for (InputScopeIter si(effectiveScope); si; si++) {
+ if (si.scope().kind() == ScopeKind::ClassBody) {
+ uint32_t slots = 0;
+ bool success = si.scope().match([&](auto& scope_ref) {
+ for (auto bi = InputBindingIter(scope_ref); bi; bi++) {
+ if (bi.kind() == BindingKind::PrivateMethod ||
+ (bi.kind() == BindingKind::Synthetic &&
+ IsPrivateField(scope_ref, bi.name()))) {
+ InputName binding(scope_ref, bi.name());
+ auto parserName =
+ binding.internInto(fc, parserAtoms, input.atomCache);
+ if (!parserName) {
+ return false;
+ }
+
+ NameLocation loc = NameLocation::DebugEnvironmentCoordinate(
+ bi.kind(), hops, slots);
+
+ if (!effectiveScopePrivateFieldCache_->put(parserName, loc)) {
+ ReportOutOfMemory(fc);
+ return false;
+ }
+ }
+ slots++;
+ }
+ return true;
+ });
+ if (!success) {
+ return false;
+ }
+ }
+
+ // Hops is only consumed by GetAliasedDebugVar, which uses this to
+ // traverse the debug environment chain. See the [SMDOC] for Debug
+ // Environment Chain, which explains why we don't check for
+ // isEnvironment when computing hops here (basically, debug proxies
+ // pretend all scopes have environments, even if they were actually
+ // optimized out).
+ hops++;
+ }
+
+ return true;
+}
+
+#ifdef DEBUG
+static bool NameIsOnEnvironment(FrontendContext* fc,
+ ParserAtomsTable& parserAtoms,
+ CompilationAtomCache& atomCache,
+ InputScope& scope, TaggedParserAtomIndex name) {
+ JSAtom* jsname = nullptr;
+ return scope.match([&](auto& scope_ref) {
+ for (auto bi = InputBindingIter(scope_ref); bi; bi++) {
+ // If found, the name must already be on the environment or an import,
+ // or else there is a bug in the closed-over name analysis in the
+ // Parser.
+ InputName binding(scope_ref, bi.name());
+ if (binding.isEqualTo(fc, parserAtoms, atomCache, name, &jsname)) {
+ BindingLocation::Kind kind = bi.location().kind();
+
+ if (bi.hasArgumentSlot()) {
+ // The following is equivalent to
+ // functionScope.script()->functionAllowsParameterRedeclaration()
+ if (scope.hasMappedArgsObj()) {
+ // Check for duplicate positional formal parameters.
+ using InputBindingIter = decltype(bi);
+ for (InputBindingIter bi2(bi); bi2 && bi2.hasArgumentSlot();
+ bi2++) {
+ InputName binding2(scope_ref, bi2.name());
+ if (binding2.isEqualTo(fc, parserAtoms, atomCache, name,
+ &jsname)) {
+ kind = bi2.location().kind();
+ }
+ }
+ }
+ }
+
+ return kind == BindingLocation::Kind::Global ||
+ kind == BindingLocation::Kind::Environment ||
+ kind == BindingLocation::Kind::Import;
+ }
+ }
+
+ // If not found, assume it's on the global or dynamically accessed.
+ return true;
+ });
+}
+#endif
+
+NameLocation ScopeContext::searchInEnclosingScope(FrontendContext* fc,
+ CompilationInput& input,
+ ParserAtomsTable& parserAtoms,
+ TaggedParserAtomIndex name) {
+ MOZ_ASSERT(input.target ==
+ CompilationInput::CompilationTarget::Delazification ||
+ input.target == CompilationInput::CompilationTarget::Eval);
+
+ MOZ_ASSERT(scopeCache);
+ if (scopeCacheGen != scopeCache->getCurrentGeneration()) {
+ return searchInEnclosingScopeNoCache(fc, input, parserAtoms, name);
+ }
+
+#ifdef DEBUG
+ // Catch assertion failures in the NoCache variant before looking at the
+ // cached content.
+ NameLocation expect =
+ searchInEnclosingScopeNoCache(fc, input, parserAtoms, name);
+#endif
+
+ NameLocation found =
+ searchInEnclosingScopeWithCache(fc, input, parserAtoms, name);
+ MOZ_ASSERT(expect == found);
+ return found;
+}
+
+NameLocation ScopeContext::searchInEnclosingScopeWithCache(
+ FrontendContext* fc, CompilationInput& input, ParserAtomsTable& parserAtoms,
+ TaggedParserAtomIndex name) {
+ MOZ_ASSERT(input.target ==
+ CompilationInput::CompilationTarget::Delazification ||
+ input.target == CompilationInput::CompilationTarget::Eval);
+
+ // Generic atom of the looked up name.
+ GenericAtom genName(fc, parserAtoms, input.atomCache, name);
+ mozilla::Maybe<NameLocation> found;
+
+ // Number of enclosing scope we walked over.
+ uint8_t hops = 0;
+
+ for (InputScopeIter si(input.enclosingScope); si; si++) {
+ MOZ_ASSERT(NameIsOnEnvironment(fc, parserAtoms, input.atomCache, si.scope(),
+ name));
+
+ // If the result happens to be in the cached content of the scope that we
+ // are iterating over, then return it.
+ si.scope().match([&](auto& scope_ref) {
+ using BindingMapPtr =
+ decltype(scopeCache->lookupScope(scope_ref, scopeCacheGen));
+ BindingMapPtr bindingMapPtr =
+ scopeCache->lookupScope(scope_ref, scopeCacheGen);
+ MOZ_ASSERT(bindingMapPtr);
+
+ auto& bindingMap = *bindingMapPtr;
+ if (bindingMap.catchAll.isSome()) {
+ found = bindingMap.catchAll;
+ return;
+ }
+
+ // The scope_ref is given as argument to know where to lookup the key
+ // index of the hash table if the names have to be compared.
+ using Lookup = typename std::remove_pointer_t<BindingMapPtr>::Lookup;
+ Lookup ctxName(scope_ref, genName);
+ auto ptr = bindingMap.hashMap.lookup(ctxName);
+ if (!ptr) {
+ return;
+ }
+
+ found.emplace(ptr->value());
+ });
+
+ if (found.isSome()) {
+ // Cached entries do not store the number of hops, as it might be reused
+ // by multiple inner functions, which might different number of hops.
+ found = found.map([&hops](NameLocation loc) {
+ if (loc.kind() != NameLocation::Kind::EnvironmentCoordinate) {
+ return loc;
+ }
+ return loc.addHops(hops);
+ });
+ return found.value();
+ }
+
+ bool hasEnv = si.hasSyntacticEnvironment();
+
+ if (hasEnv) {
+ MOZ_ASSERT(hops < ENVCOORD_HOPS_LIMIT - 1);
+ hops++;
+ }
+ }
+
+ MOZ_CRASH("Malformed scope chain");
+}
+
+NameLocation ScopeContext::searchInEnclosingScopeNoCache(
+ FrontendContext* fc, CompilationInput& input, ParserAtomsTable& parserAtoms,
+ TaggedParserAtomIndex name) {
+ MOZ_ASSERT(input.target ==
+ CompilationInput::CompilationTarget::Delazification ||
+ input.target == CompilationInput::CompilationTarget::Eval);
+
+ // Cached JSAtom equivalent of the TaggedParserAtomIndex `name` argument.
+ JSAtom* jsname = nullptr;
+
+ // NameLocation which contains relative locations to access `name`.
+ mozilla::Maybe<NameLocation> result;
+
+ // Number of enclosing scoep we walked over.
+ uint8_t hops = 0;
+
+ for (InputScopeIter si(input.enclosingScope); si; si++) {
+ MOZ_ASSERT(NameIsOnEnvironment(fc, parserAtoms, input.atomCache, si.scope(),
+ name));
+
+ bool hasEnv = si.hasSyntacticEnvironment();
+ switch (si.kind()) {
+ case ScopeKind::Function:
+ if (hasEnv) {
+ if (si.scope().funHasExtensibleScope()) {
+ return NameLocation::Dynamic();
+ }
+
+ si.scope().match([&](auto& scope_ref) {
+ for (auto bi = InputBindingIter(scope_ref); bi; bi++) {
+ InputName binding(scope_ref, bi.name());
+ if (!binding.isEqualTo(fc, parserAtoms, input.atomCache, name,
+ &jsname)) {
+ continue;
+ }
+
+ BindingLocation bindLoc = bi.location();
+ // hasMappedArgsObj == script.functionAllowsParameterRedeclaration
+ if (bi.hasArgumentSlot() && si.scope().hasMappedArgsObj()) {
+ // Check for duplicate positional formal parameters.
+ using InputBindingIter = decltype(bi);
+ for (InputBindingIter bi2(bi); bi2 && bi2.hasArgumentSlot();
+ bi2++) {
+ if (bi.name() == bi2.name()) {
+ bindLoc = bi2.location();
+ }
+ }
+ }
+
+ MOZ_ASSERT(bindLoc.kind() == BindingLocation::Kind::Environment);
+ result.emplace(NameLocation::EnvironmentCoordinate(
+ bi.kind(), hops, bindLoc.slot()));
+ return;
+ }
+ });
+ }
+ break;
+
+ case ScopeKind::StrictEval:
+ case ScopeKind::FunctionBodyVar:
+ case ScopeKind::Lexical:
+ case ScopeKind::NamedLambda:
+ case ScopeKind::StrictNamedLambda:
+ case ScopeKind::SimpleCatch:
+ case ScopeKind::Catch:
+ case ScopeKind::FunctionLexical:
+ case ScopeKind::ClassBody:
+ if (hasEnv) {
+ si.scope().match([&](auto& scope_ref) {
+ for (auto bi = InputBindingIter(scope_ref); bi; bi++) {
+ InputName binding(scope_ref, bi.name());
+ if (!binding.isEqualTo(fc, parserAtoms, input.atomCache, name,
+ &jsname)) {
+ continue;
+ }
+
+ // The name must already have been marked as closed
+ // over. If this assertion is hit, there is a bug in the
+ // name analysis.
+ BindingLocation bindLoc = bi.location();
+ MOZ_ASSERT(bindLoc.kind() == BindingLocation::Kind::Environment);
+ result.emplace(NameLocation::EnvironmentCoordinate(
+ bi.kind(), hops, bindLoc.slot()));
+ return;
+ }
+ });
+ }
+ break;
+
+ case ScopeKind::Module:
+ // This case is used only when delazifying a function inside
+ // module.
+ // Initial compilation of module doesn't have enlcosing scope.
+ if (hasEnv) {
+ si.scope().match([&](auto& scope_ref) {
+ for (auto bi = InputBindingIter(scope_ref); bi; bi++) {
+ InputName binding(scope_ref, bi.name());
+ if (!binding.isEqualTo(fc, parserAtoms, input.atomCache, name,
+ &jsname)) {
+ continue;
+ }
+
+ BindingLocation bindLoc = bi.location();
+
+ // Imports are on the environment but are indirect
+ // bindings and must be accessed dynamically instead of
+ // using an EnvironmentCoordinate.
+ if (bindLoc.kind() == BindingLocation::Kind::Import) {
+ MOZ_ASSERT(si.kind() == ScopeKind::Module);
+ result.emplace(NameLocation::Import());
+ return;
+ }
+
+ MOZ_ASSERT(bindLoc.kind() == BindingLocation::Kind::Environment);
+ result.emplace(NameLocation::EnvironmentCoordinate(
+ bi.kind(), hops, bindLoc.slot()));
+ return;
+ }
+ });
+ }
+ break;
+
+ case ScopeKind::Eval:
+ // As an optimization, if the eval doesn't have its own var
+ // environment and its immediate enclosing scope is a global
+ // scope, all accesses are global.
+ if (!hasEnv) {
+ ScopeKind kind = si.scope().enclosing().kind();
+ if (kind == ScopeKind::Global || kind == ScopeKind::NonSyntactic) {
+ return NameLocation::Global(BindingKind::Var);
+ }
+ }
+ return NameLocation::Dynamic();
+
+ case ScopeKind::Global:
+ return NameLocation::Global(BindingKind::Var);
+
+ case ScopeKind::With:
+ case ScopeKind::NonSyntactic:
+ return NameLocation::Dynamic();
+
+ case ScopeKind::WasmInstance:
+ case ScopeKind::WasmFunction:
+ MOZ_CRASH("No direct eval inside wasm functions");
+ }
+
+ if (result.isSome()) {
+ return result.value();
+ }
+
+ if (hasEnv) {
+ MOZ_ASSERT(hops < ENVCOORD_HOPS_LIMIT - 1);
+ hops++;
+ }
+ }
+
+ MOZ_CRASH("Malformed scope chain");
+}
+
+mozilla::Maybe<ScopeContext::EnclosingLexicalBindingKind>
+ScopeContext::lookupLexicalBindingInEnclosingScope(TaggedParserAtomIndex name) {
+ auto p = enclosingLexicalBindingCache_->lookup(name);
+ if (!p) {
+ return mozilla::Nothing();
+ }
+
+ return mozilla::Some(p->value());
+}
+
+bool ScopeContext::effectiveScopePrivateFieldCacheHas(
+ TaggedParserAtomIndex name) {
+ return effectiveScopePrivateFieldCache_->has(name);
+}
+
+mozilla::Maybe<NameLocation> ScopeContext::getPrivateFieldLocation(
+ TaggedParserAtomIndex name) {
+ // The locations returned by this method are only valid for
+ // traversing debug environments.
+ //
+ // See the comment in cachePrivateFieldsForEval
+ MOZ_ASSERT(enclosingEnvironmentIsDebugProxy_);
+ auto p = effectiveScopePrivateFieldCache_->lookup(name);
+ if (!p) {
+ return mozilla::Nothing();
+ }
+ return mozilla::Some(p->value());
+}
+
+bool CompilationInput::initScriptSource(FrontendContext* fc) {
+ source = do_AddRef(fc->getAllocator()->new_<ScriptSource>());
+ if (!source) {
+ return false;
+ }
+
+ return source->initFromOptions(fc, options);
+}
+
+bool CompilationInput::initForStandaloneFunctionInNonSyntacticScope(
+ FrontendContext* fc, Handle<Scope*> functionEnclosingScope) {
+ MOZ_ASSERT(!functionEnclosingScope->as<GlobalScope>().isSyntactic());
+
+ target = CompilationTarget::StandaloneFunctionInNonSyntacticScope;
+ if (!initScriptSource(fc)) {
+ return false;
+ }
+ enclosingScope = InputScope(functionEnclosingScope);
+ return true;
+}
+
+FunctionSyntaxKind CompilationInput::functionSyntaxKind() const {
+ if (functionFlags().isClassConstructor()) {
+ if (functionFlags().hasBaseScript() && isDerivedClassConstructor()) {
+ return FunctionSyntaxKind::DerivedClassConstructor;
+ }
+ return FunctionSyntaxKind::ClassConstructor;
+ }
+ if (functionFlags().isMethod()) {
+ if (functionFlags().hasBaseScript() && isSyntheticFunction()) {
+ // return FunctionSyntaxKind::FieldInitializer;
+ MOZ_ASSERT_UNREACHABLE(
+ "Lazy parsing of class field initializers not supported (yet)");
+ }
+ return FunctionSyntaxKind::Method;
+ }
+ if (functionFlags().isGetter()) {
+ return FunctionSyntaxKind::Getter;
+ }
+ if (functionFlags().isSetter()) {
+ return FunctionSyntaxKind::Setter;
+ }
+ if (functionFlags().isArrow()) {
+ return FunctionSyntaxKind::Arrow;
+ }
+ return FunctionSyntaxKind::Statement;
+}
+
+void InputScope::trace(JSTracer* trc) {
+ using ScopePtr = Scope*;
+ if (scope_.is<ScopePtr>()) {
+ ScopePtr* ptrAddr = &scope_.as<ScopePtr>();
+ TraceNullableRoot(trc, ptrAddr, "compilation-input-scope");
+ }
+}
+
+void InputScript::trace(JSTracer* trc) {
+ using ScriptPtr = BaseScript*;
+ if (script_.is<ScriptPtr>()) {
+ ScriptPtr* ptrAddr = &script_.as<ScriptPtr>();
+ TraceNullableRoot(trc, ptrAddr, "compilation-input-lazy");
+ }
+}
+
+void CompilationInput::trace(JSTracer* trc) {
+ atomCache.trace(trc);
+ lazy_.trace(trc);
+ enclosingScope.trace(trc);
+}
+
+bool CompilationSyntaxParseCache::init(FrontendContext* fc, LifoAlloc& alloc,
+ ParserAtomsTable& parseAtoms,
+ CompilationAtomCache& atomCache,
+ const InputScript& lazy) {
+ if (!copyFunctionInfo(fc, parseAtoms, atomCache, lazy)) {
+ return false;
+ }
+ bool success = lazy.raw().match([&](auto& ref) {
+ if (!copyScriptInfo(fc, alloc, parseAtoms, atomCache, ref)) {
+ return false;
+ }
+ if (!copyClosedOverBindings(fc, alloc, parseAtoms, atomCache, ref)) {
+ return false;
+ }
+ return true;
+ });
+ if (!success) {
+ return false;
+ }
+#ifdef DEBUG
+ isInitialized = true;
+#endif
+ return true;
+}
+
+bool CompilationSyntaxParseCache::copyFunctionInfo(
+ FrontendContext* fc, ParserAtomsTable& parseAtoms,
+ CompilationAtomCache& atomCache, const InputScript& lazy) {
+ InputName name = lazy.displayAtom();
+ if (!name.isNull()) {
+ displayAtom_ = name.internInto(fc, parseAtoms, atomCache);
+ if (!displayAtom_) {
+ return false;
+ }
+ }
+
+ funExtra_.immutableFlags = lazy.immutableFlags();
+ funExtra_.extent = lazy.extent();
+ if (funExtra_.useMemberInitializers()) {
+ funExtra_.setMemberInitializers(lazy.getMemberInitializers());
+ }
+
+ return true;
+}
+
+bool CompilationSyntaxParseCache::copyScriptInfo(
+ FrontendContext* fc, LifoAlloc& alloc, ParserAtomsTable& parseAtoms,
+ CompilationAtomCache& atomCache, BaseScript* lazy) {
+ using GCThingsSpan = mozilla::Span<TaggedScriptThingIndex>;
+ using ScriptDataSpan = mozilla::Span<ScriptStencil>;
+ using ScriptExtraSpan = mozilla::Span<ScriptStencilExtra>;
+ cachedGCThings_ = GCThingsSpan(nullptr);
+ cachedScriptData_ = ScriptDataSpan(nullptr);
+ cachedScriptExtra_ = ScriptExtraSpan(nullptr);
+
+ auto gcthings = lazy->gcthings();
+ size_t length = gcthings.Length();
+ if (length == 0) {
+ return true;
+ }
+
+ // Reduce the length to the first element which is not a function.
+ for (size_t i = 0; i < length; i++) {
+ gc::Cell* cell = gcthings[i].asCell();
+ if (!cell || !cell->is<JSObject>()) {
+ length = i;
+ break;
+ }
+ MOZ_ASSERT(cell->as<JSObject>()->is<JSFunction>());
+ }
+
+ TaggedScriptThingIndex* gcThingsData =
+ alloc.newArrayUninitialized<TaggedScriptThingIndex>(length);
+ ScriptStencil* scriptData =
+ alloc.newArrayUninitialized<ScriptStencil>(length);
+ ScriptStencilExtra* scriptExtra =
+ alloc.newArrayUninitialized<ScriptStencilExtra>(length);
+ if (!gcThingsData || !scriptData || !scriptExtra) {
+ ReportOutOfMemory(fc);
+ return false;
+ }
+
+ for (size_t i = 0; i < length; i++) {
+ gc::Cell* cell = gcthings[i].asCell();
+ JSFunction* fun = &cell->as<JSObject>()->as<JSFunction>();
+ gcThingsData[i] = TaggedScriptThingIndex(ScriptIndex(i));
+ new (mozilla::KnownNotNull, &scriptData[i]) ScriptStencil();
+ ScriptStencil& data = scriptData[i];
+ new (mozilla::KnownNotNull, &scriptExtra[i]) ScriptStencilExtra();
+ ScriptStencilExtra& extra = scriptExtra[i];
+
+ if (fun->displayAtom()) {
+ TaggedParserAtomIndex displayAtom =
+ parseAtoms.internJSAtom(fc, atomCache, fun->displayAtom());
+ if (!displayAtom) {
+ return false;
+ }
+ data.functionAtom = displayAtom;
+ }
+ data.functionFlags = fun->flags();
+
+ BaseScript* lazy = fun->baseScript();
+ extra.immutableFlags = lazy->immutableFlags();
+ extra.extent = lazy->extent();
+
+ // Info derived from parent compilation should not be set yet for our inner
+ // lazy functions. Instead that info will be updated when we finish our
+ // compilation.
+ MOZ_ASSERT(lazy->hasEnclosingScript());
+ }
+
+ cachedGCThings_ = GCThingsSpan(gcThingsData, length);
+ cachedScriptData_ = ScriptDataSpan(scriptData, length);
+ cachedScriptExtra_ = ScriptExtraSpan(scriptExtra, length);
+ return true;
+}
+
+bool CompilationSyntaxParseCache::copyScriptInfo(
+ FrontendContext* fc, LifoAlloc& alloc, ParserAtomsTable& parseAtoms,
+ CompilationAtomCache& atomCache, const ScriptStencilRef& lazy) {
+ using GCThingsSpan = mozilla::Span<TaggedScriptThingIndex>;
+ using ScriptDataSpan = mozilla::Span<ScriptStencil>;
+ using ScriptExtraSpan = mozilla::Span<ScriptStencilExtra>;
+ cachedGCThings_ = GCThingsSpan(nullptr);
+ cachedScriptData_ = ScriptDataSpan(nullptr);
+ cachedScriptExtra_ = ScriptExtraSpan(nullptr);
+
+ size_t offset = lazy.scriptData().gcThingsOffset.index;
+ size_t length = lazy.scriptData().gcThingsLength;
+ if (length == 0) {
+ return true;
+ }
+
+ // Reduce the length to the first element which is not a function.
+ for (size_t i = offset; i < offset + length; i++) {
+ if (!lazy.context_.gcThingData[i].isFunction()) {
+ length = i - offset;
+ break;
+ }
+ }
+
+ TaggedScriptThingIndex* gcThingsData =
+ alloc.newArrayUninitialized<TaggedScriptThingIndex>(length);
+ ScriptStencil* scriptData =
+ alloc.newArrayUninitialized<ScriptStencil>(length);
+ ScriptStencilExtra* scriptExtra =
+ alloc.newArrayUninitialized<ScriptStencilExtra>(length);
+ if (!gcThingsData || !scriptData || !scriptExtra) {
+ ReportOutOfMemory(fc);
+ return false;
+ }
+
+ for (size_t i = 0; i < length; i++) {
+ ScriptStencilRef inner{lazy.context_,
+ lazy.context_.gcThingData[i + offset].toFunction()};
+ gcThingsData[i] = TaggedScriptThingIndex(ScriptIndex(i));
+ new (mozilla::KnownNotNull, &scriptData[i]) ScriptStencil();
+ ScriptStencil& data = scriptData[i];
+ ScriptStencilExtra& extra = scriptExtra[i];
+
+ InputName name{inner, inner.scriptData().functionAtom};
+ if (!name.isNull()) {
+ auto displayAtom = name.internInto(fc, parseAtoms, atomCache);
+ if (!displayAtom) {
+ return false;
+ }
+ data.functionAtom = displayAtom;
+ }
+ data.functionFlags = inner.scriptData().functionFlags;
+
+ extra = inner.scriptExtra();
+ }
+
+ cachedGCThings_ = GCThingsSpan(gcThingsData, length);
+ cachedScriptData_ = ScriptDataSpan(scriptData, length);
+ cachedScriptExtra_ = ScriptExtraSpan(scriptExtra, length);
+ return true;
+}
+
+bool CompilationSyntaxParseCache::copyClosedOverBindings(
+ FrontendContext* fc, LifoAlloc& alloc, ParserAtomsTable& parseAtoms,
+ CompilationAtomCache& atomCache, BaseScript* lazy) {
+ using ClosedOverBindingsSpan = mozilla::Span<TaggedParserAtomIndex>;
+ closedOverBindings_ = ClosedOverBindingsSpan(nullptr);
+
+ // The gcthings() array contains the inner function list followed by the
+ // closed-over bindings data. Skip the inner function list, as it is already
+ // cached in cachedGCThings_. See also: BaseScript::CreateLazy.
+ size_t start = cachedGCThings_.Length();
+ auto gcthings = lazy->gcthings();
+ size_t length = gcthings.Length();
+ MOZ_ASSERT(start <= length);
+ if (length - start == 0) {
+ return true;
+ }
+
+ TaggedParserAtomIndex* closedOverBindings =
+ alloc.newArrayUninitialized<TaggedParserAtomIndex>(length - start);
+ if (!closedOverBindings) {
+ ReportOutOfMemory(fc);
+ return false;
+ }
+
+ for (size_t i = start; i < length; i++) {
+ gc::Cell* cell = gcthings[i].asCell();
+ if (!cell) {
+ closedOverBindings[i - start] = TaggedParserAtomIndex::null();
+ continue;
+ }
+
+ MOZ_ASSERT(cell->as<JSString>()->isAtom());
+
+ auto name = static_cast<JSAtom*>(cell);
+ auto parserAtom = parseAtoms.internJSAtom(fc, atomCache, name);
+ if (!parserAtom) {
+ return false;
+ }
+
+ closedOverBindings[i - start] = parserAtom;
+ }
+
+ closedOverBindings_ =
+ ClosedOverBindingsSpan(closedOverBindings, length - start);
+ return true;
+}
+
+bool CompilationSyntaxParseCache::copyClosedOverBindings(
+ FrontendContext* fc, LifoAlloc& alloc, ParserAtomsTable& parseAtoms,
+ CompilationAtomCache& atomCache, const ScriptStencilRef& lazy) {
+ using ClosedOverBindingsSpan = mozilla::Span<TaggedParserAtomIndex>;
+ closedOverBindings_ = ClosedOverBindingsSpan(nullptr);
+
+ // The gcthings array contains the inner function list followed by the
+ // closed-over bindings data. Skip the inner function list, as it is already
+ // cached in cachedGCThings_. See also: BaseScript::CreateLazy.
+ size_t offset = lazy.scriptData().gcThingsOffset.index;
+ size_t length = lazy.scriptData().gcThingsLength;
+ size_t start = cachedGCThings_.Length();
+ MOZ_ASSERT(start <= length);
+ if (length - start == 0) {
+ return true;
+ }
+ length -= start;
+ start += offset;
+
+ // Atoms from the lazy.context (CompilationStencil) are not registered in the
+ // the parseAtoms table. Thus we create a new span which will contain all the
+ // interned atoms.
+ TaggedParserAtomIndex* closedOverBindings =
+ alloc.newArrayUninitialized<TaggedParserAtomIndex>(length);
+ if (!closedOverBindings) {
+ ReportOutOfMemory(fc);
+ return false;
+ }
+
+ for (size_t i = 0; i < length; i++) {
+ auto gcThing = lazy.context_.gcThingData[i + start];
+ if (gcThing.isNull()) {
+ closedOverBindings[i] = TaggedParserAtomIndex::null();
+ continue;
+ }
+
+ MOZ_ASSERT(gcThing.isAtom());
+ InputName name(lazy, gcThing.toAtom());
+ auto parserAtom = name.internInto(fc, parseAtoms, atomCache);
+ if (!parserAtom) {
+ return false;
+ }
+
+ closedOverBindings[i] = parserAtom;
+ }
+
+ closedOverBindings_ = ClosedOverBindingsSpan(closedOverBindings, length);
+ return true;
+}
+
+void CompilationAtomCache::trace(JSTracer* trc) { atoms_.trace(trc); }
+
+void CompilationGCOutput::trace(JSTracer* trc) {
+ TraceNullableRoot(trc, &script, "compilation-gc-output-script");
+ TraceNullableRoot(trc, &module, "compilation-gc-output-module");
+ TraceNullableRoot(trc, &sourceObject, "compilation-gc-output-source");
+ functions.trace(trc);
+ scopes.trace(trc);
+}
+
+void JS::InstantiationStorage::trace(JSTracer* trc) {
+ if (gcOutput_) {
+ gcOutput_->trace(trc);
+ }
+}
+
+RegExpObject* RegExpStencil::createRegExp(
+ JSContext* cx, const CompilationAtomCache& atomCache) const {
+ Rooted<JSAtom*> atom(cx, atomCache.getExistingAtomAt(cx, atom_));
+ return RegExpObject::createSyntaxChecked(cx, atom, flags(), TenuredObject);
+}
+
+RegExpObject* RegExpStencil::createRegExpAndEnsureAtom(
+ JSContext* cx, FrontendContext* fc, ParserAtomsTable& parserAtoms,
+ CompilationAtomCache& atomCache) const {
+ Rooted<JSAtom*> atom(cx, parserAtoms.toJSAtom(cx, fc, atom_, atomCache));
+ if (!atom) {
+ return nullptr;
+ }
+ return RegExpObject::createSyntaxChecked(cx, atom, flags(), TenuredObject);
+}
+
+AbstractScopePtr ScopeStencil::enclosing(
+ CompilationState& compilationState) const {
+ if (hasEnclosing()) {
+ return AbstractScopePtr(compilationState, enclosing());
+ }
+
+ return AbstractScopePtr::compilationEnclosingScope(compilationState);
+}
+
+Scope* ScopeStencil::enclosingExistingScope(
+ const CompilationInput& input, const CompilationGCOutput& gcOutput) const {
+ if (hasEnclosing()) {
+ Scope* result = gcOutput.getScopeNoBaseIndex(enclosing());
+ MOZ_ASSERT(result, "Scope must already exist to use this method");
+ return result;
+ }
+
+ // When creating a scope based on the input and a gc-output, we assume that
+ // the scope stencil that we are looking at has not been merged into another
+ // stencil, and thus that we still have the compilation input of the stencil.
+ //
+ // Otherwise, if this was in the case of an input generated from a Stencil
+ // instead of live-gc values, we would not know its associated gcOutput as it
+ // might not even have one yet.
+ return input.enclosingScope.variant().as<Scope*>();
+}
+
+Scope* ScopeStencil::createScope(JSContext* cx, CompilationInput& input,
+ CompilationGCOutput& gcOutput,
+ BaseParserScopeData* baseScopeData) const {
+ Rooted<Scope*> enclosingScope(cx, enclosingExistingScope(input, gcOutput));
+ return createScope(cx, input.atomCache, enclosingScope, baseScopeData);
+}
+
+Scope* ScopeStencil::createScope(JSContext* cx, CompilationAtomCache& atomCache,
+ Handle<Scope*> enclosingScope,
+ BaseParserScopeData* baseScopeData) const {
+ switch (kind()) {
+ case ScopeKind::Function: {
+ using ScopeType = FunctionScope;
+ MOZ_ASSERT(matchScopeKind<ScopeType>(kind()));
+ return createSpecificScope<ScopeType, CallObject>(
+ cx, atomCache, enclosingScope, baseScopeData);
+ }
+ case ScopeKind::Lexical:
+ case ScopeKind::SimpleCatch:
+ case ScopeKind::Catch:
+ case ScopeKind::NamedLambda:
+ case ScopeKind::StrictNamedLambda:
+ case ScopeKind::FunctionLexical: {
+ using ScopeType = LexicalScope;
+ MOZ_ASSERT(matchScopeKind<ScopeType>(kind()));
+ return createSpecificScope<ScopeType, BlockLexicalEnvironmentObject>(
+ cx, atomCache, enclosingScope, baseScopeData);
+ }
+ case ScopeKind::ClassBody: {
+ using ScopeType = ClassBodyScope;
+ MOZ_ASSERT(matchScopeKind<ScopeType>(kind()));
+ return createSpecificScope<ScopeType, BlockLexicalEnvironmentObject>(
+ cx, atomCache, enclosingScope, baseScopeData);
+ }
+ case ScopeKind::FunctionBodyVar: {
+ using ScopeType = VarScope;
+ MOZ_ASSERT(matchScopeKind<ScopeType>(kind()));
+ return createSpecificScope<ScopeType, VarEnvironmentObject>(
+ cx, atomCache, enclosingScope, baseScopeData);
+ }
+ case ScopeKind::Global:
+ case ScopeKind::NonSyntactic: {
+ using ScopeType = GlobalScope;
+ MOZ_ASSERT(matchScopeKind<ScopeType>(kind()));
+ return createSpecificScope<ScopeType, std::nullptr_t>(
+ cx, atomCache, enclosingScope, baseScopeData);
+ }
+ case ScopeKind::Eval:
+ case ScopeKind::StrictEval: {
+ using ScopeType = EvalScope;
+ MOZ_ASSERT(matchScopeKind<ScopeType>(kind()));
+ return createSpecificScope<ScopeType, VarEnvironmentObject>(
+ cx, atomCache, enclosingScope, baseScopeData);
+ }
+ case ScopeKind::Module: {
+ using ScopeType = ModuleScope;
+ MOZ_ASSERT(matchScopeKind<ScopeType>(kind()));
+ return createSpecificScope<ScopeType, ModuleEnvironmentObject>(
+ cx, atomCache, enclosingScope, baseScopeData);
+ }
+ case ScopeKind::With: {
+ using ScopeType = WithScope;
+ MOZ_ASSERT(matchScopeKind<ScopeType>(kind()));
+ return createSpecificScope<ScopeType, std::nullptr_t>(
+ cx, atomCache, enclosingScope, baseScopeData);
+ }
+ case ScopeKind::WasmFunction:
+ case ScopeKind::WasmInstance: {
+ // ScopeStencil does not support WASM
+ break;
+ }
+ }
+ MOZ_CRASH();
+}
+
+bool CompilationState::prepareSharedDataStorage(FrontendContext* fc) {
+ size_t allScriptCount = scriptData.length();
+ size_t nonLazyScriptCount = nonLazyFunctionCount;
+ if (!scriptData[0].isFunction()) {
+ nonLazyScriptCount++;
+ }
+ return sharedData.prepareStorageFor(fc, nonLazyScriptCount, allScriptCount);
+}
+
+static bool CreateLazyScript(JSContext* cx,
+ const CompilationAtomCache& atomCache,
+ const CompilationStencil& stencil,
+ CompilationGCOutput& gcOutput,
+ const ScriptStencil& script,
+ const ScriptStencilExtra& scriptExtra,
+ ScriptIndex scriptIndex, HandleFunction function) {
+ Rooted<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, atomCache, stencil, gcOutput,
+ script.gcthings(stencil),
+ lazy->gcthingsForInit())) {
+ return false;
+ }
+ }
+
+ if (scriptExtra.useMemberInitializers()) {
+ lazy->setMemberInitializers(scriptExtra.memberInitializers());
+ }
+
+ function->initScript(lazy);
+
+ return true;
+}
+
+// Parser-generated functions with the same prototype will share the same shape.
+// By computing the correct values up front, we can save a lot of time in the
+// Object creation code. For simplicity, we focus only on plain synchronous
+// functions which are by far the most common.
+//
+// NOTE: Keep this in sync with `js::NewFunctionWithProto`.
+static JSFunction* CreateFunctionFast(JSContext* cx,
+ CompilationAtomCache& atomCache,
+ Handle<SharedShape*> shape,
+ const ScriptStencil& script,
+ const ScriptStencilExtra& scriptExtra) {
+ MOZ_ASSERT(
+ !scriptExtra.immutableFlags.hasFlag(ImmutableScriptFlagsEnum::IsAsync));
+ MOZ_ASSERT(!scriptExtra.immutableFlags.hasFlag(
+ ImmutableScriptFlagsEnum::IsGenerator));
+ MOZ_ASSERT(!script.functionFlags.isAsmJSNative());
+
+ FunctionFlags flags = script.functionFlags;
+ gc::AllocKind allocKind = flags.isExtended()
+ ? gc::AllocKind::FUNCTION_EXTENDED
+ : gc::AllocKind::FUNCTION;
+
+ JSFunction* fun = JSFunction::create(cx, allocKind, gc::Heap::Tenured, shape);
+ if (!fun) {
+ return nullptr;
+ }
+
+ fun->setArgCount(scriptExtra.nargs);
+ fun->setFlags(flags);
+
+ fun->initScript(nullptr);
+ fun->initEnvironment(nullptr);
+
+ if (script.functionAtom) {
+ JSAtom* atom = atomCache.getExistingAtomAt(cx, script.functionAtom);
+ MOZ_ASSERT(atom);
+ fun->initAtom(atom);
+ }
+
+ return fun;
+}
+
+static JSFunction* CreateFunction(JSContext* cx,
+ CompilationAtomCache& atomCache,
+ const CompilationStencil& stencil,
+ const ScriptStencil& script,
+ const ScriptStencilExtra& scriptExtra,
+ ScriptIndex functionIndex) {
+ GeneratorKind generatorKind =
+ scriptExtra.immutableFlags.hasFlag(ImmutableScriptFlagsEnum::IsGenerator)
+ ? GeneratorKind::Generator
+ : GeneratorKind::NotGenerator;
+ FunctionAsyncKind asyncKind =
+ scriptExtra.immutableFlags.hasFlag(ImmutableScriptFlagsEnum::IsAsync)
+ ? FunctionAsyncKind::AsyncFunction
+ : FunctionAsyncKind::SyncFunction;
+
+ // Determine the new function's proto. This must be done for singleton
+ // functions.
+ RootedObject proto(cx);
+ if (!GetFunctionPrototype(cx, generatorKind, asyncKind, &proto)) {
+ return nullptr;
+ }
+
+ gc::AllocKind allocKind = script.functionFlags.isExtended()
+ ? gc::AllocKind::FUNCTION_EXTENDED
+ : gc::AllocKind::FUNCTION;
+ bool isAsmJS = script.functionFlags.isAsmJSNative();
+
+ JSNative maybeNative = isAsmJS ? InstantiateAsmJS : nullptr;
+
+ Rooted<JSAtom*> displayAtom(cx);
+ if (script.functionAtom) {
+ displayAtom.set(atomCache.getExistingAtomAt(cx, script.functionAtom));
+ MOZ_ASSERT(displayAtom);
+ }
+ RootedFunction fun(
+ cx, NewFunctionWithProto(cx, maybeNative, scriptExtra.nargs,
+ script.functionFlags, nullptr, displayAtom,
+ proto, allocKind, TenuredObject));
+ if (!fun) {
+ return nullptr;
+ }
+
+ if (isAsmJS) {
+ RefPtr<const JS::WasmModule> asmJS =
+ stencil.asmJS->moduleMap.lookup(functionIndex)->value();
+
+ JSObject* moduleObj = asmJS->createObjectForAsmJS(cx);
+ if (!moduleObj) {
+ return nullptr;
+ }
+
+ fun->setExtendedSlot(FunctionExtended::ASMJS_MODULE_SLOT,
+ ObjectValue(*moduleObj));
+ }
+
+ return fun;
+}
+
+static bool InstantiateAtoms(JSContext* cx, FrontendContext* fc,
+ CompilationAtomCache& atomCache,
+ const CompilationStencil& stencil) {
+ return InstantiateMarkedAtoms(cx, fc, stencil.parserAtomData, atomCache);
+}
+
+static bool InstantiateScriptSourceObject(JSContext* cx,
+ const JS::InstantiateOptions& options,
+ const CompilationStencil& stencil,
+ CompilationGCOutput& gcOutput) {
+ MOZ_ASSERT(stencil.source);
+
+ gcOutput.sourceObject = ScriptSourceObject::create(cx, stencil.source.get());
+ if (!gcOutput.sourceObject) {
+ return false;
+ }
+
+ MOZ_ASSERT(!cx->isHelperThreadContext());
+ Rooted<ScriptSourceObject*> sourceObject(cx, gcOutput.sourceObject);
+ if (!ScriptSourceObject::initFromOptions(cx, sourceObject, options)) {
+ return false;
+ }
+
+ return true;
+}
+
+// Instantiate ModuleObject. Further initialization is done after the associated
+// BaseScript is instantiated in InstantiateTopLevel.
+static bool InstantiateModuleObject(JSContext* cx, FrontendContext* fc,
+ CompilationAtomCache& atomCache,
+ const CompilationStencil& stencil,
+ CompilationGCOutput& gcOutput) {
+ MOZ_ASSERT(stencil.isModule());
+
+ gcOutput.module = ModuleObject::create(cx);
+ if (!gcOutput.module) {
+ return false;
+ }
+
+ Rooted<ModuleObject*> module(cx, gcOutput.module);
+ return stencil.moduleMetadata->initModule(cx, fc, atomCache, module);
+}
+
+// Instantiate JSFunctions for each FunctionBox.
+static bool InstantiateFunctions(JSContext* cx, FrontendContext* fc,
+ CompilationAtomCache& atomCache,
+ const CompilationStencil& stencil,
+ CompilationGCOutput& gcOutput) {
+ using ImmutableFlags = ImmutableScriptFlagsEnum;
+
+ if (!gcOutput.functions.resize(stencil.scriptData.size())) {
+ ReportOutOfMemory(fc);
+ return false;
+ }
+
+ // Most JSFunctions will be have the same Shape so we can compute it now to
+ // allow fast object creation. Generators / Async will use the slow path
+ // instead.
+ Rooted<SharedShape*> functionShape(
+ cx, GlobalObject::getFunctionShapeWithDefaultProto(
+ cx, /* extended = */ false));
+ if (!functionShape) {
+ return false;
+ }
+
+ Rooted<SharedShape*> extendedShape(
+ cx, GlobalObject::getFunctionShapeWithDefaultProto(
+ cx, /* extended = */ true));
+ if (!extendedShape) {
+ return false;
+ }
+
+ for (auto item :
+ CompilationStencil::functionScriptStencils(stencil, gcOutput)) {
+ const auto& scriptStencil = item.script;
+ const auto& scriptExtra = (*item.scriptExtra);
+ auto index = item.index;
+
+ MOZ_ASSERT(!item.function);
+
+ // Plain functions can use a fast path.
+ bool useFastPath =
+ !scriptExtra.immutableFlags.hasFlag(ImmutableFlags::IsAsync) &&
+ !scriptExtra.immutableFlags.hasFlag(ImmutableFlags::IsGenerator) &&
+ !scriptStencil.functionFlags.isAsmJSNative();
+
+ JSFunction* fun;
+ if (useFastPath) {
+ Handle<SharedShape*> shape = scriptStencil.functionFlags.isExtended()
+ ? extendedShape
+ : functionShape;
+ fun =
+ CreateFunctionFast(cx, atomCache, shape, scriptStencil, scriptExtra);
+ } else {
+ fun = CreateFunction(cx, atomCache, stencil, scriptStencil, scriptExtra,
+ index);
+ }
+
+ if (!fun) {
+ return false;
+ }
+
+ // Self-hosted functions may have a canonical name to use when instantiating
+ // into other realms.
+ if (scriptStencil.hasSelfHostedCanonicalName()) {
+ JSAtom* canonicalName = atomCache.getExistingAtomAt(
+ cx, scriptStencil.selfHostedCanonicalName());
+ fun->setAtom(canonicalName);
+ }
+
+ gcOutput.getFunctionNoBaseIndex(index) = fun;
+ }
+
+ return true;
+}
+
+// Instantiate Scope for each ScopeStencil.
+//
+// This should be called after InstantiateFunctions, given FunctionScope needs
+// associated JSFunction pointer, and also should be called before
+// InstantiateScriptStencils, given JSScript needs Scope pointer in gc things.
+static bool InstantiateScopes(JSContext* cx, CompilationInput& input,
+ const CompilationStencil& stencil,
+ CompilationGCOutput& gcOutput) {
+ // While allocating Scope object from ScopeStencil, Scope object for the
+ // enclosing Scope should already be allocated.
+ //
+ // Enclosing scope of ScopeStencil can be either ScopeStencil or Scope*
+ // pointer.
+ //
+ // If the enclosing scope is ScopeStencil, it's guaranteed to be earlier
+ // element in stencil.scopeData, because enclosing_ field holds
+ // index into it, and newly created ScopeStencil is pushed back to the vector.
+ //
+ // If the enclosing scope is Scope*, it's CompilationInput.enclosingScope.
+
+ MOZ_ASSERT(stencil.scopeData.size() == stencil.scopeNames.size());
+ size_t scopeCount = stencil.scopeData.size();
+ for (size_t i = 0; i < scopeCount; i++) {
+ Scope* scope = stencil.scopeData[i].createScope(cx, input, gcOutput,
+ stencil.scopeNames[i]);
+ if (!scope) {
+ return false;
+ }
+ gcOutput.scopes.infallibleAppend(scope);
+ }
+
+ return true;
+}
+
+// Instantiate js::BaseScripts from ScriptStencils for inner functions of the
+// compilation. Note that standalone functions and functions being delazified
+// are handled below with other top-levels.
+static bool InstantiateScriptStencils(JSContext* cx,
+ CompilationAtomCache& atomCache,
+ const CompilationStencil& stencil,
+ CompilationGCOutput& gcOutput) {
+ MOZ_ASSERT(stencil.isInitialStencil());
+
+ Rooted<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(){});`.
+ //
+ // `wasEmittedByEnclosingScript` is false also for standalone
+ // functions. They are handled in InstantiateTopLevel.
+ if (!scriptStencil.wasEmittedByEnclosingScript()) {
+ continue;
+ }
+
+ RootedScript script(
+ cx, JSScript::fromStencil(cx, atomCache, stencil, gcOutput, index));
+ if (!script) {
+ return false;
+ }
+
+ if (scriptStencil.allowRelazify()) {
+ MOZ_ASSERT(script->isRelazifiable());
+ script->setAllowRelazify();
+ }
+ } else if (scriptStencil.functionFlags.isAsmJSNative()) {
+ MOZ_ASSERT(fun->isAsmJSNative());
+ } else {
+ MOZ_ASSERT(fun->isIncomplete());
+ if (!CreateLazyScript(cx, atomCache, stencil, gcOutput, scriptStencil,
+ *scriptExtra, index, fun)) {
+ return false;
+ }
+ }
+ }
+
+ return true;
+}
+
+// Instantiate the Stencil for the top-level script of the compilation. This
+// includes standalone functions and functions being delazified.
+static bool InstantiateTopLevel(JSContext* cx, CompilationInput& input,
+ const CompilationStencil& stencil,
+ CompilationGCOutput& gcOutput) {
+ const ScriptStencil& scriptStencil =
+ stencil.scriptData[CompilationStencil::TopLevelIndex];
+
+ // Top-level asm.js does not generate a JSScript.
+ if (scriptStencil.functionFlags.isAsmJSNative()) {
+ return true;
+ }
+
+ MOZ_ASSERT(scriptStencil.hasSharedData());
+ MOZ_ASSERT(stencil.sharedData.get(CompilationStencil::TopLevelIndex));
+
+ if (!stencil.isInitialStencil()) {
+ MOZ_ASSERT(input.lazyOuterBaseScript());
+ RootedScript script(cx,
+ JSScript::CastFromLazy(input.lazyOuterBaseScript()));
+ if (!JSScript::fullyInitFromStencil(cx, input.atomCache, stencil, gcOutput,
+ script,
+ CompilationStencil::TopLevelIndex)) {
+ return false;
+ }
+
+ if (scriptStencil.allowRelazify()) {
+ MOZ_ASSERT(script->isRelazifiable());
+ script->setAllowRelazify();
+ }
+
+ gcOutput.script = script;
+ return true;
+ }
+
+ gcOutput.script =
+ JSScript::fromStencil(cx, input.atomCache, stencil, gcOutput,
+ CompilationStencil::TopLevelIndex);
+ if (!gcOutput.script) {
+ return false;
+ }
+
+ if (scriptStencil.allowRelazify()) {
+ MOZ_ASSERT(gcOutput.script->isRelazifiable());
+ gcOutput.script->setAllowRelazify();
+ }
+
+ const ScriptStencilExtra& scriptExtra =
+ stencil.scriptExtra[CompilationStencil::TopLevelIndex];
+
+ // Finish initializing the ModuleObject if needed.
+ if (scriptExtra.isModule()) {
+ RootedScript script(cx, gcOutput.script);
+ Rooted<ModuleObject*> module(cx, gcOutput.module);
+
+ script->outermostScope()->as<ModuleScope>().initModule(module);
+
+ module->initScriptSlots(script);
+
+ if (!ModuleObject::createEnvironment(cx, module)) {
+ return false;
+ }
+
+ MOZ_ASSERT(!cx->isHelperThreadContext());
+ if (!ModuleObject::Freeze(cx, module)) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+// When a function is first referenced by enclosing script's bytecode, we need
+// to update it with information determined by the BytecodeEmitter. This applies
+// to both initial and delazification parses. The functions being update may or
+// may not have bytecode at this point.
+static void UpdateEmittedInnerFunctions(JSContext* cx,
+ CompilationAtomCache& atomCache,
+ const CompilationStencil& stencil,
+ CompilationGCOutput& gcOutput) {
+ for (auto item :
+ CompilationStencil::functionScriptStencils(stencil, gcOutput)) {
+ auto& scriptStencil = item.script;
+ auto& fun = item.function;
+ if (!scriptStencil.wasEmittedByEnclosingScript()) {
+ continue;
+ }
+
+ if (scriptStencil.functionFlags.isAsmJSNative() ||
+ fun->baseScript()->hasBytecode()) {
+ // Non-lazy inner functions don't use the enclosingScope_ field.
+ MOZ_ASSERT(!scriptStencil.hasLazyFunctionEnclosingScopeIndex());
+ } else {
+ // Apply updates from FunctionEmitter::emitLazy().
+ BaseScript* script = fun->baseScript();
+
+ ScopeIndex index = scriptStencil.lazyFunctionEnclosingScopeIndex();
+ Scope* scope = gcOutput.getScopeNoBaseIndex(index);
+ script->setEnclosingScope(scope);
+
+ // Inferred and Guessed names are computed by BytecodeEmitter and so may
+ // need to be applied to existing JSFunctions during delazification.
+ if (fun->displayAtom() == nullptr) {
+ JSAtom* funcAtom = nullptr;
+ if (scriptStencil.functionFlags.hasInferredName() ||
+ scriptStencil.functionFlags.hasGuessedAtom()) {
+ funcAtom =
+ atomCache.getExistingAtomAt(cx, scriptStencil.functionAtom);
+ MOZ_ASSERT(funcAtom);
+ }
+ if (scriptStencil.functionFlags.hasInferredName()) {
+ fun->setInferredName(funcAtom);
+ }
+ if (scriptStencil.functionFlags.hasGuessedAtom()) {
+ fun->setGuessedAtom(funcAtom);
+ }
+ }
+ }
+ }
+}
+
+// During initial parse we must link lazy-functions-inside-lazy-functions to
+// their enclosing script.
+static void LinkEnclosingLazyScript(const CompilationStencil& stencil,
+ CompilationGCOutput& gcOutput) {
+ for (auto item :
+ CompilationStencil::functionScriptStencils(stencil, gcOutput)) {
+ auto& scriptStencil = item.script;
+ auto& fun = item.function;
+ if (!scriptStencil.functionFlags.hasBaseScript()) {
+ continue;
+ }
+
+ if (!fun->baseScript()) {
+ continue;
+ }
+
+ if (fun->baseScript()->hasBytecode()) {
+ continue;
+ }
+
+ BaseScript* script = fun->baseScript();
+ MOZ_ASSERT(!script->hasBytecode());
+
+ for (auto inner : script->gcthings()) {
+ if (!inner.is<JSObject>()) {
+ continue;
+ }
+ JSFunction* innerFun = &inner.as<JSObject>().as<JSFunction>();
+
+ MOZ_ASSERT(innerFun->hasBaseScript(),
+ "inner function should have base script");
+ if (!innerFun->hasBaseScript()) {
+ continue;
+ }
+
+ // Check for the case that the inner function has the base script flag,
+ // but still doesn't have the actual base script pointer.
+ // `baseScript` method asserts the pointer itself, so no extra MOZ_ASSERT
+ // here.
+ if (!innerFun->baseScript()) {
+ continue;
+ }
+
+ innerFun->setEnclosingLazyScript(script);
+ }
+ }
+}
+
+#ifdef DEBUG
+// Some fields aren't used in delazification, given the target functions and
+// scripts are already instantiated, but they still should match.
+static void AssertDelazificationFieldsMatch(const CompilationStencil& stencil,
+ CompilationGCOutput& gcOutput) {
+ for (auto item :
+ CompilationStencil::functionScriptStencils(stencil, gcOutput)) {
+ auto& scriptStencil = item.script;
+ auto* scriptExtra = item.scriptExtra;
+ auto& fun = item.function;
+
+ MOZ_ASSERT(scriptExtra == nullptr);
+
+ // Names are updated by UpdateInnerFunctions.
+ constexpr uint16_t HAS_INFERRED_NAME =
+ uint16_t(FunctionFlags::Flags::HAS_INFERRED_NAME);
+ constexpr uint16_t HAS_GUESSED_ATOM =
+ uint16_t(FunctionFlags::Flags::HAS_GUESSED_ATOM);
+ constexpr uint16_t MUTABLE_FLAGS =
+ uint16_t(FunctionFlags::Flags::MUTABLE_FLAGS);
+ constexpr uint16_t acceptableDifferenceForFunction =
+ HAS_INFERRED_NAME | HAS_GUESSED_ATOM | MUTABLE_FLAGS;
+
+ MOZ_ASSERT((fun->flags().toRaw() | acceptableDifferenceForFunction) ==
+ (scriptStencil.functionFlags.toRaw() |
+ acceptableDifferenceForFunction));
+
+ // Delazification shouldn't delazify inner scripts.
+ MOZ_ASSERT_IF(item.index == CompilationStencil::TopLevelIndex,
+ scriptStencil.hasSharedData());
+ MOZ_ASSERT_IF(item.index > CompilationStencil::TopLevelIndex,
+ !scriptStencil.hasSharedData());
+ }
+}
+#endif // DEBUG
+
+// When delazifying, use the existing JSFunctions. The initial and delazifying
+// parse are required to generate the same sequence of functions for lazy
+// parsing to work at all.
+static void FunctionsFromExistingLazy(CompilationInput& input,
+ CompilationGCOutput& gcOutput) {
+ MOZ_ASSERT(gcOutput.functions.empty());
+ gcOutput.functions.infallibleAppend(input.function());
+
+ for (JS::GCCellPtr elem : input.lazyOuterBaseScript()->gcthings()) {
+ if (!elem.is<JSObject>()) {
+ continue;
+ }
+ JSFunction* fun = &elem.as<JSObject>().as<JSFunction>();
+ gcOutput.functions.infallibleAppend(fun);
+ }
+}
+
+void CompilationStencil::borrowFromExtensibleCompilationStencil(
+ ExtensibleCompilationStencil& extensibleStencil) {
+ canLazilyParse = extensibleStencil.canLazilyParse;
+ functionKey = extensibleStencil.functionKey;
+
+ // Borrow the vector content as span.
+ scriptData = extensibleStencil.scriptData;
+ scriptExtra = extensibleStencil.scriptExtra;
+
+ gcThingData = extensibleStencil.gcThingData;
+
+ scopeData = extensibleStencil.scopeData;
+ scopeNames = extensibleStencil.scopeNames;
+
+ regExpData = extensibleStencil.regExpData;
+ bigIntData = extensibleStencil.bigIntData;
+ objLiteralData = extensibleStencil.objLiteralData;
+
+ // Borrow the parser atoms as span.
+ parserAtomData = extensibleStencil.parserAtoms.entries_;
+
+ // Borrow container.
+ sharedData.setBorrow(&extensibleStencil.sharedData);
+
+ // Share ref-counted data.
+ source = extensibleStencil.source;
+ asmJS = extensibleStencil.asmJS;
+ moduleMetadata = extensibleStencil.moduleMetadata;
+}
+
+#ifdef DEBUG
+void CompilationStencil::assertBorrowingFromExtensibleCompilationStencil(
+ const ExtensibleCompilationStencil& extensibleStencil) const {
+ MOZ_ASSERT(canLazilyParse == extensibleStencil.canLazilyParse);
+ MOZ_ASSERT(functionKey == extensibleStencil.functionKey);
+
+ AssertBorrowingSpan(scriptData, extensibleStencil.scriptData);
+ AssertBorrowingSpan(scriptExtra, extensibleStencil.scriptExtra);
+
+ AssertBorrowingSpan(gcThingData, extensibleStencil.gcThingData);
+
+ AssertBorrowingSpan(scopeData, extensibleStencil.scopeData);
+ AssertBorrowingSpan(scopeNames, extensibleStencil.scopeNames);
+
+ AssertBorrowingSpan(regExpData, extensibleStencil.regExpData);
+ AssertBorrowingSpan(bigIntData, extensibleStencil.bigIntData);
+ AssertBorrowingSpan(objLiteralData, extensibleStencil.objLiteralData);
+
+ AssertBorrowingSpan(parserAtomData, extensibleStencil.parserAtoms.entries_);
+
+ MOZ_ASSERT(sharedData.isBorrow());
+ MOZ_ASSERT(sharedData.asBorrow() == &extensibleStencil.sharedData);
+
+ MOZ_ASSERT(source == extensibleStencil.source);
+ MOZ_ASSERT(asmJS == extensibleStencil.asmJS);
+ MOZ_ASSERT(moduleMetadata == extensibleStencil.moduleMetadata);
+}
+#endif
+
+CompilationStencil::CompilationStencil(
+ UniquePtr<ExtensibleCompilationStencil>&& extensibleStencil)
+ : alloc(LifoAllocChunkSize) {
+ ownedBorrowStencil = std::move(extensibleStencil);
+
+ storageType = StorageType::OwnedExtensible;
+
+ borrowFromExtensibleCompilationStencil(*ownedBorrowStencil);
+
+#ifdef DEBUG
+ assertNoExternalDependency();
+#endif
+}
+
+/* static */
+bool CompilationStencil::instantiateStencils(JSContext* cx,
+ CompilationInput& input,
+ const CompilationStencil& stencil,
+ CompilationGCOutput& gcOutput) {
+ AutoReportFrontendContext fc(cx);
+ if (!prepareForInstantiate(&fc, input.atomCache, stencil, gcOutput)) {
+ return false;
+ }
+
+ return instantiateStencilAfterPreparation(cx, input, stencil, gcOutput);
+}
+
+/* static */
+bool CompilationStencil::instantiateStencilAfterPreparation(
+ JSContext* cx, CompilationInput& input, const CompilationStencil& stencil,
+ CompilationGCOutput& gcOutput) {
+ // Distinguish between the initial (possibly lazy) compile and any subsequent
+ // delazification compiles. Delazification will update existing GC things.
+ bool isInitialParse = stencil.isInitialStencil();
+ MOZ_ASSERT(stencil.isInitialStencil() == input.isInitialStencil());
+
+ CompilationAtomCache& atomCache = input.atomCache;
+ const JS::InstantiateOptions options(input.options);
+
+ // Phase 1: Instantiate JSAtom/JSStrings.
+ AutoReportFrontendContext fc(cx);
+ if (!InstantiateAtoms(cx, &fc, atomCache, stencil)) {
+ return false;
+ }
+
+ // Phase 2: Instantiate ScriptSourceObject, ModuleObject, JSFunctions.
+ if (isInitialParse) {
+ if (!InstantiateScriptSourceObject(cx, options, stencil, gcOutput)) {
+ return false;
+ }
+
+ if (stencil.moduleMetadata) {
+ // The enclosing script of a module is always the global scope. Fetch the
+ // scope of the current global and update input data.
+ MOZ_ASSERT(input.enclosingScope.isNull());
+ input.enclosingScope = InputScope(&cx->global()->emptyGlobalScope());
+ MOZ_ASSERT(input.enclosingScope.environmentChainLength() ==
+ ModuleScope::EnclosingEnvironmentChainLength);
+
+ if (!InstantiateModuleObject(cx, &fc, atomCache, stencil, gcOutput)) {
+ return false;
+ }
+ }
+
+ if (!InstantiateFunctions(cx, &fc, atomCache, stencil, gcOutput)) {
+ return false;
+ }
+ } else {
+ MOZ_ASSERT(
+ stencil.scriptData[CompilationStencil::TopLevelIndex].isFunction());
+
+ // FunctionKey is used when caching to map a delazification stencil to a
+ // specific lazy script. It is not used by instantiation, but we should
+ // ensure it is correctly defined.
+ MOZ_ASSERT(stencil.functionKey == input.extent().toFunctionKey());
+
+ FunctionsFromExistingLazy(input, gcOutput);
+ MOZ_ASSERT(gcOutput.functions.length() == stencil.scriptData.size());
+
+#ifdef DEBUG
+ AssertDelazificationFieldsMatch(stencil, gcOutput);
+#endif
+ }
+
+ // Phase 3: Instantiate js::Scopes.
+ if (!InstantiateScopes(cx, input, stencil, gcOutput)) {
+ return false;
+ }
+
+ // Phase 4: Instantiate (inner) BaseScripts.
+ if (isInitialParse) {
+ if (!InstantiateScriptStencils(cx, atomCache, stencil, gcOutput)) {
+ return false;
+ }
+ }
+
+ // Phase 5: Finish top-level handling
+ if (!InstantiateTopLevel(cx, input, stencil, gcOutput)) {
+ return false;
+ }
+
+ // !! Must be infallible from here forward !!
+
+ // Phase 6: Update lazy scripts.
+ if (stencil.canLazilyParse) {
+ UpdateEmittedInnerFunctions(cx, atomCache, stencil, gcOutput);
+
+ if (isInitialParse) {
+ LinkEnclosingLazyScript(stencil, gcOutput);
+ }
+ }
+
+ return true;
+}
+
+// The top-level self-hosted script is created and executed in each realm that
+// needs it. While the stencil has a gcthings list for the various top-level
+// functions, we use special machinery to create them on demand. So instead we
+// use a placeholder JSFunction that should never be called.
+static bool SelfHostedDummyFunction(JSContext* cx, unsigned argc,
+ JS::Value* vp) {
+ MOZ_CRASH("Self-hosting top-level should not use functions directly");
+}
+
+bool CompilationStencil::instantiateSelfHostedAtoms(
+ JSContext* cx, AtomSet& atomSet, CompilationAtomCache& atomCache) const {
+ MOZ_ASSERT(isInitialStencil());
+
+ // We must instantiate atoms during startup so they can be made permanent
+ // across multiple runtimes.
+ AutoReportFrontendContext fc(cx);
+ return InstantiateMarkedAtomsAsPermanent(cx, &fc, atomSet, parserAtomData,
+ atomCache);
+}
+
+JSScript* CompilationStencil::instantiateSelfHostedTopLevelForRealm(
+ JSContext* cx, CompilationInput& input) {
+ MOZ_ASSERT(isInitialStencil());
+
+ Rooted<CompilationGCOutput> gcOutput(cx);
+
+ gcOutput.get().sourceObject = SelfHostingScriptSourceObject(cx);
+ if (!gcOutput.get().sourceObject) {
+ return nullptr;
+ }
+
+ // The top-level script has ScriptIndex references in its gcthings list, but
+ // we do not want to instantiate those functions here since they are instead
+ // created on demand from the stencil. Create a dummy function and populate
+ // the functions array of the CompilationGCOutput with references to it.
+ RootedFunction dummy(
+ cx, NewNativeFunction(cx, SelfHostedDummyFunction, 0, nullptr));
+ if (!dummy) {
+ return nullptr;
+ }
+ if (!gcOutput.get().functions.appendN(dummy, scriptData.size())) {
+ ReportOutOfMemory(cx);
+ return nullptr;
+ }
+
+ if (!InstantiateTopLevel(cx, input, *this, gcOutput.get())) {
+ return nullptr;
+ }
+
+ return gcOutput.get().script;
+}
+
+JSFunction* CompilationStencil::instantiateSelfHostedLazyFunction(
+ JSContext* cx, CompilationAtomCache& atomCache, ScriptIndex index,
+ Handle<JSAtom*> name) {
+ GeneratorKind generatorKind = scriptExtra[index].immutableFlags.hasFlag(
+ ImmutableScriptFlagsEnum::IsGenerator)
+ ? GeneratorKind::Generator
+ : GeneratorKind::NotGenerator;
+ FunctionAsyncKind asyncKind = scriptExtra[index].immutableFlags.hasFlag(
+ ImmutableScriptFlagsEnum::IsAsync)
+ ? FunctionAsyncKind::AsyncFunction
+ : FunctionAsyncKind::SyncFunction;
+
+ Rooted<JSAtom*> funName(cx);
+ if (scriptData[index].hasSelfHostedCanonicalName()) {
+ // SetCanonicalName was used to override the name.
+ funName = atomCache.getExistingAtomAt(
+ cx, scriptData[index].selfHostedCanonicalName());
+ } else if (name) {
+ // Our caller has a name it wants to use.
+ funName = name;
+ } else {
+ MOZ_ASSERT(scriptData[index].functionAtom);
+ funName = atomCache.getExistingAtomAt(cx, scriptData[index].functionAtom);
+ }
+
+ RootedObject proto(cx);
+ if (!GetFunctionPrototype(cx, generatorKind, asyncKind, &proto)) {
+ return nullptr;
+ }
+
+ RootedObject env(cx, &cx->global()->lexicalEnvironment());
+
+ RootedFunction fun(
+ cx,
+ NewFunctionWithProto(cx, nullptr, scriptExtra[index].nargs,
+ scriptData[index].functionFlags, env, funName, proto,
+ gc::AllocKind::FUNCTION_EXTENDED, TenuredObject));
+ if (!fun) {
+ return nullptr;
+ }
+
+ fun->initSelfHostedLazyScript(&cx->runtime()->selfHostedLazyScript.ref());
+
+ JSAtom* selfHostedName =
+ atomCache.getExistingAtomAt(cx, scriptData[index].functionAtom);
+ SetClonedSelfHostedFunctionName(fun, selfHostedName->asPropertyName());
+
+ return fun;
+}
+
+bool CompilationStencil::delazifySelfHostedFunction(
+ JSContext* cx, CompilationAtomCache& atomCache, ScriptIndexRange range,
+ HandleFunction fun) {
+ // Determine the equivalent ScopeIndex range by looking at the outermost scope
+ // of the scripts defining the range. Take special care if this is the last
+ // script in the list.
+ auto getOutermostScope = [this](ScriptIndex scriptIndex) -> ScopeIndex {
+ MOZ_ASSERT(scriptData[scriptIndex].hasSharedData());
+ auto gcthings = scriptData[scriptIndex].gcthings(*this);
+ return gcthings[GCThingIndex::outermostScopeIndex()].toScope();
+ };
+ ScopeIndex scopeIndex = getOutermostScope(range.start);
+ ScopeIndex scopeLimit = (range.limit < scriptData.size())
+ ? getOutermostScope(range.limit)
+ : ScopeIndex(scopeData.size());
+
+ // Prepare to instantiate by reserving the output vectors. We also set a base
+ // index to avoid allocations in most cases.
+ AutoReportFrontendContext fc(cx);
+ Rooted<CompilationGCOutput> gcOutput(cx);
+ if (!gcOutput.get().ensureReservedWithBaseIndex(&fc, range.start, range.limit,
+ scopeIndex, scopeLimit)) {
+ return false;
+ }
+
+ // Phase 1: Instantiate JSAtoms.
+ // NOTE: The self-hosted atoms are all "permanent" and the
+ // CompilationAtomCache is already stored on the JSRuntime.
+
+ // Phase 2: Instantiate ScriptSourceObject, ModuleObject, JSFunctions.
+
+ // Get the corresponding ScriptSourceObject to use in current realm.
+ gcOutput.get().sourceObject = SelfHostingScriptSourceObject(cx);
+ if (!gcOutput.get().sourceObject) {
+ return false;
+ }
+
+ // Delazification target function.
+ gcOutput.get().functions.infallibleAppend(fun);
+
+ // Allocate inner functions. Self-hosted functions do not allocate these with
+ // the initial function.
+ for (size_t i = range.start + 1; i < range.limit; i++) {
+ JSFunction* innerFun = CreateFunction(cx, atomCache, *this, scriptData[i],
+ scriptExtra[i], ScriptIndex(i));
+ if (!innerFun) {
+ return false;
+ }
+ gcOutput.get().functions.infallibleAppend(innerFun);
+ }
+
+ // Phase 3: Instantiate js::Scopes.
+ // NOTE: When the enclosing scope is not a stencil, directly use the
+ // `emptyGlobalScope` instead of reading from CompilationInput. This is
+ // a special case for self-hosted delazification that allows us to reuse
+ // the CompilationInput between different realms.
+ for (size_t i = scopeIndex; i < scopeLimit; i++) {
+ ScopeStencil& data = scopeData[i];
+ Rooted<Scope*> enclosingScope(
+ cx, data.hasEnclosing() ? gcOutput.get().getScope(data.enclosing())
+ : &cx->global()->emptyGlobalScope());
+
+ js::Scope* scope =
+ data.createScope(cx, atomCache, enclosingScope, scopeNames[i]);
+ if (!scope) {
+ return false;
+ }
+ gcOutput.get().scopes.infallibleAppend(scope);
+ }
+
+ // Phase 4: Instantiate (inner) BaseScripts.
+ ScriptIndex innerStart(range.start + 1);
+ for (size_t i = innerStart; i < range.limit; i++) {
+ if (!JSScript::fromStencil(cx, atomCache, *this, gcOutput.get(),
+ ScriptIndex(i))) {
+ return false;
+ }
+ }
+
+ // Phase 5: Finish top-level handling
+ // NOTE: We do not have a `CompilationInput` handy here, so avoid using the
+ // `InstantiateTopLevel` helper and directly create the JSScript. Our
+ // caller also handles the `AllowRelazify` flag for us since self-hosted
+ // delazification is a special case.
+ if (!JSScript::fromStencil(cx, atomCache, *this, gcOutput.get(),
+ range.start)) {
+ return false;
+ }
+
+ // Phase 6: Update lazy scripts.
+ // NOTE: Self-hosting is always fully parsed so there is nothing to do here.
+
+ return true;
+}
+
+/* static */
+bool CompilationStencil::prepareForInstantiate(
+ FrontendContext* fc, CompilationAtomCache& atomCache,
+ const CompilationStencil& stencil, CompilationGCOutput& gcOutput) {
+ // Reserve the `gcOutput` vectors.
+ if (!gcOutput.ensureReserved(fc, stencil.scriptData.size(),
+ stencil.scopeData.size())) {
+ return false;
+ }
+
+ return atomCache.allocate(fc, stencil.parserAtomData.size());
+}
+
+bool CompilationStencil::serializeStencils(JSContext* cx,
+ CompilationInput& input,
+ JS::TranscodeBuffer& buf,
+ bool* succeededOut) const {
+ if (succeededOut) {
+ *succeededOut = false;
+ }
+ AutoReportFrontendContext fc(cx);
+ XDRStencilEncoder encoder(&fc, buf);
+
+ XDRResult res = encoder.codeStencil(*this);
+ if (res.isErr()) {
+ if (JS::IsTranscodeFailureResult(res.unwrapErr())) {
+ buf.clear();
+ return true;
+ }
+ MOZ_ASSERT(res.unwrapErr() == JS::TranscodeResult::Throw);
+
+ return false;
+ }
+
+ if (succeededOut) {
+ *succeededOut = true;
+ }
+ return true;
+}
+
+bool CompilationStencil::deserializeStencils(
+ FrontendContext* fc, const JS::ReadOnlyCompileOptions& compileOptions,
+ const JS::TranscodeRange& range, bool* succeededOut) {
+ if (succeededOut) {
+ *succeededOut = false;
+ }
+ MOZ_ASSERT(parserAtomData.empty());
+ XDRStencilDecoder decoder(fc, range);
+ JS::DecodeOptions options(compileOptions);
+
+ XDRResult res = decoder.codeStencil(options, *this);
+ if (res.isErr()) {
+ if (JS::IsTranscodeFailureResult(res.unwrapErr())) {
+ return true;
+ }
+ MOZ_ASSERT(res.unwrapErr() == JS::TranscodeResult::Throw);
+
+ return false;
+ }
+
+ if (succeededOut) {
+ *succeededOut = true;
+ }
+ return true;
+}
+
+ExtensibleCompilationStencil::ExtensibleCompilationStencil(ScriptSource* source)
+ : alloc(CompilationStencil::LifoAllocChunkSize),
+ source(source),
+ parserAtoms(alloc) {}
+
+ExtensibleCompilationStencil::ExtensibleCompilationStencil(
+ CompilationInput& input)
+ : canLazilyParse(CanLazilyParse(input.options)),
+ alloc(CompilationStencil::LifoAllocChunkSize),
+ source(input.source),
+ parserAtoms(alloc) {}
+
+ExtensibleCompilationStencil::ExtensibleCompilationStencil(
+ const JS::ReadOnlyCompileOptions& options, RefPtr<ScriptSource> source)
+ : canLazilyParse(CanLazilyParse(options)),
+ alloc(CompilationStencil::LifoAllocChunkSize),
+ source(std::move(source)),
+ parserAtoms(alloc) {}
+
+CompilationState::CompilationState(FrontendContext* fc,
+ LifoAllocScope& parserAllocScope,
+ CompilationInput& input)
+ : ExtensibleCompilationStencil(input),
+ directives(input.options.forceStrictMode()),
+ usedNames(fc),
+ parserAllocScope(parserAllocScope),
+ input(input) {}
+
+BorrowingCompilationStencil::BorrowingCompilationStencil(
+ ExtensibleCompilationStencil& extensibleStencil)
+ : CompilationStencil(extensibleStencil.source) {
+ storageType = StorageType::Borrowed;
+
+ borrowFromExtensibleCompilationStencil(extensibleStencil);
+}
+
+SharedDataContainer::~SharedDataContainer() {
+ if (isEmpty()) {
+ // Nothing to do.
+ } else if (isSingle()) {
+ asSingle()->Release();
+ } else if (isVector()) {
+ js_delete(asVector());
+ } else if (isMap()) {
+ js_delete(asMap());
+ } else {
+ MOZ_ASSERT(isBorrow());
+ // Nothing to do.
+ }
+}
+
+bool SharedDataContainer::initVector(FrontendContext* fc) {
+ MOZ_ASSERT(isEmpty());
+
+ auto* vec = js_new<SharedDataVector>();
+ if (!vec) {
+ ReportOutOfMemory(fc);
+ return false;
+ }
+ data_ = uintptr_t(vec) | VectorTag;
+ return true;
+}
+
+bool SharedDataContainer::initMap(FrontendContext* fc) {
+ MOZ_ASSERT(isEmpty());
+
+ auto* map = js_new<SharedDataMap>();
+ if (!map) {
+ ReportOutOfMemory(fc);
+ return false;
+ }
+ data_ = uintptr_t(map) | MapTag;
+ return true;
+}
+
+bool SharedDataContainer::prepareStorageFor(FrontendContext* fc,
+ size_t nonLazyScriptCount,
+ size_t allScriptCount) {
+ MOZ_ASSERT(isEmpty());
+
+ if (nonLazyScriptCount <= 1) {
+ MOZ_ASSERT(isSingle());
+ return true;
+ }
+
+ // If the ratio of scripts with bytecode is small, allocating the Vector
+ // storage with the number of all scripts isn't space-efficient.
+ // In that case use HashMap instead.
+ //
+ // In general, we expect either all scripts to contain bytecode (priviledge
+ // and self-hosted), or almost none to (eg standard lazy parsing output).
+ constexpr size_t thresholdRatio = 8;
+ bool useHashMap = nonLazyScriptCount < allScriptCount / thresholdRatio;
+ if (useHashMap) {
+ if (!initMap(fc)) {
+ return false;
+ }
+ if (!asMap()->reserve(nonLazyScriptCount)) {
+ ReportOutOfMemory(fc);
+ return false;
+ }
+ } else {
+ if (!initVector(fc)) {
+ return false;
+ }
+ if (!asVector()->resize(allScriptCount)) {
+ ReportOutOfMemory(fc);
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool SharedDataContainer::cloneFrom(FrontendContext* fc,
+ const SharedDataContainer& other) {
+ MOZ_ASSERT(isEmpty());
+
+ if (other.isBorrow()) {
+ return cloneFrom(fc, *other.asBorrow());
+ }
+
+ if (other.isSingle()) {
+ // As we clone, we add an extra reference.
+ RefPtr<SharedImmutableScriptData> ref(other.asSingle());
+ setSingle(ref.forget());
+ } else if (other.isVector()) {
+ if (!initVector(fc)) {
+ return false;
+ }
+ if (!asVector()->appendAll(*other.asVector())) {
+ ReportOutOfMemory(fc);
+ return false;
+ }
+ } else if (other.isMap()) {
+ if (!initMap(fc)) {
+ return false;
+ }
+ auto& otherMap = *other.asMap();
+ if (!asMap()->reserve(otherMap.count())) {
+ ReportOutOfMemory(fc);
+ return false;
+ }
+ auto& map = *asMap();
+ for (auto iter = otherMap.iter(); !iter.done(); iter.next()) {
+ auto& entry = iter.get();
+ map.putNewInfallible(entry.key(), entry.value());
+ }
+ }
+ return true;
+}
+
+js::SharedImmutableScriptData* SharedDataContainer::get(
+ ScriptIndex index) const {
+ if (isSingle()) {
+ if (index == CompilationStencil::TopLevelIndex) {
+ return asSingle();
+ }
+ return nullptr;
+ }
+
+ if (isVector()) {
+ auto& vec = *asVector();
+ if (index.index < vec.length()) {
+ return vec[index];
+ }
+ return nullptr;
+ }
+
+ if (isMap()) {
+ auto& map = *asMap();
+ auto p = map.lookup(index);
+ if (p) {
+ return p->value();
+ }
+ return nullptr;
+ }
+
+ MOZ_ASSERT(isBorrow());
+ return asBorrow()->get(index);
+}
+
+bool SharedDataContainer::convertFromSingleToMap(FrontendContext* fc) {
+ MOZ_ASSERT(isSingle());
+
+ // Use a temporary container so that on OOM we do not break the stencil.
+ SharedDataContainer other;
+ if (!other.initMap(fc)) {
+ return false;
+ }
+
+ if (!other.asMap()->putNew(CompilationStencil::TopLevelIndex, asSingle())) {
+ ReportOutOfMemory(fc);
+ return false;
+ }
+
+ std::swap(data_, other.data_);
+ return true;
+}
+
+bool SharedDataContainer::addAndShare(FrontendContext* fc, ScriptIndex index,
+ js::SharedImmutableScriptData* data) {
+ MOZ_ASSERT(!isBorrow());
+
+ if (isSingle()) {
+ MOZ_ASSERT(index == CompilationStencil::TopLevelIndex);
+ RefPtr<SharedImmutableScriptData> ref(data);
+ if (!SharedImmutableScriptData::shareScriptData(fc, ref)) {
+ return false;
+ }
+ setSingle(ref.forget());
+ return true;
+ }
+
+ if (isVector()) {
+ auto& vec = *asVector();
+ // Resized by SharedDataContainer::prepareStorageFor.
+ vec[index] = data;
+ return SharedImmutableScriptData::shareScriptData(fc, vec[index]);
+ }
+
+ MOZ_ASSERT(isMap());
+ auto& map = *asMap();
+ // Reserved by SharedDataContainer::prepareStorageFor.
+ map.putNewInfallible(index, data);
+ auto p = map.lookup(index);
+ MOZ_ASSERT(p);
+ return SharedImmutableScriptData::shareScriptData(fc, p->value());
+}
+
+bool SharedDataContainer::addExtraWithoutShare(
+ FrontendContext* fc, ScriptIndex index,
+ js::SharedImmutableScriptData* data) {
+ MOZ_ASSERT(!isEmpty());
+
+ if (isSingle()) {
+ if (!convertFromSingleToMap(fc)) {
+ return false;
+ }
+ }
+
+ if (isVector()) {
+ // SharedDataContainer::prepareStorageFor allocates space for all scripts.
+ (*asVector())[index] = data;
+ return true;
+ }
+
+ MOZ_ASSERT(isMap());
+ // SharedDataContainer::prepareStorageFor doesn't allocate space for
+ // delazification, and this can fail.
+ if (!asMap()->putNew(index, data)) {
+ ReportOutOfMemory(fc);
+ return false;
+ }
+ return true;
+}
+
+#ifdef DEBUG
+void CompilationStencil::assertNoExternalDependency() const {
+ if (ownedBorrowStencil) {
+ ownedBorrowStencil->assertNoExternalDependency();
+
+ assertBorrowingFromExtensibleCompilationStencil(*ownedBorrowStencil);
+ return;
+ }
+
+ MOZ_ASSERT_IF(!scriptData.empty(), alloc.contains(scriptData.data()));
+ MOZ_ASSERT_IF(!scriptExtra.empty(), alloc.contains(scriptExtra.data()));
+
+ MOZ_ASSERT_IF(!scopeData.empty(), alloc.contains(scopeData.data()));
+ MOZ_ASSERT_IF(!scopeNames.empty(), alloc.contains(scopeNames.data()));
+ for (const auto* data : scopeNames) {
+ MOZ_ASSERT_IF(data, alloc.contains(data));
+ }
+
+ MOZ_ASSERT_IF(!regExpData.empty(), alloc.contains(regExpData.data()));
+
+ MOZ_ASSERT_IF(!bigIntData.empty(), alloc.contains(bigIntData.data()));
+ for (const auto& data : bigIntData) {
+ MOZ_ASSERT(data.isContainedIn(alloc));
+ }
+
+ MOZ_ASSERT_IF(!objLiteralData.empty(), alloc.contains(objLiteralData.data()));
+ for (const auto& data : objLiteralData) {
+ MOZ_ASSERT(data.isContainedIn(alloc));
+ }
+
+ MOZ_ASSERT_IF(!parserAtomData.empty(), alloc.contains(parserAtomData.data()));
+ for (const auto* data : parserAtomData) {
+ MOZ_ASSERT_IF(data, alloc.contains(data));
+ }
+
+ MOZ_ASSERT(!sharedData.isBorrow());
+}
+
+void ExtensibleCompilationStencil::assertNoExternalDependency() const {
+ for (const auto& data : bigIntData) {
+ MOZ_ASSERT(data.isContainedIn(alloc));
+ }
+
+ for (const auto& data : objLiteralData) {
+ MOZ_ASSERT(data.isContainedIn(alloc));
+ }
+
+ for (const auto* data : scopeNames) {
+ MOZ_ASSERT_IF(data, alloc.contains(data));
+ }
+
+ for (const auto* data : parserAtoms.entries()) {
+ MOZ_ASSERT_IF(data, alloc.contains(data));
+ }
+
+ MOZ_ASSERT(!sharedData.isBorrow());
+}
+#endif // DEBUG
+
+template <typename T, typename VectorT>
+[[nodiscard]] bool CopySpanToVector(FrontendContext* fc, VectorT& vec,
+ mozilla::Span<T>& span) {
+ auto len = span.size();
+ if (len == 0) {
+ return true;
+ }
+
+ if (!vec.append(span.data(), len)) {
+ js::ReportOutOfMemory(fc);
+ return false;
+ }
+ return true;
+}
+
+template <typename T, typename IntoSpanT, size_t Inline, typename AllocPolicy>
+[[nodiscard]] bool CopyToVector(FrontendContext* fc,
+ mozilla::Vector<T, Inline, AllocPolicy>& vec,
+ const IntoSpanT& source) {
+ mozilla::Span<const T> span = source;
+ return CopySpanToVector(fc, vec, span);
+}
+
+// Span and Vector do not share the same method names.
+template <typename T, size_t Inline, typename AllocPolicy>
+size_t GetLength(const mozilla::Vector<T, Inline, AllocPolicy>& vec) {
+ return vec.length();
+}
+template <typename T>
+size_t GetLength(const mozilla::Span<T>& span) {
+ return span.Length();
+}
+
+// Copy scope names from `src` into `alloc`, and returns the allocated data.
+BaseParserScopeData* CopyScopeData(FrontendContext* fc, LifoAlloc& alloc,
+ ScopeKind kind,
+ const BaseParserScopeData* src) {
+ MOZ_ASSERT(kind != ScopeKind::With);
+
+ size_t dataSize = SizeOfParserScopeData(kind, src->length);
+
+ auto* dest = static_cast<BaseParserScopeData*>(alloc.alloc(dataSize));
+ if (!dest) {
+ js::ReportOutOfMemory(fc);
+ return nullptr;
+ }
+ memcpy(dest, src, dataSize);
+
+ return dest;
+}
+
+template <typename Stencil>
+bool ExtensibleCompilationStencil::cloneFromImpl(FrontendContext* fc,
+ const Stencil& other) {
+ MOZ_ASSERT(alloc.isEmpty());
+
+ canLazilyParse = other.canLazilyParse;
+ functionKey = other.functionKey;
+
+ if (!CopyToVector(fc, scriptData, other.scriptData)) {
+ return false;
+ }
+
+ if (!CopyToVector(fc, scriptExtra, other.scriptExtra)) {
+ return false;
+ }
+
+ if (!CopyToVector(fc, gcThingData, other.gcThingData)) {
+ return false;
+ }
+
+ size_t scopeSize = GetLength(other.scopeData);
+ if (!CopyToVector(fc, scopeData, other.scopeData)) {
+ return false;
+ }
+ if (!scopeNames.reserve(scopeSize)) {
+ js::ReportOutOfMemory(fc);
+ return false;
+ }
+ for (size_t i = 0; i < scopeSize; i++) {
+ if (other.scopeNames[i]) {
+ BaseParserScopeData* data = CopyScopeData(
+ fc, alloc, other.scopeData[i].kind(), other.scopeNames[i]);
+ if (!data) {
+ return false;
+ }
+ scopeNames.infallibleEmplaceBack(data);
+ } else {
+ scopeNames.infallibleEmplaceBack(nullptr);
+ }
+ }
+
+ if (!CopyToVector(fc, regExpData, other.regExpData)) {
+ return false;
+ }
+
+ // If CompilationStencil has external dependency, peform deep copy.
+
+ size_t bigIntSize = GetLength(other.bigIntData);
+ if (!bigIntData.resize(bigIntSize)) {
+ js::ReportOutOfMemory(fc);
+ return false;
+ }
+ for (size_t i = 0; i < bigIntSize; i++) {
+ if (!bigIntData[i].init(fc, alloc, other.bigIntData[i].source())) {
+ return false;
+ }
+ }
+
+ size_t objLiteralSize = GetLength(other.objLiteralData);
+ if (!objLiteralData.reserve(objLiteralSize)) {
+ js::ReportOutOfMemory(fc);
+ return false;
+ }
+ for (const auto& data : other.objLiteralData) {
+ size_t length = data.code().size();
+ auto* code = alloc.newArrayUninitialized<uint8_t>(length);
+ if (!code) {
+ js::ReportOutOfMemory(fc);
+ return false;
+ }
+ memcpy(code, data.code().data(), length);
+ objLiteralData.infallibleEmplaceBack(code, length, data.kind(),
+ data.flags(), data.propertyCount());
+ }
+
+ // Regardless of whether CompilationStencil has external dependency or not,
+ // ParserAtoms should be interned, to populate internal HashMap.
+ for (const auto* entry : other.parserAtomsSpan()) {
+ if (!entry) {
+ if (!parserAtoms.addPlaceholder(fc)) {
+ return false;
+ }
+ continue;
+ }
+
+ auto index = parserAtoms.internExternalParserAtom(fc, entry);
+ if (!index) {
+ return false;
+ }
+ }
+
+ // We copy the stencil and increment the reference count of each
+ // SharedImmutableScriptData.
+ if (!sharedData.cloneFrom(fc, other.sharedData)) {
+ return false;
+ }
+
+ // Note: moduleMetadata and asmJS are known after the first parse, and are
+ // not mutated by any delazifications later on. Thus we can safely increment
+ // the reference counter and keep these as-is.
+ moduleMetadata = other.moduleMetadata;
+ asmJS = other.asmJS;
+
+#ifdef DEBUG
+ assertNoExternalDependency();
+#endif
+
+ return true;
+}
+
+bool ExtensibleCompilationStencil::cloneFrom(FrontendContext* fc,
+ const CompilationStencil& other) {
+ return cloneFromImpl(fc, other);
+}
+bool ExtensibleCompilationStencil::cloneFrom(
+ FrontendContext* fc, const ExtensibleCompilationStencil& other) {
+ return cloneFromImpl(fc, other);
+}
+
+bool ExtensibleCompilationStencil::steal(FrontendContext* fc,
+ RefPtr<CompilationStencil>&& other) {
+ MOZ_ASSERT(alloc.isEmpty());
+ using StorageType = CompilationStencil::StorageType;
+ StorageType storageType = other->storageType;
+ if (other->refCount > 1) {
+ storageType = StorageType::Borrowed;
+ }
+
+ if (storageType == StorageType::OwnedExtensible) {
+ auto& otherExtensible = other->ownedBorrowStencil;
+
+ canLazilyParse = otherExtensible->canLazilyParse;
+ functionKey = otherExtensible->functionKey;
+
+ alloc.steal(&otherExtensible->alloc);
+
+ source = std::move(otherExtensible->source);
+
+ scriptData = std::move(otherExtensible->scriptData);
+ scriptExtra = std::move(otherExtensible->scriptExtra);
+ gcThingData = std::move(otherExtensible->gcThingData);
+ scopeData = std::move(otherExtensible->scopeData);
+ scopeNames = std::move(otherExtensible->scopeNames);
+ regExpData = std::move(otherExtensible->regExpData);
+ bigIntData = std::move(otherExtensible->bigIntData);
+ objLiteralData = std::move(otherExtensible->objLiteralData);
+
+ parserAtoms = std::move(otherExtensible->parserAtoms);
+ parserAtoms.fixupAlloc(alloc);
+
+ sharedData = std::move(otherExtensible->sharedData);
+ moduleMetadata = std::move(otherExtensible->moduleMetadata);
+ asmJS = std::move(otherExtensible->asmJS);
+
+#ifdef DEBUG
+ assertNoExternalDependency();
+#endif
+
+ return true;
+ }
+
+ if (storageType == StorageType::Borrowed) {
+ return cloneFrom(fc, *other);
+ }
+
+ MOZ_ASSERT(storageType == StorageType::Owned);
+
+ canLazilyParse = other->canLazilyParse;
+ functionKey = other->functionKey;
+
+#ifdef DEBUG
+ other->assertNoExternalDependency();
+ MOZ_ASSERT(other->refCount == 1);
+#endif
+
+ // If CompilationStencil has no external dependency,
+ // steal LifoAlloc and perform shallow copy.
+ alloc.steal(&other->alloc);
+
+ if (!CopySpanToVector(fc, scriptData, other->scriptData)) {
+ return false;
+ }
+
+ if (!CopySpanToVector(fc, scriptExtra, other->scriptExtra)) {
+ return false;
+ }
+
+ if (!CopySpanToVector(fc, gcThingData, other->gcThingData)) {
+ return false;
+ }
+
+ if (!CopySpanToVector(fc, scopeData, other->scopeData)) {
+ return false;
+ }
+ if (!CopySpanToVector(fc, scopeNames, other->scopeNames)) {
+ return false;
+ }
+
+ if (!CopySpanToVector(fc, regExpData, other->regExpData)) {
+ return false;
+ }
+
+ if (!CopySpanToVector(fc, bigIntData, other->bigIntData)) {
+ return false;
+ }
+
+ if (!CopySpanToVector(fc, objLiteralData, other->objLiteralData)) {
+ return false;
+ }
+
+ // Regardless of whether CompilationStencil has external dependency or not,
+ // ParserAtoms should be interned, to populate internal HashMap.
+ for (const auto* entry : other->parserAtomData) {
+ if (!entry) {
+ if (!parserAtoms.addPlaceholder(fc)) {
+ return false;
+ }
+ continue;
+ }
+
+ auto index = parserAtoms.internExternalParserAtom(fc, entry);
+ if (!index) {
+ return false;
+ }
+ }
+
+ sharedData = std::move(other->sharedData);
+ moduleMetadata = std::move(other->moduleMetadata);
+ asmJS = std::move(other->asmJS);
+
+#ifdef DEBUG
+ assertNoExternalDependency();
+#endif
+
+ return true;
+}
+
+bool CompilationStencil::isModule() const {
+ return scriptExtra[CompilationStencil::TopLevelIndex].isModule();
+}
+
+bool ExtensibleCompilationStencil::isModule() const {
+ return scriptExtra[CompilationStencil::TopLevelIndex].isModule();
+}
+
+mozilla::Span<TaggedScriptThingIndex> ScriptStencil::gcthings(
+ const CompilationStencil& stencil) const {
+ return stencil.gcThingData.Subspan(gcThingsOffset, gcThingsLength);
+}
+
+bool BigIntStencil::init(FrontendContext* fc, LifoAlloc& alloc,
+ const mozilla::Span<const char16_t> buf) {
+#ifdef DEBUG
+ // Assert we have no separators; if we have a separator then the algorithm
+ // used in BigInt::literalIsZero will be incorrect.
+ for (char16_t c : buf) {
+ MOZ_ASSERT(c != '_');
+ }
+#endif
+ size_t length = buf.size();
+ char16_t* p = alloc.template newArrayUninitialized<char16_t>(length);
+ if (!p) {
+ ReportOutOfMemory(fc);
+ return false;
+ }
+ mozilla::PodCopy(p, buf.data(), length);
+ source_ = mozilla::Span(p, length);
+ return true;
+}
+
+BigInt* BigIntStencil::createBigInt(JSContext* cx) const {
+ mozilla::Range<const char16_t> source(source_.data(), source_.size());
+ return js::ParseBigIntLiteral(cx, source);
+}
+
+bool BigIntStencil::isZero() const {
+ mozilla::Range<const char16_t> source(source_.data(), source_.size());
+ return js::BigIntLiteralIsZero(source);
+}
+
+#ifdef DEBUG
+bool BigIntStencil::isContainedIn(const LifoAlloc& alloc) const {
+ return alloc.contains(source_.data());
+}
+#endif
+
+#if defined(DEBUG) || defined(JS_JITSPEW)
+
+void frontend::DumpTaggedParserAtomIndex(js::JSONPrinter& json,
+ TaggedParserAtomIndex taggedIndex,
+ const CompilationStencil* stencil) {
+ if (taggedIndex.isParserAtomIndex()) {
+ json.property("tag", "AtomIndex");
+ auto index = taggedIndex.toParserAtomIndex();
+ if (stencil && stencil->parserAtomData[index]) {
+ GenericPrinter& out = json.beginStringProperty("atom");
+ stencil->parserAtomData[index]->dumpCharsNoQuote(out);
+ json.endString();
+ } else {
+ json.property("index", size_t(index));
+ }
+ return;
+ }
+
+ if (taggedIndex.isWellKnownAtomId()) {
+ json.property("tag", "WellKnown");
+ auto index = taggedIndex.toWellKnownAtomId();
+ switch (index) {
+ case WellKnownAtomId::empty:
+ json.property("atom", "");
+ break;
+
+# define CASE_(_, name, _2) case WellKnownAtomId::name:
+ FOR_EACH_NONTINY_COMMON_PROPERTYNAME(CASE_)
+# undef CASE_
+
+# define CASE_(name, _) case WellKnownAtomId::name:
+ JS_FOR_EACH_PROTOTYPE(CASE_)
+# undef CASE_
+
+# define CASE_(name) case WellKnownAtomId::name:
+ JS_FOR_EACH_WELL_KNOWN_SYMBOL(CASE_)
+# undef CASE_
+
+ {
+ GenericPrinter& out = json.beginStringProperty("atom");
+ ParserAtomsTable::dumpCharsNoQuote(out, index);
+ json.endString();
+ break;
+ }
+
+ default:
+ // This includes tiny WellKnownAtomId atoms, which is invalid.
+ json.property("index", size_t(index));
+ break;
+ }
+ return;
+ }
+
+ if (taggedIndex.isLength1StaticParserString()) {
+ json.property("tag", "Length1Static");
+ auto index = taggedIndex.toLength1StaticParserString();
+ GenericPrinter& out = json.beginStringProperty("atom");
+ ParserAtomsTable::dumpCharsNoQuote(out, index);
+ json.endString();
+ return;
+ }
+
+ if (taggedIndex.isLength2StaticParserString()) {
+ json.property("tag", "Length2Static");
+ auto index = taggedIndex.toLength2StaticParserString();
+ GenericPrinter& out = json.beginStringProperty("atom");
+ ParserAtomsTable::dumpCharsNoQuote(out, index);
+ json.endString();
+ return;
+ }
+
+ if (taggedIndex.isLength3StaticParserString()) {
+ json.property("tag", "Length3Static");
+ auto index = taggedIndex.toLength3StaticParserString();
+ GenericPrinter& out = json.beginStringProperty("atom");
+ ParserAtomsTable::dumpCharsNoQuote(out, index);
+ json.endString();
+ return;
+ }
+
+ MOZ_ASSERT(taggedIndex.isNull());
+ json.property("tag", "null");
+}
+
+void frontend::DumpTaggedParserAtomIndexNoQuote(
+ GenericPrinter& out, TaggedParserAtomIndex taggedIndex,
+ const CompilationStencil* stencil) {
+ if (taggedIndex.isParserAtomIndex()) {
+ auto index = taggedIndex.toParserAtomIndex();
+ if (stencil && stencil->parserAtomData[index]) {
+ stencil->parserAtomData[index]->dumpCharsNoQuote(out);
+ } else {
+ out.printf("AtomIndex#%zu", size_t(index));
+ }
+ return;
+ }
+
+ if (taggedIndex.isWellKnownAtomId()) {
+ auto index = taggedIndex.toWellKnownAtomId();
+ switch (index) {
+ case WellKnownAtomId::empty:
+ out.put("#<zero-length name>");
+ break;
+
+# define CASE_(_, name, _2) case WellKnownAtomId::name:
+ FOR_EACH_NONTINY_COMMON_PROPERTYNAME(CASE_)
+# undef CASE_
+
+# define CASE_(name, _) case WellKnownAtomId::name:
+ JS_FOR_EACH_PROTOTYPE(CASE_)
+# undef CASE_
+
+# define CASE_(name) case WellKnownAtomId::name:
+ JS_FOR_EACH_WELL_KNOWN_SYMBOL(CASE_)
+# undef CASE_
+
+ {
+ ParserAtomsTable::dumpCharsNoQuote(out, index);
+ break;
+ }
+
+ default:
+ // This includes tiny WellKnownAtomId atoms, which is invalid.
+ out.printf("WellKnown#%zu", size_t(index));
+ break;
+ }
+ return;
+ }
+
+ if (taggedIndex.isLength1StaticParserString()) {
+ auto index = taggedIndex.toLength1StaticParserString();
+ ParserAtomsTable::dumpCharsNoQuote(out, index);
+ return;
+ }
+
+ if (taggedIndex.isLength2StaticParserString()) {
+ auto index = taggedIndex.toLength2StaticParserString();
+ ParserAtomsTable::dumpCharsNoQuote(out, index);
+ return;
+ }
+
+ if (taggedIndex.isLength3StaticParserString()) {
+ auto index = taggedIndex.toLength3StaticParserString();
+ ParserAtomsTable::dumpCharsNoQuote(out, index);
+ return;
+ }
+
+ MOZ_ASSERT(taggedIndex.isNull());
+ out.put("#<null name>");
+}
+
+void RegExpStencil::dump() const {
+ js::Fprinter out(stderr);
+ js::JSONPrinter json(out);
+ dump(json, nullptr);
+}
+
+void RegExpStencil::dump(js::JSONPrinter& json,
+ const CompilationStencil* stencil) const {
+ json.beginObject();
+ dumpFields(json, stencil);
+ json.endObject();
+}
+
+void RegExpStencil::dumpFields(js::JSONPrinter& json,
+ const CompilationStencil* stencil) const {
+ json.beginObjectProperty("pattern");
+ DumpTaggedParserAtomIndex(json, atom_, stencil);
+ json.endObject();
+
+ GenericPrinter& out = json.beginStringProperty("flags");
+
+ if (flags().global()) {
+ out.put("g");
+ }
+ if (flags().ignoreCase()) {
+ out.put("i");
+ }
+ if (flags().multiline()) {
+ out.put("m");
+ }
+ if (flags().dotAll()) {
+ out.put("s");
+ }
+ if (flags().unicode()) {
+ out.put("u");
+ }
+ if (flags().sticky()) {
+ out.put("y");
+ }
+
+ json.endStringProperty();
+}
+
+void BigIntStencil::dump() const {
+ js::Fprinter out(stderr);
+ js::JSONPrinter json(out);
+ dump(json);
+}
+
+void BigIntStencil::dump(js::JSONPrinter& json) const {
+ GenericPrinter& out = json.beginString();
+ dumpCharsNoQuote(out);
+ json.endString();
+}
+
+void BigIntStencil::dumpCharsNoQuote(GenericPrinter& out) const {
+ for (char16_t c : source_) {
+ out.putChar(char(c));
+ }
+}
+
+void ScopeStencil::dump() const {
+ js::Fprinter out(stderr);
+ js::JSONPrinter json(out);
+ dump(json, nullptr, nullptr);
+}
+
+void ScopeStencil::dump(js::JSONPrinter& json,
+ const BaseParserScopeData* baseScopeData,
+ const CompilationStencil* stencil) const {
+ json.beginObject();
+ dumpFields(json, baseScopeData, stencil);
+ json.endObject();
+}
+
+void ScopeStencil::dumpFields(js::JSONPrinter& json,
+ const BaseParserScopeData* baseScopeData,
+ const CompilationStencil* stencil) const {
+ json.property("kind", ScopeKindString(kind_));
+
+ if (hasEnclosing()) {
+ json.formatProperty("enclosing", "ScopeIndex(%zu)", size_t(enclosing()));
+ }
+
+ json.property("firstFrameSlot", firstFrameSlot_);
+
+ if (hasEnvironmentShape()) {
+ json.formatProperty("numEnvironmentSlots", "%zu",
+ size_t(numEnvironmentSlots_));
+ }
+
+ if (isFunction()) {
+ json.formatProperty("functionIndex", "ScriptIndex(%zu)",
+ size_t(functionIndex_));
+ }
+
+ json.beginListProperty("flags");
+ if (flags_ & HasEnclosing) {
+ json.value("HasEnclosing");
+ }
+ if (flags_ & HasEnvironmentShape) {
+ json.value("HasEnvironmentShape");
+ }
+ if (flags_ & IsArrow) {
+ json.value("IsArrow");
+ }
+ json.endList();
+
+ if (!baseScopeData) {
+ return;
+ }
+
+ json.beginObjectProperty("data");
+
+ mozilla::Span<const ParserBindingName> trailingNames;
+ switch (kind_) {
+ case ScopeKind::Function: {
+ const auto* data =
+ static_cast<const 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 = GetScopeDataTrailingNames(data);
+ break;
+ }
+
+ case ScopeKind::FunctionBodyVar: {
+ const auto* data =
+ static_cast<const VarScope::ParserData*>(baseScopeData);
+ json.property("nextFrameSlot", data->slotInfo.nextFrameSlot);
+
+ trailingNames = GetScopeDataTrailingNames(data);
+ break;
+ }
+
+ case ScopeKind::Lexical:
+ case ScopeKind::SimpleCatch:
+ case ScopeKind::Catch:
+ case ScopeKind::NamedLambda:
+ case ScopeKind::StrictNamedLambda:
+ case ScopeKind::FunctionLexical: {
+ const auto* data =
+ static_cast<const LexicalScope::ParserData*>(baseScopeData);
+ json.property("nextFrameSlot", data->slotInfo.nextFrameSlot);
+ json.property("constStart", data->slotInfo.constStart);
+
+ trailingNames = GetScopeDataTrailingNames(data);
+ break;
+ }
+
+ case ScopeKind::ClassBody: {
+ const auto* data =
+ static_cast<const ClassBodyScope::ParserData*>(baseScopeData);
+ json.property("nextFrameSlot", data->slotInfo.nextFrameSlot);
+ json.property("privateMethodStart", data->slotInfo.privateMethodStart);
+
+ trailingNames = GetScopeDataTrailingNames(data);
+ break;
+ }
+
+ case ScopeKind::With: {
+ break;
+ }
+
+ case ScopeKind::Eval:
+ case ScopeKind::StrictEval: {
+ const auto* data =
+ static_cast<const EvalScope::ParserData*>(baseScopeData);
+ json.property("nextFrameSlot", data->slotInfo.nextFrameSlot);
+
+ trailingNames = GetScopeDataTrailingNames(data);
+ break;
+ }
+
+ case ScopeKind::Global:
+ case ScopeKind::NonSyntactic: {
+ const auto* data =
+ static_cast<const GlobalScope::ParserData*>(baseScopeData);
+ json.property("letStart", data->slotInfo.letStart);
+ json.property("constStart", data->slotInfo.constStart);
+
+ trailingNames = GetScopeDataTrailingNames(data);
+ break;
+ }
+
+ case ScopeKind::Module: {
+ const auto* data =
+ static_cast<const 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 = GetScopeDataTrailingNames(data);
+ break;
+ }
+
+ case ScopeKind::WasmInstance: {
+ const auto* data =
+ static_cast<const WasmInstanceScope::ParserData*>(baseScopeData);
+ json.property("nextFrameSlot", data->slotInfo.nextFrameSlot);
+ json.property("globalsStart", data->slotInfo.globalsStart);
+
+ trailingNames = GetScopeDataTrailingNames(data);
+ break;
+ }
+
+ case ScopeKind::WasmFunction: {
+ const auto* data =
+ static_cast<const WasmFunctionScope::ParserData*>(baseScopeData);
+ json.property("nextFrameSlot", data->slotInfo.nextFrameSlot);
+
+ trailingNames = GetScopeDataTrailingNames(data);
+ break;
+ }
+
+ default: {
+ MOZ_CRASH("Unexpected ScopeKind");
+ break;
+ }
+ }
+
+ if (!trailingNames.empty()) {
+ char index[64];
+ json.beginObjectProperty("trailingNames");
+ for (size_t i = 0; i < trailingNames.size(); i++) {
+ const auto& name = trailingNames[i];
+ SprintfLiteral(index, "%zu", i);
+ json.beginObjectProperty(index);
+
+ json.boolProperty("closedOver", name.closedOver());
+
+ json.boolProperty("isTopLevelFunction", name.isTopLevelFunction());
+
+ json.beginObjectProperty("name");
+ DumpTaggedParserAtomIndex(json, name.name(), stencil);
+ json.endObject();
+
+ json.endObject();
+ }
+ json.endObject();
+ }
+
+ json.endObject();
+}
+
+static void DumpModuleRequestVectorItems(
+ js::JSONPrinter& json, const StencilModuleMetadata::RequestVector& requests,
+ const CompilationStencil* stencil) {
+ for (const auto& request : requests) {
+ json.beginObject();
+ if (request.specifier) {
+ json.beginObjectProperty("specifier");
+ DumpTaggedParserAtomIndex(json, request.specifier, stencil);
+ json.endObject();
+ }
+ json.endObject();
+ }
+}
+
+static void DumpModuleEntryVectorItems(
+ js::JSONPrinter& json, const StencilModuleMetadata::EntryVector& entries,
+ const CompilationStencil* stencil) {
+ for (const auto& entry : entries) {
+ json.beginObject();
+ if (entry.moduleRequest) {
+ json.property("moduleRequest", entry.moduleRequest.value());
+ }
+ if (entry.localName) {
+ json.beginObjectProperty("localName");
+ DumpTaggedParserAtomIndex(json, entry.localName, stencil);
+ json.endObject();
+ }
+ if (entry.importName) {
+ json.beginObjectProperty("importName");
+ DumpTaggedParserAtomIndex(json, entry.importName, stencil);
+ json.endObject();
+ }
+ if (entry.exportName) {
+ json.beginObjectProperty("exportName");
+ DumpTaggedParserAtomIndex(json, entry.exportName, stencil);
+ json.endObject();
+ }
+ // TODO: Dump assertions.
+ json.endObject();
+ }
+}
+
+void StencilModuleMetadata::dump() const {
+ js::Fprinter out(stderr);
+ js::JSONPrinter json(out);
+ dump(json, nullptr);
+}
+
+void StencilModuleMetadata::dump(js::JSONPrinter& json,
+ const CompilationStencil* stencil) const {
+ json.beginObject();
+ dumpFields(json, stencil);
+ json.endObject();
+}
+
+void StencilModuleMetadata::dumpFields(
+ js::JSONPrinter& json, const CompilationStencil* stencil) const {
+ json.beginListProperty("moduleRequests");
+ DumpModuleRequestVectorItems(json, moduleRequests, stencil);
+ json.endList();
+
+ json.beginListProperty("requestedModules");
+ DumpModuleEntryVectorItems(json, requestedModules, stencil);
+ json.endList();
+
+ json.beginListProperty("importEntries");
+ DumpModuleEntryVectorItems(json, importEntries, stencil);
+ json.endList();
+
+ json.beginListProperty("localExportEntries");
+ DumpModuleEntryVectorItems(json, localExportEntries, stencil);
+ json.endList();
+
+ json.beginListProperty("indirectExportEntries");
+ DumpModuleEntryVectorItems(json, indirectExportEntries, stencil);
+ json.endList();
+
+ json.beginListProperty("starExportEntries");
+ DumpModuleEntryVectorItems(json, starExportEntries, stencil);
+ json.endList();
+
+ json.beginListProperty("functionDecls");
+ for (const auto& index : functionDecls) {
+ json.value("ScriptIndex(%zu)", size_t(index));
+ }
+ json.endList();
+
+ json.boolProperty("isAsync", isAsync);
+}
+
+void js::DumpImmutableScriptFlags(js::JSONPrinter& json,
+ ImmutableScriptFlags immutableFlags) {
+ for (uint32_t i = 1; i; i = i << 1) {
+ if (uint32_t(immutableFlags) & i) {
+ switch (ImmutableScriptFlagsEnum(i)) {
+ case ImmutableScriptFlagsEnum::IsForEval:
+ json.value("IsForEval");
+ break;
+ case ImmutableScriptFlagsEnum::IsModule:
+ json.value("IsModule");
+ break;
+ case ImmutableScriptFlagsEnum::IsFunction:
+ json.value("IsFunction");
+ break;
+ case ImmutableScriptFlagsEnum::SelfHosted:
+ json.value("SelfHosted");
+ break;
+ case ImmutableScriptFlagsEnum::ForceStrict:
+ json.value("ForceStrict");
+ break;
+ case ImmutableScriptFlagsEnum::HasNonSyntacticScope:
+ json.value("HasNonSyntacticScope");
+ break;
+ case ImmutableScriptFlagsEnum::NoScriptRval:
+ json.value("NoScriptRval");
+ break;
+ case ImmutableScriptFlagsEnum::TreatAsRunOnce:
+ json.value("TreatAsRunOnce");
+ break;
+ case ImmutableScriptFlagsEnum::Strict:
+ json.value("Strict");
+ break;
+ case ImmutableScriptFlagsEnum::HasModuleGoal:
+ json.value("HasModuleGoal");
+ break;
+ case ImmutableScriptFlagsEnum::HasInnerFunctions:
+ json.value("HasInnerFunctions");
+ break;
+ case ImmutableScriptFlagsEnum::HasDirectEval:
+ json.value("HasDirectEval");
+ break;
+ case ImmutableScriptFlagsEnum::BindingsAccessedDynamically:
+ json.value("BindingsAccessedDynamically");
+ break;
+ case ImmutableScriptFlagsEnum::HasCallSiteObj:
+ json.value("HasCallSiteObj");
+ break;
+ case ImmutableScriptFlagsEnum::IsAsync:
+ json.value("IsAsync");
+ break;
+ case ImmutableScriptFlagsEnum::IsGenerator:
+ json.value("IsGenerator");
+ break;
+ case ImmutableScriptFlagsEnum::FunHasExtensibleScope:
+ json.value("FunHasExtensibleScope");
+ break;
+ case ImmutableScriptFlagsEnum::FunctionHasThisBinding:
+ json.value("FunctionHasThisBinding");
+ break;
+ case ImmutableScriptFlagsEnum::NeedsHomeObject:
+ json.value("NeedsHomeObject");
+ break;
+ case ImmutableScriptFlagsEnum::IsDerivedClassConstructor:
+ json.value("IsDerivedClassConstructor");
+ break;
+ case ImmutableScriptFlagsEnum::IsSyntheticFunction:
+ json.value("IsSyntheticFunction");
+ break;
+ case ImmutableScriptFlagsEnum::UseMemberInitializers:
+ json.value("UseMemberInitializers");
+ break;
+ case ImmutableScriptFlagsEnum::HasRest:
+ json.value("HasRest");
+ break;
+ case ImmutableScriptFlagsEnum::NeedsFunctionEnvironmentObjects:
+ json.value("NeedsFunctionEnvironmentObjects");
+ break;
+ case ImmutableScriptFlagsEnum::FunctionHasExtraBodyVarScope:
+ json.value("FunctionHasExtraBodyVarScope");
+ break;
+ case ImmutableScriptFlagsEnum::ShouldDeclareArguments:
+ json.value("ShouldDeclareArguments");
+ break;
+ case ImmutableScriptFlagsEnum::NeedsArgsObj:
+ json.value("NeedsArgsObj");
+ break;
+ case ImmutableScriptFlagsEnum::HasMappedArgsObj:
+ json.value("HasMappedArgsObj");
+ break;
+ case ImmutableScriptFlagsEnum::IsInlinableLargeFunction:
+ json.value("IsInlinableLargeFunction");
+ break;
+ case ImmutableScriptFlagsEnum::FunctionHasNewTargetBinding:
+ json.value("FunctionHasNewTargetBinding");
+ break;
+ case ImmutableScriptFlagsEnum::UsesArgumentsIntrinsics:
+ json.value("UsesArgumentsIntrinsics");
+ break;
+ default:
+ json.value("Unknown(%x)", i);
+ break;
+ }
+ }
+ }
+}
+
+void js::DumpFunctionFlagsItems(js::JSONPrinter& json,
+ FunctionFlags functionFlags) {
+ switch (functionFlags.kind()) {
+ case FunctionFlags::FunctionKind::NormalFunction:
+ json.value("NORMAL_KIND");
+ break;
+ case FunctionFlags::FunctionKind::AsmJS:
+ json.value("ASMJS_KIND");
+ break;
+ case FunctionFlags::FunctionKind::Wasm:
+ json.value("WASM_KIND");
+ break;
+ case FunctionFlags::FunctionKind::Arrow:
+ json.value("ARROW_KIND");
+ break;
+ case FunctionFlags::FunctionKind::Method:
+ json.value("METHOD_KIND");
+ break;
+ case FunctionFlags::FunctionKind::ClassConstructor:
+ json.value("CLASSCONSTRUCTOR_KIND");
+ break;
+ case FunctionFlags::FunctionKind::Getter:
+ json.value("GETTER_KIND");
+ break;
+ case FunctionFlags::FunctionKind::Setter:
+ json.value("SETTER_KIND");
+ break;
+ default:
+ json.value("Unknown(%x)", uint8_t(functionFlags.kind()));
+ break;
+ }
+
+ static_assert(FunctionFlags::FUNCTION_KIND_MASK == 0x0007,
+ "FunctionKind should use the lowest 3 bits");
+ for (uint16_t i = 1 << 3; i; i = i << 1) {
+ if (functionFlags.toRaw() & i) {
+ switch (FunctionFlags::Flags(i)) {
+ case FunctionFlags::Flags::EXTENDED:
+ json.value("EXTENDED");
+ break;
+ case FunctionFlags::Flags::SELF_HOSTED:
+ json.value("SELF_HOSTED");
+ break;
+ case FunctionFlags::Flags::BASESCRIPT:
+ json.value("BASESCRIPT");
+ break;
+ case FunctionFlags::Flags::SELFHOSTLAZY:
+ json.value("SELFHOSTLAZY");
+ break;
+ case FunctionFlags::Flags::CONSTRUCTOR:
+ json.value("CONSTRUCTOR");
+ break;
+ case FunctionFlags::Flags::LAMBDA:
+ json.value("LAMBDA");
+ break;
+ case FunctionFlags::Flags::WASM_JIT_ENTRY:
+ json.value("WASM_JIT_ENTRY");
+ break;
+ case FunctionFlags::Flags::HAS_INFERRED_NAME:
+ json.value("HAS_INFERRED_NAME");
+ break;
+ case FunctionFlags::Flags::HAS_GUESSED_ATOM:
+ json.value("HAS_GUESSED_ATOM");
+ break;
+ case FunctionFlags::Flags::RESOLVED_NAME:
+ json.value("RESOLVED_NAME");
+ break;
+ case FunctionFlags::Flags::RESOLVED_LENGTH:
+ json.value("RESOLVED_LENGTH");
+ break;
+ case FunctionFlags::Flags::GHOST_FUNCTION:
+ json.value("GHOST_FUNCTION");
+ break;
+ default:
+ json.value("Unknown(%x)", i);
+ break;
+ }
+ }
+ }
+}
+
+static void DumpScriptThing(js::JSONPrinter& json,
+ const CompilationStencil* stencil,
+ TaggedScriptThingIndex thing) {
+ switch (thing.tag()) {
+ case TaggedScriptThingIndex::Kind::ParserAtomIndex:
+ case TaggedScriptThingIndex::Kind::WellKnown:
+ json.beginObject();
+ json.property("type", "Atom");
+ DumpTaggedParserAtomIndex(json, thing.toAtom(), stencil);
+ json.endObject();
+ break;
+ case TaggedScriptThingIndex::Kind::Null:
+ json.nullValue();
+ break;
+ case TaggedScriptThingIndex::Kind::BigInt:
+ json.value("BigIntIndex(%zu)", size_t(thing.toBigInt()));
+ break;
+ case TaggedScriptThingIndex::Kind::ObjLiteral:
+ json.value("ObjLiteralIndex(%zu)", size_t(thing.toObjLiteral()));
+ break;
+ case TaggedScriptThingIndex::Kind::RegExp:
+ json.value("RegExpIndex(%zu)", size_t(thing.toRegExp()));
+ break;
+ case TaggedScriptThingIndex::Kind::Scope:
+ json.value("ScopeIndex(%zu)", size_t(thing.toScope()));
+ break;
+ case TaggedScriptThingIndex::Kind::Function:
+ json.value("ScriptIndex(%zu)", size_t(thing.toFunction()));
+ break;
+ case TaggedScriptThingIndex::Kind::EmptyGlobalScope:
+ json.value("EmptyGlobalScope");
+ break;
+ }
+}
+
+void ScriptStencil::dump() const {
+ js::Fprinter out(stderr);
+ js::JSONPrinter json(out);
+ dump(json, nullptr);
+}
+
+void ScriptStencil::dump(js::JSONPrinter& json,
+ const CompilationStencil* stencil) const {
+ json.beginObject();
+ dumpFields(json, stencil);
+ json.endObject();
+}
+
+void ScriptStencil::dumpFields(js::JSONPrinter& json,
+ const CompilationStencil* stencil) const {
+ json.formatProperty("gcThingsOffset", "CompilationGCThingIndex(%u)",
+ gcThingsOffset.index);
+ json.property("gcThingsLength", gcThingsLength);
+
+ if (stencil) {
+ json.beginListProperty("gcThings");
+ for (const auto& thing : gcthings(*stencil)) {
+ DumpScriptThing(json, stencil, thing);
+ }
+ json.endList();
+ }
+
+ json.beginListProperty("flags");
+ if (flags_ & WasEmittedByEnclosingScriptFlag) {
+ json.value("WasEmittedByEnclosingScriptFlag");
+ }
+ if (flags_ & AllowRelazifyFlag) {
+ json.value("AllowRelazifyFlag");
+ }
+ if (flags_ & HasSharedDataFlag) {
+ json.value("HasSharedDataFlag");
+ }
+ if (flags_ & HasLazyFunctionEnclosingScopeIndexFlag) {
+ json.value("HasLazyFunctionEnclosingScopeIndexFlag");
+ }
+ json.endList();
+
+ if (isFunction()) {
+ json.beginObjectProperty("functionAtom");
+ DumpTaggedParserAtomIndex(json, functionAtom, stencil);
+ json.endObject();
+
+ json.beginListProperty("functionFlags");
+ DumpFunctionFlagsItems(json, functionFlags);
+ json.endList();
+
+ if (hasLazyFunctionEnclosingScopeIndex()) {
+ json.formatProperty("lazyFunctionEnclosingScopeIndex", "ScopeIndex(%zu)",
+ size_t(lazyFunctionEnclosingScopeIndex()));
+ }
+
+ if (hasSelfHostedCanonicalName()) {
+ json.beginObjectProperty("selfHostCanonicalName");
+ DumpTaggedParserAtomIndex(json, selfHostedCanonicalName(), stencil);
+ json.endObject();
+ }
+ }
+}
+
+void ScriptStencilExtra::dump() const {
+ js::Fprinter out(stderr);
+ js::JSONPrinter json(out);
+ dump(json);
+}
+
+void ScriptStencilExtra::dump(js::JSONPrinter& json) const {
+ json.beginObject();
+ dumpFields(json);
+ json.endObject();
+}
+
+void ScriptStencilExtra::dumpFields(js::JSONPrinter& json) const {
+ json.beginListProperty("immutableFlags");
+ DumpImmutableScriptFlags(json, immutableFlags);
+ json.endList();
+
+ json.beginObjectProperty("extent");
+ json.property("sourceStart", extent.sourceStart);
+ json.property("sourceEnd", extent.sourceEnd);
+ json.property("toStringStart", extent.toStringStart);
+ json.property("toStringEnd", extent.toStringEnd);
+ json.property("lineno", extent.lineno);
+ json.property("column", extent.column);
+ json.endObject();
+
+ json.property("memberInitializers", memberInitializers_);
+
+ json.property("nargs", nargs);
+}
+
+void SharedDataContainer::dump() const {
+ js::Fprinter out(stderr);
+ js::JSONPrinter json(out);
+ dump(json);
+}
+
+void SharedDataContainer::dump(js::JSONPrinter& json) const {
+ json.beginObject();
+ dumpFields(json);
+ json.endObject();
+}
+
+void SharedDataContainer::dumpFields(js::JSONPrinter& json) const {
+ if (isEmpty()) {
+ json.nullProperty("ScriptIndex(0)");
+ return;
+ }
+
+ if (isSingle()) {
+ json.formatProperty("ScriptIndex(0)", "u8[%zu]",
+ asSingle()->immutableDataLength());
+ return;
+ }
+
+ if (isVector()) {
+ auto& vec = *asVector();
+
+ char index[64];
+ for (size_t i = 0; i < vec.length(); i++) {
+ SprintfLiteral(index, "ScriptIndex(%zu)", i);
+ if (vec[i]) {
+ json.formatProperty(index, "u8[%zu]", vec[i]->immutableDataLength());
+ } else {
+ json.nullProperty(index);
+ }
+ }
+ return;
+ }
+
+ if (isMap()) {
+ auto& map = *asMap();
+
+ char index[64];
+ for (auto iter = map.iter(); !iter.done(); iter.next()) {
+ SprintfLiteral(index, "ScriptIndex(%u)", iter.get().key().index);
+ json.formatProperty(index, "u8[%zu]",
+ iter.get().value()->immutableDataLength());
+ }
+ return;
+ }
+
+ MOZ_ASSERT(isBorrow());
+ asBorrow()->dumpFields(json);
+}
+
+struct DumpOptionsFields {
+ js::JSONPrinter& json;
+
+ void operator()(const char* name, JS::AsmJSOption value) {
+ const char* valueStr = nullptr;
+ switch (value) {
+ case JS::AsmJSOption::Enabled:
+ valueStr = "JS::AsmJSOption::Enabled";
+ break;
+ case JS::AsmJSOption::DisabledByAsmJSPref:
+ valueStr = "JS::AsmJSOption::DisabledByAsmJSPref";
+ break;
+ case JS::AsmJSOption::DisabledByLinker:
+ valueStr = "JS::AsmJSOption::DisabledByLinker";
+ break;
+ case JS::AsmJSOption::DisabledByNoWasmCompiler:
+ valueStr = "JS::AsmJSOption::DisabledByNoWasmCompiler";
+ break;
+ case JS::AsmJSOption::DisabledByDebugger:
+ valueStr = "JS::AsmJSOption::DisabledByDebugger";
+ break;
+ }
+ json.property(name, valueStr);
+ }
+
+ void operator()(const char* name, JS::DelazificationOption value) {
+ const char* valueStr = nullptr;
+ switch (value) {
+# define SelectValueStr_(Strategy) \
+ case JS::DelazificationOption::Strategy: \
+ valueStr = "JS::DelazificationOption::" #Strategy; \
+ break;
+
+ FOREACH_DELAZIFICATION_STRATEGY(SelectValueStr_)
+# undef SelectValueStr_
+ }
+ json.property(name, valueStr);
+ }
+
+ void operator()(const char* name, char16_t* value) {}
+
+ void operator()(const char* name, bool value) { json.property(name, value); }
+
+ void operator()(const char* name, uint32_t value) {
+ json.property(name, value);
+ }
+
+ void operator()(const char* name, uint64_t value) {
+ json.property(name, value);
+ }
+
+ void operator()(const char* name, const char* value) {
+ if (value) {
+ json.property(name, value);
+ return;
+ }
+ json.nullProperty(name);
+ }
+};
+
+static void DumpOptionsFields(js::JSONPrinter& json,
+ const JS::ReadOnlyCompileOptions& options) {
+ struct DumpOptionsFields printer {
+ json
+ };
+ options.dumpWith(printer);
+}
+
+static void DumpInputScopeFields(js::JSONPrinter& json,
+ const InputScope& scope) {
+ json.property("kind", ScopeKindString(scope.kind()));
+
+ InputScope enclosing = scope.enclosing();
+ if (enclosing.isNull()) {
+ json.nullProperty("enclosing");
+ } else {
+ json.beginObjectProperty("enclosing");
+ DumpInputScopeFields(json, enclosing);
+ json.endObject();
+ }
+}
+
+static void DumpInputScriptFields(js::JSONPrinter& json,
+ const InputScript& script) {
+ json.beginObjectProperty("extent");
+ {
+ SourceExtent extent = script.extent();
+ json.property("sourceStart", extent.sourceStart);
+ json.property("sourceEnd", extent.sourceEnd);
+ json.property("toStringStart", extent.toStringStart);
+ json.property("toStringEnd", extent.toStringEnd);
+ json.property("lineno", extent.lineno);
+ json.property("column", extent.column);
+ }
+ json.endObject();
+
+ json.beginListProperty("immutableFlags");
+ DumpImmutableScriptFlags(json, script.immutableFlags());
+ json.endList();
+
+ json.beginListProperty("functionFlags");
+ DumpFunctionFlagsItems(json, script.functionFlags());
+ json.endList();
+
+ json.property("hasPrivateScriptData", script.hasPrivateScriptData());
+
+ InputScope scope = script.enclosingScope();
+ if (scope.isNull()) {
+ json.nullProperty("enclosingScope");
+ } else {
+ json.beginObjectProperty("enclosingScope");
+ DumpInputScopeFields(json, scope);
+ json.endObject();
+ }
+
+ if (script.useMemberInitializers()) {
+ json.property("memberInitializers",
+ script.getMemberInitializers().serialize());
+ }
+}
+
+void CompilationInput::dump() const {
+ js::Fprinter out(stderr);
+ js::JSONPrinter json(out);
+ dump(json);
+ out.put("\n");
+}
+
+void CompilationInput::dump(js::JSONPrinter& json) const {
+ json.beginObject();
+ dumpFields(json);
+ json.endObject();
+}
+
+void CompilationInput::dumpFields(js::JSONPrinter& json) const {
+ const char* targetStr = nullptr;
+ switch (target) {
+ case CompilationTarget::Global:
+ targetStr = "CompilationTarget::Global";
+ break;
+ case CompilationTarget::SelfHosting:
+ targetStr = "CompilationTarget::SelfHosting";
+ break;
+ case CompilationTarget::StandaloneFunction:
+ targetStr = "CompilationTarget::StandaloneFunction";
+ break;
+ case CompilationTarget::StandaloneFunctionInNonSyntacticScope:
+ targetStr = "CompilationTarget::StandaloneFunctionInNonSyntacticScope";
+ break;
+ case CompilationTarget::Eval:
+ targetStr = "CompilationTarget::Eval";
+ break;
+ case CompilationTarget::Module:
+ targetStr = "CompilationTarget::Module";
+ break;
+ case CompilationTarget::Delazification:
+ targetStr = "CompilationTarget::Delazification";
+ break;
+ }
+ json.property("target", targetStr);
+
+ json.beginObjectProperty("options");
+ DumpOptionsFields(json, options);
+ json.endObject();
+
+ if (lazy_.isNull()) {
+ json.nullProperty("lazy_");
+ } else {
+ json.beginObjectProperty("lazy_");
+ DumpInputScriptFields(json, lazy_);
+ json.endObject();
+ }
+
+ if (enclosingScope.isNull()) {
+ json.nullProperty("enclosingScope");
+ } else {
+ json.beginObjectProperty("enclosingScope");
+ DumpInputScopeFields(json, enclosingScope);
+ json.endObject();
+ }
+
+ // TODO: Support printing the atomCache and the source fields.
+}
+
+void CompilationStencil::dump() const {
+ js::Fprinter out(stderr);
+ js::JSONPrinter json(out);
+ dump(json);
+ out.put("\n");
+}
+
+void CompilationStencil::dump(js::JSONPrinter& json) const {
+ json.beginObject();
+ dumpFields(json);
+ json.endObject();
+}
+
+void CompilationStencil::dumpFields(js::JSONPrinter& json) const {
+ char index[64];
+
+ json.beginObjectProperty("scriptData");
+ for (size_t i = 0; i < scriptData.size(); i++) {
+ SprintfLiteral(index, "ScriptIndex(%zu)", i);
+ json.beginObjectProperty(index);
+ scriptData[i].dumpFields(json, this);
+ json.endObject();
+ }
+ json.endObject();
+
+ json.beginObjectProperty("scriptExtra");
+ for (size_t i = 0; i < scriptExtra.size(); i++) {
+ SprintfLiteral(index, "ScriptIndex(%zu)", i);
+ json.beginObjectProperty(index);
+ scriptExtra[i].dumpFields(json);
+ json.endObject();
+ }
+ json.endObject();
+
+ json.beginObjectProperty("scopeData");
+ MOZ_ASSERT(scopeData.size() == scopeNames.size());
+ for (size_t i = 0; i < scopeData.size(); i++) {
+ SprintfLiteral(index, "ScopeIndex(%zu)", i);
+ json.beginObjectProperty(index);
+ scopeData[i].dumpFields(json, scopeNames[i], this);
+ json.endObject();
+ }
+ json.endObject();
+
+ json.beginObjectProperty("sharedData");
+ sharedData.dumpFields(json);
+ json.endObject();
+
+ json.beginObjectProperty("regExpData");
+ for (size_t i = 0; i < regExpData.size(); i++) {
+ SprintfLiteral(index, "RegExpIndex(%zu)", i);
+ json.beginObjectProperty(index);
+ regExpData[i].dumpFields(json, this);
+ json.endObject();
+ }
+ json.endObject();
+
+ json.beginObjectProperty("bigIntData");
+ for (size_t i = 0; i < bigIntData.size(); i++) {
+ SprintfLiteral(index, "BigIntIndex(%zu)", i);
+ GenericPrinter& out = json.beginStringProperty(index);
+ bigIntData[i].dumpCharsNoQuote(out);
+ json.endStringProperty();
+ }
+ json.endObject();
+
+ json.beginObjectProperty("objLiteralData");
+ for (size_t i = 0; i < objLiteralData.size(); i++) {
+ SprintfLiteral(index, "ObjLiteralIndex(%zu)", i);
+ json.beginObjectProperty(index);
+ objLiteralData[i].dumpFields(json, this);
+ json.endObject();
+ }
+ json.endObject();
+
+ if (moduleMetadata) {
+ json.beginObjectProperty("moduleMetadata");
+ moduleMetadata->dumpFields(json, this);
+ json.endObject();
+ }
+
+ json.beginObjectProperty("asmJS");
+ if (asmJS) {
+ for (auto iter = asmJS->moduleMap.iter(); !iter.done(); iter.next()) {
+ SprintfLiteral(index, "ScriptIndex(%u)", iter.get().key().index);
+ json.formatProperty(index, "asm.js");
+ }
+ }
+ json.endObject();
+}
+
+void CompilationStencil::dumpAtom(TaggedParserAtomIndex index) const {
+ js::Fprinter out(stderr);
+ js::JSONPrinter json(out);
+ json.beginObject();
+ DumpTaggedParserAtomIndex(json, index, this);
+ json.endObject();
+}
+
+void ExtensibleCompilationStencil::dump() {
+ frontend::BorrowingCompilationStencil borrowingStencil(*this);
+ borrowingStencil.dump();
+}
+
+void ExtensibleCompilationStencil::dump(js::JSONPrinter& json) {
+ frontend::BorrowingCompilationStencil borrowingStencil(*this);
+ borrowingStencil.dump(json);
+}
+
+void ExtensibleCompilationStencil::dumpFields(js::JSONPrinter& json) {
+ frontend::BorrowingCompilationStencil borrowingStencil(*this);
+ borrowingStencil.dumpFields(json);
+}
+
+void ExtensibleCompilationStencil::dumpAtom(TaggedParserAtomIndex index) {
+ frontend::BorrowingCompilationStencil borrowingStencil(*this);
+ borrowingStencil.dumpAtom(index);
+}
+
+#endif // defined(DEBUG) || defined(JS_JITSPEW)
+
+JSString* CompilationAtomCache::getExistingStringAt(
+ ParserAtomIndex index) const {
+ MOZ_RELEASE_ASSERT(atoms_.length() >= index);
+ return atoms_[index];
+}
+
+JSString* CompilationAtomCache::getExistingStringAt(
+ JSContext* cx, TaggedParserAtomIndex taggedIndex) const {
+ if (taggedIndex.isParserAtomIndex()) {
+ auto index = taggedIndex.toParserAtomIndex();
+ return getExistingStringAt(index);
+ }
+
+ if (taggedIndex.isWellKnownAtomId()) {
+ auto index = taggedIndex.toWellKnownAtomId();
+ return GetWellKnownAtom(cx, index);
+ }
+
+ if (taggedIndex.isLength1StaticParserString()) {
+ auto index = taggedIndex.toLength1StaticParserString();
+ return cx->staticStrings().getUnit(char16_t(index));
+ }
+
+ if (taggedIndex.isLength2StaticParserString()) {
+ auto index = taggedIndex.toLength2StaticParserString();
+ return cx->staticStrings().getLength2FromIndex(size_t(index));
+ }
+
+ MOZ_ASSERT(taggedIndex.isLength3StaticParserString());
+ auto index = taggedIndex.toLength3StaticParserString();
+ return cx->staticStrings().getUint(uint32_t(index));
+}
+
+JSString* CompilationAtomCache::getStringAt(ParserAtomIndex index) const {
+ if (size_t(index) >= atoms_.length()) {
+ return nullptr;
+ }
+ return atoms_[index];
+}
+
+JSAtom* CompilationAtomCache::getExistingAtomAt(ParserAtomIndex index) const {
+ return &getExistingStringAt(index)->asAtom();
+}
+
+JSAtom* CompilationAtomCache::getExistingAtomAt(
+ JSContext* cx, TaggedParserAtomIndex taggedIndex) const {
+ return &getExistingStringAt(cx, taggedIndex)->asAtom();
+}
+
+JSAtom* CompilationAtomCache::getAtomAt(ParserAtomIndex index) const {
+ if (size_t(index) >= atoms_.length()) {
+ return nullptr;
+ }
+ if (!atoms_[index]) {
+ return nullptr;
+ }
+ return &atoms_[index]->asAtom();
+}
+
+bool CompilationAtomCache::hasAtomAt(ParserAtomIndex index) const {
+ if (size_t(index) >= atoms_.length()) {
+ return false;
+ }
+ return !!atoms_[index];
+}
+
+bool CompilationAtomCache::setAtomAt(FrontendContext* fc, ParserAtomIndex index,
+ JSString* atom) {
+ if (size_t(index) < atoms_.length()) {
+ atoms_[index] = atom;
+ return true;
+ }
+
+ if (!atoms_.resize(size_t(index) + 1)) {
+ ReportOutOfMemory(fc);
+ return false;
+ }
+
+ atoms_[index] = atom;
+ return true;
+}
+
+bool CompilationAtomCache::allocate(FrontendContext* fc, size_t length) {
+ MOZ_ASSERT(length >= atoms_.length());
+ if (length == atoms_.length()) {
+ return true;
+ }
+
+ if (!atoms_.resize(length)) {
+ ReportOutOfMemory(fc);
+ return false;
+ }
+
+ return true;
+}
+
+void CompilationAtomCache::stealBuffer(AtomCacheVector& atoms) {
+ atoms_ = std::move(atoms);
+ // Destroy elements, without unreserving.
+ atoms_.clear();
+}
+
+void CompilationAtomCache::releaseBuffer(AtomCacheVector& atoms) {
+ atoms = std::move(atoms_);
+}
+
+bool CompilationState::allocateGCThingsUninitialized(
+ FrontendContext* fc, ScriptIndex scriptIndex, size_t length,
+ TaggedScriptThingIndex** cursor) {
+ MOZ_ASSERT(gcThingData.length() <= UINT32_MAX);
+
+ auto gcThingsOffset = CompilationGCThingIndex(gcThingData.length());
+
+ if (length > INDEX_LIMIT) {
+ ReportAllocationOverflow(fc);
+ return false;
+ }
+ uint32_t gcThingsLength = length;
+
+ if (!gcThingData.growByUninitialized(length)) {
+ js::ReportOutOfMemory(fc);
+ return false;
+ }
+
+ if (gcThingData.length() > UINT32_MAX) {
+ ReportAllocationOverflow(fc);
+ return false;
+ }
+
+ ScriptStencil& script = scriptData[scriptIndex];
+ script.gcThingsOffset = gcThingsOffset;
+ script.gcThingsLength = gcThingsLength;
+
+ *cursor = gcThingData.begin() + gcThingsOffset;
+ return true;
+}
+
+bool CompilationState::appendScriptStencilAndData(FrontendContext* fc) {
+ if (!scriptData.emplaceBack()) {
+ js::ReportOutOfMemory(fc);
+ return false;
+ }
+
+ if (isInitialStencil()) {
+ if (!scriptExtra.emplaceBack()) {
+ scriptData.popBack();
+ MOZ_ASSERT(scriptData.length() == scriptExtra.length());
+
+ js::ReportOutOfMemory(fc);
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool CompilationState::appendGCThings(
+ FrontendContext* fc, ScriptIndex scriptIndex,
+ mozilla::Span<const TaggedScriptThingIndex> things) {
+ MOZ_ASSERT(gcThingData.length() <= UINT32_MAX);
+
+ auto gcThingsOffset = CompilationGCThingIndex(gcThingData.length());
+
+ if (things.size() > INDEX_LIMIT) {
+ ReportAllocationOverflow(fc);
+ return false;
+ }
+ uint32_t gcThingsLength = uint32_t(things.size());
+
+ if (!gcThingData.append(things.data(), things.size())) {
+ js::ReportOutOfMemory(fc);
+ return false;
+ }
+
+ if (gcThingData.length() > UINT32_MAX) {
+ ReportAllocationOverflow(fc);
+ return false;
+ }
+
+ ScriptStencil& script = scriptData[scriptIndex];
+ script.gcThingsOffset = gcThingsOffset;
+ script.gcThingsLength = gcThingsLength;
+ return true;
+}
+
+CompilationState::CompilationStatePosition CompilationState::getPosition() {
+ return CompilationStatePosition{scriptData.length(),
+ asmJS ? asmJS->moduleMap.count() : 0};
+}
+
+void CompilationState::rewind(
+ const CompilationState::CompilationStatePosition& pos) {
+ if (asmJS && asmJS->moduleMap.count() != pos.asmJSCount) {
+ for (size_t i = pos.scriptDataLength; i < scriptData.length(); i++) {
+ asmJS->moduleMap.remove(ScriptIndex(i));
+ }
+ MOZ_ASSERT(asmJS->moduleMap.count() == pos.asmJSCount);
+ }
+ // scriptExtra is empty for delazification.
+ if (scriptExtra.length()) {
+ MOZ_ASSERT(scriptExtra.length() == scriptData.length());
+ scriptExtra.shrinkTo(pos.scriptDataLength);
+ }
+ scriptData.shrinkTo(pos.scriptDataLength);
+}
+
+void CompilationState::markGhost(
+ const CompilationState::CompilationStatePosition& pos) {
+ for (size_t i = pos.scriptDataLength; i < scriptData.length(); i++) {
+ scriptData[i].setIsGhost();
+ }
+}
+
+bool CompilationStencilMerger::buildFunctionKeyToIndex(FrontendContext* fc) {
+ if (!functionKeyToInitialScriptIndex_.reserve(initial_->scriptExtra.length() -
+ 1)) {
+ ReportOutOfMemory(fc);
+ return false;
+ }
+
+ for (size_t i = 1; i < initial_->scriptExtra.length(); i++) {
+ const auto& extra = initial_->scriptExtra[i];
+ auto key = extra.extent.toFunctionKey();
+
+ // There can be multiple ScriptStencilExtra with same extent if
+ // the function is parsed multiple times because of rewind for
+ // arrow function, and in that case the last one's index should be used.
+ // Overwrite with the last one.
+ //
+ // Already reserved above, but OOMTest can hit failure mode in
+ // HashTable::add.
+ if (!functionKeyToInitialScriptIndex_.put(key, ScriptIndex(i))) {
+ ReportOutOfMemory(fc);
+ return false;
+ }
+ }
+
+ return true;
+}
+
+ScriptIndex CompilationStencilMerger::getInitialScriptIndexFor(
+ const CompilationStencil& delazification) const {
+ auto p = functionKeyToInitialScriptIndex_.lookup(delazification.functionKey);
+ MOZ_ASSERT(p);
+ return p->value();
+}
+
+bool CompilationStencilMerger::buildAtomIndexMap(
+ FrontendContext* fc, const CompilationStencil& delazification,
+ AtomIndexMap& atomIndexMap) {
+ uint32_t atomCount = delazification.parserAtomData.size();
+ if (!atomIndexMap.reserve(atomCount)) {
+ ReportOutOfMemory(fc);
+ return false;
+ }
+ for (const auto& atom : delazification.parserAtomData) {
+ auto mappedIndex = initial_->parserAtoms.internExternalParserAtom(fc, atom);
+ if (!mappedIndex) {
+ return false;
+ }
+ atomIndexMap.infallibleAppend(mappedIndex);
+ }
+ return true;
+}
+
+bool CompilationStencilMerger::setInitial(
+ FrontendContext* fc, UniquePtr<ExtensibleCompilationStencil>&& initial) {
+ MOZ_ASSERT(!initial_);
+
+ initial_ = std::move(initial);
+
+ return buildFunctionKeyToIndex(fc);
+}
+
+template <typename GCThingIndexMapFunc, typename AtomIndexMapFunc,
+ typename ScopeIndexMapFunc>
+static void MergeScriptStencil(ScriptStencil& dest, const ScriptStencil& src,
+ GCThingIndexMapFunc mapGCThingIndex,
+ AtomIndexMapFunc mapAtomIndex,
+ ScopeIndexMapFunc mapScopeIndex,
+ bool isTopLevel) {
+ // If this function was lazy, all inner functions should have been lazy.
+ MOZ_ASSERT(!dest.hasSharedData());
+
+ // If the inner lazy function is skipped, gcThingsLength is empty.
+ if (src.gcThingsLength) {
+ dest.gcThingsOffset = mapGCThingIndex(src.gcThingsOffset);
+ dest.gcThingsLength = src.gcThingsLength;
+ }
+
+ if (src.functionAtom) {
+ dest.functionAtom = mapAtomIndex(src.functionAtom);
+ }
+
+ if (!dest.hasLazyFunctionEnclosingScopeIndex() &&
+ src.hasLazyFunctionEnclosingScopeIndex()) {
+ // Both enclosing function and this function were lazy, and
+ // now enclosing function is non-lazy and this function is still lazy.
+ dest.setLazyFunctionEnclosingScopeIndex(
+ mapScopeIndex(src.lazyFunctionEnclosingScopeIndex()));
+ } else if (dest.hasLazyFunctionEnclosingScopeIndex() &&
+ !src.hasLazyFunctionEnclosingScopeIndex()) {
+ // The enclosing function was non-lazy and this function was lazy, and
+ // now this function is non-lazy.
+ dest.resetHasLazyFunctionEnclosingScopeIndexAfterStencilMerge();
+ } else {
+ // The enclosing function is still lazy.
+ MOZ_ASSERT(!dest.hasLazyFunctionEnclosingScopeIndex());
+ MOZ_ASSERT(!src.hasLazyFunctionEnclosingScopeIndex());
+ }
+
+#ifdef DEBUG
+ uint16_t BASESCRIPT = uint16_t(FunctionFlags::Flags::BASESCRIPT);
+ uint16_t HAS_INFERRED_NAME =
+ uint16_t(FunctionFlags::Flags::HAS_INFERRED_NAME);
+ uint16_t HAS_GUESSED_ATOM = uint16_t(FunctionFlags::Flags::HAS_GUESSED_ATOM);
+ uint16_t acceptableDifferenceForLazy = HAS_INFERRED_NAME | HAS_GUESSED_ATOM;
+ uint16_t acceptableDifferenceForNonLazy =
+ BASESCRIPT | HAS_INFERRED_NAME | HAS_GUESSED_ATOM;
+
+ MOZ_ASSERT_IF(
+ isTopLevel,
+ (dest.functionFlags.toRaw() | acceptableDifferenceForNonLazy) ==
+ (src.functionFlags.toRaw() | acceptableDifferenceForNonLazy));
+
+ // NOTE: Currently we don't delazify inner functions.
+ MOZ_ASSERT_IF(!isTopLevel,
+ (dest.functionFlags.toRaw() | acceptableDifferenceForLazy) ==
+ (src.functionFlags.toRaw() | acceptableDifferenceForLazy));
+#endif // DEBUG
+ dest.functionFlags = src.functionFlags;
+
+ // Other flags.
+
+ if (src.wasEmittedByEnclosingScript()) {
+ // NOTE: the top-level function of the delazification have
+ // src.wasEmittedByEnclosingScript() == false, and that shouldn't
+ // be copied.
+ dest.setWasEmittedByEnclosingScript();
+ }
+
+ if (src.allowRelazify()) {
+ dest.setAllowRelazify();
+ }
+
+ if (src.hasSharedData()) {
+ dest.setHasSharedData();
+ }
+}
+
+bool CompilationStencilMerger::addDelazification(
+ FrontendContext* fc, const CompilationStencil& delazification) {
+ MOZ_ASSERT(initial_);
+
+ auto delazifiedFunctionIndex = getInitialScriptIndexFor(delazification);
+ auto& destFun = initial_->scriptData[delazifiedFunctionIndex];
+
+ if (destFun.hasSharedData()) {
+ // If the function was already non-lazy, it means the following happened:
+ // A. delazified twice within single incremental encoding
+ // 1. this function is lazily parsed
+ // 2. incremental encoding is started
+ // 3. this function is delazified, encoded, and merged
+ // 4. this function is relazified
+ // 5. this function is delazified, encoded, and merged
+ //
+ // B. delazified twice across decode
+ // 1. this function is lazily parsed
+ // 2. incremental encoding is started
+ // 3. this function is delazified, encoded, and merged
+ // 4. incremental encoding is finished
+ // 5. decoded
+ // 6. incremental encoding is started
+ // here, this function is non-lazy
+ // 7. this function is relazified
+ // 8. this function is delazified, encoded, and merged
+ //
+ // A can happen with public API.
+ //
+ // B cannot happen with public API, but can happen if incremental
+ // encoding at step B.6 is explicitly started by internal function.
+ // See Evaluate and StartIncrementalEncoding in js/src/shell/js.cpp.
+ return true;
+ }
+
+ // If any failure happens, the initial stencil is left in the broken state.
+ // Immediately discard it.
+ auto failureCase = mozilla::MakeScopeExit([&] { initial_.reset(); });
+
+ mozilla::Maybe<ScopeIndex> functionEnclosingScope;
+ if (destFun.hasLazyFunctionEnclosingScopeIndex()) {
+ // lazyFunctionEnclosingScopeIndex_ can be Nothing if this is
+ // top-level function.
+ functionEnclosingScope =
+ mozilla::Some(destFun.lazyFunctionEnclosingScopeIndex());
+ }
+
+ // A map from ParserAtomIndex in delazification to TaggedParserAtomIndex
+ // in initial_.
+ AtomIndexMap atomIndexMap;
+ if (!buildAtomIndexMap(fc, delazification, atomIndexMap)) {
+ return false;
+ }
+ auto mapAtomIndex = [&](TaggedParserAtomIndex index) {
+ if (index.isParserAtomIndex()) {
+ return atomIndexMap[index.toParserAtomIndex()];
+ }
+
+ return index;
+ };
+
+ size_t gcThingOffset = initial_->gcThingData.length();
+ size_t regExpOffset = initial_->regExpData.length();
+ size_t bigIntOffset = initial_->bigIntData.length();
+ size_t objLiteralOffset = initial_->objLiteralData.length();
+ size_t scopeOffset = initial_->scopeData.length();
+
+ // Map delazification's ScriptIndex to initial's ScriptIndex.
+ //
+ // The lazy function's gcthings list stores inner function's ScriptIndex.
+ // The n-th gcthing holds the ScriptIndex of the (n+1)-th script in
+ // delazification.
+ //
+ // NOTE: Currently we don't delazify inner functions.
+ auto lazyFunctionGCThingsOffset = destFun.gcThingsOffset;
+ auto mapScriptIndex = [&](ScriptIndex index) {
+ if (index == CompilationStencil::TopLevelIndex) {
+ return delazifiedFunctionIndex;
+ }
+
+ return initial_->gcThingData[lazyFunctionGCThingsOffset + index.index - 1]
+ .toFunction();
+ };
+
+ // Map other delazification's indices into initial's indices.
+ auto mapGCThingIndex = [&](CompilationGCThingIndex offset) {
+ return CompilationGCThingIndex(gcThingOffset + offset.index);
+ };
+ auto mapRegExpIndex = [&](RegExpIndex index) {
+ return RegExpIndex(regExpOffset + index.index);
+ };
+ auto mapBigIntIndex = [&](BigIntIndex index) {
+ return BigIntIndex(bigIntOffset + index.index);
+ };
+ auto mapObjLiteralIndex = [&](ObjLiteralIndex index) {
+ return ObjLiteralIndex(objLiteralOffset + index.index);
+ };
+ auto mapScopeIndex = [&](ScopeIndex index) {
+ return ScopeIndex(scopeOffset + index.index);
+ };
+
+ // Append gcThingData, with mapping TaggedScriptThingIndex.
+ if (!initial_->gcThingData.append(delazification.gcThingData.data(),
+ delazification.gcThingData.size())) {
+ js::ReportOutOfMemory(fc);
+ return false;
+ }
+ for (size_t i = gcThingOffset; i < initial_->gcThingData.length(); i++) {
+ auto& index = initial_->gcThingData[i];
+ if (index.isNull()) {
+ // Nothing to do.
+ } else if (index.isAtom()) {
+ index = TaggedScriptThingIndex(mapAtomIndex(index.toAtom()));
+ } else if (index.isBigInt()) {
+ index = TaggedScriptThingIndex(mapBigIntIndex(index.toBigInt()));
+ } else if (index.isObjLiteral()) {
+ index = TaggedScriptThingIndex(mapObjLiteralIndex(index.toObjLiteral()));
+ } else if (index.isRegExp()) {
+ index = TaggedScriptThingIndex(mapRegExpIndex(index.toRegExp()));
+ } else if (index.isScope()) {
+ index = TaggedScriptThingIndex(mapScopeIndex(index.toScope()));
+ } else if (index.isFunction()) {
+ index = TaggedScriptThingIndex(mapScriptIndex(index.toFunction()));
+ } else {
+ MOZ_ASSERT(index.isEmptyGlobalScope());
+ // Nothing to do
+ }
+ }
+
+ // Append regExpData, with mapping RegExpStencil.atom_.
+ if (!initial_->regExpData.append(delazification.regExpData.data(),
+ delazification.regExpData.size())) {
+ js::ReportOutOfMemory(fc);
+ return false;
+ }
+ for (size_t i = regExpOffset; i < initial_->regExpData.length(); i++) {
+ auto& data = initial_->regExpData[i];
+ data.atom_ = mapAtomIndex(data.atom_);
+ }
+
+ // Append bigIntData, with copying BigIntStencil.source_.
+ if (!initial_->bigIntData.reserve(bigIntOffset +
+ delazification.bigIntData.size())) {
+ js::ReportOutOfMemory(fc);
+ return false;
+ }
+ for (const auto& data : delazification.bigIntData) {
+ initial_->bigIntData.infallibleEmplaceBack();
+ if (!initial_->bigIntData.back().init(fc, initial_->alloc, data.source())) {
+ return false;
+ }
+ }
+
+ // Append objLiteralData, with copying ObjLiteralStencil.code_, and mapping
+ // TaggedParserAtomIndex in it.
+ if (!initial_->objLiteralData.reserve(objLiteralOffset +
+ delazification.objLiteralData.size())) {
+ js::ReportOutOfMemory(fc);
+ return false;
+ }
+ for (const auto& data : delazification.objLiteralData) {
+ size_t length = data.code().size();
+ auto* code = initial_->alloc.newArrayUninitialized<uint8_t>(length);
+ if (!code) {
+ js::ReportOutOfMemory(fc);
+ return false;
+ }
+ memcpy(code, data.code().data(), length);
+
+ ObjLiteralModifier modifier(mozilla::Span(code, length));
+ modifier.mapAtom(mapAtomIndex);
+
+ initial_->objLiteralData.infallibleEmplaceBack(
+ code, length, data.kind(), data.flags(), data.propertyCount());
+ }
+
+ // Append scopeData, with mapping indices in ScopeStencil fields.
+ // And append scopeNames, with copying the entire data, and mapping
+ // trailingNames.
+ if (!initial_->scopeData.reserve(scopeOffset +
+ delazification.scopeData.size())) {
+ js::ReportOutOfMemory(fc);
+ return false;
+ }
+ if (!initial_->scopeNames.reserve(scopeOffset +
+ delazification.scopeNames.size())) {
+ js::ReportOutOfMemory(fc);
+ return false;
+ }
+ for (size_t i = 0; i < delazification.scopeData.size(); i++) {
+ const auto& srcData = delazification.scopeData[i];
+ const auto* srcNames = delazification.scopeNames[i];
+
+ mozilla::Maybe<ScriptIndex> functionIndex = mozilla::Nothing();
+ if (srcData.isFunction()) {
+ // Inner functions should be in the same order as initial, beginning from
+ // the delazification's index.
+ functionIndex = mozilla::Some(mapScriptIndex(srcData.functionIndex()));
+ }
+
+ BaseParserScopeData* destNames = nullptr;
+ if (srcNames) {
+ destNames = CopyScopeData(fc, initial_->alloc, srcData.kind(), srcNames);
+ if (!destNames) {
+ return false;
+ }
+ auto trailingNames =
+ GetParserScopeDataTrailingNames(srcData.kind(), destNames);
+ for (auto& name : trailingNames) {
+ if (name.name()) {
+ name.updateNameAfterStencilMerge(mapAtomIndex(name.name()));
+ }
+ }
+ }
+
+ initial_->scopeData.infallibleEmplaceBack(
+ srcData.kind(),
+ srcData.hasEnclosing()
+ ? mozilla::Some(mapScopeIndex(srcData.enclosing()))
+ : functionEnclosingScope,
+ srcData.firstFrameSlot(),
+ srcData.hasEnvironmentShape()
+ ? mozilla::Some(srcData.numEnvironmentSlots())
+ : mozilla::Nothing(),
+ functionIndex, srcData.isArrow());
+
+ initial_->scopeNames.infallibleEmplaceBack(destNames);
+ }
+
+ // Add delazified function's shared data.
+ //
+ // NOTE: Currently we don't delazify inner functions.
+ if (!initial_->sharedData.addExtraWithoutShare(
+ fc, delazifiedFunctionIndex,
+ delazification.sharedData.get(CompilationStencil::TopLevelIndex))) {
+ return false;
+ }
+
+ // Update scriptData, with mapping indices in ScriptStencil fields.
+ for (uint32_t i = 0; i < delazification.scriptData.size(); i++) {
+ auto destIndex = mapScriptIndex(ScriptIndex(i));
+ MergeScriptStencil(initial_->scriptData[destIndex],
+ delazification.scriptData[i], mapGCThingIndex,
+ mapAtomIndex, mapScopeIndex,
+ i == CompilationStencil::TopLevelIndex);
+ }
+
+ // WARNING: moduleMetadata and asmJS fields are known at script/module
+ // top-level parsing, any mutation made in this function should be reflected
+ // to ExtensibleCompilationStencil::steal and CompilationStencil::clone.
+
+ // Function shouldn't be a module.
+ MOZ_ASSERT(!delazification.moduleMetadata);
+
+ // asm.js shouldn't appear inside delazification, given asm.js forces
+ // full-parse.
+ MOZ_ASSERT(!delazification.asmJS);
+
+ failureCase.release();
+ return true;
+}
+
+void JS::StencilAddRef(JS::Stencil* stencil) { stencil->refCount++; }
+void JS::StencilRelease(JS::Stencil* stencil) {
+ MOZ_RELEASE_ASSERT(stencil->refCount > 0);
+ if (--stencil->refCount == 0) {
+ js_delete(stencil);
+ }
+}
+
+template <typename CharT>
+static already_AddRefed<JS::Stencil> CompileGlobalScriptToStencilImpl(
+ JSContext* cx, const JS::ReadOnlyCompileOptions& options,
+ JS::SourceText<CharT>& srcBuf) {
+ ScopeKind scopeKind =
+ options.nonSyntacticScope ? ScopeKind::NonSyntactic : ScopeKind::Global;
+
+ AutoReportFrontendContext fc(cx);
+ NoScopeBindingCache scopeCache;
+ Rooted<CompilationInput> input(cx, CompilationInput(options));
+ RefPtr<JS::Stencil> stencil = js::frontend::CompileGlobalScriptToStencil(
+ cx, &fc, cx->tempLifoAlloc(), input.get(), &scopeCache, srcBuf,
+ scopeKind);
+ if (!stencil) {
+ return nullptr;
+ }
+
+ // Convert the UniquePtr to a RefPtr and increment the count (to 1).
+ return stencil.forget();
+}
+
+already_AddRefed<JS::Stencil> JS::CompileGlobalScriptToStencil(
+ JSContext* cx, const JS::ReadOnlyCompileOptions& options,
+ JS::SourceText<mozilla::Utf8Unit>& srcBuf) {
+ return CompileGlobalScriptToStencilImpl(cx, options, srcBuf);
+}
+
+already_AddRefed<JS::Stencil> JS::CompileGlobalScriptToStencil(
+ JSContext* cx, const JS::ReadOnlyCompileOptions& options,
+ JS::SourceText<char16_t>& srcBuf) {
+ return CompileGlobalScriptToStencilImpl(cx, options, srcBuf);
+}
+
+template <typename CharT>
+static already_AddRefed<JS::Stencil> CompileModuleScriptToStencilImpl(
+ JSContext* cx, const JS::ReadOnlyCompileOptions& optionsInput,
+ JS::SourceText<CharT>& srcBuf) {
+ JS::CompileOptions options(cx, optionsInput);
+ options.setModule();
+
+ AutoReportFrontendContext fc(cx);
+ NoScopeBindingCache scopeCache;
+ Rooted<CompilationInput> input(cx, CompilationInput(options));
+ RefPtr<JS::Stencil> stencil = js::frontend::ParseModuleToStencil(
+ cx, &fc, cx->tempLifoAlloc(), input.get(), &scopeCache, srcBuf);
+ if (!stencil) {
+ return nullptr;
+ }
+
+ // Convert the UniquePtr to a RefPtr and increment the count (to 1).
+ return stencil.forget();
+}
+
+already_AddRefed<JS::Stencil> JS::CompileModuleScriptToStencil(
+ JSContext* cx, const JS::ReadOnlyCompileOptions& options,
+ JS::SourceText<mozilla::Utf8Unit>& srcBuf) {
+ return CompileModuleScriptToStencilImpl(cx, options, srcBuf);
+}
+
+already_AddRefed<JS::Stencil> JS::CompileModuleScriptToStencil(
+ JSContext* cx, const JS::ReadOnlyCompileOptions& options,
+ JS::SourceText<char16_t>& srcBuf) {
+ return CompileModuleScriptToStencilImpl(cx, options, srcBuf);
+}
+
+JS_PUBLIC_API JSScript* JS::InstantiateGlobalStencil(
+ JSContext* cx, const JS::InstantiateOptions& options, JS::Stencil* stencil,
+ JS::InstantiationStorage* storage) {
+ MOZ_ASSERT_IF(storage, storage->isValid());
+
+ CompileOptions compileOptions(cx);
+ options.copyTo(compileOptions);
+ Rooted<CompilationInput> input(cx, CompilationInput(compileOptions));
+ Rooted<CompilationGCOutput> gcOutput(cx);
+ CompilationGCOutput& output = storage ? *storage->gcOutput_ : gcOutput.get();
+
+ if (!InstantiateStencils(cx, input.get(), *stencil, output)) {
+ return nullptr;
+ }
+ return output.script;
+}
+
+JS_PUBLIC_API bool JS::StencilIsBorrowed(Stencil* stencil) {
+ return stencil->storageType == CompilationStencil::StorageType::Borrowed;
+}
+
+JS_PUBLIC_API JSObject* JS::InstantiateModuleStencil(
+ JSContext* cx, const JS::InstantiateOptions& options, JS::Stencil* stencil,
+ JS::InstantiationStorage* storage) {
+ MOZ_ASSERT_IF(storage, storage->isValid());
+
+ CompileOptions compileOptions(cx);
+ options.copyTo(compileOptions);
+ compileOptions.setModule();
+ Rooted<CompilationInput> input(cx, CompilationInput(compileOptions));
+ Rooted<CompilationGCOutput> gcOutput(cx);
+ CompilationGCOutput& output = storage ? *storage->gcOutput_ : gcOutput.get();
+
+ if (!InstantiateStencils(cx, input.get(), *stencil, output)) {
+ return nullptr;
+ }
+ return output.module;
+}
+
+JS::TranscodeResult JS::EncodeStencil(JSContext* cx, JS::Stencil* stencil,
+ TranscodeBuffer& buffer) {
+ AutoReportFrontendContext fc(cx);
+ XDRStencilEncoder encoder(&fc, buffer);
+ XDRResult res = encoder.codeStencil(*stencil);
+ if (res.isErr()) {
+ return res.unwrapErr();
+ }
+ return TranscodeResult::Ok;
+}
+
+JS::TranscodeResult JS::DecodeStencil(JSContext* cx,
+ const JS::DecodeOptions& options,
+ const JS::TranscodeRange& range,
+ JS::Stencil** stencilOut) {
+ AutoReportFrontendContext fc(cx);
+ return JS::DecodeStencil(&fc, options, range, stencilOut);
+}
+
+JS::TranscodeResult JS::DecodeStencil(JS::FrontendContext* fc,
+ const JS::DecodeOptions& options,
+ const JS::TranscodeRange& range,
+ JS::Stencil** stencilOut) {
+ RefPtr<ScriptSource> source = fc->getAllocator()->new_<ScriptSource>();
+ if (!source) {
+ return TranscodeResult::Throw;
+ }
+ RefPtr<JS::Stencil> stencil(
+ fc->getAllocator()->new_<CompilationStencil>(source));
+ if (!stencil) {
+ return TranscodeResult::Throw;
+ }
+ XDRStencilDecoder decoder(fc, range);
+ XDRResult res = decoder.codeStencil(options, *stencil);
+ if (res.isErr()) {
+ return res.unwrapErr();
+ }
+ *stencilOut = stencil.forget().take();
+ return TranscodeResult::Ok;
+}
+
+JS_PUBLIC_API size_t JS::SizeOfStencil(Stencil* stencil,
+ mozilla::MallocSizeOf mallocSizeOf) {
+ return stencil->sizeOfIncludingThis(mallocSizeOf);
+}
+
+JS::InstantiationStorage::~InstantiationStorage() {
+ if (gcOutput_) {
+ js_delete(gcOutput_);
+ gcOutput_ = nullptr;
+ }
+}
diff --git a/js/src/frontend/Stencil.h b/js/src/frontend/Stencil.h
new file mode 100644
index 0000000000..a5aa84e787
--- /dev/null
+++ b/js/src/frontend/Stencil.h
@@ -0,0 +1,1141 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef frontend_Stencil_h
+#define frontend_Stencil_h
+
+#include "mozilla/Assertions.h" // MOZ_ASSERT
+#include "mozilla/Maybe.h" // mozilla::{Maybe, Nothing}
+#include "mozilla/MemoryReporting.h" // mozilla::MallocSizeOf
+#include "mozilla/Span.h" // mozilla::Span
+
+#include <stddef.h> // size_t
+#include <stdint.h> // char16_t, uint8_t, uint16_t, uint32_t
+
+#include "frontend/AbstractScopePtr.h" // AbstractScopePtr, ScopeIndex
+#include "frontend/ObjLiteral.h" // ObjLiteralStencil
+#include "frontend/ParserAtom.h" // TaggedParserAtomIndex
+#include "frontend/ScriptIndex.h" // ScriptIndex
+#include "frontend/TypedIndex.h" // TypedIndex
+#include "js/AllocPolicy.h" // SystemAllocPolicy
+#include "js/RefCounted.h" // AtomicRefCounted
+#include "js/RegExpFlags.h" // JS::RegExpFlags
+#include "js/RootingAPI.h" // Handle
+#include "js/TypeDecls.h" // JSContext
+#include "js/UniquePtr.h" // js::UniquePtr
+#include "js/Utility.h" // UniqueTwoByteChars
+#include "js/Vector.h" // js::Vector
+#include "vm/FunctionFlags.h" // FunctionFlags
+#include "vm/Scope.h" // Scope, BaseScopeData, FunctionScope, LexicalScope, VarScope, GlobalScope, EvalScope, ModuleScope
+#include "vm/ScopeKind.h" // ScopeKind
+#include "vm/SharedStencil.h" // ImmutableScriptFlags, GCThingIndex, js::SharedImmutableScriptData, MemberInitializers, SourceExtent
+
+namespace js {
+
+class LifoAlloc;
+class JSONPrinter;
+class RegExpObject;
+
+namespace frontend {
+
+struct CompilationInput;
+struct CompilationStencil;
+struct CompilationAtomCache;
+struct CompilationGCOutput;
+struct CompilationStencilMerger;
+class RegExpStencil;
+class BigIntStencil;
+class StencilXDR;
+
+using BaseParserScopeData = AbstractBaseScopeData<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 ParserClassBodyScopeSlotInfo = ParserScopeSlotInfo<ClassBodyScope>;
+using ParserFunctionScopeSlotInfo = ParserScopeSlotInfo<FunctionScope>;
+using ParserModuleScopeSlotInfo = ParserScopeSlotInfo<ModuleScope>;
+using ParserVarScopeSlotInfo = ParserScopeSlotInfo<VarScope>;
+
+using ParserBindingIter = AbstractBindingIter<TaggedParserAtomIndex>;
+using ParserPositionalFormalParameterIter =
+ AbstractPositionalFormalParameterIter<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 / ExtensibleCompilationStencil
+// -------------------------------------------------
+// Parsing a single JavaScript file may generate a tree of `ScriptStencil` that
+// we then package up into the `ExtensibleCompilationStencil` type or
+// `CompilationStencil`. They contain a series of vectors/spans segregated by
+// data type for fast processing (a.k.a Data Oriented Design).
+//
+// `ExtensibleCompilationStencil` is mutable type used during parsing, and
+// can be allocated either on stack or heap.
+// `ExtensibleCompilationStencil` holds vectors of stencils.
+//
+// `CompilationStencil` is immutable type used for caching the compilation
+// result, and is allocated on heap with refcount.
+// `CompilationStencil` holds spans of stencils, and it can point either
+// owned data or borrowed data.
+// The borrowed data can be from other `ExtensibleCompilationStencil` or
+// from serialized stencil (XDR) on memory or mmap.
+//
+// Delazifying a function will generate its bytecode but some fields remain
+// unchanged from the initial lazy parse.
+// When we delazify a function that was lazily parsed, we generate a new
+// Stencil at the point too. These delazifications can be merged into the
+// Stencil of the initial parse by using `CompilationStencilMerger`.
+//
+// Conversion from ExtensibleCompilationStencil to CompilationStencil
+// ------------------------------------------------------------------
+// There are multiple ways to convert from `ExtensibleCompilationStencil` to
+// `CompilationStencil`:
+//
+// 1. Temporarily borrow `ExtensibleCompilationStencil` content and call
+// function that takes `CompilationStencil` reference, and keep using the
+// `ExtensibleCompilationStencil` after that:
+//
+// ExtensibleCompilationStencil extensible = ...;
+// {
+// BorrowingCompilationStencil stencil(extensible);
+// // Use `stencil reference.
+// }
+//
+// 2. Take the ownership of an on-heap ExtensibleCompilationStencil. This makes
+// the `CompilationStencil` self-contained and it's useful for caching:
+//
+// UniquePtr<ExtensibleCompilationStencil> extensible = ...;
+// CompilationStencil stencil(std::move(extensible));
+//
+// Conversion from CompilationStencil to ExtensibleCompilationStencil
+// ------------------------------------------------------------------
+// In the same way, there are multiple ways to convert from
+// `CompilationStencil` to `ExtensibleCompilationStencil`:
+//
+// 1. Take the ownership of `CompilationStencil`'s underlying data, Only when
+// stencil owns the data and the refcount is 1:
+//
+// RefPtr<CompilationStencil> stencil = ...;
+// ExtensibleCompilationStencil extensible(...);
+// // NOTE: This is equivalent to cloneFrom below if `stencil` has refcount
+// // more than 2, or it doesn't own the data.
+// extensible.steal(fc, std::move(stencil));
+//
+// 2. Clone the underlying data. This is slow but safe operation:
+//
+// CompilationStencil stencil = ...;
+// ExtensibleCompilationStencil extensible(...);
+// extensible.cloneFrom(fc, stencil);
+//
+// 3. Take the ownership back from the `CompilationStencil` which is created by
+// taking the ownership of an on-heap `ExtensibleCompilationStencil`:
+//
+// CompilationStencil stencil = ...;
+// ExtensibleCompilationStencil* extensible = stencil.takeOwnedBorrow();
+//
+// CompilationGCOutput
+// -------------------
+// When a Stencil is instantiated the equivalent script objects are allocated on
+// the GC heap and their pointers are collected into the `CompilationGCOutput`
+// structure. This is only used temporarily during instantiation.
+//
+//
+// CompilationState
+// ----------------
+// This is another temporary structure used by the parser while the Stencil is
+// being generated. Once the `CompilationStencil` is complete, this can be
+// released.
+
+// Typed indices for the different stencil elements in the compilation result.
+using RegExpIndex = TypedIndex<RegExpStencil>;
+using BigIntIndex = TypedIndex<BigIntStencil>;
+using ObjLiteralIndex = TypedIndex<ObjLiteralStencil>;
+
+// Index into {ExtensibleCompilationStencil,CompilationStencil}.gcThingData.
+class CompilationGCThingType {};
+using CompilationGCThingIndex = TypedIndex<CompilationGCThingType>;
+
+// A syntax-checked regular expression string.
+class RegExpStencil {
+ friend class StencilXDR;
+
+ TaggedParserAtomIndex atom_;
+ // Use uint32_t to make this struct fully-packed.
+ uint32_t flags_;
+
+ friend struct CompilationStencilMerger;
+
+ public:
+ RegExpStencil() = default;
+
+ RegExpStencil(TaggedParserAtomIndex atom, JS::RegExpFlags flags)
+ : atom_(atom), flags_(flags.value()) {}
+
+ JS::RegExpFlags flags() const { return JS::RegExpFlags(flags_); }
+
+ RegExpObject* createRegExp(JSContext* cx,
+ const CompilationAtomCache& atomCache) const;
+
+ // This is used by `Reflect.parse` when we need the RegExpObject but are not
+ // doing a complete instantiation of the CompilationStencil.
+ RegExpObject* createRegExpAndEnsureAtom(
+ JSContext* cx, FrontendContext* fc, ParserAtomsTable& parserAtoms,
+ CompilationAtomCache& atomCache) const;
+
+#if defined(DEBUG) || defined(JS_JITSPEW)
+ void dump() const;
+ void dump(JSONPrinter& json, const CompilationStencil* stencil) const;
+ void dumpFields(JSONPrinter& json, const CompilationStencil* stencil) const;
+#endif
+};
+
+// This owns a set of characters guaranteed to parse into a BigInt via
+// ParseBigIntLiteral. Used to avoid allocating the BigInt on the
+// GC heap during parsing.
+class BigIntStencil {
+ friend class StencilXDR;
+
+ // Source of the BigInt literal.
+ // It's not null-terminated, and also trailing 'n' suffix is not included.
+ mozilla::Span<char16_t> source_;
+
+ public:
+ BigIntStencil() = default;
+
+ [[nodiscard]] bool init(FrontendContext* fc, LifoAlloc& alloc,
+ const mozilla::Span<const char16_t> buf);
+
+ BigInt* createBigInt(JSContext* cx) const;
+
+ bool isZero() const;
+
+ mozilla::Span<const char16_t> source() const { return source_; }
+
+#ifdef DEBUG
+ bool isContainedIn(const LifoAlloc& alloc) const;
+#endif
+
+#if defined(DEBUG) || defined(JS_JITSPEW)
+ void dump() const;
+ void dump(JSONPrinter& json) const;
+ void dumpCharsNoQuote(GenericPrinter& out) const;
+#endif
+};
+
+class ScopeStencil {
+ friend class StencilXDR;
+ friend class InputScope;
+ friend class AbstractBindingIter<frontend::TaggedParserAtomIndex>;
+ friend struct CompilationStencil;
+ friend struct CompilationStencilMerger;
+
+ // The enclosing scope. Valid only if HasEnclosing flag is set.
+ // compilation applies.
+ ScopeIndex enclosing_;
+
+ // First frame slot to use, or LOCALNO_LIMIT if none are allowed.
+ uint32_t firstFrameSlot_ = UINT32_MAX;
+
+ // The number of environment shape's slots. Valid only if
+ // HasEnvironmentShape flag is set.
+ uint32_t numEnvironmentSlots_;
+
+ // Canonical function if this is a FunctionScope. Valid only if
+ // kind_ is ScopeKind::Function.
+ ScriptIndex functionIndex_;
+
+ // The kind determines the corresponding BaseParserScopeData.
+ ScopeKind kind_{UINT8_MAX};
+
+ // True if this scope has enclosing scope stencil. Otherwise, the enclosing
+ // scope will be read from CompilationInput while instantiating. Self-hosting
+ // is a special case and will use `emptyGlobalScope` when there is no
+ // enclosing scope stencil.
+ static constexpr uint8_t HasEnclosing = 1 << 0;
+
+ // If true, an environment Shape must be created. The shape itself may
+ // have no slots if the environment may be extensible later.
+ static constexpr uint8_t HasEnvironmentShape = 1 << 1;
+
+ // True if this is a FunctionScope for an arrow function.
+ static constexpr uint8_t IsArrow = 1 << 2;
+
+ uint8_t flags_ = 0;
+
+ // To make this struct packed, add explicit field for padding.
+ uint16_t padding_ = 0;
+
+ public:
+ // For XDR only.
+ ScopeStencil() = default;
+
+ ScopeStencil(ScopeKind kind, mozilla::Maybe<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.
+ (void)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(FrontendContext* fc,
+ CompilationState& compilationState,
+ BaseParserScopeData* data,
+ ScopeIndex* indexOut, Args&&... args);
+
+ public:
+ static bool createForFunctionScope(
+ FrontendContext* fc, CompilationState& compilationState,
+ FunctionScope::ParserData* dataArg, bool hasParameterExprs,
+ bool needsEnvironment, ScriptIndex functionIndex, bool isArrow,
+ mozilla::Maybe<ScopeIndex> enclosing, ScopeIndex* index);
+
+ static bool createForLexicalScope(
+ FrontendContext* fc, CompilationState& compilationState, ScopeKind kind,
+ LexicalScope::ParserData* dataArg, uint32_t firstFrameSlot,
+ mozilla::Maybe<ScopeIndex> enclosing, ScopeIndex* index);
+
+ static bool createForClassBodyScope(
+ FrontendContext* fc, CompilationState& compilationState, ScopeKind kind,
+ ClassBodyScope::ParserData* dataArg, uint32_t firstFrameSlot,
+ mozilla::Maybe<ScopeIndex> enclosing, ScopeIndex* index);
+
+ static bool createForVarScope(FrontendContext* fc,
+ CompilationState& compilationState,
+ ScopeKind kind, VarScope::ParserData* dataArg,
+ uint32_t firstFrameSlot, bool needsEnvironment,
+ mozilla::Maybe<ScopeIndex> enclosing,
+ ScopeIndex* index);
+
+ static bool createForGlobalScope(FrontendContext* fc,
+ CompilationState& compilationState,
+ ScopeKind kind,
+ GlobalScope::ParserData* dataArg,
+ ScopeIndex* index);
+
+ static bool createForEvalScope(FrontendContext* fc,
+ CompilationState& compilationState,
+ ScopeKind kind, EvalScope::ParserData* dataArg,
+ mozilla::Maybe<ScopeIndex> enclosing,
+ ScopeIndex* index);
+
+ static bool createForModuleScope(FrontendContext* fc,
+ CompilationState& compilationState,
+ ModuleScope::ParserData* dataArg,
+ mozilla::Maybe<ScopeIndex> enclosing,
+ ScopeIndex* index);
+
+ static bool createForWithScope(FrontendContext* fc,
+ 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;
+ Scope* createScope(JSContext* cx, CompilationAtomCache& atomCache,
+ Handle<Scope*> enclosingScope,
+ BaseParserScopeData* baseScopeData) const;
+
+#if defined(DEBUG) || defined(JS_JITSPEW)
+ void dump() const;
+ void dump(JSONPrinter& json, const BaseParserScopeData* baseScopeData,
+ const CompilationStencil* stencil) const;
+ void dumpFields(JSONPrinter& json, const BaseParserScopeData* baseScopeData,
+ const CompilationStencil* stencil) const;
+#endif
+
+ private:
+ // Transfer ownership into a new UniquePtr.
+ template <typename SpecificScopeType>
+ UniquePtr<typename SpecificScopeType::RuntimeData> createSpecificScopeData(
+ JSContext* cx, CompilationAtomCache& atomCache,
+ BaseParserScopeData* baseData) const;
+
+ template <typename SpecificEnvironmentType>
+ [[nodiscard]] bool createSpecificShape(
+ JSContext* cx, ScopeKind kind, BaseScopeData* scopeData,
+ MutableHandle<SharedShape*> shape) const;
+
+ template <typename SpecificScopeType, typename SpecificEnvironmentType>
+ Scope* createSpecificScope(JSContext* cx, CompilationAtomCache& atomCache,
+ Handle<Scope*> enclosingScope,
+ 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: {
+ return std::is_same_v<ScopeT, LexicalScope>;
+ }
+ case ScopeKind::ClassBody: {
+ return std::is_same_v<ScopeT, ClassBodyScope>;
+ }
+ 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;
+ }
+};
+
+class StencilModuleAssertion {
+ public:
+ TaggedParserAtomIndex key;
+ TaggedParserAtomIndex value;
+
+ StencilModuleAssertion() = default;
+ StencilModuleAssertion(TaggedParserAtomIndex key, TaggedParserAtomIndex value)
+ : key(key), value(value) {}
+};
+
+class StencilModuleRequest {
+ public:
+ TaggedParserAtomIndex specifier;
+
+ using AssertionVector =
+ Vector<StencilModuleAssertion, 0, js::SystemAllocPolicy>;
+ AssertionVector assertions;
+
+ // For XDR only.
+ StencilModuleRequest() = default;
+
+ explicit StencilModuleRequest(TaggedParserAtomIndex specifier)
+ : specifier(specifier) {
+ MOZ_ASSERT(specifier);
+ }
+
+ StencilModuleRequest(const StencilModuleRequest& other)
+ : specifier(other.specifier) {
+ AutoEnterOOMUnsafeRegion oomUnsafe;
+ if (!assertions.appendAll(other.assertions)) {
+ oomUnsafe.crash("StencilModuleRequest::StencilModuleRequest");
+ }
+ }
+
+ StencilModuleRequest(StencilModuleRequest&& other) noexcept
+ : specifier(other.specifier), assertions(std::move(other.assertions)) {}
+
+ StencilModuleRequest& operator=(StencilModuleRequest& other) {
+ specifier = other.specifier;
+ assertions = std::move(other.assertions);
+ return *this;
+ }
+
+ StencilModuleRequest& operator=(StencilModuleRequest&& other) noexcept {
+ specifier = other.specifier;
+ assertions = std::move(other.assertions);
+ return *this;
+ }
+};
+
+class MaybeModuleRequestIndex {
+ static constexpr uint32_t NOTHING = UINT32_MAX;
+
+ uint32_t bits = NOTHING;
+
+ public:
+ MaybeModuleRequestIndex() = default;
+ explicit MaybeModuleRequestIndex(uint32_t index) : bits(index) {
+ MOZ_ASSERT(isSome());
+ }
+
+ MaybeModuleRequestIndex(const MaybeModuleRequestIndex& other) = default;
+ MaybeModuleRequestIndex& operator=(const MaybeModuleRequestIndex& other) =
+ default;
+
+ bool isNothing() const { return bits == NOTHING; }
+ bool isSome() const { return !isNothing(); }
+ explicit operator bool() const { return isSome(); }
+
+ uint32_t value() const {
+ MOZ_ASSERT(isSome());
+ return bits;
+ }
+
+ uint32_t* operator&() { return &bits; }
+};
+
+// Common type for ImportEntry / ExportEntry / ModuleRequest within frontend. We
+// use a shared stencil class type to simplify serialization.
+//
+// https://tc39.es/ecma262/#importentry-record
+// https://tc39.es/ecma262/#exportentry-record
+//
+// Note: We subdivide the spec's ExportEntry into ExportAs / ExportFrom forms
+// for readability.
+class StencilModuleEntry {
+ public:
+ // clang-format off
+ //
+ // | RequestedModule | ImportEntry | ImportNamespaceEntry | ExportAs | ExportFrom | ExportNamespaceFrom | ExportBatchFrom |
+ // |----------------------------------------------------------------------------------------------------------------------|
+ // moduleRequest | required | required | required | null | required | required | required |
+ // localName | null | required | required | required | null | null | null |
+ // importName | null | required | null | null | required | null | null |
+ // exportName | null | null | null | required | required | required | null |
+ //
+ // clang-format on
+ MaybeModuleRequestIndex moduleRequest;
+ TaggedParserAtomIndex localName;
+ TaggedParserAtomIndex importName;
+ TaggedParserAtomIndex exportName;
+
+ // Location used for error messages. If this is for a module request entry
+ // then it is the module specifier string, otherwise the import/export spec
+ // that failed. Exports may not fill these fields if an error cannot be
+ // generated such as `export let x;`.
+ uint32_t lineno = 0;
+ uint32_t column = 0;
+
+ private:
+ StencilModuleEntry(uint32_t lineno, uint32_t column)
+ : lineno(lineno), column(column) {}
+
+ public:
+ // For XDR only.
+ StencilModuleEntry() = default;
+
+ StencilModuleEntry(const StencilModuleEntry& other)
+ : moduleRequest(other.moduleRequest),
+ localName(other.localName),
+ importName(other.importName),
+ exportName(other.exportName),
+ lineno(other.lineno),
+ column(other.column) {}
+
+ StencilModuleEntry(StencilModuleEntry&& other) noexcept
+ : moduleRequest(other.moduleRequest),
+ localName(other.localName),
+ importName(other.importName),
+ exportName(other.exportName),
+ lineno(other.lineno),
+ column(other.column) {}
+
+ StencilModuleEntry& operator=(StencilModuleEntry& other) {
+ moduleRequest = other.moduleRequest;
+ localName = other.localName;
+ importName = other.importName;
+ exportName = other.exportName;
+ lineno = other.lineno;
+ column = other.column;
+ return *this;
+ }
+
+ StencilModuleEntry& operator=(StencilModuleEntry&& other) noexcept {
+ moduleRequest = other.moduleRequest;
+ localName = other.localName;
+ importName = other.importName;
+ exportName = other.exportName;
+ lineno = other.lineno;
+ column = other.column;
+ return *this;
+ }
+
+ static StencilModuleEntry requestedModule(
+ MaybeModuleRequestIndex moduleRequest, uint32_t lineno, uint32_t column) {
+ MOZ_ASSERT(moduleRequest.isSome());
+ StencilModuleEntry entry(lineno, column);
+ entry.moduleRequest = moduleRequest;
+ return entry;
+ }
+
+ static StencilModuleEntry importEntry(MaybeModuleRequestIndex moduleRequest,
+ TaggedParserAtomIndex localName,
+ TaggedParserAtomIndex importName,
+ uint32_t lineno, uint32_t column) {
+ MOZ_ASSERT(moduleRequest.isSome());
+ MOZ_ASSERT(localName && importName);
+ StencilModuleEntry entry(lineno, column);
+ entry.moduleRequest = moduleRequest;
+ entry.localName = localName;
+ entry.importName = importName;
+ return entry;
+ }
+
+ static StencilModuleEntry importNamespaceEntry(
+ MaybeModuleRequestIndex moduleRequest, TaggedParserAtomIndex localName,
+ uint32_t lineno, uint32_t column) {
+ MOZ_ASSERT(moduleRequest.isSome());
+ MOZ_ASSERT(localName);
+ StencilModuleEntry entry(lineno, column);
+ entry.moduleRequest = moduleRequest;
+ entry.localName = localName;
+ return entry;
+ }
+
+ static StencilModuleEntry exportAsEntry(TaggedParserAtomIndex localName,
+ TaggedParserAtomIndex exportName,
+ uint32_t lineno, uint32_t column) {
+ MOZ_ASSERT(localName && exportName);
+ StencilModuleEntry entry(lineno, column);
+ entry.localName = localName;
+ entry.exportName = exportName;
+ return entry;
+ }
+
+ static StencilModuleEntry exportFromEntry(
+ MaybeModuleRequestIndex moduleRequest, TaggedParserAtomIndex importName,
+ TaggedParserAtomIndex exportName, uint32_t lineno, uint32_t column) {
+ MOZ_ASSERT(moduleRequest.isSome());
+ MOZ_ASSERT(importName && exportName);
+ StencilModuleEntry entry(lineno, column);
+ entry.moduleRequest = moduleRequest;
+ entry.importName = importName;
+ entry.exportName = exportName;
+ return entry;
+ }
+
+ static StencilModuleEntry exportNamespaceFromEntry(
+ MaybeModuleRequestIndex moduleRequest, TaggedParserAtomIndex exportName,
+ uint32_t lineno, uint32_t column) {
+ MOZ_ASSERT(moduleRequest.isSome());
+ MOZ_ASSERT(exportName);
+ StencilModuleEntry entry(lineno, column);
+ entry.moduleRequest = MaybeModuleRequestIndex(moduleRequest);
+ entry.exportName = exportName;
+ return entry;
+ }
+
+ static StencilModuleEntry exportBatchFromEntry(
+ MaybeModuleRequestIndex moduleRequest, uint32_t lineno, uint32_t column) {
+ MOZ_ASSERT(moduleRequest.isSome());
+ StencilModuleEntry entry(lineno, column);
+ entry.moduleRequest = MaybeModuleRequestIndex(moduleRequest);
+ return entry;
+ }
+};
+
+// Metadata generated by parsing module scripts, including import/export tables.
+class StencilModuleMetadata
+ : public js::AtomicRefCounted<StencilModuleMetadata> {
+ public:
+ using RequestVector = Vector<StencilModuleRequest, 0, js::SystemAllocPolicy>;
+ using EntryVector = Vector<StencilModuleEntry, 0, js::SystemAllocPolicy>;
+
+ RequestVector moduleRequests;
+ EntryVector requestedModules;
+ EntryVector importEntries;
+ EntryVector localExportEntries;
+ EntryVector indirectExportEntries;
+ EntryVector starExportEntries;
+ FunctionDeclarationVector functionDecls;
+ // Set to true if the module has a top-level await keyword.
+ bool isAsync = false;
+
+ StencilModuleMetadata() = default;
+
+ bool initModule(JSContext* cx, FrontendContext* fc,
+ CompilationAtomCache& atomCache,
+ JS::Handle<ModuleObject*> module) const;
+
+ size_t sizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const {
+ return mallocSizeOf(this) +
+ requestedModules.sizeOfExcludingThis(mallocSizeOf) +
+ importEntries.sizeOfExcludingThis(mallocSizeOf) +
+ localExportEntries.sizeOfExcludingThis(mallocSizeOf) +
+ indirectExportEntries.sizeOfExcludingThis(mallocSizeOf) +
+ starExportEntries.sizeOfExcludingThis(mallocSizeOf) +
+ functionDecls.sizeOfExcludingThis(mallocSizeOf);
+ }
+
+#if defined(DEBUG) || defined(JS_JITSPEW)
+ void dump() const;
+ void dump(JSONPrinter& json, const CompilationStencil* stencil) const;
+ void dumpFields(JSONPrinter& json, const CompilationStencil* stencil) const;
+#endif
+
+ private:
+ bool createModuleRequestObjects(
+ JSContext* cx, CompilationAtomCache& atomCache,
+ MutableHandle<ModuleRequestVector> output) const;
+ bool createRequestedModules(
+ JSContext* cx, CompilationAtomCache& atomCache,
+ Handle<ModuleRequestVector> moduleRequests,
+ MutableHandle<RequestedModuleVector> output) const;
+ bool createImportEntries(JSContext* cx, CompilationAtomCache& atomCache,
+ Handle<ModuleRequestVector> moduleRequests,
+ MutableHandle<ImportEntryVector> output) const;
+ bool createExportEntries(JSContext* cx, CompilationAtomCache& atomCache,
+ Handle<ModuleRequestVector> moduleRequests,
+ const EntryVector& input,
+ MutableHandle<ExportEntryVector> output) const;
+ ModuleRequestObject* createModuleRequestObject(
+ JSContext* cx, CompilationAtomCache& atomCache,
+ const StencilModuleRequest& request) const;
+};
+
+// As an alternative to a ScopeIndex (which references a ScopeStencil), we may
+// instead refer to an existing scope from GlobalObject::emptyGlobalScope().
+//
+// NOTE: This is only used for the self-hosting global.
+class EmptyGlobalScopeType {};
+
+// Things pointed by this index all end up being baked into GC things as part
+// of stencil instantiation.
+//
+// 0x0000_0000 Null
+// 0x1YYY_YYYY 28-bit ParserAtom
+// 0x2YYY_YYYY Well-known/static atom (See TaggedParserAtomIndex)
+// 0x3YYY_YYYY 28-bit BigInt
+// 0x4YYY_YYYY 28-bit ObjLiteral
+// 0x5YYY_YYYY 28-bit RegExp
+// 0x6YYY_YYYY 28-bit Scope
+// 0x7YYY_YYYY 28-bit Function
+// 0x8000_0000 EmptyGlobalScope
+class TaggedScriptThingIndex {
+ uint32_t data_;
+
+ static constexpr size_t IndexBit = TaggedParserAtomIndex::IndexBit;
+ static constexpr size_t IndexMask = TaggedParserAtomIndex::IndexMask;
+
+ static constexpr size_t TagShift = TaggedParserAtomIndex::TagShift;
+ static constexpr size_t TagBit = TaggedParserAtomIndex::TagBit;
+ static constexpr size_t TagMask = TaggedParserAtomIndex::TagMask;
+
+ public:
+ enum class Kind : uint32_t {
+ Null = uint32_t(TaggedParserAtomIndex::Kind::Null),
+ ParserAtomIndex = uint32_t(TaggedParserAtomIndex::Kind::ParserAtomIndex),
+ WellKnown = uint32_t(TaggedParserAtomIndex::Kind::WellKnown),
+ BigInt,
+ ObjLiteral,
+ RegExp,
+ Scope,
+ Function,
+ EmptyGlobalScope,
+ };
+
+ private:
+ static constexpr uint32_t NullTag = uint32_t(Kind::Null) << TagShift;
+ static_assert(NullTag == TaggedParserAtomIndex::NullTag);
+ static constexpr uint32_t ParserAtomIndexTag = uint32_t(Kind::ParserAtomIndex)
+ << TagShift;
+ static_assert(ParserAtomIndexTag ==
+ TaggedParserAtomIndex::ParserAtomIndexTag);
+ static constexpr uint32_t WellKnownTag = uint32_t(Kind::WellKnown)
+ << TagShift;
+ static_assert(WellKnownTag == TaggedParserAtomIndex::WellKnownTag);
+
+ static constexpr uint32_t BigIntTag = uint32_t(Kind::BigInt) << TagShift;
+ static constexpr uint32_t ObjLiteralTag = uint32_t(Kind::ObjLiteral)
+ << TagShift;
+ static constexpr uint32_t RegExpTag = uint32_t(Kind::RegExp) << TagShift;
+ static constexpr uint32_t ScopeTag = uint32_t(Kind::Scope) << TagShift;
+ static constexpr uint32_t FunctionTag = uint32_t(Kind::Function) << TagShift;
+ static constexpr uint32_t EmptyGlobalScopeTag =
+ uint32_t(Kind::EmptyGlobalScope) << TagShift;
+
+ public:
+ static constexpr uint32_t IndexLimit = Bit(IndexBit);
+
+ TaggedScriptThingIndex() : data_(NullTag) {}
+
+ explicit TaggedScriptThingIndex(TaggedParserAtomIndex index)
+ : data_(index.rawData()) {}
+ explicit TaggedScriptThingIndex(BigIntIndex index)
+ : data_(uint32_t(index) | BigIntTag) {
+ MOZ_ASSERT(uint32_t(index) < IndexLimit);
+ }
+ explicit TaggedScriptThingIndex(ObjLiteralIndex index)
+ : data_(uint32_t(index) | ObjLiteralTag) {
+ MOZ_ASSERT(uint32_t(index) < IndexLimit);
+ }
+ explicit TaggedScriptThingIndex(RegExpIndex index)
+ : data_(uint32_t(index) | RegExpTag) {
+ MOZ_ASSERT(uint32_t(index) < IndexLimit);
+ }
+ explicit TaggedScriptThingIndex(ScopeIndex index)
+ : data_(uint32_t(index) | ScopeTag) {
+ MOZ_ASSERT(uint32_t(index) < IndexLimit);
+ }
+ explicit TaggedScriptThingIndex(ScriptIndex index)
+ : data_(uint32_t(index) | FunctionTag) {
+ MOZ_ASSERT(uint32_t(index) < IndexLimit);
+ }
+ explicit TaggedScriptThingIndex(EmptyGlobalScopeType t)
+ : data_(EmptyGlobalScopeTag) {}
+
+ bool isAtom() const {
+ return (data_ & TagMask) == ParserAtomIndexTag ||
+ (data_ & TagMask) == WellKnownTag;
+ }
+ bool isNull() const {
+ bool result = !data_;
+ MOZ_ASSERT_IF(result, (data_ & TagMask) == NullTag);
+ return result;
+ }
+ bool isBigInt() const { return (data_ & TagMask) == BigIntTag; }
+ bool isObjLiteral() const { return (data_ & TagMask) == ObjLiteralTag; }
+ bool isRegExp() const { return (data_ & TagMask) == RegExpTag; }
+ bool isScope() const { return (data_ & TagMask) == ScopeTag; }
+ bool isFunction() const { return (data_ & TagMask) == FunctionTag; }
+ bool isEmptyGlobalScope() const {
+ return (data_ & TagMask) == EmptyGlobalScopeTag;
+ }
+
+ TaggedParserAtomIndex toAtom() const {
+ MOZ_ASSERT(isAtom());
+ return TaggedParserAtomIndex::fromRaw(data_);
+ }
+ BigIntIndex toBigInt() const { return BigIntIndex(data_ & IndexMask); }
+ ObjLiteralIndex toObjLiteral() const {
+ return ObjLiteralIndex(data_ & IndexMask);
+ }
+ RegExpIndex toRegExp() const { return RegExpIndex(data_ & IndexMask); }
+ ScopeIndex toScope() const { return ScopeIndex(data_ & IndexMask); }
+ ScriptIndex toFunction() const { return ScriptIndex(data_ & IndexMask); }
+
+ TaggedParserAtomIndex toAtomOrNull() const {
+ MOZ_ASSERT(isAtom() || isNull());
+ return TaggedParserAtomIndex::fromRaw(data_);
+ }
+
+ uint32_t* rawDataRef() { return &data_; }
+ uint32_t rawData() const { return data_; }
+
+ Kind tag() const { return Kind((data_ & TagMask) >> TagShift); }
+
+ bool operator==(const TaggedScriptThingIndex& rhs) const {
+ return data_ == rhs.data_;
+ }
+};
+
+// Data generated by frontend that will be used to create a js::BaseScript.
+class ScriptStencil {
+ friend struct CompilationStencilMerger;
+
+ public:
+ // Fields for BaseScript.
+ // Used by:
+ // * Global script
+ // * Eval
+ // * Module
+ // * non-lazy Function (except asm.js module)
+ // * lazy Function (cannot be asm.js module)
+
+ // GCThings are stored into
+ // {ExtensibleCompilationStencil,CompilationStencil}.gcThingData,
+ // in [gcThingsOffset, gcThingsOffset + gcThingsLength) range.
+ CompilationGCThingIndex gcThingsOffset;
+ uint32_t gcThingsLength = 0;
+
+ // Fields for JSFunction.
+ // Used by:
+ // * non-lazy Function
+ // * lazy Function
+ // * asm.js module
+
+ // The explicit or implicit name of the function. The FunctionFlags indicate
+ // the kind of name.
+ TaggedParserAtomIndex functionAtom;
+
+ // If this ScriptStencil refers to a lazy child of the function being
+ // compiled, this field holds the child's immediately enclosing scope's index.
+ // Once compilation succeeds, we will store the scope pointed by this in the
+ // child's BaseScript. (Debugger may become confused if lazy scripts refer to
+ // partially initialized enclosing scopes, so we must avoid storing the
+ // scope in the BaseScript until compilation has completed
+ // successfully.)
+ //
+ // OR
+ //
+ // This may be used for self-hosting canonical name (TaggedParserAtomIndex).
+ TaggedScriptThingIndex enclosingScopeOrCanonicalName;
+
+ // See: `FunctionFlags`.
+ FunctionFlags functionFlags = {};
+
+ // This is set by the BytecodeEmitter of the enclosing script when a reference
+ // to this function is generated.
+ static constexpr uint16_t WasEmittedByEnclosingScriptFlag = 1 << 0;
+
+ // If this is for the root of delazification, this represents
+ // MutableScriptFlagsEnum::AllowRelazify value of the script *after*
+ // delazification.
+ // False otherwise.
+ static constexpr uint16_t AllowRelazifyFlag = 1 << 1;
+
+ // Set if this is non-lazy script and shared data is created.
+ // The shared data is stored into CompilationStencil.sharedData.
+ static constexpr uint16_t HasSharedDataFlag = 1 << 2;
+
+ // True if this script is lazy function and has enclosing scope. In that
+ // case, `enclosingScopeOrCanonicalName` will hold the ScopeIndex.
+ static constexpr uint16_t HasLazyFunctionEnclosingScopeIndexFlag = 1 << 3;
+
+ // True if this script is a self-hosted function with a canonical name
+ // explicitly set. In that case, `enclosingScopeOrCanonicalName` will hold the
+ // TaggedParserAtomIndex.
+ static constexpr uint16_t HasSelfHostedCanonicalName = 1 << 4;
+
+ uint16_t flags_ = 0;
+
+ // End of fields.
+
+ ScriptStencil() = default;
+
+ bool isFunction() const {
+ bool result = functionFlags.toRaw() != 0x0000;
+ MOZ_ASSERT_IF(
+ result, functionFlags.isAsmJSNative() || functionFlags.hasBaseScript());
+ return result;
+ }
+
+ bool hasGCThings() const { return gcThingsLength; }
+
+ mozilla::Span<TaggedScriptThingIndex> gcthings(
+ const CompilationStencil& stencil) const;
+
+ bool wasEmittedByEnclosingScript() const {
+ return flags_ & WasEmittedByEnclosingScriptFlag;
+ }
+
+ void setWasEmittedByEnclosingScript() {
+ flags_ |= WasEmittedByEnclosingScriptFlag;
+ }
+
+ bool allowRelazify() const { return flags_ & AllowRelazifyFlag; }
+
+ void setAllowRelazify() { flags_ |= AllowRelazifyFlag; }
+
+ bool isGhost() const { return functionFlags.isGhost(); }
+ void setIsGhost() { functionFlags.setIsGhost(); }
+
+ bool hasSharedData() const { return flags_ & HasSharedDataFlag; }
+
+ void setHasSharedData() { flags_ |= HasSharedDataFlag; }
+
+ bool hasLazyFunctionEnclosingScopeIndex() const {
+ return flags_ & HasLazyFunctionEnclosingScopeIndexFlag;
+ }
+
+ bool hasSelfHostedCanonicalName() const {
+ return flags_ & HasSelfHostedCanonicalName;
+ }
+
+ private:
+ void setHasLazyFunctionEnclosingScopeIndex() {
+ flags_ |= HasLazyFunctionEnclosingScopeIndexFlag;
+ }
+
+ void setHasSelfHostedCanonicalName() { flags_ |= HasSelfHostedCanonicalName; }
+
+ public:
+ void setLazyFunctionEnclosingScopeIndex(ScopeIndex index) {
+ MOZ_ASSERT(enclosingScopeOrCanonicalName.isNull());
+ enclosingScopeOrCanonicalName = TaggedScriptThingIndex(index);
+ setHasLazyFunctionEnclosingScopeIndex();
+ }
+
+ void resetHasLazyFunctionEnclosingScopeIndexAfterStencilMerge() {
+ flags_ &= ~HasLazyFunctionEnclosingScopeIndexFlag;
+ enclosingScopeOrCanonicalName = TaggedScriptThingIndex();
+ }
+
+ ScopeIndex lazyFunctionEnclosingScopeIndex() const {
+ MOZ_ASSERT(hasLazyFunctionEnclosingScopeIndex());
+ return enclosingScopeOrCanonicalName.toScope();
+ }
+
+ void setSelfHostedCanonicalName(TaggedParserAtomIndex name) {
+ MOZ_ASSERT(enclosingScopeOrCanonicalName.isNull());
+ enclosingScopeOrCanonicalName = TaggedScriptThingIndex(name);
+ setHasSelfHostedCanonicalName();
+ }
+
+ TaggedParserAtomIndex selfHostedCanonicalName() const {
+ MOZ_ASSERT(hasSelfHostedCanonicalName());
+ return enclosingScopeOrCanonicalName.toAtom();
+ }
+
+#if defined(DEBUG) || defined(JS_JITSPEW)
+ void dump() const;
+ void dump(JSONPrinter& json, const CompilationStencil* stencil) const;
+ void dumpFields(JSONPrinter& json, const CompilationStencil* stencil) const;
+#endif
+};
+
+// In addition to ScriptStencil, data generated only while initial-parsing.
+class ScriptStencilExtra {
+ public:
+ // See `BaseScript::immutableFlags_`.
+ ImmutableScriptFlags immutableFlags;
+
+ // The location of this script in the source.
+ SourceExtent extent;
+
+ // See `PrivateScriptData::memberInitializers_`.
+ // This data only valid when `UseMemberInitializers` script flag is true.
+ uint32_t memberInitializers_ = 0;
+
+ // See `JSFunction::nargs_`.
+ uint16_t nargs = 0;
+
+ // To make this struct packed, add explicit field for padding.
+ uint16_t padding_ = 0;
+
+ ScriptStencilExtra() = default;
+
+ RO_IMMUTABLE_SCRIPT_FLAGS(immutableFlags)
+
+ void setMemberInitializers(MemberInitializers member) {
+ MOZ_ASSERT(useMemberInitializers());
+ memberInitializers_ = member.serialize();
+ }
+
+ MemberInitializers memberInitializers() const {
+ MOZ_ASSERT(useMemberInitializers());
+ return MemberInitializers::deserialize(memberInitializers_);
+ }
+
+#if defined(DEBUG) || defined(JS_JITSPEW)
+ void dump() const;
+ void dump(JSONPrinter& json) const;
+ void dumpFields(JSONPrinter& json) const;
+#endif
+};
+
+#if defined(DEBUG) || defined(JS_JITSPEW)
+void DumpTaggedParserAtomIndex(js::JSONPrinter& json,
+ TaggedParserAtomIndex taggedIndex,
+ const CompilationStencil* stencil);
+
+void DumpTaggedParserAtomIndexNoQuote(GenericPrinter& out,
+ TaggedParserAtomIndex taggedIndex,
+ const CompilationStencil* stencil);
+#endif
+
+} /* namespace frontend */
+
+#if defined(DEBUG) || defined(JS_JITSPEW)
+void DumpImmutableScriptFlags(js::JSONPrinter& json,
+ ImmutableScriptFlags immutableFlags);
+void DumpFunctionFlagsItems(js::JSONPrinter& json, FunctionFlags functionFlags);
+#endif
+
+} /* namespace js */
+
+#endif /* frontend_Stencil_h */
diff --git a/js/src/frontend/StencilXdr.cpp b/js/src/frontend/StencilXdr.cpp
new file mode 100644
index 0000000000..2c6d7ec49c
--- /dev/null
+++ b/js/src/frontend/StencilXdr.cpp
@@ -0,0 +1,1536 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "frontend/StencilXdr.h" // StencilXDR
+
+#include "mozilla/ArrayUtils.h" // mozilla::ArrayEqual
+#include "mozilla/OperatorNewExtensions.h" // mozilla::KnownNotNull
+#include "mozilla/ScopeExit.h" // mozilla::MakeScopeExit
+
+#include <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 "ds/LifoAlloc.h" // LifoAlloc
+#include "frontend/CompilationStencil.h" // CompilationStencil, ExtensibleCompilationStencil
+#include "frontend/ScriptIndex.h" // ScriptIndex
+#include "vm/Scope.h" // SizeOfParserScopeData
+#include "vm/StencilEnums.h" // js::ImmutableScriptFlagsEnum
+
+using namespace js;
+using namespace js::frontend;
+
+using mozilla::Utf8Unit;
+
+template <typename NameType>
+struct CanEncodeNameType {
+ static constexpr bool value = false;
+};
+
+template <>
+struct CanEncodeNameType<TaggedParserAtomIndex> {
+ static constexpr bool value = true;
+};
+
+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->fc());
+ 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->fc());
+ 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, LifoAlloc& alloc,
+ 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 = alloc.template newArrayUninitialized<T>(size);
+ if (!p) {
+ js::ReportOutOfMemory(xdr->fc());
+ return xdr->fail(JS::TranscodeResult::Throw);
+ }
+ span = mozilla::Span(p, size);
+
+ for (size_t i = 0; i < size; i++) {
+ new (mozilla::KnownNotNull, &span[i]) T();
+ }
+ }
+ }
+
+ return Ok();
+}
+
+template <XDRMode mode, typename T>
+static XDRResult XDRSpanContent(XDRState<mode>* xdr, LifoAlloc& alloc,
+ 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 constexpr (mode == XDR_ENCODE) {
+ data = span.data();
+ MOZ_TRY(xdr->codeBytes(data, sizeof(T) * size));
+ } else {
+ const auto& options = static_cast<XDRStencilDecoder*>(xdr)->options();
+ if (options.borrowBuffer) {
+ MOZ_TRY(xdr->borrowedData(&data, sizeof(T) * size));
+ } else {
+ data = alloc.template newArrayUninitialized<T>(size);
+ if (!data) {
+ js::ReportOutOfMemory(xdr->fc());
+ return xdr->fail(JS::TranscodeResult::Throw);
+ }
+ MOZ_TRY(xdr->codeBytes(data, sizeof(T) * size));
+ }
+ }
+ if (mode == XDR_DECODE) {
+ span = mozilla::Span(data, size);
+ }
+ }
+
+ return Ok();
+}
+
+template <XDRMode mode, typename T>
+static XDRResult XDRSpanContent(XDRState<mode>* xdr, LifoAlloc& alloc,
+ 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, alloc, span, size);
+}
+
+template <XDRMode mode>
+/* static */ XDRResult StencilXDR::codeBigInt(XDRState<mode>* xdr,
+ LifoAlloc& alloc,
+ BigIntStencil& stencil) {
+ uint32_t size;
+ if (mode == XDR_ENCODE) {
+ size = stencil.source_.size();
+ }
+ MOZ_TRY(xdr->codeUint32(&size));
+
+ return XDRSpanContent(xdr, alloc, stencil.source_, size);
+}
+
+template <XDRMode mode>
+/* static */ XDRResult StencilXDR::codeObjLiteral(XDRState<mode>* xdr,
+ LifoAlloc& alloc,
+ ObjLiteralStencil& stencil) {
+ uint8_t kindAndFlags = 0;
+
+ if (mode == XDR_ENCODE) {
+ static_assert(sizeof(ObjLiteralKindAndFlags) == sizeof(uint8_t));
+ kindAndFlags = stencil.kindAndFlags_.toRaw();
+ }
+ MOZ_TRY(xdr->codeUint8(&kindAndFlags));
+ if (mode == XDR_DECODE) {
+ stencil.kindAndFlags_.setRaw(kindAndFlags);
+ }
+
+ MOZ_TRY(xdr->codeUint32(&stencil.propertyCount_));
+
+ MOZ_TRY(XDRSpanContent(xdr, alloc, stencil.code_));
+
+ return Ok();
+}
+
+template <typename ScopeT>
+/* static */ void AssertScopeSpecificDataIsEncodable() {
+ using ScopeDataT = typename ScopeT::ParserData;
+
+ static_assert(CanEncodeNameType<typename ScopeDataT::NameType>::value);
+ static_assert(CanCopyDataToDisk<ScopeDataT>::value,
+ "ScopeData cannot be bulk-copied to disk");
+}
+
+template <XDRMode mode>
+/* static */ XDRResult StencilXDR::codeScopeData(
+ XDRState<mode>* xdr, LifoAlloc& alloc, ScopeStencil& stencil,
+ BaseParserScopeData*& baseScopeData) {
+ // WasmInstanceScope & WasmFunctionScope should not appear in stencils.
+ MOZ_ASSERT(stencil.kind_ != ScopeKind::WasmInstance);
+ MOZ_ASSERT(stencil.kind_ != ScopeKind::WasmFunction);
+ if (stencil.kind_ == ScopeKind::With) {
+ return Ok();
+ }
+
+ MOZ_TRY(xdr->align32());
+
+ static_assert(offsetof(BaseParserScopeData, length) == 0,
+ "length should be the first field");
+ uint32_t length;
+ if (mode == XDR_ENCODE) {
+ length = baseScopeData->length;
+ } else {
+ MOZ_TRY(xdr->peekUint32(&length));
+ }
+
+ AssertScopeSpecificDataIsEncodable<FunctionScope>();
+ AssertScopeSpecificDataIsEncodable<VarScope>();
+ AssertScopeSpecificDataIsEncodable<LexicalScope>();
+ AssertScopeSpecificDataIsEncodable<ClassBodyScope>();
+ AssertScopeSpecificDataIsEncodable<EvalScope>();
+ AssertScopeSpecificDataIsEncodable<GlobalScope>();
+ AssertScopeSpecificDataIsEncodable<ModuleScope>();
+
+ // In both decoding and encoding, stencil.kind_ is now known, and
+ // can be assumed. This allows the encoding to write out the bytes
+ // for the specialized scope-data type without needing to encode
+ // a distinguishing prefix.
+ uint32_t totalLength = SizeOfParserScopeData(stencil.kind_, length);
+ if constexpr (mode == XDR_ENCODE) {
+ MOZ_TRY(xdr->codeBytes(baseScopeData, totalLength));
+ } else {
+ const auto& options = static_cast<XDRStencilDecoder*>(xdr)->options();
+ if (options.borrowBuffer) {
+ MOZ_TRY(xdr->borrowedData(&baseScopeData, totalLength));
+ } else {
+ baseScopeData =
+ reinterpret_cast<BaseParserScopeData*>(alloc.alloc(totalLength));
+ if (!baseScopeData) {
+ js::ReportOutOfMemory(xdr->fc());
+ return xdr->fail(JS::TranscodeResult::Throw);
+ }
+ MOZ_TRY(xdr->codeBytes(baseScopeData, totalLength));
+ }
+ }
+
+ return Ok();
+}
+
+template <XDRMode mode>
+/* static */
+XDRResult StencilXDR::codeSharedData(XDRState<mode>* xdr,
+ RefPtr<SharedImmutableScriptData>& sisd) {
+ static_assert(frontend::CanCopyDataToDisk<ImmutableScriptData>::value,
+ "ImmutableScriptData cannot be bulk-copied to disk");
+ static_assert(frontend::CanCopyDataToDisk<jsbytecode>::value,
+ "jsbytecode cannot be bulk-copied to disk");
+ static_assert(frontend::CanCopyDataToDisk<SrcNote>::value,
+ "SrcNote cannot be bulk-copied to disk");
+ static_assert(frontend::CanCopyDataToDisk<ScopeNote>::value,
+ "ScopeNote cannot be bulk-copied to disk");
+ static_assert(frontend::CanCopyDataToDisk<TryNote>::value,
+ "TryNote cannot be bulk-copied to disk");
+
+ uint32_t size;
+ uint32_t hash;
+ if (mode == XDR_ENCODE) {
+ if (sisd) {
+ size = sisd->immutableDataLength();
+ hash = sisd->hash();
+ } else {
+ size = 0;
+ hash = 0;
+ }
+ }
+ MOZ_TRY(xdr->codeUint32(&size));
+
+ // A size of zero is used when the `sisd` is nullptr. This can occur for
+ // certain outer container modes. In this case, there is no further
+ // transcoding to do.
+ if (!size) {
+ MOZ_ASSERT(!sisd);
+ return Ok();
+ }
+
+ MOZ_TRY(xdr->align32());
+ static_assert(alignof(ImmutableScriptData) <= alignof(uint32_t));
+
+ MOZ_TRY(xdr->codeUint32(&hash));
+
+ if constexpr (mode == XDR_ENCODE) {
+ uint8_t* data = const_cast<uint8_t*>(sisd->get()->immutableData().data());
+ MOZ_ASSERT(data == reinterpret_cast<const uint8_t*>(sisd->get()),
+ "Decode below relies on the data placement");
+ MOZ_TRY(xdr->codeBytes(data, size));
+ } else {
+ sisd = SharedImmutableScriptData::create(xdr->fc());
+ if (!sisd) {
+ return xdr->fail(JS::TranscodeResult::Throw);
+ }
+
+ const auto& options = static_cast<XDRStencilDecoder*>(xdr)->options();
+ if (options.usePinnedBytecode) {
+ MOZ_ASSERT(options.borrowBuffer);
+ ImmutableScriptData* isd;
+ MOZ_TRY(xdr->borrowedData(&isd, size));
+ sisd->setExternal(isd, hash);
+ } else {
+ auto isd = ImmutableScriptData::new_(xdr->fc(), size);
+ if (!isd) {
+ return xdr->fail(JS::TranscodeResult::Throw);
+ }
+ uint8_t* data = reinterpret_cast<uint8_t*>(isd.get());
+ MOZ_TRY(xdr->codeBytes(data, size));
+ sisd->setOwn(std::move(isd), hash);
+ }
+
+ if (!sisd->get()->validateLayout(size)) {
+ MOZ_ASSERT(false, "Bad ImmutableScriptData");
+ return xdr->fail(JS::TranscodeResult::Failure_BadDecode);
+ }
+ }
+
+ if (mode == XDR_DECODE) {
+ if (!SharedImmutableScriptData::shareScriptData(xdr->fc(), sisd)) {
+ return xdr->fail(JS::TranscodeResult::Throw);
+ }
+ }
+
+ return Ok();
+}
+
+// Called from js::XDRScript.
+template /* static */ XDRResult StencilXDR::codeSharedData(
+ XDRState<XDR_ENCODE>* xdr, RefPtr<SharedImmutableScriptData>& sisd);
+template /* static */ XDRResult StencilXDR::codeSharedData(
+ XDRState<XDR_DECODE>* xdr, RefPtr<SharedImmutableScriptData>& sisd);
+
+template <XDRMode mode>
+/* static */ XDRResult StencilXDR::codeSharedDataContainer(
+ XDRState<mode>* xdr, SharedDataContainer& sharedData) {
+ if (mode == XDR_ENCODE) {
+ if (sharedData.isBorrow()) {
+ return codeSharedDataContainer(xdr, *sharedData.asBorrow());
+ }
+ }
+
+ enum Kind {
+ Single,
+ Vector,
+ Map,
+ };
+
+ Kind kind;
+ if (mode == XDR_ENCODE) {
+ if (sharedData.isSingle()) {
+ kind = Kind::Single;
+ } else if (sharedData.isVector()) {
+ kind = Kind::Vector;
+ } else {
+ MOZ_ASSERT(sharedData.isMap());
+ kind = Kind::Map;
+ }
+ }
+ MOZ_TRY(xdr->codeEnum32(&kind));
+
+ switch (kind) {
+ case Kind::Single: {
+ RefPtr<SharedImmutableScriptData> ref;
+ if (mode == XDR_ENCODE) {
+ ref = sharedData.asSingle();
+ }
+ MOZ_TRY(codeSharedData<mode>(xdr, ref));
+ if (mode == XDR_DECODE) {
+ sharedData.setSingle(ref.forget());
+ }
+ break;
+ }
+
+ case Kind::Vector: {
+ if (mode == XDR_DECODE) {
+ if (!sharedData.initVector(xdr->fc())) {
+ return xdr->fail(JS::TranscodeResult::Throw);
+ }
+ }
+ auto& vec = *sharedData.asVector();
+ MOZ_TRY(XDRVectorInitialized(xdr, vec));
+ for (auto& entry : vec) {
+ // NOTE: There can be nullptr, even if we don't perform syntax parsing,
+ // because of constant folding.
+ MOZ_TRY(codeSharedData<mode>(xdr, entry));
+ }
+ break;
+ }
+
+ case Kind::Map: {
+ if (mode == XDR_DECODE) {
+ if (!sharedData.initMap(xdr->fc())) {
+ return xdr->fail(JS::TranscodeResult::Throw);
+ }
+ }
+ auto& map = *sharedData.asMap();
+ uint32_t count;
+ if (mode == XDR_ENCODE) {
+ count = map.count();
+ }
+ MOZ_TRY(xdr->codeUint32(&count));
+ if (mode == XDR_DECODE) {
+ if (!map.reserve(count)) {
+ js::ReportOutOfMemory(xdr->fc());
+ return xdr->fail(JS::TranscodeResult::Throw);
+ }
+ }
+
+ if (mode == XDR_ENCODE) {
+ for (auto iter = map.iter(); !iter.done(); iter.next()) {
+ uint32_t index = iter.get().key().index;
+ auto& data = iter.get().value();
+ MOZ_TRY(xdr->codeUint32(&index));
+ MOZ_TRY(codeSharedData<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(codeSharedData<mode>(xdr, data));
+
+ if (!map.putNew(index, data)) {
+ js::ReportOutOfMemory(xdr->fc());
+ return xdr->fail(JS::TranscodeResult::Throw);
+ }
+ }
+ }
+
+ break;
+ }
+
+ default:
+ return xdr->fail(JS::TranscodeResult::Failure_BadDecode);
+ }
+
+ return Ok();
+}
+
+template <XDRMode mode>
+/* static */ XDRResult StencilXDR::codeParserAtom(XDRState<mode>* xdr,
+ LifoAlloc& alloc,
+ ParserAtom** atomp) {
+ static_assert(CanCopyDataToDisk<ParserAtom>::value,
+ "ParserAtom cannot be bulk-copied to disk.");
+
+ MOZ_TRY(xdr->align32());
+
+ const ParserAtom* header;
+ if (mode == XDR_ENCODE) {
+ header = *atomp;
+ } else {
+ MOZ_TRY(xdr->peekData(&header));
+ }
+
+ const uint32_t CharSize =
+ header->hasLatin1Chars() ? sizeof(JS::Latin1Char) : sizeof(char16_t);
+ uint32_t totalLength = sizeof(ParserAtom) + (CharSize * header->length());
+
+ if constexpr (mode == XDR_ENCODE) {
+ MOZ_TRY(xdr->codeBytes(*atomp, totalLength));
+ } else {
+ const auto& options = static_cast<XDRStencilDecoder*>(xdr)->options();
+ if (options.borrowBuffer) {
+ MOZ_TRY(xdr->borrowedData(atomp, totalLength));
+ } else {
+ *atomp = reinterpret_cast<ParserAtom*>(alloc.alloc(totalLength));
+ if (!*atomp) {
+ js::ReportOutOfMemory(xdr->fc());
+ return xdr->fail(JS::TranscodeResult::Throw);
+ }
+ MOZ_TRY(xdr->codeBytes(*atomp, totalLength));
+ }
+ }
+
+ return Ok();
+}
+
+template <XDRMode mode>
+static XDRResult XDRAtomCount(XDRState<mode>* xdr, uint32_t* atomCount) {
+ return xdr->codeUint32(atomCount);
+}
+
+template <XDRMode mode>
+/* static */ XDRResult StencilXDR::codeParserAtomSpan(
+ XDRState<mode>* xdr, LifoAlloc& alloc, ParserAtomSpan& parserAtomData) {
+ if (mode == XDR_ENCODE) {
+ uint32_t atomVectorLength = parserAtomData.size();
+ MOZ_TRY(XDRAtomCount(xdr, &atomVectorLength));
+
+ uint32_t atomCount = 0;
+ for (const auto& entry : parserAtomData) {
+ if (!entry) {
+ continue;
+ }
+ if (entry->isUsedByStencil()) {
+ atomCount++;
+ }
+ }
+ MOZ_TRY(XDRAtomCount(xdr, &atomCount));
+
+ for (uint32_t i = 0; i < atomVectorLength; i++) {
+ auto& entry = parserAtomData[i];
+ if (!entry) {
+ continue;
+ }
+ if (entry->isUsedByStencil()) {
+ MOZ_TRY(xdr->codeUint32(&i));
+ MOZ_TRY(codeParserAtom(xdr, alloc, &entry));
+ }
+ }
+
+ return Ok();
+ }
+
+ uint32_t atomVectorLength;
+ MOZ_TRY(XDRAtomCount(xdr, &atomVectorLength));
+
+ frontend::ParserAtomSpanBuilder builder(parserAtomData);
+ if (!builder.allocate(xdr->fc(), alloc, atomVectorLength)) {
+ return xdr->fail(JS::TranscodeResult::Throw);
+ }
+
+ uint32_t atomCount;
+ MOZ_TRY(XDRAtomCount(xdr, &atomCount));
+
+ for (uint32_t i = 0; i < atomCount; i++) {
+ frontend::ParserAtom* entry = nullptr;
+ uint32_t index;
+ MOZ_TRY(xdr->codeUint32(&index));
+ MOZ_TRY(codeParserAtom(xdr, alloc, &entry));
+ if (mode == XDR_DECODE) {
+ if (index >= atomVectorLength) {
+ return xdr->fail(JS::TranscodeResult::Failure_BadDecode);
+ }
+ }
+ builder.set(frontend::ParserAtomIndex(index), entry);
+ }
+
+ return Ok();
+}
+
+template <XDRMode mode>
+/* static */ XDRResult StencilXDR::codeModuleRequest(
+ XDRState<mode>* xdr, StencilModuleRequest& stencil) {
+ MOZ_TRY(xdr->codeUint32(stencil.specifier.rawDataRef()));
+ MOZ_TRY(XDRVectorContent(xdr, stencil.assertions));
+
+ return Ok();
+}
+
+template <XDRMode mode>
+/* static */ XDRResult StencilXDR::codeModuleRequestVector(
+ XDRState<mode>* xdr, StencilModuleMetadata::RequestVector& vector) {
+ MOZ_TRY(XDRVectorInitialized(xdr, vector));
+
+ for (auto& entry : vector) {
+ MOZ_TRY(codeModuleRequest<mode>(xdr, entry));
+ }
+
+ return Ok();
+}
+
+template <XDRMode mode>
+/* static */ XDRResult StencilXDR::codeModuleEntry(
+ XDRState<mode>* xdr, StencilModuleEntry& stencil) {
+ MOZ_TRY(xdr->codeUint32(&stencil.moduleRequest));
+ MOZ_TRY(xdr->codeUint32(stencil.localName.rawDataRef()));
+ MOZ_TRY(xdr->codeUint32(stencil.importName.rawDataRef()));
+ MOZ_TRY(xdr->codeUint32(stencil.exportName.rawDataRef()));
+ MOZ_TRY(xdr->codeUint32(&stencil.lineno));
+ MOZ_TRY(xdr->codeUint32(&stencil.column));
+
+ return Ok();
+}
+
+template <XDRMode mode>
+/* static */ XDRResult StencilXDR::codeModuleEntryVector(
+ XDRState<mode>* xdr, StencilModuleMetadata::EntryVector& vector) {
+ MOZ_TRY(XDRVectorInitialized(xdr, vector));
+
+ for (auto& entry : vector) {
+ MOZ_TRY(codeModuleEntry<mode>(xdr, entry));
+ }
+
+ return Ok();
+}
+
+template <XDRMode mode>
+/* static */ XDRResult StencilXDR::codeModuleMetadata(
+ XDRState<mode>* xdr, StencilModuleMetadata& stencil) {
+ MOZ_TRY(codeModuleRequestVector(xdr, stencil.moduleRequests));
+ MOZ_TRY(codeModuleEntryVector(xdr, stencil.requestedModules));
+ MOZ_TRY(codeModuleEntryVector(xdr, stencil.importEntries));
+ MOZ_TRY(codeModuleEntryVector(xdr, stencil.localExportEntries));
+ MOZ_TRY(codeModuleEntryVector(xdr, stencil.indirectExportEntries));
+ MOZ_TRY(codeModuleEntryVector(xdr, stencil.starExportEntries));
+ MOZ_TRY(XDRVectorContent(xdr, stencil.functionDecls));
+
+ uint8_t isAsync = 0;
+ if (mode == XDR_ENCODE) {
+ if (stencil.isAsync) {
+ isAsync = stencil.isAsync ? 1 : 0;
+ }
+ }
+
+ MOZ_TRY(xdr->codeUint8(&isAsync));
+
+ if (mode == XDR_DECODE) {
+ stencil.isAsync = isAsync == 1;
+ }
+
+ return Ok();
+}
+
+template <XDRMode mode>
+XDRResult XDRCompilationStencilSpanSize(
+ XDRState<mode>* xdr, uint32_t* scriptSize, uint32_t* gcThingSize,
+ uint32_t* scopeSize, uint32_t* scriptExtraSize, uint32_t* regExpSize,
+ uint32_t* bigIntSize, uint32_t* objLiteralSize) {
+ // Compress the series of span sizes, to avoid consuming extra space for
+ // unused/small span sizes.
+ // There will be align32 shortly after this section, so try to make the
+ // padding smaller.
+
+ enum XDRSpanSizeKind {
+ // All of the size values fit in 1 byte each. The entire section takes 7
+ // bytes, and expect no padding.
+ All8Kind,
+
+ // Other cases. All of the size values fit in 4 bytes each. Expect 3 bytes
+ // padding for `sizeKind`.
+ All32Kind,
+ };
+
+ uint8_t sizeKind = All32Kind;
+ if (mode == XDR_ENCODE) {
+ uint32_t mask = (*scriptSize) | (*gcThingSize) | (*scopeSize) |
+ (*scriptExtraSize) | (*regExpSize) | (*bigIntSize) |
+ (*objLiteralSize);
+
+ if (mask <= 0xff) {
+ sizeKind = All8Kind;
+ }
+ }
+ MOZ_TRY(xdr->codeUint8(&sizeKind));
+
+ if (sizeKind == All32Kind) {
+ MOZ_TRY(xdr->codeUint32(scriptSize));
+ MOZ_TRY(xdr->codeUint32(gcThingSize));
+ MOZ_TRY(xdr->codeUint32(scopeSize));
+ MOZ_TRY(xdr->codeUint32(scriptExtraSize));
+ MOZ_TRY(xdr->codeUint32(regExpSize));
+ MOZ_TRY(xdr->codeUint32(bigIntSize));
+ MOZ_TRY(xdr->codeUint32(objLiteralSize));
+ } else {
+ uint8_t scriptSize8 = 0;
+ uint8_t gcThingSize8 = 0;
+ uint8_t scopeSize8 = 0;
+ uint8_t scriptExtraSize8 = 0;
+ uint8_t regExpSize8 = 0;
+ uint8_t bigIntSize8 = 0;
+ uint8_t objLiteralSize8 = 0;
+
+ if (mode == XDR_ENCODE) {
+ scriptSize8 = uint8_t(*scriptSize);
+ gcThingSize8 = uint8_t(*gcThingSize);
+ scopeSize8 = uint8_t(*scopeSize);
+ scriptExtraSize8 = uint8_t(*scriptExtraSize);
+ regExpSize8 = uint8_t(*regExpSize);
+ bigIntSize8 = uint8_t(*bigIntSize);
+ objLiteralSize8 = uint8_t(*objLiteralSize);
+ }
+
+ MOZ_TRY(xdr->codeUint8(&scriptSize8));
+ MOZ_TRY(xdr->codeUint8(&gcThingSize8));
+ MOZ_TRY(xdr->codeUint8(&scopeSize8));
+ MOZ_TRY(xdr->codeUint8(&scriptExtraSize8));
+ MOZ_TRY(xdr->codeUint8(&regExpSize8));
+ MOZ_TRY(xdr->codeUint8(&bigIntSize8));
+ MOZ_TRY(xdr->codeUint8(&objLiteralSize8));
+
+ if (mode == XDR_DECODE) {
+ *scriptSize = scriptSize8;
+ *gcThingSize = gcThingSize8;
+ *scopeSize = scopeSize8;
+ *scriptExtraSize = scriptExtraSize8;
+ *regExpSize = regExpSize8;
+ *bigIntSize = bigIntSize8;
+ *objLiteralSize = objLiteralSize8;
+ }
+ }
+
+ return Ok();
+}
+
+// Marker between each section inside CompilationStencil.
+//
+// These values should meet the following requirement:
+// * No same value (differ more than single bit flip)
+// * Bit pattern that won't frequently appear inside other XDR data
+//
+// Currently they're randomly chosen prime numbers that doesn't have same
+// byte pattern.
+enum class SectionMarker : uint32_t {
+ ParserAtomData = 0xD9C098D3,
+ ScopeData = 0x892C25EF,
+ ScopeNames = 0x638C4FB3,
+ RegExpData = 0xB030C2AF,
+ BigIntData = 0x4B24F449,
+ ObjLiteralData = 0x9AFAAE45,
+ SharedData = 0xAAD52687,
+ GCThingData = 0x1BD8F533,
+ ScriptData = 0x840458FF,
+ ScriptExtra = 0xA90E489D,
+ ModuleMetadata = 0x94FDCE6D,
+ End = 0x16DDA135,
+};
+
+template <XDRMode mode>
+static XDRResult CodeMarker(XDRState<mode>* xdr, SectionMarker marker) {
+ return xdr->codeMarker(uint32_t(marker));
+}
+
+template <XDRMode mode>
+/* static */ XDRResult StencilXDR::codeCompilationStencil(
+ XDRState<mode>* xdr, CompilationStencil& stencil) {
+ MOZ_ASSERT(!stencil.asmJS);
+
+ if constexpr (mode == XDR_DECODE) {
+ const auto& options = static_cast<XDRStencilDecoder*>(xdr)->options();
+ if (options.borrowBuffer) {
+ stencil.storageType = CompilationStencil::StorageType::Borrowed;
+ } else {
+ stencil.storageType = CompilationStencil::StorageType::Owned;
+ }
+ }
+
+ MOZ_TRY(CodeMarker(xdr, SectionMarker::ParserAtomData));
+ MOZ_TRY(codeParserAtomSpan(xdr, stencil.alloc, stencil.parserAtomData));
+
+ uint8_t canLazilyParse = 0;
+
+ if (mode == XDR_ENCODE) {
+ canLazilyParse = stencil.canLazilyParse;
+ }
+ MOZ_TRY(xdr->codeUint8(&canLazilyParse));
+ if (mode == XDR_DECODE) {
+ stencil.canLazilyParse = canLazilyParse;
+ // NOTE: stencil.canLazilyParse can be different than
+ // CanLazilyParse(static_cast<XDRStencilDecoder*>(xdr)->options()).
+ // See bug 1726498 for removing the redundancy.
+ }
+
+ MOZ_TRY(xdr->codeUint32(&stencil.functionKey));
+
+ uint32_t scriptSize, gcThingSize, scopeSize, scriptExtraSize;
+ uint32_t regExpSize, bigIntSize, objLiteralSize;
+ if (mode == XDR_ENCODE) {
+ scriptSize = stencil.scriptData.size();
+ gcThingSize = stencil.gcThingData.size();
+ scopeSize = stencil.scopeData.size();
+ MOZ_ASSERT(scopeSize == stencil.scopeNames.size());
+
+ scriptExtraSize = stencil.scriptExtra.size();
+
+ regExpSize = stencil.regExpData.size();
+ bigIntSize = stencil.bigIntData.size();
+ objLiteralSize = stencil.objLiteralData.size();
+ }
+ MOZ_TRY(XDRCompilationStencilSpanSize(
+ xdr, &scriptSize, &gcThingSize, &scopeSize, &scriptExtraSize, &regExpSize,
+ &bigIntSize, &objLiteralSize));
+
+ // All of the vector-indexed data elements referenced by the
+ // main script tree must be materialized first.
+
+ MOZ_TRY(CodeMarker(xdr, SectionMarker::ScopeData));
+ MOZ_TRY(XDRSpanContent(xdr, stencil.alloc, stencil.scopeData, scopeSize));
+
+ MOZ_TRY(CodeMarker(xdr, SectionMarker::ScopeNames));
+ MOZ_TRY(
+ XDRSpanInitialized(xdr, stencil.alloc, stencil.scopeNames, scopeSize));
+ MOZ_ASSERT(stencil.scopeData.size() == stencil.scopeNames.size());
+ for (uint32_t i = 0; i < scopeSize; i++) {
+ MOZ_TRY(codeScopeData(xdr, stencil.alloc, stencil.scopeData[i],
+ stencil.scopeNames[i]));
+ }
+
+ MOZ_TRY(CodeMarker(xdr, SectionMarker::RegExpData));
+ MOZ_TRY(XDRSpanContent(xdr, stencil.alloc, stencil.regExpData, regExpSize));
+
+ MOZ_TRY(CodeMarker(xdr, SectionMarker::BigIntData));
+ MOZ_TRY(
+ XDRSpanInitialized(xdr, stencil.alloc, stencil.bigIntData, bigIntSize));
+ for (auto& entry : stencil.bigIntData) {
+ MOZ_TRY(codeBigInt(xdr, stencil.alloc, entry));
+ }
+
+ MOZ_TRY(CodeMarker(xdr, SectionMarker::ObjLiteralData));
+ MOZ_TRY(XDRSpanInitialized(xdr, stencil.alloc, stencil.objLiteralData,
+ objLiteralSize));
+ for (auto& entry : stencil.objLiteralData) {
+ MOZ_TRY(codeObjLiteral(xdr, stencil.alloc, entry));
+ }
+
+ MOZ_TRY(CodeMarker(xdr, SectionMarker::SharedData));
+ MOZ_TRY(codeSharedDataContainer(xdr, stencil.sharedData));
+
+ MOZ_TRY(CodeMarker(xdr, SectionMarker::GCThingData));
+ MOZ_TRY(XDRSpanContent(xdr, stencil.alloc, stencil.gcThingData, gcThingSize));
+
+ // Now serialize the vector of ScriptStencils.
+ MOZ_TRY(CodeMarker(xdr, SectionMarker::ScriptData));
+ MOZ_TRY(XDRSpanContent(xdr, stencil.alloc, stencil.scriptData, scriptSize));
+
+ MOZ_TRY(CodeMarker(xdr, SectionMarker::ScriptExtra));
+ MOZ_TRY(
+ XDRSpanContent(xdr, stencil.alloc, stencil.scriptExtra, scriptExtraSize));
+
+ // We don't support coding non-initial CompilationStencil.
+ MOZ_ASSERT(stencil.isInitialStencil());
+
+ if (stencil.scriptExtra[CompilationStencil::TopLevelIndex].isModule()) {
+ if (mode == XDR_DECODE) {
+ stencil.moduleMetadata =
+ xdr->fc()->getAllocator()->template new_<StencilModuleMetadata>();
+ if (!stencil.moduleMetadata) {
+ return xdr->fail(JS::TranscodeResult::Throw);
+ }
+ }
+
+ MOZ_TRY(CodeMarker(xdr, SectionMarker::ModuleMetadata));
+ MOZ_TRY(codeModuleMetadata(xdr, *stencil.moduleMetadata));
+
+ // codeModuleMetadata doesn't guarantee alignment.
+ MOZ_TRY(xdr->align32());
+ }
+
+ MOZ_TRY(CodeMarker(xdr, SectionMarker::End));
+
+ // The result should be aligned.
+ //
+ // NOTE:
+ // If the top-level isn't a module, ScriptData/ScriptExtra sections
+ // guarantee the alignment because there should be at least 1 item,
+ // and XDRSpanContent adds alignment before span content, and the struct size
+ // should also be aligned.
+ static_assert(sizeof(ScriptStencil) % 4 == 0,
+ "size of ScriptStencil should be aligned");
+ static_assert(sizeof(ScriptStencilExtra) % 4 == 0,
+ "size of ScriptStencilExtra should be aligned");
+ MOZ_RELEASE_ASSERT(xdr->isAligned32());
+
+ return Ok();
+}
+
+template <typename Unit>
+struct UnretrievableSourceDecoder {
+ XDRState<XDR_DECODE>* const xdr_;
+ ScriptSource* const scriptSource_;
+ const uint32_t uncompressedLength_;
+
+ public:
+ UnretrievableSourceDecoder(XDRState<XDR_DECODE>* xdr,
+ ScriptSource* scriptSource,
+ uint32_t uncompressedLength)
+ : xdr_(xdr),
+ scriptSource_(scriptSource),
+ uncompressedLength_(uncompressedLength) {}
+
+ XDRResult decode() {
+ auto sourceUnits = xdr_->fc()->getAllocator()->make_pod_array<Unit>(
+ std::max<size_t>(uncompressedLength_, 1));
+ if (!sourceUnits) {
+ return xdr_->fail(JS::TranscodeResult::Throw);
+ }
+
+ MOZ_TRY(xdr_->codeChars(sourceUnits.get(), uncompressedLength_));
+
+ if (!scriptSource_->initializeUnretrievableUncompressedSource(
+ xdr_->fc(), std::move(sourceUnits), uncompressedLength_)) {
+ return xdr_->fail(JS::TranscodeResult::Throw);
+ }
+
+ return Ok();
+ }
+};
+
+template <>
+XDRResult StencilXDR::codeSourceUnretrievableUncompressed<XDR_DECODE>(
+ XDRState<XDR_DECODE>* xdr, ScriptSource* ss, uint8_t sourceCharSize,
+ uint32_t uncompressedLength) {
+ MOZ_ASSERT(sourceCharSize == 1 || sourceCharSize == 2);
+
+ if (sourceCharSize == 1) {
+ UnretrievableSourceDecoder<Utf8Unit> decoder(xdr, ss, uncompressedLength);
+ return decoder.decode();
+ }
+
+ UnretrievableSourceDecoder<char16_t> decoder(xdr, ss, uncompressedLength);
+ return decoder.decode();
+}
+
+template <typename Unit>
+struct UnretrievableSourceEncoder {
+ XDRState<XDR_ENCODE>* const xdr_;
+ ScriptSource* const source_;
+ const uint32_t uncompressedLength_;
+
+ UnretrievableSourceEncoder(XDRState<XDR_ENCODE>* xdr, ScriptSource* source,
+ uint32_t uncompressedLength)
+ : xdr_(xdr), source_(source), uncompressedLength_(uncompressedLength) {}
+
+ XDRResult encode() {
+ Unit* sourceUnits =
+ const_cast<Unit*>(source_->uncompressedData<Unit>()->units());
+
+ return xdr_->codeChars(sourceUnits, uncompressedLength_);
+ }
+};
+
+template <>
+/* static */
+XDRResult StencilXDR::codeSourceUnretrievableUncompressed<XDR_ENCODE>(
+ XDRState<XDR_ENCODE>* xdr, ScriptSource* ss, uint8_t sourceCharSize,
+ uint32_t uncompressedLength) {
+ MOZ_ASSERT(sourceCharSize == 1 || sourceCharSize == 2);
+
+ if (sourceCharSize == 1) {
+ UnretrievableSourceEncoder<Utf8Unit> encoder(xdr, ss, uncompressedLength);
+ return encoder.encode();
+ }
+
+ UnretrievableSourceEncoder<char16_t> encoder(xdr, ss, uncompressedLength);
+ return encoder.encode();
+}
+
+template <typename Unit, XDRMode mode>
+/* static */
+XDRResult StencilXDR::codeSourceUncompressedData(XDRState<mode>* const xdr,
+ ScriptSource* const ss) {
+ static_assert(
+ std::is_same_v<Unit, Utf8Unit> || std::is_same_v<Unit, char16_t>,
+ "should handle UTF-8 and UTF-16");
+
+ if (mode == XDR_ENCODE) {
+ MOZ_ASSERT(ss->isUncompressed<Unit>());
+ } else {
+ MOZ_ASSERT(ss->data.is<ScriptSource::Missing>());
+ }
+
+ uint32_t uncompressedLength;
+ if (mode == XDR_ENCODE) {
+ uncompressedLength = ss->uncompressedData<Unit>()->length();
+ }
+ MOZ_TRY(xdr->codeUint32(&uncompressedLength));
+
+ return codeSourceUnretrievableUncompressed(xdr, ss, sizeof(Unit),
+ uncompressedLength);
+}
+
+template <typename Unit, XDRMode mode>
+/* static */
+XDRResult StencilXDR::codeSourceCompressedData(XDRState<mode>* const xdr,
+ ScriptSource* const ss) {
+ static_assert(
+ std::is_same_v<Unit, Utf8Unit> || std::is_same_v<Unit, char16_t>,
+ "should handle UTF-8 and UTF-16");
+
+ if (mode == XDR_ENCODE) {
+ MOZ_ASSERT(ss->isCompressed<Unit>());
+ } else {
+ MOZ_ASSERT(ss->data.is<ScriptSource::Missing>());
+ }
+
+ uint32_t uncompressedLength;
+ if (mode == XDR_ENCODE) {
+ uncompressedLength =
+ ss->data.as<ScriptSource::Compressed<Unit, SourceRetrievable::No>>()
+ .uncompressedLength;
+ }
+ MOZ_TRY(xdr->codeUint32(&uncompressedLength));
+
+ uint32_t compressedLength;
+ if (mode == XDR_ENCODE) {
+ compressedLength =
+ ss->data.as<ScriptSource::Compressed<Unit, SourceRetrievable::No>>()
+ .raw.length();
+ }
+ MOZ_TRY(xdr->codeUint32(&compressedLength));
+
+ if (mode == XDR_DECODE) {
+ // Compressed data is always single-byte chars.
+ auto bytes = xdr->fc()->getAllocator()->template make_pod_array<char>(
+ compressedLength);
+ if (!bytes) {
+ return xdr->fail(JS::TranscodeResult::Throw);
+ }
+ MOZ_TRY(xdr->codeBytes(bytes.get(), compressedLength));
+
+ if (!ss->initializeWithUnretrievableCompressedSource<Unit>(
+ xdr->fc(), std::move(bytes), compressedLength,
+ uncompressedLength)) {
+ return xdr->fail(JS::TranscodeResult::Throw);
+ }
+ } else {
+ void* bytes = const_cast<char*>(ss->compressedData<Unit>()->raw.chars());
+ MOZ_TRY(xdr->codeBytes(bytes, compressedLength));
+ }
+
+ return Ok();
+}
+
+template <typename Unit,
+ template <typename U, SourceRetrievable CanRetrieve> class Data,
+ XDRMode mode>
+/* static */
+void StencilXDR::codeSourceRetrievable(ScriptSource* const ss) {
+ static_assert(
+ std::is_same_v<Unit, Utf8Unit> || std::is_same_v<Unit, char16_t>,
+ "should handle UTF-8 and UTF-16");
+
+ if (mode == XDR_ENCODE) {
+ MOZ_ASSERT((ss->data.is<Data<Unit, SourceRetrievable::Yes>>()));
+ } else {
+ MOZ_ASSERT(ss->data.is<ScriptSource::Missing>());
+ ss->data = ScriptSource::SourceType(ScriptSource::Retrievable<Unit>());
+ }
+}
+
+template <typename Unit, XDRMode mode>
+/* static */
+void StencilXDR::codeSourceRetrievableData(ScriptSource* ss) {
+ // There's nothing to code for retrievable data. Just be sure to set
+ // retrievable data when decoding.
+ if (mode == XDR_ENCODE) {
+ MOZ_ASSERT(ss->data.is<ScriptSource::Retrievable<Unit>>());
+ } else {
+ MOZ_ASSERT(ss->data.is<ScriptSource::Missing>());
+ ss->data = ScriptSource::SourceType(ScriptSource::Retrievable<Unit>());
+ }
+}
+
+template <XDRMode mode>
+/* static */
+XDRResult StencilXDR::codeSourceData(XDRState<mode>* const xdr,
+ ScriptSource* const ss) {
+ // The order here corresponds to the type order in |ScriptSource::SourceType|
+ // so number->internal Variant tag is a no-op.
+ enum class DataType {
+ CompressedUtf8Retrievable,
+ UncompressedUtf8Retrievable,
+ CompressedUtf8NotRetrievable,
+ UncompressedUtf8NotRetrievable,
+ CompressedUtf16Retrievable,
+ UncompressedUtf16Retrievable,
+ CompressedUtf16NotRetrievable,
+ UncompressedUtf16NotRetrievable,
+ RetrievableUtf8,
+ RetrievableUtf16,
+ Missing,
+ };
+
+ DataType tag;
+ {
+ // This is terrible, but we can't do better. When |mode == XDR_DECODE| we
+ // don't have a |ScriptSource::data| |Variant| to match -- the entire XDR
+ // idiom for tagged unions depends on coding a tag-number, then the
+ // corresponding tagged data. So we must manually define a tag-enum, code
+ // it, then switch on it (and ignore the |Variant::match| API).
+ class XDRDataTag {
+ public:
+ DataType operator()(
+ const ScriptSource::Compressed<Utf8Unit, SourceRetrievable::Yes>&) {
+ return DataType::CompressedUtf8Retrievable;
+ }
+ DataType operator()(
+ const ScriptSource::Uncompressed<Utf8Unit, SourceRetrievable::Yes>&) {
+ return DataType::UncompressedUtf8Retrievable;
+ }
+ DataType operator()(
+ const ScriptSource::Compressed<Utf8Unit, SourceRetrievable::No>&) {
+ return DataType::CompressedUtf8NotRetrievable;
+ }
+ DataType operator()(
+ const ScriptSource::Uncompressed<Utf8Unit, SourceRetrievable::No>&) {
+ return DataType::UncompressedUtf8NotRetrievable;
+ }
+ DataType operator()(
+ const ScriptSource::Compressed<char16_t, SourceRetrievable::Yes>&) {
+ return DataType::CompressedUtf16Retrievable;
+ }
+ DataType operator()(
+ const ScriptSource::Uncompressed<char16_t, SourceRetrievable::Yes>&) {
+ return DataType::UncompressedUtf16Retrievable;
+ }
+ DataType operator()(
+ const ScriptSource::Compressed<char16_t, SourceRetrievable::No>&) {
+ return DataType::CompressedUtf16NotRetrievable;
+ }
+ DataType operator()(
+ const ScriptSource::Uncompressed<char16_t, SourceRetrievable::No>&) {
+ return DataType::UncompressedUtf16NotRetrievable;
+ }
+ DataType operator()(const ScriptSource::Retrievable<Utf8Unit>&) {
+ return DataType::RetrievableUtf8;
+ }
+ DataType operator()(const ScriptSource::Retrievable<char16_t>&) {
+ return DataType::RetrievableUtf16;
+ }
+ DataType operator()(const ScriptSource::Missing&) {
+ return DataType::Missing;
+ }
+ };
+
+ uint8_t type;
+ if (mode == XDR_ENCODE) {
+ type = static_cast<uint8_t>(ss->data.match(XDRDataTag()));
+ }
+ MOZ_TRY(xdr->codeUint8(&type));
+
+ if (type > static_cast<uint8_t>(DataType::Missing)) {
+ // Fail in debug, but only soft-fail in release, if the type is invalid.
+ MOZ_ASSERT_UNREACHABLE("bad tag");
+ return xdr->fail(JS::TranscodeResult::Failure_BadDecode);
+ }
+
+ tag = static_cast<DataType>(type);
+ }
+
+ switch (tag) {
+ case DataType::CompressedUtf8Retrievable:
+ codeSourceRetrievable<Utf8Unit, ScriptSource::Compressed, mode>(ss);
+ return Ok();
+
+ case DataType::CompressedUtf8NotRetrievable:
+ return codeSourceCompressedData<Utf8Unit>(xdr, ss);
+
+ case DataType::UncompressedUtf8Retrievable:
+ codeSourceRetrievable<Utf8Unit, ScriptSource::Uncompressed, mode>(ss);
+ return Ok();
+
+ case DataType::UncompressedUtf8NotRetrievable:
+ return codeSourceUncompressedData<Utf8Unit>(xdr, ss);
+
+ case DataType::CompressedUtf16Retrievable:
+ codeSourceRetrievable<char16_t, ScriptSource::Compressed, mode>(ss);
+ return Ok();
+
+ case DataType::CompressedUtf16NotRetrievable:
+ return codeSourceCompressedData<char16_t>(xdr, ss);
+
+ case DataType::UncompressedUtf16Retrievable:
+ codeSourceRetrievable<char16_t, ScriptSource::Uncompressed, mode>(ss);
+ return Ok();
+
+ case DataType::UncompressedUtf16NotRetrievable:
+ return codeSourceUncompressedData<char16_t>(xdr, ss);
+
+ case DataType::Missing: {
+ MOZ_ASSERT(ss->data.is<ScriptSource::Missing>(),
+ "ScriptSource::data is initialized as missing, so neither "
+ "encoding nor decoding has to change anything");
+
+ // There's no data to XDR for missing source.
+ break;
+ }
+
+ case DataType::RetrievableUtf8:
+ codeSourceRetrievableData<Utf8Unit, mode>(ss);
+ return Ok();
+
+ case DataType::RetrievableUtf16:
+ codeSourceRetrievableData<char16_t, mode>(ss);
+ return Ok();
+ }
+
+ // The range-check on |type| far above ought ensure the above |switch| is
+ // exhaustive and all cases will return, but not all compilers understand
+ // this. Make the Missing case break to here so control obviously never flows
+ // off the end.
+ MOZ_ASSERT(tag == DataType::Missing);
+ return Ok();
+}
+
+template <XDRMode mode>
+/* static */
+XDRResult StencilXDR::codeSource(XDRState<mode>* xdr,
+ const JS::DecodeOptions* maybeOptions,
+ RefPtr<ScriptSource>& source) {
+ FrontendContext* fc = xdr->fc();
+
+ if (mode == XDR_DECODE) {
+ // Allocate a new ScriptSource and root it with the holder.
+ source = do_AddRef(fc->getAllocator()->new_<ScriptSource>());
+ if (!source) {
+ return xdr->fail(JS::TranscodeResult::Throw);
+ }
+ }
+
+ static constexpr uint8_t HasFilename = 1 << 0;
+ static constexpr uint8_t HasDisplayURL = 1 << 1;
+ static constexpr uint8_t HasSourceMapURL = 1 << 2;
+ static constexpr uint8_t MutedErrors = 1 << 3;
+
+ uint8_t flags = 0;
+ if (mode == XDR_ENCODE) {
+ if (source->filename_) {
+ flags |= HasFilename;
+ }
+ if (source->hasDisplayURL()) {
+ flags |= HasDisplayURL;
+ }
+ if (source->hasSourceMapURL()) {
+ flags |= HasSourceMapURL;
+ }
+ if (source->mutedErrors()) {
+ flags |= MutedErrors;
+ }
+ }
+
+ MOZ_TRY(xdr->codeUint8(&flags));
+
+ if (flags & HasFilename) {
+ XDRTranscodeString<char> chars;
+
+ if (mode == XDR_ENCODE) {
+ chars.construct<const char*>(source->filename());
+ }
+ MOZ_TRY(xdr->codeCharsZ(chars));
+ if (mode == XDR_DECODE) {
+ if (!source->setFilename(fc, std::move(chars.ref<UniqueChars>()))) {
+ return xdr->fail(JS::TranscodeResult::Throw);
+ }
+ }
+ }
+
+ if (flags & HasDisplayURL) {
+ XDRTranscodeString<char16_t> chars;
+
+ if (mode == XDR_ENCODE) {
+ chars.construct<const char16_t*>(source->displayURL());
+ }
+ MOZ_TRY(xdr->codeCharsZ(chars));
+ if (mode == XDR_DECODE) {
+ if (!source->setDisplayURL(fc,
+ std::move(chars.ref<UniqueTwoByteChars>()))) {
+ return xdr->fail(JS::TranscodeResult::Throw);
+ }
+ }
+ }
+
+ if (flags & HasSourceMapURL) {
+ XDRTranscodeString<char16_t> chars;
+
+ if (mode == XDR_ENCODE) {
+ chars.construct<const char16_t*>(source->sourceMapURL());
+ }
+ MOZ_TRY(xdr->codeCharsZ(chars));
+ if (mode == XDR_DECODE) {
+ if (!source->setSourceMapURL(
+ fc, std::move(chars.ref<UniqueTwoByteChars>()))) {
+ return xdr->fail(JS::TranscodeResult::Throw);
+ }
+ }
+ }
+
+ MOZ_ASSERT(source->parameterListEnd_ == 0);
+
+ if (flags & MutedErrors) {
+ if (mode == XDR_DECODE) {
+ source->mutedErrors_ = true;
+ }
+ }
+
+ MOZ_TRY(xdr->codeUint32(&source->startLine_));
+ MOZ_TRY(xdr->codeUint32(&source->startColumn_));
+
+ // The introduction info doesn't persist across encode/decode.
+ if (mode == XDR_DECODE) {
+ source->introductionType_ = maybeOptions->introductionType;
+ source->setIntroductionOffset(maybeOptions->introductionOffset);
+ if (maybeOptions->introducerFilename) {
+ if (!source->setIntroducerFilename(fc,
+ maybeOptions->introducerFilename)) {
+ return xdr->fail(JS::TranscodeResult::Throw);
+ }
+ }
+ }
+
+ MOZ_TRY(codeSourceData(xdr, source.get()));
+
+ return Ok();
+}
+
+template /* static */
+ XDRResult
+ StencilXDR::codeSource(XDRState<XDR_ENCODE>* xdr,
+ const JS::DecodeOptions* maybeOptions,
+ RefPtr<ScriptSource>& holder);
+template /* static */
+ XDRResult
+ StencilXDR::codeSource(XDRState<XDR_DECODE>* xdr,
+ const JS::DecodeOptions* maybeOptions,
+ RefPtr<ScriptSource>& holder);
+
+JS_PUBLIC_API bool JS::GetScriptTranscodingBuildId(
+ JS::BuildIdCharVector* buildId) {
+ MOZ_ASSERT(buildId->empty());
+ MOZ_ASSERT(GetBuildId);
+
+ if (!GetBuildId(buildId)) {
+ return false;
+ }
+
+ // Note: the buildId returned here is also used for the bytecode cache MIME
+ // type so use plain ASCII characters.
+
+ if (!buildId->reserve(buildId->length() + 4)) {
+ return false;
+ }
+
+ buildId->infallibleAppend('-');
+
+ // XDR depends on pointer size and endianness.
+ static_assert(sizeof(uintptr_t) == 4 || sizeof(uintptr_t) == 8);
+ buildId->infallibleAppend(sizeof(uintptr_t) == 4 ? '4' : '8');
+ buildId->infallibleAppend(MOZ_LITTLE_ENDIAN() ? 'l' : 'b');
+
+ return true;
+}
+
+template <XDRMode mode>
+static XDRResult VersionCheck(XDRState<mode>* xdr) {
+ JS::BuildIdCharVector buildId;
+ if (!JS::GetScriptTranscodingBuildId(&buildId)) {
+ ReportOutOfMemory(xdr->fc());
+ return xdr->fail(JS::TranscodeResult::Throw);
+ }
+ MOZ_ASSERT(!buildId.empty());
+
+ uint32_t buildIdLength;
+ if (mode == XDR_ENCODE) {
+ buildIdLength = buildId.length();
+ }
+
+ MOZ_TRY(xdr->codeUint32(&buildIdLength));
+
+ if (mode == XDR_DECODE && buildIdLength != buildId.length()) {
+ return xdr->fail(JS::TranscodeResult::Failure_BadBuildId);
+ }
+
+ if (mode == XDR_ENCODE) {
+ MOZ_TRY(xdr->codeBytes(buildId.begin(), buildIdLength));
+ } else {
+ JS::BuildIdCharVector decodedBuildId;
+
+ // buildIdLength is already checked against the length of current
+ // buildId.
+ if (!decodedBuildId.resize(buildIdLength)) {
+ ReportOutOfMemory(xdr->fc());
+ return xdr->fail(JS::TranscodeResult::Throw);
+ }
+
+ MOZ_TRY(xdr->codeBytes(decodedBuildId.begin(), buildIdLength));
+
+ // We do not provide binary compatibility with older scripts.
+ if (!mozilla::ArrayEqual(decodedBuildId.begin(), buildId.begin(),
+ buildIdLength)) {
+ return xdr->fail(JS::TranscodeResult::Failure_BadBuildId);
+ }
+ }
+
+ return Ok();
+}
+
+XDRResult XDRStencilEncoder::codeStencil(
+ const RefPtr<ScriptSource>& source,
+ const frontend::CompilationStencil& stencil) {
+#ifdef DEBUG
+ auto sanityCheck = mozilla::MakeScopeExit(
+ [&] { MOZ_ASSERT(validateResultCode(fc(), resultCode())); });
+#endif
+
+ MOZ_TRY(frontend::StencilXDR::checkCompilationStencil(this, stencil));
+
+ MOZ_TRY(VersionCheck(this));
+
+ uint32_t dummy = 0;
+ size_t lengthOffset = buf->cursor();
+ MOZ_TRY(codeUint32(&dummy));
+ size_t hashOffset = buf->cursor();
+ MOZ_TRY(codeUint32(&dummy));
+
+ size_t contentOffset = buf->cursor();
+ MOZ_TRY(frontend::StencilXDR::codeSource(
+ this, nullptr, const_cast<RefPtr<ScriptSource>&>(source)));
+ MOZ_TRY(frontend::StencilXDR::codeCompilationStencil(
+ this, const_cast<frontend::CompilationStencil&>(stencil)));
+ size_t endOffset = buf->cursor();
+
+ if (endOffset > UINT32_MAX) {
+ ReportOutOfMemory(fc());
+ return fail(JS::TranscodeResult::Throw);
+ }
+
+ uint32_t length = endOffset - contentOffset;
+ codeUint32At(&length, lengthOffset);
+
+ const uint8_t* contentBegin = buf->bufferAt(contentOffset);
+ uint32_t hash = mozilla::HashBytes(contentBegin, length);
+ codeUint32At(&hash, hashOffset);
+
+ return Ok();
+}
+
+XDRResult XDRStencilEncoder::codeStencil(
+ const frontend::CompilationStencil& stencil) {
+ return codeStencil(stencil.source, stencil);
+}
+
+void StencilIncrementalEncoderPtr::reset() {
+ if (merger_) {
+ js_delete(merger_);
+ }
+ merger_ = nullptr;
+}
+
+bool StencilIncrementalEncoderPtr::setInitial(
+ JSContext* cx,
+ UniquePtr<frontend::ExtensibleCompilationStencil>&& initial) {
+ AutoReportFrontendContext fc(cx);
+ merger_ = fc.getAllocator()->new_<frontend::CompilationStencilMerger>();
+ if (!merger_) {
+ return false;
+ }
+
+ return merger_->setInitial(
+ &fc,
+ std::forward<UniquePtr<frontend::ExtensibleCompilationStencil>>(initial));
+}
+
+bool StencilIncrementalEncoderPtr::addDelazification(
+ JSContext* cx, const frontend::CompilationStencil& delazification) {
+ AutoReportFrontendContext fc(cx);
+ return merger_->addDelazification(&fc, delazification);
+}
+
+XDRResult XDRStencilDecoder::codeStencil(
+ const JS::DecodeOptions& options, frontend::CompilationStencil& stencil) {
+#ifdef DEBUG
+ auto sanityCheck = mozilla::MakeScopeExit(
+ [&] { MOZ_ASSERT(validateResultCode(fc(), resultCode())); });
+#endif
+
+ auto resetOptions = mozilla::MakeScopeExit([&] { options_ = nullptr; });
+ options_ = &options;
+
+ MOZ_TRY(VersionCheck(this));
+
+ uint32_t length;
+ MOZ_TRY(codeUint32(&length));
+
+ uint32_t hash;
+ MOZ_TRY(codeUint32(&hash));
+
+ const uint8_t* contentBegin;
+ MOZ_TRY(peekArray(length, &contentBegin));
+ uint32_t actualHash = mozilla::HashBytes(contentBegin, length);
+
+ if (MOZ_UNLIKELY(actualHash != hash)) {
+ return fail(JS::TranscodeResult::Failure_BadDecode);
+ }
+
+ MOZ_TRY(frontend::StencilXDR::codeSource(this, &options, stencil.source));
+ MOZ_TRY(frontend::StencilXDR::codeCompilationStencil(this, stencil));
+
+ return Ok();
+}
+
+template /* static */ XDRResult StencilXDR::codeCompilationStencil(
+ XDRState<XDR_ENCODE>* xdr, CompilationStencil& stencil);
+
+template /* static */ XDRResult StencilXDR::codeCompilationStencil(
+ XDRState<XDR_DECODE>* xdr, CompilationStencil& stencil);
+
+/* static */ XDRResult StencilXDR::checkCompilationStencil(
+ XDRStencilEncoder* encoder, const CompilationStencil& stencil) {
+ if (stencil.asmJS) {
+ return encoder->fail(JS::TranscodeResult::Failure_AsmJSNotSupported);
+ }
+
+ return Ok();
+}
+
+/* static */ XDRResult StencilXDR::checkCompilationStencil(
+ const ExtensibleCompilationStencil& stencil) {
+ if (stencil.asmJS) {
+ return mozilla::Err(JS::TranscodeResult::Failure_AsmJSNotSupported);
+ }
+
+ return Ok();
+}
diff --git a/js/src/frontend/StencilXdr.h b/js/src/frontend/StencilXdr.h
new file mode 100644
index 0000000000..a07e7787fa
--- /dev/null
+++ b/js/src/frontend/StencilXdr.h
@@ -0,0 +1,217 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef frontend_StencilXdr_h
+#define frontend_StencilXdr_h
+
+#include "mozilla/RefPtr.h" // RefPtr
+
+#include "frontend/ParserAtom.h" // ParserAtom, ParserAtomSpan
+#include "frontend/Stencil.h" // BitIntStencil, ScopeStencil, BaseParserScopeData
+#include "vm/Xdr.h" // XDRMode, XDRResult, XDRState
+
+namespace JS {
+
+class DecodeOptions;
+
+} // namespace JS
+
+namespace js {
+
+class LifoAlloc;
+class ObjLiteralStencil;
+class ScriptSource;
+class SharedImmutableScriptData;
+
+class XDRStencilDecoder;
+class XDRStencilEncoder;
+
+namespace frontend {
+
+struct CompilationStencil;
+struct ExtensibleCompilationStencil;
+struct SharedDataContainer;
+
+// Check that we can copy data to disk and restore it in another instance of
+// the program in a different address space.
+template <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 {
+ private:
+ template <XDRMode mode>
+ [[nodiscard]] static XDRResult codeSourceUnretrievableUncompressed(
+ XDRState<mode>* xdr, ScriptSource* ss, uint8_t sourceCharSize,
+ uint32_t uncompressedLength);
+
+ template <typename Unit,
+ template <typename U, SourceRetrievable CanRetrieve> class Data,
+ XDRMode mode>
+ static void codeSourceRetrievable(ScriptSource* ss);
+
+ template <typename Unit, XDRMode mode>
+ [[nodiscard]] static XDRResult codeSourceUncompressedData(
+ XDRState<mode>* const xdr, ScriptSource* const ss);
+
+ template <typename Unit, XDRMode mode>
+ [[nodiscard]] static XDRResult codeSourceCompressedData(
+ XDRState<mode>* const xdr, ScriptSource* const ss);
+
+ template <typename Unit, XDRMode mode>
+ static void codeSourceRetrievableData(ScriptSource* ss);
+
+ template <XDRMode mode>
+ [[nodiscard]] static XDRResult codeSourceData(XDRState<mode>* const xdr,
+ ScriptSource* const ss);
+
+ public:
+ template <XDRMode mode>
+ static XDRResult codeSource(XDRState<mode>* xdr,
+ const JS::DecodeOptions* maybeOptions,
+ RefPtr<ScriptSource>& source);
+
+ template <XDRMode mode>
+ static XDRResult codeBigInt(XDRState<mode>* xdr, LifoAlloc& alloc,
+ BigIntStencil& stencil);
+
+ template <XDRMode mode>
+ static XDRResult codeObjLiteral(XDRState<mode>* xdr, LifoAlloc& alloc,
+ ObjLiteralStencil& stencil);
+
+ template <XDRMode mode>
+ static XDRResult codeScopeData(XDRState<mode>* xdr, LifoAlloc& alloc,
+ ScopeStencil& stencil,
+ BaseParserScopeData*& baseScopeData);
+
+ template <XDRMode mode>
+ static XDRResult codeSharedData(XDRState<mode>* xdr,
+ RefPtr<SharedImmutableScriptData>& sisd);
+
+ template <XDRMode mode>
+ static XDRResult codeSharedDataContainer(XDRState<mode>* xdr,
+ SharedDataContainer& sharedData);
+
+ template <XDRMode mode>
+ static XDRResult codeParserAtom(XDRState<mode>* xdr, LifoAlloc& alloc,
+ ParserAtom** atomp);
+
+ template <XDRMode mode>
+ static XDRResult codeParserAtomSpan(XDRState<mode>* xdr, LifoAlloc& alloc,
+ ParserAtomSpan& parserAtomData);
+
+ template <XDRMode mode>
+ static XDRResult codeModuleRequest(XDRState<mode>* xdr,
+ StencilModuleRequest& stencil);
+
+ template <XDRMode mode>
+ static XDRResult codeModuleRequestVector(
+ XDRState<mode>* xdr, StencilModuleMetadata::RequestVector& vector);
+
+ template <XDRMode mode>
+ static XDRResult codeModuleEntry(XDRState<mode>* xdr,
+ StencilModuleEntry& stencil);
+
+ template <XDRMode mode>
+ static XDRResult codeModuleEntryVector(
+ XDRState<mode>* xdr, StencilModuleMetadata::EntryVector& vector);
+
+ template <XDRMode mode>
+ static XDRResult codeModuleMetadata(XDRState<mode>* xdr,
+ StencilModuleMetadata& stencil);
+
+ static XDRResult checkCompilationStencil(XDRStencilEncoder* encoder,
+ const CompilationStencil& stencil);
+
+ static XDRResult checkCompilationStencil(
+ const ExtensibleCompilationStencil& stencil);
+
+ template <XDRMode mode>
+ static XDRResult codeCompilationStencil(XDRState<mode>* xdr,
+ CompilationStencil& stencil);
+};
+
+} /* namespace frontend */
+
+/*
+ * The structure of the Stencil XDR buffer is:
+ *
+ * 1. Version
+ * 2. length of content
+ * 3. checksum of content
+ * 4. content
+ * a. ScriptSource
+ * b. CompilationStencil
+ */
+
+/*
+ * The stencil decoder accepts `range` as input.
+ *
+ * The decoded stencils are outputted to the default-initialized
+ * `stencil` parameter of `codeStencil` method.
+ *
+ * The decoded stencils borrow the input `buffer`/`range`, and the consumer
+ * has to keep the buffer alive while the decoded stencils are alive.
+ */
+class XDRStencilDecoder : public XDRState<XDR_DECODE> {
+ using Base = XDRState<XDR_DECODE>;
+
+ public:
+ XDRStencilDecoder(FrontendContext* fc, const JS::TranscodeRange& range)
+ : Base(fc, range) {
+ MOZ_ASSERT(JS::IsTranscodingBytecodeAligned(range.begin().get()));
+ }
+
+ XDRResult codeStencil(const JS::DecodeOptions& options,
+ frontend::CompilationStencil& stencil);
+
+ const JS::DecodeOptions& options() {
+ MOZ_ASSERT(options_);
+ return *options_;
+ }
+
+ private:
+ const JS::DecodeOptions* options_ = nullptr;
+};
+
+class XDRStencilEncoder : public XDRState<XDR_ENCODE> {
+ using Base = XDRState<XDR_ENCODE>;
+
+ public:
+ XDRStencilEncoder(FrontendContext* fc, JS::TranscodeBuffer& buffer)
+ : Base(fc, buffer, buffer.length()) {
+ // NOTE: If buffer is empty, buffer.begin() doesn't point valid buffer.
+ MOZ_ASSERT_IF(!buffer.empty(),
+ JS::IsTranscodingBytecodeAligned(buffer.begin()));
+ MOZ_ASSERT(JS::IsTranscodingBytecodeOffsetAligned(buffer.length()));
+ }
+
+ XDRResult codeStencil(const RefPtr<ScriptSource>& source,
+ const frontend::CompilationStencil& stencil);
+
+ XDRResult codeStencil(const frontend::CompilationStencil& stencil);
+};
+
+} /* namespace js */
+
+#endif /* frontend_StencilXdr_h */
diff --git a/js/src/frontend/SwitchEmitter.cpp b/js/src/frontend/SwitchEmitter.cpp
new file mode 100644
index 0000000000..9c3ad7aa7d
--- /dev/null
+++ b/js/src/frontend/SwitchEmitter.cpp
@@ -0,0 +1,414 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "frontend/SwitchEmitter.h"
+
+#include "mozilla/Assertions.h" // MOZ_ASSERT
+#include "mozilla/Span.h" // mozilla::Span
+
+#include <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;
+
+bool SwitchEmitter::TableGenerator::addNumber(int32_t caseValue) {
+ if (isInvalid()) {
+ return true;
+ }
+
+ if (unsigned(caseValue + int(Bit(15))) >= unsigned(Bit(16))) {
+ setInvalid();
+ return true;
+ }
+
+ if (intmap_.isNothing()) {
+ intmap_.emplace();
+ }
+
+ low_ = std::min(low_, caseValue);
+ high_ = std::max(high_, caseValue);
+
+ // Check for duplicates, which are not supported in a table switch.
+ // We bias caseValue by 65536 if it's negative, and hope that's a rare case
+ // (because it requires a malloc'd bitmap).
+ if (caseValue < 0) {
+ caseValue += Bit(16);
+ }
+ if (caseValue >= intmapBitLength_) {
+ size_t newLength = NumWordsForBitArrayOfLength(caseValue + 1);
+ if (!intmap_->resize(newLength)) {
+ ReportOutOfMemory(bce_->fc);
+ return false;
+ }
+ intmapBitLength_ = newLength * BitArrayElementBits;
+ }
+ if (IsBitArrayElementSet(intmap_->begin(), intmap_->length(), caseValue)) {
+ // Duplicate entry is not supported in table switch.
+ setInvalid();
+ return true;
+ }
+ SetBitArrayElement(intmap_->begin(), intmap_->length(), caseValue);
+ return true;
+}
+
+void SwitchEmitter::TableGenerator::finish(uint32_t caseCount) {
+ intmap_.reset();
+
+#ifdef DEBUG
+ finished_ = true;
+#endif
+
+ if (isInvalid()) {
+ return;
+ }
+
+ if (caseCount == 0) {
+ low_ = 0;
+ high_ = -1;
+ return;
+ }
+
+ // Compute table length. Don't use table switch if overlarge or more than
+ // half-sparse.
+ tableLength_ = uint32_t(high_ - low_ + 1);
+ if (tableLength_ >= Bit(16) || tableLength_ > 2 * caseCount) {
+ setInvalid();
+ }
+}
+
+uint32_t SwitchEmitter::TableGenerator::toCaseIndex(int32_t caseValue) const {
+ MOZ_ASSERT(finished_);
+ MOZ_ASSERT(isValid());
+ uint32_t caseIndex = uint32_t(caseValue - low_);
+ MOZ_ASSERT(caseIndex < tableLength_);
+ return caseIndex;
+}
+
+uint32_t SwitchEmitter::TableGenerator::tableLength() const {
+ MOZ_ASSERT(finished_);
+ MOZ_ASSERT(isValid());
+ return tableLength_;
+}
+
+SwitchEmitter::SwitchEmitter(BytecodeEmitter* bce) : bce_(bce) {}
+
+bool SwitchEmitter::emitDiscriminant(uint32_t switchPos) {
+ MOZ_ASSERT(state_ == State::Start);
+ switchPos_ = switchPos;
+
+ // Ensure that the column of the switch statement is set properly.
+ if (!bce_->updateSourceCoordNotes(switchPos_)) {
+ return false;
+ }
+
+ state_ = State::Discriminant;
+ return true;
+}
+
+bool SwitchEmitter::emitLexical(LexicalScope::ParserData* bindings) {
+ MOZ_ASSERT(state_ == State::Discriminant);
+ MOZ_ASSERT(bindings);
+
+ tdzCacheLexical_.emplace(bce_);
+ emitterScope_.emplace(bce_);
+ if (!emitterScope_->enterLexical(bce_, ScopeKind::Lexical, bindings)) {
+ return false;
+ }
+
+ state_ = State::Lexical;
+ return true;
+}
+
+bool SwitchEmitter::validateCaseCount(uint32_t caseCount) {
+ MOZ_ASSERT(state_ == State::Discriminant || state_ == State::Lexical);
+ if (caseCount > Bit(16)) {
+ bce_->reportError(switchPos_, JSMSG_TOO_MANY_CASES);
+ return false;
+ }
+ caseCount_ = caseCount;
+
+ state_ = State::CaseCount;
+ return true;
+}
+
+bool SwitchEmitter::emitCond() {
+ MOZ_ASSERT(state_ == State::CaseCount);
+
+ kind_ = Kind::Cond;
+
+ // After entering the scope if necessary, push the switch control.
+ controlInfo_.emplace(bce_, StatementKind::Switch);
+ top_ = bce_->bytecodeSection().offset();
+
+ if (!caseOffsets_.resize(caseCount_)) {
+ ReportOutOfMemory(bce_->fc);
+ return false;
+ }
+
+ MOZ_ASSERT(top_ == bce_->bytecodeSection().offset());
+
+ tdzCacheCaseAndBody_.emplace(bce_);
+
+ state_ = State::Cond;
+ return true;
+}
+
+bool SwitchEmitter::emitTable(const TableGenerator& tableGen) {
+ MOZ_ASSERT(state_ == State::CaseCount);
+ kind_ = Kind::Table;
+
+ // After entering the scope if necessary, push the switch control.
+ controlInfo_.emplace(bce_, StatementKind::Switch);
+ top_ = bce_->bytecodeSection().offset();
+
+ if (!caseOffsets_.resize(tableGen.tableLength())) {
+ ReportOutOfMemory(bce_->fc);
+ return false;
+ }
+
+ MOZ_ASSERT(top_ == bce_->bytecodeSection().offset());
+ if (!bce_->emitN(JSOp::TableSwitch,
+ JSOpLength_TableSwitch - sizeof(jsbytecode))) {
+ return false;
+ }
+
+ // Skip default offset.
+ jsbytecode* pc =
+ bce_->bytecodeSection().code(top_ + BytecodeOffsetDiff(JUMP_OFFSET_LEN));
+
+ // Fill in switch bounds, which we know fit in 16-bit offsets.
+ SET_JUMP_OFFSET(pc, tableGen.low());
+ SET_JUMP_OFFSET(pc + JUMP_OFFSET_LEN, tableGen.high());
+
+ state_ = State::Table;
+ return true;
+}
+
+bool SwitchEmitter::emitCaseOrDefaultJump(uint32_t caseIndex, bool isDefault) {
+ MOZ_ASSERT(kind_ == Kind::Cond);
+
+ if (isDefault) {
+ if (!bce_->emitJump(JSOp::Default, &condSwitchDefaultOffset_)) {
+ return false;
+ }
+ return true;
+ }
+
+ JumpList caseJump;
+ if (!bce_->emitJump(JSOp::Case, &caseJump)) {
+ return false;
+ }
+ caseOffsets_[caseIndex] = caseJump.offset;
+ lastCaseOffset_ = caseJump.offset;
+
+ return true;
+}
+
+bool SwitchEmitter::prepareForCaseValue() {
+ MOZ_ASSERT(kind_ == Kind::Cond);
+ MOZ_ASSERT(state_ == State::Cond || state_ == State::Case);
+
+ if (!bce_->emit1(JSOp::Dup)) {
+ return false;
+ }
+
+ state_ = State::CaseValue;
+ return true;
+}
+
+bool SwitchEmitter::emitCaseJump() {
+ MOZ_ASSERT(kind_ == Kind::Cond);
+ MOZ_ASSERT(state_ == State::CaseValue);
+
+ if (!bce_->emit1(JSOp::StrictEq)) {
+ return false;
+ }
+
+ if (!emitCaseOrDefaultJump(caseIndex_, false)) {
+ return false;
+ }
+ caseIndex_++;
+
+ state_ = State::Case;
+ return true;
+}
+
+bool SwitchEmitter::emitImplicitDefault() {
+ MOZ_ASSERT(kind_ == Kind::Cond);
+ MOZ_ASSERT(state_ == State::Cond || state_ == State::Case);
+ if (!emitCaseOrDefaultJump(0, true)) {
+ return false;
+ }
+
+ caseIndex_ = 0;
+
+ // No internal state after emitting default jump.
+ return true;
+}
+
+bool SwitchEmitter::emitCaseBody() {
+ MOZ_ASSERT(kind_ == Kind::Cond);
+ MOZ_ASSERT(state_ == State::Cond || state_ == State::Case ||
+ state_ == State::CaseBody || state_ == State::DefaultBody);
+
+ tdzCacheCaseAndBody_.reset();
+
+ if (state_ == State::Cond || state_ == State::Case) {
+ // For cond switch, JSOp::Default is always emitted.
+ if (!emitImplicitDefault()) {
+ return false;
+ }
+ }
+
+ JumpList caseJump;
+ caseJump.offset = caseOffsets_[caseIndex_];
+ if (!bce_->emitJumpTargetAndPatch(caseJump)) {
+ return false;
+ }
+
+ JumpTarget here;
+ if (!bce_->emitJumpTarget(&here)) {
+ return false;
+ }
+ caseIndex_++;
+
+ tdzCacheCaseAndBody_.emplace(bce_);
+
+ state_ = State::CaseBody;
+ return true;
+}
+
+bool SwitchEmitter::emitCaseBody(int32_t caseValue,
+ const TableGenerator& tableGen) {
+ MOZ_ASSERT(kind_ == Kind::Table);
+ MOZ_ASSERT(state_ == State::Table || state_ == State::CaseBody ||
+ state_ == State::DefaultBody);
+
+ tdzCacheCaseAndBody_.reset();
+
+ JumpTarget here;
+ if (!bce_->emitJumpTarget(&here)) {
+ return false;
+ }
+ caseOffsets_[tableGen.toCaseIndex(caseValue)] = here.offset;
+
+ tdzCacheCaseAndBody_.emplace(bce_);
+
+ state_ = State::CaseBody;
+ return true;
+}
+
+bool SwitchEmitter::emitDefaultBody() {
+ MOZ_ASSERT(state_ == State::Cond || state_ == State::Table ||
+ state_ == State::Case || state_ == State::CaseBody);
+ MOZ_ASSERT(!hasDefault_);
+
+ tdzCacheCaseAndBody_.reset();
+
+ if (state_ == State::Cond || state_ == State::Case) {
+ // For cond switch, JSOp::Default is always emitted.
+ if (!emitImplicitDefault()) {
+ return false;
+ }
+ }
+ JumpTarget here;
+ if (!bce_->emitJumpTarget(&here)) {
+ return false;
+ }
+ defaultJumpTargetOffset_ = here;
+
+ tdzCacheCaseAndBody_.emplace(bce_);
+
+ hasDefault_ = true;
+ state_ = State::DefaultBody;
+ return true;
+}
+
+bool SwitchEmitter::emitEnd() {
+ MOZ_ASSERT(state_ == State::Cond || state_ == State::Table ||
+ state_ == State::CaseBody || state_ == State::DefaultBody);
+
+ tdzCacheCaseAndBody_.reset();
+
+ if (!hasDefault_) {
+ // If no default case, offset for default is to end of switch.
+ if (!bce_->emitJumpTarget(&defaultJumpTargetOffset_)) {
+ return false;
+ }
+ }
+ MOZ_ASSERT(defaultJumpTargetOffset_.offset.valid());
+
+ // Set the default offset (to end of switch if no default).
+ jsbytecode* pc;
+ if (kind_ == Kind::Cond) {
+ pc = nullptr;
+ bce_->patchJumpsToTarget(condSwitchDefaultOffset_,
+ defaultJumpTargetOffset_);
+ } else {
+ // Fill in the default jump target.
+ pc = bce_->bytecodeSection().code(top_);
+ SET_JUMP_OFFSET(pc, (defaultJumpTargetOffset_.offset - top_).value());
+ pc += JUMP_OFFSET_LEN;
+ }
+
+ if (kind_ == Kind::Table) {
+ // Skip over the already-initialized switch bounds.
+ pc += 2 * JUMP_OFFSET_LEN;
+
+ // Use the 'default' offset for missing cases.
+ for (uint32_t i = 0, length = caseOffsets_.length(); i < length; i++) {
+ if (caseOffsets_[i].value() == 0) {
+ caseOffsets_[i] = defaultJumpTargetOffset_.offset;
+ }
+ }
+
+ // Allocate resume index range.
+ uint32_t firstResumeIndex = 0;
+ mozilla::Span<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;
+}
+
+InternalSwitchEmitter::InternalSwitchEmitter(BytecodeEmitter* bce)
+ : SwitchEmitter(bce) {
+#ifdef DEBUG
+ // Skip emitDiscriminant (see the comment above InternalSwitchEmitter)
+ state_ = State::Discriminant;
+#endif
+}
diff --git a/js/src/frontend/SwitchEmitter.h b/js/src/frontend/SwitchEmitter.h
new file mode 100644
index 0000000000..59d45fc71c
--- /dev/null
+++ b/js/src/frontend/SwitchEmitter.h
@@ -0,0 +1,474 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef frontend_SwitchEmitter_h
+#define frontend_SwitchEmitter_h
+
+#include "mozilla/Assertions.h" // MOZ_ASSERT
+#include "mozilla/Attributes.h" // MOZ_STACK_CLASS
+#include "mozilla/Maybe.h" // mozilla::Maybe
+
+#include <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 "js/AllocPolicy.h" // SystemAllocPolicy
+#include "js/Value.h" // JSVAL_INT_MAX, JSVAL_INT_MIN
+#include "js/Vector.h" // Vector
+#include "vm/Scope.h" // LexicalScope
+
+namespace js {
+namespace frontend {
+
+struct BytecodeEmitter;
+
+// Class for emitting bytecode for switch-case-default block.
+//
+// Usage: (check for the return value is omitted for simplicity)
+//
+// `switch (discriminant) { case c1_expr: c1_body; }`
+// SwitchEmitter se(this);
+// se.emitDiscriminant(offset_of_switch);
+// emit(discriminant);
+//
+// se.validateCaseCount(1);
+// se.emitCond();
+//
+// se.prepareForCaseValue();
+// emit(c1_expr);
+// se.emitCaseJump();
+//
+// se.emitCaseBody();
+// emit(c1_body);
+//
+// se.emitEnd();
+//
+// `switch (discriminant) { case c1_expr: c1_body; case c2_expr: c2_body;
+// default: def_body; }`
+// SwitchEmitter se(this);
+// se.emitDiscriminant(offset_of_switch);
+// emit(discriminant);
+//
+// se.validateCaseCount(2);
+// se.emitCond();
+//
+// se.prepareForCaseValue();
+// emit(c1_expr);
+// se.emitCaseJump();
+//
+// se.prepareForCaseValue();
+// emit(c2_expr);
+// se.emitCaseJump();
+//
+// se.emitCaseBody();
+// emit(c1_body);
+//
+// se.emitCaseBody();
+// emit(c2_body);
+//
+// se.emitDefaultBody();
+// emit(def_body);
+//
+// se.emitEnd();
+//
+// `switch (discriminant) { case c1_expr: c1_body; case c2_expr: c2_body; }`
+// with Table Switch
+// SwitchEmitter::TableGenerator tableGen(this);
+// tableGen.addNumber(c1_expr_value);
+// tableGen.addNumber(c2_expr_value);
+// tableGen.finish(2);
+//
+// // If `!tableGen.isValid()` here, `emitCond` should be used instead.
+//
+// SwitchEmitter se(this);
+// se.emitDiscriminant(offset_of_switch);
+// emit(discriminant);
+// se.validateCaseCount(2);
+// se.emitTable(tableGen);
+//
+// se.emitCaseBody(c1_expr_value, tableGen);
+// emit(c1_body);
+//
+// se.emitCaseBody(c2_expr_value, tableGen);
+// emit(c2_body);
+//
+// se.emitEnd();
+//
+// `switch (discriminant) { case c1_expr: c1_body; case c2_expr: c2_body;
+// default: def_body; }`
+// with Table Switch
+// SwitchEmitter::TableGenerator tableGen(bce);
+// tableGen.addNumber(c1_expr_value);
+// tableGen.addNumber(c2_expr_value);
+// tableGen.finish(2);
+//
+// // If `!tableGen.isValid()` here, `emitCond` should be used instead.
+//
+// SwitchEmitter se(this);
+// se.emitDiscriminant(offset_of_switch);
+// emit(discriminant);
+// se.validateCaseCount(2);
+// se.emitTable(tableGen);
+//
+// se.emitCaseBody(c1_expr_value, tableGen);
+// emit(c1_body);
+//
+// se.emitCaseBody(c2_expr_value, tableGen);
+// emit(c2_body);
+//
+// se.emitDefaultBody();
+// emit(def_body);
+//
+// se.emitEnd();
+//
+// `switch (discriminant) { case c1_expr: c1_body; }`
+// in case c1_body contains lexical bindings
+// SwitchEmitter se(this);
+// se.emitDiscriminant(offset_of_switch);
+// emit(discriminant);
+//
+// se.validateCaseCount(1);
+//
+// se.emitLexical(bindings);
+//
+// se.emitCond();
+//
+// se.prepareForCaseValue();
+// emit(c1_expr);
+// se.emitCaseJump();
+//
+// se.emitCaseBody();
+// emit(c1_body);
+//
+// se.emitEnd();
+//
+// `switch (discriminant) { case c1_expr: c1_body; }`
+// in case c1_body contains hosted functions
+// SwitchEmitter se(this);
+// se.emitDiscriminant(offset_of_switch);
+// emit(discriminant);
+//
+// se.validateCaseCount(1);
+//
+// se.emitLexical(bindings);
+// emit(hosted functions);
+//
+// se.emitCond();
+//
+// se.prepareForCaseValue();
+// emit(c1_expr);
+// se.emitCaseJump();
+//
+// se.emitCaseBody();
+// emit(c1_body);
+//
+// se.emitEnd();
+//
+class MOZ_STACK_CLASS SwitchEmitter {
+ // Bytecode for each case.
+ //
+ // Cond Switch (uses an equality comparison for each case)
+ // {discriminant}
+ //
+ // {c1_expr}
+ // JSOp::Case c1
+ //
+ // JSOp::JumpTarget
+ // {c2_expr}
+ // JSOp::Case c2
+ //
+ // ...
+ //
+ // JSOp::JumpTarget
+ // JSOp::Default default
+ //
+ // c1:
+ // JSOp::JumpTarget
+ // {c1_body}
+ // JSOp::Goto end
+ //
+ // c2:
+ // JSOp::JumpTarget
+ // {c2_body}
+ // JSOp::Goto end
+ //
+ // default:
+ // end:
+ // JSOp::JumpTarget
+ //
+ // Table Switch
+ // {discriminant}
+ // JSOp::TableSwitch c1, c2, ...
+ //
+ // c1:
+ // JSOp::JumpTarget
+ // {c1_body}
+ // JSOp::Goto end
+ //
+ // c2:
+ // JSOp::JumpTarget
+ // {c2_body}
+ // JSOp::Goto end
+ //
+ // ...
+ //
+ // end:
+ // JSOp::JumpTarget
+
+ public:
+ enum class Kind { Table, Cond };
+
+ // Class for generating optimized table switch data.
+ class MOZ_STACK_CLASS TableGenerator {
+ BytecodeEmitter* bce_;
+
+ // Bit array for given numbers.
+ mozilla::Maybe<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; }
+ [[nodiscard]] bool isValid() const { return valid_; }
+ [[nodiscard]] bool isInvalid() const { return !valid_; }
+
+ // Add the given number to the table. The number is the value of
+ // `expr` for `case expr:` syntax.
+ [[nodiscard]] bool addNumber(int32_t caseValue);
+
+ // Finish generating the table.
+ // `caseCount` should be the number of cases in the switch statement,
+ // excluding the default case.
+ void finish(uint32_t caseCount);
+
+ private:
+ friend SwitchEmitter;
+
+ // The following methods can be used only after calling `finish`.
+
+ // Returns the lower bound of the added numbers.
+ int32_t low() const {
+ MOZ_ASSERT(finished_);
+ return low_;
+ }
+
+ // Returns the higher bound of the numbers.
+ int32_t high() const {
+ MOZ_ASSERT(finished_);
+ return high_;
+ }
+
+ // Returns the index in SwitchEmitter.caseOffsets_ for table switch.
+ uint32_t toCaseIndex(int32_t caseValue) const;
+
+ // Returns the length of the table.
+ // This method can be called only if `isValid()` is true.
+ uint32_t tableLength() const;
+ };
+
+ private:
+ BytecodeEmitter* bce_;
+
+ // `kind_` should be set to the correct value in emitCond/emitTable.
+ Kind kind_ = Kind::Cond;
+
+ // True if there's explicit default case.
+ bool hasDefault_ = false;
+
+ // The number of cases in the switch statement, excluding the default case.
+ uint32_t caseCount_ = 0;
+
+ // Internal index for case jump and case body, used by cond switch.
+ uint32_t caseIndex_ = 0;
+
+ // Bytecode offset after emitting `discriminant`.
+ BytecodeOffset top_;
+
+ // Bytecode offset of the previous JSOp::Case.
+ BytecodeOffset lastCaseOffset_;
+
+ // Bytecode offset of the JSOp::JumpTarget for default body.
+ JumpTarget defaultJumpTargetOffset_;
+
+ // Bytecode offset of the JSOp::Default.
+ JumpList condSwitchDefaultOffset_;
+
+ // Instantiated when there's lexical scope for entire switch.
+ mozilla::Maybe<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_;
+
+ uint32_t switchPos_ = 0;
+
+ // 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 |-+ |
+ // | +-------------+ |
+ // | |
+ // +-------------------------------------+
+ //
+ protected:
+ enum class State {
+ // The initial state.
+ Start,
+
+ // After calling emitDiscriminant.
+ Discriminant,
+
+ // After calling validateCaseCount.
+ CaseCount,
+
+ // After calling emitLexical.
+ Lexical,
+
+ // After calling emitCond.
+ Cond,
+
+ // After calling emitTable.
+ Table,
+
+ // After calling prepareForCaseValue.
+ CaseValue,
+
+ // After calling emitCaseJump.
+ Case,
+
+ // After calling emitCaseBody.
+ CaseBody,
+
+ // After calling emitDefaultBody.
+ DefaultBody,
+
+ // After calling emitEnd.
+ End
+ };
+ State state_ = State::Start;
+
+ public:
+ explicit SwitchEmitter(BytecodeEmitter* bce);
+
+ // `switchPos` is the offset in the source code for the character below:
+ //
+ // switch ( cond ) { ... }
+ // ^
+ // |
+ // switchPos
+ [[nodiscard]] bool emitDiscriminant(uint32_t switchPos);
+
+ // `caseCount` should be the number of cases in the switch statement,
+ // excluding the default case.
+ [[nodiscard]] bool validateCaseCount(uint32_t caseCount);
+
+ // `bindings` is a lexical scope for the entire switch, in case there's
+ // let/const effectively directly under case or default blocks.
+ [[nodiscard]] bool emitLexical(LexicalScope::ParserData* bindings);
+
+ [[nodiscard]] bool emitCond();
+ [[nodiscard]] bool emitTable(const TableGenerator& tableGen);
+
+ [[nodiscard]] bool prepareForCaseValue();
+ [[nodiscard]] bool emitCaseJump();
+
+ [[nodiscard]] bool emitCaseBody();
+ [[nodiscard]] bool emitCaseBody(int32_t caseValue,
+ const TableGenerator& tableGen);
+ [[nodiscard]] bool emitDefaultBody();
+ [[nodiscard]] bool emitEnd();
+
+ private:
+ [[nodiscard]] bool emitCaseOrDefaultJump(uint32_t caseIndex, bool isDefault);
+ [[nodiscard]] bool emitImplicitDefault();
+};
+
+// Class for emitting bytecode for switch-case-default block that doesn't
+// correspond to a syntactic `switch`.
+// Compared to SwitchEmitter, this class doesn't require `emitDiscriminant`,
+// and the discriminant can already be on the stack. Usage is otherwise
+// the same as SwitchEmitter.
+class MOZ_STACK_CLASS InternalSwitchEmitter : public SwitchEmitter {
+ public:
+ explicit InternalSwitchEmitter(BytecodeEmitter* bce);
+};
+
+} /* namespace frontend */
+} /* namespace js */
+
+#endif /* frontend_SwitchEmitter_h */
diff --git a/js/src/frontend/SyntaxParseHandler.h b/js/src/frontend/SyntaxParseHandler.h
new file mode 100644
index 0000000000..d56591e463
--- /dev/null
+++ b/js/src/frontend/SyntaxParseHandler.h
@@ -0,0 +1,797 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef frontend_SyntaxParseHandler_h
+#define frontend_SyntaxParseHandler_h
+
+#include "mozilla/Assertions.h"
+#include "mozilla/Maybe.h" // mozilla::Maybe
+
+#include <string.h>
+
+#include "jstypes.h"
+
+#include "frontend/CompilationStencil.h" // CompilationState
+#include "frontend/FunctionSyntaxKind.h" // FunctionSyntaxKind
+#include "frontend/NameAnalysisTypes.h" // PrivateNameKind
+#include "frontend/ParseNode.h"
+#include "frontend/ParserAtom.h" // TaggedParserAtomIndex
+#include "frontend/TokenStream.h"
+
+namespace js {
+
+namespace frontend {
+
+// Parse handler used when processing the syntax in a block of code, to generate
+// the minimal information which is required to detect syntax errors and allow
+// bytecode to be emitted for outer functions.
+//
+// When parsing, we start at the top level with a full parse, and when possible
+// only check the syntax for inner functions, so that they can be lazily parsed
+// into bytecode when/if they first run. Checking the syntax of a function is
+// several times faster than doing a full parse/emit, and lazy parsing improves
+// both performance and memory usage significantly when pages contain large
+// amounts of code that never executes (which happens often).
+class SyntaxParseHandler {
+ // Remember the last encountered name or string literal during syntax parses.
+ TaggedParserAtomIndex lastAtom;
+ TokenPos lastStringPos;
+
+ public:
+ enum Node {
+ NodeFailure = 0,
+ NodeGeneric,
+ NodeGetProp,
+ NodeStringExprStatement,
+ NodeReturn,
+ NodeBreak,
+ NodeThrow,
+ NodeEmptyStatement,
+
+ NodeVarDeclaration,
+ NodeLexicalDeclaration,
+
+ // A non-arrow function expression with block body, from bog-standard
+ // ECMAScript.
+ NodeFunctionExpression,
+
+ NodeFunctionArrow,
+ NodeFunctionStatement,
+
+ // This is needed for proper assignment-target handling. ES6 formally
+ // requires function calls *not* pass IsValidSimpleAssignmentTarget,
+ // but at last check there were still sites with |f() = 5| and similar
+ // in code not actually executed (or at least not executed enough to be
+ // noticed).
+ NodeFunctionCall,
+
+ NodeOptionalFunctionCall,
+
+ // Node representing normal names which don't require any special
+ // casing.
+ NodeName,
+
+ // Nodes representing the names "arguments" and "eval".
+ NodeArgumentsName,
+ NodeEvalName,
+
+ // Node representing the "async" name, which may actually be a
+ // contextual keyword.
+ NodePotentialAsyncKeyword,
+
+ // Node representing private names.
+ NodePrivateName,
+
+ NodeDottedProperty,
+ NodeOptionalDottedProperty,
+ NodeElement,
+ NodeOptionalElement,
+ // A distinct node for [PrivateName], to make detecting delete this.#x
+ // detectable in syntax parse
+ NodePrivateMemberAccess,
+ NodeOptionalPrivateMemberAccess,
+
+ // Destructuring target patterns can't be parenthesized: |([a]) = [3];|
+ // must be a syntax error. (We can't use NodeGeneric instead of these
+ // because that would trigger invalid-left-hand-side ReferenceError
+ // semantics when SyntaxError semantics are desired.)
+ NodeParenthesizedArray,
+ NodeParenthesizedObject,
+
+ // In rare cases a parenthesized |node| doesn't have the same semantics
+ // as |node|. Each such node has a special Node value, and we use a
+ // different Node value to represent the parenthesized form. See also
+ // is{Unp,P}arenthesized*(Node), parenthesize(Node), and the various
+ // functions that deal in NodeUnparenthesized* below.
+
+ // Valuable for recognizing potential destructuring patterns.
+ NodeUnparenthesizedArray,
+ NodeUnparenthesizedObject,
+
+ // The directive prologue at the start of a FunctionBody or ScriptBody
+ // is the longest sequence (possibly empty) of string literal
+ // expression statements at the start of a function. Thus we need this
+ // to treat |"use strict";| as a possible Use Strict Directive and
+ // |("use strict");| as a useless statement.
+ NodeUnparenthesizedString,
+
+ // For destructuring patterns an assignment element with
+ // an initializer expression is not allowed be parenthesized.
+ // i.e. |{x = 1} = obj|
+ NodeUnparenthesizedAssignment,
+
+ // This node is necessary to determine if the base operand in an
+ // exponentiation operation is an unparenthesized unary expression.
+ // We want to reject |-2 ** 3|, but still need to allow |(-2) ** 3|.
+ NodeUnparenthesizedUnary,
+
+ // This node is necessary to determine if the LHS of a property access is
+ // super related.
+ NodeSuperBase
+ };
+
+#define DECLARE_TYPE(typeName, longTypeName, asMethodName) \
+ using longTypeName = Node;
+ FOR_EACH_PARSENODE_SUBCLASS(DECLARE_TYPE)
+#undef DECLARE_TYPE
+
+ using NullNode = Node;
+
+ bool isNonArrowFunctionExpression(Node node) const {
+ return node == NodeFunctionExpression;
+ }
+
+ bool isPropertyOrPrivateMemberAccess(Node node) {
+ return node == NodeDottedProperty || node == NodeElement ||
+ node == NodePrivateMemberAccess;
+ }
+
+ bool isOptionalPropertyOrPrivateMemberAccess(Node node) {
+ return node == NodeOptionalDottedProperty || node == NodeOptionalElement ||
+ node == NodeOptionalPrivateMemberAccess;
+ }
+
+ bool isFunctionCall(Node node) {
+ // Note: super() is a special form, *not* a function call.
+ return node == NodeFunctionCall;
+ }
+
+ static bool isUnparenthesizedDestructuringPattern(Node node) {
+ return node == NodeUnparenthesizedArray ||
+ node == NodeUnparenthesizedObject;
+ }
+
+ static bool isParenthesizedDestructuringPattern(Node node) {
+ // Technically this isn't a destructuring target at all -- the grammar
+ // doesn't treat it as such. But we need to know when this happens to
+ // consider it a SyntaxError rather than an invalid-left-hand-side
+ // ReferenceError.
+ return node == NodeParenthesizedArray || node == NodeParenthesizedObject;
+ }
+
+ public:
+ SyntaxParseHandler(FrontendContext* fc, CompilationState& compilationState) {
+ MOZ_ASSERT(!compilationState.input.isDelazifying());
+ }
+
+ static NullNode null() { return NodeFailure; }
+
+#define DECLARE_AS(typeName, longTypeName, asMethodName) \
+ static longTypeName asMethodName(Node node) { return node; }
+ FOR_EACH_PARSENODE_SUBCLASS(DECLARE_AS)
+#undef DECLARE_AS
+
+ NameNodeType newName(TaggedParserAtomIndex name, const TokenPos& pos) {
+ lastAtom = name;
+ if (name == TaggedParserAtomIndex::WellKnown::arguments()) {
+ return NodeArgumentsName;
+ }
+ if (pos.begin + strlen("async") == pos.end &&
+ name == TaggedParserAtomIndex::WellKnown::async()) {
+ return NodePotentialAsyncKeyword;
+ }
+ if (name == TaggedParserAtomIndex::WellKnown::eval()) {
+ return NodeEvalName;
+ }
+ return NodeName;
+ }
+
+ UnaryNodeType newComputedName(Node expr, uint32_t start, uint32_t end) {
+ return NodeGeneric;
+ }
+
+ UnaryNodeType newSyntheticComputedName(Node expr, uint32_t start,
+ uint32_t end) {
+ return NodeGeneric;
+ }
+
+ NameNodeType newObjectLiteralPropertyName(TaggedParserAtomIndex atom,
+ const TokenPos& pos) {
+ return NodeName;
+ }
+
+ NameNodeType newPrivateName(TaggedParserAtomIndex atom, const TokenPos& pos) {
+ return NodePrivateName;
+ }
+
+ NumericLiteralType newNumber(double value, DecimalPoint decimalPoint,
+ const TokenPos& pos) {
+ return NodeGeneric;
+ }
+
+ BigIntLiteralType newBigInt() { return NodeGeneric; }
+
+ BooleanLiteralType newBooleanLiteral(bool cond, const TokenPos& pos) {
+ return NodeGeneric;
+ }
+
+ NameNodeType newStringLiteral(TaggedParserAtomIndex atom,
+ const TokenPos& pos) {
+ lastAtom = atom;
+ lastStringPos = pos;
+ return NodeUnparenthesizedString;
+ }
+
+ NameNodeType newTemplateStringLiteral(TaggedParserAtomIndex atom,
+ const TokenPos& pos) {
+ return NodeGeneric;
+ }
+
+ CallSiteNodeType newCallSiteObject(uint32_t begin) { return NodeGeneric; }
+
+ void addToCallSiteObject(CallSiteNodeType callSiteObj, Node rawNode,
+ Node cookedNode) {}
+
+ ThisLiteralType newThisLiteral(const TokenPos& pos, Node thisName) {
+ return NodeGeneric;
+ }
+ NullLiteralType newNullLiteral(const TokenPos& pos) { return NodeGeneric; }
+ RawUndefinedLiteralType newRawUndefinedLiteral(const TokenPos& pos) {
+ return NodeGeneric;
+ }
+
+ RegExpLiteralType newRegExp(Node reobj, const TokenPos& pos) {
+ return NodeGeneric;
+ }
+
+ ConditionalExpressionType newConditional(Node cond, Node thenExpr,
+ Node elseExpr) {
+ return NodeGeneric;
+ }
+
+ UnaryNodeType newDelete(uint32_t begin, Node expr) {
+ return NodeUnparenthesizedUnary;
+ }
+
+ UnaryNodeType newTypeof(uint32_t begin, Node kid) {
+ return NodeUnparenthesizedUnary;
+ }
+
+ UnaryNodeType newUnary(ParseNodeKind kind, uint32_t begin, Node kid) {
+ return NodeUnparenthesizedUnary;
+ }
+
+ UnaryNodeType newUpdate(ParseNodeKind kind, uint32_t begin, Node kid) {
+ return NodeGeneric;
+ }
+
+ UnaryNodeType newSpread(uint32_t begin, Node kid) { return NodeGeneric; }
+
+ Node appendOrCreateList(ParseNodeKind kind, Node left, Node right,
+ ParseContext* pc) {
+ return NodeGeneric;
+ }
+
+ // Expressions
+
+ ListNodeType newArrayLiteral(uint32_t begin) {
+ return NodeUnparenthesizedArray;
+ }
+ [[nodiscard]] bool addElision(ListNodeType literal, const TokenPos& pos) {
+ return true;
+ }
+ [[nodiscard]] bool addSpreadElement(ListNodeType literal, uint32_t begin,
+ Node inner) {
+ return true;
+ }
+ void addArrayElement(ListNodeType literal, Node element) {}
+
+ ListNodeType newArguments(const TokenPos& pos) { return NodeGeneric; }
+ CallNodeType newCall(Node callee, Node args, JSOp callOp) {
+ return NodeFunctionCall;
+ }
+
+ CallNodeType newOptionalCall(Node callee, Node args, JSOp callOp) {
+ return NodeOptionalFunctionCall;
+ }
+
+ CallNodeType newSuperCall(Node callee, Node args, bool isSpread) {
+ return NodeGeneric;
+ }
+ CallNodeType newTaggedTemplate(Node tag, Node args, JSOp callOp) {
+ return NodeGeneric;
+ }
+
+ ListNodeType newObjectLiteral(uint32_t begin) {
+ return NodeUnparenthesizedObject;
+ }
+
+#ifdef ENABLE_RECORD_TUPLE
+ ListNodeType newRecordLiteral(uint32_t begin) { return NodeGeneric; }
+
+ ListNodeType newTupleLiteral(uint32_t begin) { return NodeGeneric; }
+#endif
+
+ ListNodeType newClassMemberList(uint32_t begin) { return NodeGeneric; }
+ ClassNamesType newClassNames(Node outer, Node inner, const TokenPos& pos) {
+ return NodeGeneric;
+ }
+ ClassNodeType newClass(Node name, Node heritage, Node methodBlock,
+#ifdef ENABLE_DECORATORS
+ ListNodeType decorators,
+#endif
+ const TokenPos& pos) {
+ return NodeGeneric;
+ }
+
+ LexicalScopeNodeType newLexicalScope(Node body) {
+ return NodeLexicalDeclaration;
+ }
+
+ ClassBodyScopeNodeType newClassBodyScope(Node body) {
+ return NodeLexicalDeclaration;
+ }
+
+ NewTargetNodeType newNewTarget(NullaryNodeType newHolder,
+ NullaryNodeType targetHolder,
+ NameNodeType newTargetName) {
+ return NodeGeneric;
+ }
+ NullaryNodeType newPosHolder(const TokenPos& pos) { return NodeGeneric; }
+ UnaryNodeType newSuperBase(Node thisName, const TokenPos& pos) {
+ return NodeSuperBase;
+ }
+
+ [[nodiscard]] bool addPrototypeMutation(ListNodeType literal, uint32_t begin,
+ Node expr) {
+ return true;
+ }
+ BinaryNodeType newPropertyDefinition(Node key, Node val) {
+ return NodeGeneric;
+ }
+ void addPropertyDefinition(ListNodeType literal, BinaryNodeType propdef) {}
+ [[nodiscard]] bool addPropertyDefinition(ListNodeType literal, Node key,
+ Node expr) {
+ return true;
+ }
+ [[nodiscard]] bool addShorthand(ListNodeType literal, NameNodeType name,
+ NameNodeType expr) {
+ return true;
+ }
+ [[nodiscard]] bool addSpreadProperty(ListNodeType literal, uint32_t begin,
+ Node inner) {
+ return true;
+ }
+ [[nodiscard]] bool addObjectMethodDefinition(ListNodeType literal, Node key,
+ FunctionNodeType funNode,
+ AccessorType atype) {
+ return true;
+ }
+ [[nodiscard]] Node newDefaultClassConstructor(Node key,
+ FunctionNodeType funNode) {
+ return NodeGeneric;
+ }
+ [[nodiscard]] Node newClassMethodDefinition(
+ Node key, FunctionNodeType funNode, AccessorType atype, bool isStatic,
+ mozilla::Maybe<FunctionNodeType> initializerIfPrivate
+#ifdef ENABLE_DECORATORS
+ ,
+ ListNodeType decorators
+#endif
+ ) {
+ return NodeGeneric;
+ }
+ [[nodiscard]] Node newClassFieldDefinition(Node name,
+ FunctionNodeType initializer,
+ bool isStatic
+#ifdef ENABLE_DECORATORS
+ ,
+ ListNodeType decorators,
+ bool hasAccessor
+#endif
+ ) {
+ return NodeGeneric;
+ }
+
+ [[nodiscard]] Node newStaticClassBlock(FunctionNodeType block) {
+ return NodeGeneric;
+ }
+
+ [[nodiscard]] bool addClassMemberDefinition(ListNodeType memberList,
+ Node member) {
+ return true;
+ }
+ UnaryNodeType newYieldExpression(uint32_t begin, Node value) {
+ return NodeGeneric;
+ }
+ UnaryNodeType newYieldStarExpression(uint32_t begin, Node value) {
+ return NodeGeneric;
+ }
+ UnaryNodeType newAwaitExpression(uint32_t begin, Node value) {
+ return NodeUnparenthesizedUnary;
+ }
+ UnaryNodeType newOptionalChain(uint32_t begin, Node value) {
+ return NodeGeneric;
+ }
+
+ // Statements
+
+ ListNodeType newStatementList(const TokenPos& pos) { return NodeGeneric; }
+ void addStatementToList(ListNodeType list, Node stmt) {}
+ void setListEndPosition(ListNodeType list, const TokenPos& pos) {}
+ void addCaseStatementToList(ListNodeType list, CaseClauseType caseClause) {}
+ [[nodiscard]] bool prependInitialYield(ListNodeType stmtList, Node genName) {
+ return true;
+ }
+ NullaryNodeType newEmptyStatement(const TokenPos& pos) {
+ return NodeEmptyStatement;
+ }
+
+ BinaryNodeType newImportAssertion(Node keyNode, Node valueNode) {
+ return NodeGeneric;
+ }
+ BinaryNodeType newModuleRequest(Node moduleSpec, Node importAssertionList,
+ const TokenPos& pos) {
+ return NodeGeneric;
+ }
+ BinaryNodeType newImportDeclaration(Node importSpecSet, Node moduleRequest,
+ const TokenPos& pos) {
+ return NodeGeneric;
+ }
+ BinaryNodeType newImportSpec(Node importNameNode, Node bindingName) {
+ return NodeGeneric;
+ }
+ UnaryNodeType newImportNamespaceSpec(uint32_t begin, Node bindingName) {
+ return NodeGeneric;
+ }
+ UnaryNodeType newExportDeclaration(Node kid, const TokenPos& pos) {
+ return NodeGeneric;
+ }
+ BinaryNodeType newExportFromDeclaration(uint32_t begin, Node exportSpecSet,
+ Node moduleRequest) {
+ return NodeGeneric;
+ }
+ BinaryNodeType newExportDefaultDeclaration(Node kid, Node maybeBinding,
+ const TokenPos& pos) {
+ return NodeGeneric;
+ }
+ BinaryNodeType newExportSpec(Node bindingName, Node exportName) {
+ return NodeGeneric;
+ }
+ UnaryNodeType newExportNamespaceSpec(uint32_t begin, Node exportName) {
+ return NodeGeneric;
+ }
+ NullaryNodeType newExportBatchSpec(const TokenPos& pos) {
+ return NodeGeneric;
+ }
+ BinaryNodeType newImportMeta(NullaryNodeType importHolder,
+ NullaryNodeType metaHolder) {
+ return NodeGeneric;
+ }
+ BinaryNodeType newCallImport(NullaryNodeType importHolder, Node singleArg) {
+ return NodeGeneric;
+ }
+ BinaryNodeType newCallImportSpec(Node specifierArg, Node optionalArg) {
+ return NodeGeneric;
+ }
+
+ BinaryNodeType newSetThis(Node thisName, Node value) { return value; }
+
+ UnaryNodeType newExprStatement(Node expr, uint32_t end) {
+ return expr == NodeUnparenthesizedString ? NodeStringExprStatement
+ : NodeGeneric;
+ }
+
+ TernaryNodeType newIfStatement(uint32_t begin, Node cond, Node thenBranch,
+ Node elseBranch) {
+ return NodeGeneric;
+ }
+ BinaryNodeType newDoWhileStatement(Node body, Node cond,
+ const TokenPos& pos) {
+ return NodeGeneric;
+ }
+ BinaryNodeType newWhileStatement(uint32_t begin, Node cond, Node body) {
+ return NodeGeneric;
+ }
+ SwitchStatementType newSwitchStatement(
+ uint32_t begin, Node discriminant,
+ LexicalScopeNodeType lexicalForCaseList, bool hasDefault) {
+ return NodeGeneric;
+ }
+ CaseClauseType newCaseOrDefault(uint32_t begin, Node expr, Node body) {
+ return NodeGeneric;
+ }
+ ContinueStatementType newContinueStatement(TaggedParserAtomIndex label,
+ const TokenPos& pos) {
+ return NodeGeneric;
+ }
+ BreakStatementType newBreakStatement(TaggedParserAtomIndex label,
+ const TokenPos& pos) {
+ return NodeBreak;
+ }
+ UnaryNodeType newReturnStatement(Node expr, const TokenPos& pos) {
+ return NodeReturn;
+ }
+ UnaryNodeType newExpressionBody(Node expr) { return NodeReturn; }
+ BinaryNodeType newWithStatement(uint32_t begin, Node expr, Node body) {
+ return NodeGeneric;
+ }
+
+ LabeledStatementType newLabeledStatement(TaggedParserAtomIndex label,
+ Node stmt, uint32_t begin) {
+ return NodeGeneric;
+ }
+
+ UnaryNodeType newThrowStatement(Node expr, const TokenPos& pos) {
+ return NodeThrow;
+ }
+ TernaryNodeType newTryStatement(uint32_t begin, Node body,
+ LexicalScopeNodeType catchScope,
+ Node finallyBlock) {
+ return NodeGeneric;
+ }
+ DebuggerStatementType newDebuggerStatement(const TokenPos& pos) {
+ return NodeGeneric;
+ }
+
+ NameNodeType newPropertyName(TaggedParserAtomIndex name,
+ const TokenPos& pos) {
+ lastAtom = name;
+ return NodeGeneric;
+ }
+
+ PropertyAccessType newPropertyAccess(Node expr, NameNodeType key) {
+ return NodeDottedProperty;
+ }
+
+ PropertyAccessType newOptionalPropertyAccess(Node expr, NameNodeType key) {
+ return NodeOptionalDottedProperty;
+ }
+
+ PropertyByValueType newPropertyByValue(Node lhs, Node index, uint32_t end) {
+ MOZ_ASSERT(!isPrivateName(index));
+ return NodeElement;
+ }
+
+ PropertyByValueType newOptionalPropertyByValue(Node lhs, Node index,
+ uint32_t end) {
+ return NodeOptionalElement;
+ }
+
+ PrivateMemberAccessType newPrivateMemberAccess(Node lhs, Node privateName,
+ uint32_t end) {
+ return NodePrivateMemberAccess;
+ }
+
+ PrivateMemberAccessType newOptionalPrivateMemberAccess(Node lhs,
+ Node privateName,
+ uint32_t end) {
+ return NodeOptionalPrivateMemberAccess;
+ }
+
+ [[nodiscard]] bool setupCatchScope(LexicalScopeNodeType lexicalScope,
+ Node catchName, Node catchBody) {
+ return true;
+ }
+
+ [[nodiscard]] bool setLastFunctionFormalParameterDefault(
+ FunctionNodeType funNode, Node defaultValue) {
+ return true;
+ }
+
+ void checkAndSetIsDirectRHSAnonFunction(Node pn) {}
+
+ ParamsBodyNodeType newParamsBody(const TokenPos& pos) { return NodeGeneric; }
+
+ FunctionNodeType newFunction(FunctionSyntaxKind syntaxKind,
+ const TokenPos& pos) {
+ switch (syntaxKind) {
+ case FunctionSyntaxKind::Statement:
+ return NodeFunctionStatement;
+ case FunctionSyntaxKind::Arrow:
+ return NodeFunctionArrow;
+ default:
+ // All non-arrow function expressions are initially presumed to have
+ // block body. This will be overridden later *if* the function
+ // expression permissibly has an AssignmentExpression body.
+ return NodeFunctionExpression;
+ }
+ }
+
+ void setFunctionFormalParametersAndBody(FunctionNodeType funNode,
+ ParamsBodyNodeType paramsBody) {}
+ void setFunctionBody(FunctionNodeType funNode, LexicalScopeNodeType body) {}
+ void setFunctionBox(FunctionNodeType funNode, FunctionBox* funbox) {}
+ void addFunctionFormalParameter(FunctionNodeType funNode, Node argpn) {}
+
+ ForNodeType newForStatement(uint32_t begin, TernaryNodeType forHead,
+ Node body, unsigned iflags) {
+ return NodeGeneric;
+ }
+
+ TernaryNodeType newForHead(Node init, Node test, Node update,
+ const TokenPos& pos) {
+ return NodeGeneric;
+ }
+
+ TernaryNodeType newForInOrOfHead(ParseNodeKind kind, Node target,
+ Node iteratedExpr, const TokenPos& pos) {
+ return NodeGeneric;
+ }
+
+ AssignmentNodeType finishInitializerAssignment(NameNodeType nameNode,
+ Node init) {
+ return NodeUnparenthesizedAssignment;
+ }
+
+ void setBeginPosition(Node pn, Node oth) {}
+ void setBeginPosition(Node pn, uint32_t begin) {}
+
+ void setEndPosition(Node pn, Node oth) {}
+ void setEndPosition(Node pn, uint32_t end) {}
+
+ uint32_t getFunctionNameOffset(Node func, TokenStreamAnyChars& ts) {
+ // XXX This offset isn't relevant to the offending function name. But
+ // we may not *have* that function name around, because of how lazy
+ // parsing works -- the actual name could be outside
+ // |tokenStream.userbuf|'s observed region. So the current offset
+ // is the best we can do.
+ return ts.currentToken().pos.begin;
+ }
+
+ ListNodeType newList(ParseNodeKind kind, const TokenPos& pos) {
+ MOZ_ASSERT(kind != ParseNodeKind::VarStmt);
+ MOZ_ASSERT(kind != ParseNodeKind::LetDecl);
+ MOZ_ASSERT(kind != ParseNodeKind::ConstDecl);
+ MOZ_ASSERT(kind != ParseNodeKind::ParamsBody);
+ return NodeGeneric;
+ }
+
+ ListNodeType newList(ParseNodeKind kind, Node kid) {
+ return newList(kind, TokenPos());
+ }
+
+ DeclarationListNodeType newDeclarationList(ParseNodeKind kind,
+ const TokenPos& pos) {
+ if (kind == ParseNodeKind::VarStmt) {
+ return NodeVarDeclaration;
+ }
+ MOZ_ASSERT(kind == ParseNodeKind::LetDecl ||
+ kind == ParseNodeKind::ConstDecl);
+ return NodeLexicalDeclaration;
+ }
+
+ ListNodeType newCommaExpressionList(Node kid) { return NodeGeneric; }
+
+ void addList(ListNodeType list, Node kid) {
+ MOZ_ASSERT(list == NodeGeneric || list == NodeUnparenthesizedArray ||
+ list == NodeUnparenthesizedObject ||
+ list == NodeVarDeclaration || list == NodeLexicalDeclaration ||
+ list == NodeFunctionCall);
+ }
+
+ CallNodeType newNewExpression(uint32_t begin, Node ctor, Node args,
+ bool isSpread) {
+ return NodeGeneric;
+ }
+
+ AssignmentNodeType newAssignment(ParseNodeKind kind, Node lhs, Node rhs) {
+ return kind == ParseNodeKind::AssignExpr ? NodeUnparenthesizedAssignment
+ : NodeGeneric;
+ }
+
+ AssignmentNodeType newInitExpr(Node lhs, Node rhs) { return NodeGeneric; }
+
+ bool isUnparenthesizedAssignment(Node node) {
+ return node == NodeUnparenthesizedAssignment;
+ }
+
+ bool isUnparenthesizedUnaryExpression(Node node) {
+ return node == NodeUnparenthesizedUnary;
+ }
+
+ bool isReturnStatement(Node node) { return node == NodeReturn; }
+
+ bool isStatementPermittedAfterReturnStatement(Node pn) {
+ return pn == NodeFunctionStatement || isNonArrowFunctionExpression(pn) ||
+ pn == NodeVarDeclaration || pn == NodeBreak || pn == NodeThrow ||
+ pn == NodeEmptyStatement;
+ }
+
+ bool isSuperBase(Node pn) { return pn == NodeSuperBase; }
+
+ void setListHasNonConstInitializer(ListNodeType literal) {}
+ [[nodiscard]] Node parenthesize(Node node) {
+ // A number of nodes have different behavior upon parenthesization, but
+ // only in some circumstances. Convert these nodes to special
+ // parenthesized forms.
+ if (node == NodeUnparenthesizedArray) {
+ return NodeParenthesizedArray;
+ }
+ if (node == NodeUnparenthesizedObject) {
+ return NodeParenthesizedObject;
+ }
+
+ // Other nodes need not be recognizable after parenthesization; convert
+ // them to a generic node.
+ if (node == NodeUnparenthesizedString ||
+ node == NodeUnparenthesizedAssignment ||
+ node == NodeUnparenthesizedUnary) {
+ return NodeGeneric;
+ }
+
+ // Convert parenthesized |async| to a normal name node.
+ if (node == NodePotentialAsyncKeyword) {
+ return NodeName;
+ }
+
+ // In all other cases, the parenthesized form of |node| is equivalent
+ // to the unparenthesized form: return |node| unchanged.
+ return node;
+ }
+ template <typename NodeType>
+ [[nodiscard]] NodeType setLikelyIIFE(NodeType node) {
+ return node; // Remain in syntax-parse mode.
+ }
+
+ bool isName(Node node) {
+ return node == NodeName || node == NodeArgumentsName ||
+ node == NodeEvalName || node == NodePotentialAsyncKeyword;
+ }
+
+ bool isArgumentsName(Node node) { return node == NodeArgumentsName; }
+ bool isEvalName(Node node) { return node == NodeEvalName; }
+ bool isAsyncKeyword(Node node) { return node == NodePotentialAsyncKeyword; }
+
+ bool isPrivateName(Node node) { return node == NodePrivateName; }
+ bool isPrivateMemberAccess(Node node) {
+ return node == NodePrivateMemberAccess;
+ }
+
+ TaggedParserAtomIndex maybeDottedProperty(Node node) {
+ // Note: |super.apply(...)| is a special form that calls an "apply"
+ // method retrieved from one value, but using a *different* value as
+ // |this|. It's not really eligible for the funapply/funcall
+ // optimizations as they're currently implemented (assuming a single
+ // value is used for both retrieval and |this|).
+ if (node != NodeDottedProperty && node != NodeOptionalDottedProperty) {
+ return TaggedParserAtomIndex::null();
+ }
+ return lastAtom;
+ }
+
+ TaggedParserAtomIndex isStringExprStatement(Node pn, TokenPos* pos) {
+ if (pn == NodeStringExprStatement) {
+ *pos = lastStringPos;
+ return lastAtom;
+ }
+ return TaggedParserAtomIndex::null();
+ }
+
+ bool reuseLazyInnerFunctions() { return false; }
+ bool reuseClosedOverBindings() { return false; }
+ TaggedParserAtomIndex nextLazyClosedOverBinding() {
+ MOZ_CRASH(
+ "SyntaxParseHandler::canSkipLazyClosedOverBindings must return false");
+ }
+
+ void setPrivateNameKind(Node node, PrivateNameKind kind) {}
+};
+
+} // namespace frontend
+} // namespace js
+
+#endif /* frontend_SyntaxParseHandler_h */
diff --git a/js/src/frontend/TDZCheckCache.cpp b/js/src/frontend/TDZCheckCache.cpp
new file mode 100644
index 0000000000..779d335759
--- /dev/null
+++ b/js/src/frontend/TDZCheckCache.cpp
@@ -0,0 +1,76 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "frontend/TDZCheckCache.h"
+
+#include "frontend/BytecodeEmitter.h"
+
+using namespace js;
+using namespace js::frontend;
+
+using mozilla::Maybe;
+using mozilla::Nothing;
+using mozilla::Some;
+
+TDZCheckCache::TDZCheckCache(BytecodeEmitter* bce)
+ : Nestable<TDZCheckCache>(&bce->innermostTDZCheckCache),
+ cache_(bce->fc->nameCollectionPool()) {}
+
+bool TDZCheckCache::ensureCache(BytecodeEmitter* bce) {
+ return cache_ || cache_.acquire(bce->fc);
+}
+
+Maybe<MaybeCheckTDZ> TDZCheckCache::needsTDZCheck(BytecodeEmitter* bce,
+ TaggedParserAtomIndex name) {
+ if (!ensureCache(bce)) {
+ return Nothing();
+ }
+
+ CheckTDZMap::AddPtr p = cache_->lookupForAdd(name);
+ if (p) {
+ return Some(p->value().wrapped);
+ }
+
+ MaybeCheckTDZ rv = CheckTDZ;
+ for (TDZCheckCache* it = enclosing(); it; it = it->enclosing()) {
+ if (it->cache_) {
+ if (CheckTDZMap::Ptr p2 = it->cache_->lookup(name)) {
+ rv = p2->value();
+ break;
+ }
+ }
+ }
+
+ if (!cache_->add(p, name, rv)) {
+ ReportOutOfMemory(bce->fc);
+ return Nothing();
+ }
+
+ return Some(rv);
+}
+
+bool TDZCheckCache::noteTDZCheck(BytecodeEmitter* bce,
+ TaggedParserAtomIndex name,
+ MaybeCheckTDZ check) {
+ if (!ensureCache(bce)) {
+ return false;
+ }
+
+ CheckTDZMap::AddPtr p = cache_->lookupForAdd(name);
+ if (p) {
+ MOZ_ASSERT(
+ !check,
+ "TDZ only needs to be checked once per binding per basic block.");
+ p->value() = check;
+ } else {
+ if (!cache_->add(p, name, check)) {
+ ReportOutOfMemory(bce->fc);
+ return false;
+ }
+ }
+
+ return true;
+}
diff --git a/js/src/frontend/TDZCheckCache.h b/js/src/frontend/TDZCheckCache.h
new file mode 100644
index 0000000000..8b6f796e87
--- /dev/null
+++ b/js/src/frontend/TDZCheckCache.h
@@ -0,0 +1,59 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef frontend_TDZCheckCache_h
+#define frontend_TDZCheckCache_h
+
+#include "mozilla/Maybe.h"
+
+#include "ds/Nestable.h"
+#include "frontend/NameCollections.h"
+
+namespace js {
+namespace frontend {
+
+struct BytecodeEmitter;
+class TaggedParserAtomIndex;
+
+enum MaybeCheckTDZ { CheckTDZ = true, DontCheckTDZ = false };
+
+using CheckTDZMap = RecyclableNameMap<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_;
+
+ [[nodiscard]] bool ensureCache(BytecodeEmitter* bce);
+
+ public:
+ explicit TDZCheckCache(BytecodeEmitter* bce);
+
+ mozilla::Maybe<MaybeCheckTDZ> needsTDZCheck(BytecodeEmitter* bce,
+ TaggedParserAtomIndex name);
+ [[nodiscard]] bool noteTDZCheck(BytecodeEmitter* bce,
+ TaggedParserAtomIndex name,
+ MaybeCheckTDZ check);
+};
+
+} /* namespace frontend */
+} /* namespace js */
+
+#endif /* frontend_TDZCheckCache_h */
diff --git a/js/src/frontend/TaggedParserAtomIndexHasher.h b/js/src/frontend/TaggedParserAtomIndexHasher.h
new file mode 100644
index 0000000000..0dc3f0eeaa
--- /dev/null
+++ b/js/src/frontend/TaggedParserAtomIndexHasher.h
@@ -0,0 +1,44 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef frontend_TaggedParserAtomIndexHasher_h
+#define frontend_TaggedParserAtomIndexHasher_h
+
+#include "frontend/ParserAtom.h" // TaggedParserAtomIndex, TrivialTaggedParserAtomIndex
+#include "js/HashTable.h" // HashNumber
+
+namespace js {
+namespace frontend {
+
+class TaggedParserAtomIndexHasher {
+ public:
+ using Lookup = TaggedParserAtomIndex;
+
+ static inline HashNumber hash(const Lookup& l) {
+ return HashNumber(l.rawData());
+ }
+ static inline bool match(TaggedParserAtomIndex entry, const Lookup& l) {
+ return l == entry;
+ }
+};
+
+class TrivialTaggedParserAtomIndexHasher {
+ public:
+ using Lookup = TrivialTaggedParserAtomIndex;
+
+ static inline HashNumber hash(const Lookup& l) {
+ return HashNumber(l.rawData());
+ }
+ static inline bool match(TrivialTaggedParserAtomIndex entry,
+ const Lookup& l) {
+ return l == entry;
+ }
+};
+
+} // namespace frontend
+} // namespace js
+
+#endif // frontend_TaggedParserAtomIndexHasher_h
diff --git a/js/src/frontend/Token.h b/js/src/frontend/Token.h
new file mode 100644
index 0000000000..da01169935
--- /dev/null
+++ b/js/src/frontend/Token.h
@@ -0,0 +1,218 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Token-affiliated data structures except for TokenKind (defined in its own
+ * header).
+ */
+
+#ifndef frontend_Token_h
+#define frontend_Token_h
+
+#include "mozilla/Assertions.h" // MOZ_ASSERT
+
+#include <stdint.h> // uint32_t
+
+#include "frontend/ParserAtom.h" // TaggedParserAtomIndex, TrivialTaggedParserAtomIndex
+#include "frontend/TokenKind.h" // js::frontend::TokenKind
+#include "js/RegExpFlags.h" // JS::RegExpFlags
+
+namespace js {
+
+namespace frontend {
+
+struct TokenPos {
+ uint32_t begin = 0; // Offset of the token's first code unit.
+ uint32_t end = 0; // Offset of 1 past the token's last code unit.
+
+ TokenPos() = default;
+ TokenPos(uint32_t begin, uint32_t end) : begin(begin), end(end) {}
+
+ // Return a TokenPos that covers left, right, and anything in between.
+ static TokenPos box(const TokenPos& left, const TokenPos& right) {
+ MOZ_ASSERT(left.begin <= left.end);
+ MOZ_ASSERT(left.end <= right.begin);
+ MOZ_ASSERT(right.begin <= right.end);
+ return TokenPos(left.begin, right.end);
+ }
+
+ bool operator==(const TokenPos& bpos) const {
+ return begin == bpos.begin && end == bpos.end;
+ }
+
+ bool operator!=(const TokenPos& bpos) const {
+ return begin != bpos.begin || end != bpos.end;
+ }
+
+ bool operator<(const TokenPos& bpos) const { return begin < bpos.begin; }
+
+ bool operator<=(const TokenPos& bpos) const { return begin <= bpos.begin; }
+
+ bool operator>(const TokenPos& bpos) const { return !(*this <= bpos); }
+
+ bool operator>=(const TokenPos& bpos) const { return !(*this < bpos); }
+
+ bool encloses(const TokenPos& pos) const {
+ return begin <= pos.begin && pos.end <= end;
+ }
+};
+
+enum DecimalPoint { NoDecimal = false, HasDecimal = true };
+
+// The only escapes found in IdentifierName are of the Unicode flavor.
+enum class IdentifierEscapes { None, SawUnicodeEscape };
+
+enum class NameVisibility { Public, Private };
+
+class TokenStreamShared;
+
+struct Token {
+ private:
+ // The lexical grammar of JavaScript has a quirk around the '/' character.
+ // As the spec puts it:
+ //
+ // > There are several situations where the identification of lexical input
+ // > elements is sensitive to the syntactic grammar context that is consuming
+ // > the input elements. This requires multiple goal symbols for the lexical
+ // > grammar. [...] The InputElementRegExp goal symbol is used in all
+ // > syntactic grammar contexts where a RegularExpressionLiteral is permitted
+ // > [...] In all other contexts, InputElementDiv is used as the lexical
+ // > goal symbol.
+ //
+ // https://tc39.github.io/ecma262/#sec-lexical-and-regexp-grammars
+ //
+ // What "sensitive to the syntactic grammar context" means is, the parser has
+ // to tell the TokenStream whether to interpret '/' as division or
+ // RegExp. Because only one or the other (or neither) will be legal at that
+ // point in the program, and only the parser knows which one.
+ //
+ // But there's a problem: the parser often gets a token, puts it back, then
+ // consumes it later; or (equivalently) peeks at a token, leaves it, peeks
+ // again later, then finally consumes it. Of course we don't actually re-scan
+ // the token every time; we cache it in the TokenStream. This leads to the
+ // following rule:
+ //
+ // The parser must not pass SlashIsRegExp when getting/peeking at a token
+ // previously scanned with SlashIsDiv; or vice versa.
+ //
+ // That way, code that asks for a SlashIsRegExp mode will never get a cached
+ // Div token. But this rule is easy to screw up, because tokens are so often
+ // peeked at on Parser.cpp line A and consumed on line B, where |A-B| is
+ // thousands of lines. We therefore enforce it with the frontend's most
+ // annoying assertion (in verifyConsistentModifier), and provide
+ // Modifier::SlashIsInvalid to help avoid tripping it.
+ //
+ // This enum belongs in TokenStream, but C++, so we define it here and
+ // typedef it there.
+ enum Modifier {
+ // Parse `/` and `/=` as the division operators. (That is, use
+ // InputElementDiv as the goal symbol.)
+ SlashIsDiv,
+
+ // Parse `/` as the beginning of a RegExp literal. (That is, use
+ // InputElementRegExp.)
+ SlashIsRegExp,
+
+ // Neither a Div token nor a RegExp token is syntactically valid here. When
+ // the parser calls `getToken(SlashIsInvalid)`, it must be prepared to see
+ // either one (and throw a SyntaxError either way).
+ //
+ // It's OK to use SlashIsInvalid to get a token that was originally scanned
+ // with SlashIsDiv or SlashIsRegExp. The reverse--peeking with
+ // SlashIsInvalid, then getting with another mode--is not OK. If either Div
+ // or RegExp is syntactically valid here, use the appropriate modifier.
+ SlashIsInvalid,
+ };
+ friend class TokenStreamShared;
+
+ public:
+ /** The type of this token. */
+ TokenKind type;
+
+ /** The token's position in the overall script. */
+ TokenPos pos;
+
+ union {
+ private:
+ friend struct Token;
+
+ TrivialTaggedParserAtomIndex atom;
+
+ struct {
+ /** Numeric literal's value. */
+ double value;
+
+ /** Does the numeric literal contain a '.'? */
+ DecimalPoint decimalPoint;
+ } number;
+
+ /** Regular expression flags; use charBuffer to access source chars. */
+ JS::RegExpFlags reflags;
+ } u;
+
+#ifdef DEBUG
+ /** The modifier used to get this token. */
+ Modifier modifier;
+#endif
+
+ // Mutators
+
+ void setName(TaggedParserAtomIndex name) {
+ MOZ_ASSERT(type == TokenKind::Name || type == TokenKind::PrivateName);
+ u.atom = TrivialTaggedParserAtomIndex::from(name);
+ }
+
+ void setAtom(TaggedParserAtomIndex atom) {
+ MOZ_ASSERT(type == TokenKind::String || type == TokenKind::TemplateHead ||
+ type == TokenKind::NoSubsTemplate);
+ u.atom = TrivialTaggedParserAtomIndex::from(atom);
+ }
+
+ void setRegExpFlags(JS::RegExpFlags flags) {
+ MOZ_ASSERT(type == TokenKind::RegExp);
+ u.reflags = flags;
+ }
+
+ void setNumber(double n, DecimalPoint decimalPoint) {
+ MOZ_ASSERT(type == TokenKind::Number);
+ u.number.value = n;
+ u.number.decimalPoint = decimalPoint;
+ }
+
+ // Type-safe accessors
+
+ TaggedParserAtomIndex name() const {
+ MOZ_ASSERT(type == TokenKind::Name || type == TokenKind::PrivateName);
+ return u.atom;
+ }
+
+ TaggedParserAtomIndex atom() const {
+ MOZ_ASSERT(type == TokenKind::String || type == TokenKind::TemplateHead ||
+ type == TokenKind::NoSubsTemplate);
+ return u.atom;
+ }
+
+ JS::RegExpFlags regExpFlags() const {
+ MOZ_ASSERT(type == TokenKind::RegExp);
+ return u.reflags;
+ }
+
+ double number() const {
+ MOZ_ASSERT(type == TokenKind::Number);
+ return u.number.value;
+ }
+
+ DecimalPoint decimalPoint() const {
+ MOZ_ASSERT(type == TokenKind::Number);
+ return u.number.decimalPoint;
+ }
+};
+
+} // namespace frontend
+
+} // namespace js
+
+#endif // frontend_Token_h
diff --git a/js/src/frontend/TokenKind.h b/js/src/frontend/TokenKind.h
new file mode 100644
index 0000000000..102b91c6c4
--- /dev/null
+++ b/js/src/frontend/TokenKind.h
@@ -0,0 +1,333 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef frontend_TokenKind_h
+#define frontend_TokenKind_h
+
+#include <stdint.h>
+
+#include "js/TypeDecls.h" // IF_RECORD_TUPLE
+
+/*
+ * 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, "'['") \
+ IF_RECORD_TUPLE(MACRO(HashBracket, "'#['")) \
+ MACRO(RightBracket, "']'") \
+ MACRO(LeftCurly, "'{'") \
+ IF_RECORD_TUPLE(MACRO(HashCurly, "'#{'")) \
+ MACRO(RightCurly, "'}'") \
+ MACRO(LeftParen, "'('") \
+ MACRO(RightParen, "')'") \
+ MACRO(Name, "identifier") \
+ MACRO(PrivateName, "private identifier") \
+ MACRO(Number, "numeric literal") \
+ MACRO(String, "string literal") \
+ MACRO(BigInt, "bigint literal") \
+ IF_DECORATORS(MACRO(At, "'@'")) \
+ \
+ /* start of template literal with substitutions */ \
+ MACRO(TemplateHead, "'${'") \
+ /* template literal without substitutions */ \
+ MACRO(NoSubsTemplate, "template literal") \
+ \
+ MACRO(RegExp, "regular expression literal") \
+ MACRO(True, "boolean literal 'true'") \
+ RANGE(ReservedWordLiteralFirst, True) \
+ MACRO(False, "boolean literal 'false'") \
+ MACRO(Null, "null literal") \
+ RANGE(ReservedWordLiteralLast, Null) \
+ MACRO(This, "keyword 'this'") \
+ RANGE(KeywordFirst, This) \
+ MACRO(Function, "keyword 'function'") \
+ MACRO(If, "keyword 'if'") \
+ MACRO(Else, "keyword 'else'") \
+ MACRO(Switch, "keyword 'switch'") \
+ MACRO(Case, "keyword 'case'") \
+ MACRO(Default, "keyword 'default'") \
+ MACRO(While, "keyword 'while'") \
+ MACRO(Do, "keyword 'do'") \
+ MACRO(For, "keyword 'for'") \
+ MACRO(Break, "keyword 'break'") \
+ MACRO(Continue, "keyword 'continue'") \
+ MACRO(Var, "keyword 'var'") \
+ MACRO(Const, "keyword 'const'") \
+ MACRO(With, "keyword 'with'") \
+ MACRO(Return, "keyword 'return'") \
+ MACRO(New, "keyword 'new'") \
+ MACRO(Delete, "keyword 'delete'") \
+ MACRO(Try, "keyword 'try'") \
+ MACRO(Catch, "keyword 'catch'") \
+ MACRO(Finally, "keyword 'finally'") \
+ MACRO(Throw, "keyword 'throw'") \
+ MACRO(Debugger, "keyword 'debugger'") \
+ MACRO(Export, "keyword 'export'") \
+ MACRO(Import, "keyword 'import'") \
+ MACRO(Class, "keyword 'class'") \
+ MACRO(Extends, "keyword 'extends'") \
+ MACRO(Super, "keyword 'super'") \
+ RANGE(KeywordLast, Super) \
+ \
+ /* contextual keywords */ \
+ MACRO(As, "'as'") \
+ RANGE(ContextualKeywordFirst, As) \
+ /* TODO: Move to alphabetical order when IF_DECORATORS is removed */ \
+ IF_DECORATORS(MACRO(Accessor, "'accessor'")) \
+ MACRO(Assert, "'assert'") \
+ MACRO(Async, "'async'") \
+ MACRO(Await, "'await'") \
+ MACRO(Each, "'each'") \
+ MACRO(From, "'from'") \
+ MACRO(Get, "'get'") \
+ MACRO(Let, "'let'") \
+ MACRO(Meta, "'meta'") \
+ MACRO(Of, "'of'") \
+ MACRO(Set, "'set'") \
+ MACRO(Static, "'static'") \
+ MACRO(Target, "'target'") \
+ MACRO(Yield, "'yield'") \
+ RANGE(ContextualKeywordLast, Yield) \
+ \
+ /* future reserved words */ \
+ MACRO(Enum, "reserved word 'enum'") \
+ RANGE(FutureReservedKeywordFirst, Enum) \
+ RANGE(FutureReservedKeywordLast, Enum) \
+ \
+ /* reserved words in strict mode */ \
+ MACRO(Implements, "reserved word 'implements'") \
+ RANGE(StrictReservedKeywordFirst, Implements) \
+ MACRO(Interface, "reserved word 'interface'") \
+ MACRO(Package, "reserved word 'package'") \
+ MACRO(Private, "reserved word 'private'") \
+ MACRO(Protected, "reserved word 'protected'") \
+ MACRO(Public, "reserved word 'public'") \
+ RANGE(StrictReservedKeywordLast, Public) \
+ \
+ /* \
+ * The following token types occupy contiguous ranges to enable easy \
+ * range-testing. \
+ */ \
+ /* \
+ * Binary operators. \
+ * This list must be kept in the same order in several places: \
+ * - the binary operators in ParseNode.h \
+ * - the precedence list in Parser.cpp \
+ * - the JSOp code list in BytecodeEmitter.cpp \
+ */ \
+ MACRO(Coalesce, "'\?\?'") /* escapes to avoid trigraphs warning */ \
+ RANGE(BinOpFirst, Coalesce) \
+ MACRO(Or, "'||'") /* logical or */ \
+ MACRO(And, "'&&'") /* logical and */ \
+ MACRO(BitOr, "'|'") /* bitwise-or */ \
+ MACRO(BitXor, "'^'") /* bitwise-xor */ \
+ MACRO(BitAnd, "'&'") /* bitwise-and */ \
+ \
+ /* Equality operation tokens, per TokenKindIsEquality. */ \
+ MACRO(StrictEq, "'==='") \
+ RANGE(EqualityStart, StrictEq) \
+ MACRO(Eq, "'=='") \
+ MACRO(StrictNe, "'!=='") \
+ MACRO(Ne, "'!='") \
+ RANGE(EqualityLast, Ne) \
+ \
+ /* Relational ops, per TokenKindIsRelational. */ \
+ MACRO(Lt, "'<'") \
+ RANGE(RelOpStart, Lt) \
+ MACRO(Le, "'<='") \
+ MACRO(Gt, "'>'") \
+ MACRO(Ge, "'>='") \
+ RANGE(RelOpLast, Ge) \
+ \
+ MACRO(InstanceOf, "keyword 'instanceof'") \
+ RANGE(KeywordBinOpFirst, InstanceOf) \
+ MACRO(In, "keyword 'in'") \
+ MACRO(PrivateIn, "keyword 'in' (private)") \
+ RANGE(KeywordBinOpLast, PrivateIn) \
+ \
+ /* Shift ops, per TokenKindIsShift. */ \
+ MACRO(Lsh, "'<<'") \
+ RANGE(ShiftOpStart, Lsh) \
+ MACRO(Rsh, "'>>'") \
+ MACRO(Ursh, "'>>>'") \
+ RANGE(ShiftOpLast, Ursh) \
+ \
+ MACRO(Add, "'+'") \
+ MACRO(Sub, "'-'") \
+ MACRO(Mul, "'*'") \
+ MACRO(Div, "'/'") \
+ MACRO(Mod, "'%'") \
+ MACRO(Pow, "'**'") \
+ RANGE(BinOpLast, Pow) \
+ \
+ /* Unary operation tokens. */ \
+ MACRO(TypeOf, "keyword 'typeof'") \
+ RANGE(KeywordUnOpFirst, TypeOf) \
+ MACRO(Void, "keyword 'void'") \
+ RANGE(KeywordUnOpLast, Void) \
+ MACRO(Not, "'!'") \
+ MACRO(BitNot, "'~'") \
+ \
+ MACRO(Arrow, "'=>'") /* function arrow */ \
+ \
+ /* Assignment ops, per TokenKindIsAssignment */ \
+ MACRO(Assign, "'='") \
+ RANGE(AssignmentStart, Assign) \
+ MACRO(AddAssign, "'+='") \
+ MACRO(SubAssign, "'-='") \
+ MACRO(CoalesceAssign, "'\?\?='") /* avoid trigraphs warning */ \
+ MACRO(OrAssign, "'||='") \
+ MACRO(AndAssign, "'&&='") \
+ MACRO(BitOrAssign, "'|='") \
+ MACRO(BitXorAssign, "'^='") \
+ MACRO(BitAndAssign, "'&='") \
+ MACRO(LshAssign, "'<<='") \
+ MACRO(RshAssign, "'>>='") \
+ MACRO(UrshAssign, "'>>>='") \
+ MACRO(MulAssign, "'*='") \
+ MACRO(DivAssign, "'/='") \
+ MACRO(ModAssign, "'%='") \
+ MACRO(PowAssign, "'**='") \
+ RANGE(AssignmentLast, PowAssign)
+
+#define TOKEN_KIND_RANGE_EMIT_NONE(name, value)
+#define FOR_EACH_TOKEN_KIND(MACRO) \
+ FOR_EACH_TOKEN_KIND_WITH_RANGE(MACRO, TOKEN_KIND_RANGE_EMIT_NONE)
+
+namespace js {
+namespace frontend {
+
+// Values of this type are used to index into arrays such as isExprEnding[],
+// so the first value must be zero.
+enum class TokenKind : uint8_t {
+#define EMIT_ENUM(name, desc) name,
+#define EMIT_ENUM_RANGE(name, value) name = value,
+ FOR_EACH_TOKEN_KIND_WITH_RANGE(EMIT_ENUM, EMIT_ENUM_RANGE)
+#undef EMIT_ENUM
+#undef EMIT_ENUM_RANGE
+ Limit // domain size
+};
+
+inline bool TokenKindIsBinaryOp(TokenKind tt) {
+ return TokenKind::BinOpFirst <= tt && tt <= TokenKind::BinOpLast;
+}
+
+inline bool TokenKindIsEquality(TokenKind tt) {
+ return TokenKind::EqualityStart <= tt && tt <= TokenKind::EqualityLast;
+}
+
+inline bool TokenKindIsRelational(TokenKind tt) {
+ return TokenKind::RelOpStart <= tt && tt <= TokenKind::RelOpLast;
+}
+
+inline bool TokenKindIsShift(TokenKind tt) {
+ return TokenKind::ShiftOpStart <= tt && tt <= TokenKind::ShiftOpLast;
+}
+
+inline bool TokenKindIsAssignment(TokenKind tt) {
+ return TokenKind::AssignmentStart <= tt && tt <= TokenKind::AssignmentLast;
+}
+
+[[nodiscard]] inline bool TokenKindIsKeyword(TokenKind tt) {
+ return (TokenKind::KeywordFirst <= tt && tt <= TokenKind::KeywordLast) ||
+ (TokenKind::KeywordBinOpFirst <= tt &&
+ tt <= TokenKind::KeywordBinOpLast) ||
+ (TokenKind::KeywordUnOpFirst <= tt &&
+ tt <= TokenKind::KeywordUnOpLast);
+}
+
+[[nodiscard]] inline bool TokenKindIsContextualKeyword(TokenKind tt) {
+ return TokenKind::ContextualKeywordFirst <= tt &&
+ tt <= TokenKind::ContextualKeywordLast;
+}
+
+[[nodiscard]] inline bool TokenKindIsFutureReservedWord(TokenKind tt) {
+ return TokenKind::FutureReservedKeywordFirst <= tt &&
+ tt <= TokenKind::FutureReservedKeywordLast;
+}
+
+[[nodiscard]] inline bool TokenKindIsStrictReservedWord(TokenKind tt) {
+ return TokenKind::StrictReservedKeywordFirst <= tt &&
+ tt <= TokenKind::StrictReservedKeywordLast;
+}
+
+[[nodiscard]] inline bool TokenKindIsReservedWordLiteral(TokenKind tt) {
+ return TokenKind::ReservedWordLiteralFirst <= tt &&
+ tt <= TokenKind::ReservedWordLiteralLast;
+}
+
+[[nodiscard]] inline bool TokenKindIsReservedWord(TokenKind tt) {
+ return TokenKindIsKeyword(tt) || TokenKindIsFutureReservedWord(tt) ||
+ TokenKindIsReservedWordLiteral(tt);
+}
+
+[[nodiscard]] inline bool TokenKindIsPossibleIdentifier(TokenKind tt) {
+ return tt == TokenKind::Name || TokenKindIsContextualKeyword(tt) ||
+ TokenKindIsStrictReservedWord(tt);
+}
+
+[[nodiscard]] inline bool TokenKindIsPossibleIdentifierName(TokenKind tt) {
+ return TokenKindIsPossibleIdentifier(tt) || TokenKindIsReservedWord(tt);
+}
+
+} // namespace frontend
+} // namespace js
+
+#endif /* frontend_TokenKind_h */
diff --git a/js/src/frontend/TokenStream.cpp b/js/src/frontend/TokenStream.cpp
new file mode 100644
index 0000000000..e05340927a
--- /dev/null
+++ b/js/src/frontend/TokenStream.cpp
@@ -0,0 +1,3829 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// JS lexical scanner.
+
+#include "frontend/TokenStream.h"
+
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/Likely.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/MemoryChecking.h"
+#include "mozilla/ScopeExit.h"
+#include "mozilla/Span.h"
+#include "mozilla/TemplateLib.h"
+#include "mozilla/TextUtils.h"
+#include "mozilla/Utf8.h"
+
+#include <algorithm>
+#include <iterator>
+#include <limits>
+#include <stdarg.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <type_traits>
+#include <utility>
+
+#include "jsnum.h"
+
+#include "frontend/BytecodeCompiler.h"
+#include "frontend/FrontendContext.h"
+#include "frontend/Parser.h"
+#include "frontend/ParserAtom.h"
+#include "frontend/ReservedWords.h"
+#include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_*
+#include "js/Printf.h" // JS_smprintf
+#include "js/RegExpFlags.h" // JS::RegExpFlags
+#include "js/UniquePtr.h"
+#include "util/Text.h"
+#include "util/Unicode.h"
+#include "vm/FrameIter.h" // js::{,NonBuiltin}FrameIter
+#include "vm/JSContext.h"
+#include "vm/Realm.h"
+#include "vm/WellKnownAtom.h" // js_*_str
+
+using mozilla::AsciiAlphanumericToNumber;
+using mozilla::AssertedCast;
+using mozilla::DecodeOneUtf8CodePoint;
+using mozilla::IsAscii;
+using mozilla::IsAsciiAlpha;
+using mozilla::IsAsciiDigit;
+using mozilla::IsAsciiHexDigit;
+using mozilla::IsTrailingUnit;
+using mozilla::MakeScopeExit;
+using mozilla::Maybe;
+using mozilla::PointerRangeSize;
+using mozilla::Span;
+using mozilla::Utf8Unit;
+
+using JS::ReadOnlyCompileOptions;
+using JS::RegExpFlag;
+using JS::RegExpFlags;
+
+struct ReservedWordInfo {
+ const char* chars; // C string with reserved word text
+ js::frontend::TokenKind tokentype;
+};
+
+static const ReservedWordInfo reservedWords[] = {
+#define RESERVED_WORD_INFO(word, name, type) \
+ {js_##word##_str, js::frontend::type},
+ FOR_EACH_JAVASCRIPT_RESERVED_WORD(RESERVED_WORD_INFO)
+#undef RESERVED_WORD_INFO
+};
+
+enum class ReservedWordsIndex : size_t {
+#define ENTRY_(_1, NAME, _3) NAME,
+ FOR_EACH_JAVASCRIPT_RESERVED_WORD(ENTRY_)
+#undef ENTRY_
+};
+
+// Returns a ReservedWordInfo for the specified characters, or nullptr if the
+// string is not a reserved word.
+template <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);
+}
+
+static const ReservedWordInfo* FindReservedWord(
+ const js::frontend::TaggedParserAtomIndex atom) {
+ switch (atom.rawData()) {
+#define CASE_(_1, NAME, _3) \
+ case js::frontend::TaggedParserAtomIndex::WellKnownRawData::NAME(): \
+ return &reservedWords[size_t(ReservedWordsIndex::NAME)];
+ FOR_EACH_JAVASCRIPT_RESERVED_WORD(CASE_)
+#undef CASE_
+ }
+
+ return nullptr;
+}
+
+static char32_t GetSingleCodePoint(const char16_t** p, const char16_t* end) {
+ using namespace js;
+
+ if (MOZ_UNLIKELY(unicode::IsLeadSurrogate(**p)) && *p + 1 < end) {
+ char16_t lead = **p;
+ char16_t maybeTrail = *(*p + 1);
+ if (unicode::IsTrailSurrogate(maybeTrail)) {
+ *p += 2;
+ return unicode::UTF16Decode(lead, maybeTrail);
+ }
+ }
+
+ char32_t codePoint = **p;
+ (*p)++;
+ return codePoint;
+}
+
+template <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 IsIdentifierNameOrPrivateName(JSLinearString* str) {
+ JS::AutoCheckCannotGC nogc;
+ MOZ_ASSERT(str);
+ if (str->hasLatin1Chars()) {
+ return IsIdentifierNameOrPrivateName(str->latin1Chars(nogc), str->length());
+ }
+ return IsIdentifierNameOrPrivateName(str->twoByteChars(nogc), str->length());
+}
+
+bool IsIdentifier(const Latin1Char* chars, size_t length) {
+ if (length == 0) {
+ return false;
+ }
+
+ if (!unicode::IsIdentifierStart(char16_t(*chars))) {
+ return false;
+ }
+
+ const Latin1Char* end = chars + length;
+ while (++chars != end) {
+ if (!unicode::IsIdentifierPart(char16_t(*chars))) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool IsIdentifierASCII(char c) { return unicode::IsIdentifierStartASCII(c); }
+
+bool IsIdentifierASCII(char c1, char c2) {
+ return unicode::IsIdentifierStartASCII(c1) &&
+ unicode::IsIdentifierPartASCII(c2);
+}
+
+bool IsIdentifierNameOrPrivateName(const Latin1Char* chars, size_t length) {
+ if (length == 0) {
+ return false;
+ }
+
+ // Skip over any private name marker.
+ if (*chars == '#') {
+ ++chars;
+ --length;
+ }
+
+ return IsIdentifier(chars, length);
+}
+
+bool IsIdentifier(const char16_t* chars, size_t length) {
+ if (length == 0) {
+ return false;
+ }
+
+ const char16_t* p = chars;
+ const char16_t* end = chars + length;
+ char32_t codePoint;
+
+ codePoint = GetSingleCodePoint(&p, end);
+ if (!unicode::IsIdentifierStart(codePoint)) {
+ return false;
+ }
+
+ while (p < end) {
+ codePoint = GetSingleCodePoint(&p, end);
+ if (!unicode::IsIdentifierPart(codePoint)) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool IsIdentifierNameOrPrivateName(const char16_t* chars, size_t length) {
+ if (length == 0) {
+ return false;
+ }
+
+ const char16_t* p = chars;
+ const char16_t* end = chars + length;
+ char32_t codePoint;
+
+ codePoint = GetSingleCodePoint(&p, end);
+
+ // Skip over any private name marker.
+ if (codePoint == '#') {
+ // The identifier part of a private name mustn't be empty.
+ if (length == 1) {
+ return false;
+ }
+
+ codePoint = GetSingleCodePoint(&p, end);
+ }
+
+ if (!unicode::IsIdentifierStart(codePoint)) {
+ return false;
+ }
+
+ while (p < end) {
+ codePoint = GetSingleCodePoint(&p, end);
+ if (!unicode::IsIdentifierPart(codePoint)) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool IsKeyword(TaggedParserAtomIndex atom) {
+ if (const ReservedWordInfo* rw = FindReservedWord(atom)) {
+ return TokenKindIsKeyword(rw->tokentype);
+ }
+
+ return false;
+}
+
+TokenKind ReservedWordTokenKind(TaggedParserAtomIndex name) {
+ if (const ReservedWordInfo* rw = FindReservedWord(name)) {
+ return rw->tokentype;
+ }
+
+ return TokenKind::Limit;
+}
+
+const char* ReservedWordToCharZ(TaggedParserAtomIndex name) {
+ if (const ReservedWordInfo* rw = FindReservedWord(name)) {
+ return ReservedWordToCharZ(rw->tokentype);
+ }
+
+ return nullptr;
+}
+
+const char* ReservedWordToCharZ(TokenKind tt) {
+ MOZ_ASSERT(tt != TokenKind::Name);
+ switch (tt) {
+#define EMIT_CASE(word, name, type) \
+ case type: \
+ return js_##word##_str;
+ FOR_EACH_JAVASCRIPT_RESERVED_WORD(EMIT_CASE)
+#undef EMIT_CASE
+ default:
+ MOZ_ASSERT_UNREACHABLE("Not a reserved word PropertyName.");
+ }
+ return nullptr;
+}
+
+TaggedParserAtomIndex TokenStreamAnyChars::reservedWordToPropertyName(
+ TokenKind tt) const {
+ MOZ_ASSERT(tt != TokenKind::Name);
+ switch (tt) {
+#define EMIT_CASE(word, name, type) \
+ case type: \
+ return TaggedParserAtomIndex::WellKnown::name();
+ FOR_EACH_JAVASCRIPT_RESERVED_WORD(EMIT_CASE)
+#undef EMIT_CASE
+ default:
+ MOZ_ASSERT_UNREACHABLE("Not a reserved word TokenKind.");
+ }
+ return TaggedParserAtomIndex::null();
+}
+
+SourceCoords::SourceCoords(FrontendContext* fc, uint32_t initialLineNumber,
+ uint32_t initialOffset)
+ : lineStartOffsets_(fc), initialLineNum_(initialLineNumber), lastIndex_(0) {
+ // This is actually necessary! Removing it causes compile errors on
+ // GCC and clang. You could try declaring this:
+ //
+ // const uint32_t SourceCoords::MAX_PTR;
+ //
+ // which fixes the GCC/clang error, but causes bustage on Windows. Sigh.
+ //
+ uint32_t maxPtr = MAX_PTR;
+
+ // The first line begins at buffer offset |initialOffset|. MAX_PTR is the
+ // sentinel. The appends cannot fail because |lineStartOffsets_| has
+ // statically-allocated elements.
+ MOZ_ASSERT(lineStartOffsets_.capacity() >= 2);
+ MOZ_ALWAYS_TRUE(lineStartOffsets_.reserve(2));
+ lineStartOffsets_.infallibleAppend(initialOffset);
+ lineStartOffsets_.infallibleAppend(maxPtr);
+}
+
+MOZ_ALWAYS_INLINE bool SourceCoords::add(uint32_t lineNum,
+ uint32_t lineStartOffset) {
+ uint32_t index = indexFromLineNumber(lineNum);
+ uint32_t sentinelIndex = lineStartOffsets_.length() - 1;
+
+ MOZ_ASSERT(lineStartOffsets_[0] <= lineStartOffset);
+ MOZ_ASSERT(lineStartOffsets_[sentinelIndex] == MAX_PTR);
+
+ if (index == sentinelIndex) {
+ // We haven't seen this newline before. Update lineStartOffsets_
+ // only if lineStartOffsets_.append succeeds, to keep sentinel.
+ // Otherwise return false to tell TokenStream about OOM.
+ uint32_t maxPtr = MAX_PTR;
+ if (!lineStartOffsets_.append(maxPtr)) {
+ static_assert(std::is_same_v<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(FrontendContext* fc,
+ const ReadOnlyCompileOptions& options,
+ StrictModeGetter* smg)
+ : fc(fc),
+ options_(options),
+ strictModeGetter_(smg),
+ filename_(options.filename()),
+ longLineColumnInfo_(fc),
+ srcCoords(fc, options.lineno, options.scriptSourceOffset),
+ lineno(options.lineno),
+ mutedErrors(options.mutedErrors()) {
+ // |isExprEnding| was initially zeroed: overwrite the true entries here.
+ isExprEnding[size_t(TokenKind::Comma)] = true;
+ isExprEnding[size_t(TokenKind::Semi)] = true;
+ isExprEnding[size_t(TokenKind::Colon)] = true;
+ isExprEnding[size_t(TokenKind::RightParen)] = true;
+ isExprEnding[size_t(TokenKind::RightBracket)] = true;
+ isExprEnding[size_t(TokenKind::RightCurly)] = true;
+}
+
+template <typename Unit>
+TokenStreamCharsBase<Unit>::TokenStreamCharsBase(FrontendContext* fc,
+ ParserAtomsTable* parserAtoms,
+ const Unit* units,
+ size_t length,
+ size_t startOffset)
+ : TokenStreamCharsShared(fc, parserAtoms),
+ sourceUnits(units, length, startOffset) {}
+
+bool FillCharBufferFromSourceNormalizingAsciiLineBreaks(CharBuffer& charBuffer,
+ const char16_t* cur,
+ const char16_t* end) {
+ MOZ_ASSERT(charBuffer.length() == 0);
+
+ while (cur < end) {
+ char16_t ch = *cur++;
+ if (ch == '\r') {
+ ch = '\n';
+ if (cur < end && *cur == '\n') {
+ cur++;
+ }
+ }
+
+ if (!charBuffer.append(ch)) {
+ return false;
+ }
+ }
+
+ MOZ_ASSERT(cur == end);
+ return true;
+}
+
+bool FillCharBufferFromSourceNormalizingAsciiLineBreaks(CharBuffer& charBuffer,
+ const Utf8Unit* cur,
+ const Utf8Unit* end) {
+ MOZ_ASSERT(charBuffer.length() == 0);
+
+ while (cur < end) {
+ Utf8Unit unit = *cur++;
+ if (MOZ_LIKELY(IsAscii(unit))) {
+ char16_t ch = unit.toUint8();
+ if (ch == '\r') {
+ ch = '\n';
+ if (cur < end && *cur == Utf8Unit('\n')) {
+ cur++;
+ }
+ }
+
+ if (!charBuffer.append(ch)) {
+ return false;
+ }
+
+ continue;
+ }
+
+ Maybe<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(
+ FrontendContext* fc, ParserAtomsTable* parserAtoms,
+ const ReadOnlyCompileOptions& options, const Unit* units, size_t length)
+ : TokenStreamChars<Unit, AnyCharsAccess>(fc, parserAtoms, units, length,
+ options.scriptSourceOffset) {}
+
+bool TokenStreamAnyChars::checkOptions() {
+ // Constrain starting columns to where they will saturate.
+ if (options().column > ColumnLimit) {
+ reportErrorNoOffset(JSMSG_BAD_COLUMN_NUMBER);
+ return false;
+ }
+
+ return true;
+}
+
+void TokenStreamAnyChars::reportErrorNoOffset(unsigned errorNumber, ...) const {
+ va_list args;
+ va_start(args, errorNumber);
+
+ reportErrorNoOffsetVA(errorNumber, &args);
+
+ va_end(args);
+}
+
+void TokenStreamAnyChars::reportErrorNoOffsetVA(unsigned errorNumber,
+ va_list* args) const {
+ ErrorMetadata metadata;
+ computeErrorMetadataNoOffset(&metadata);
+
+ ReportCompileErrorLatin1(fc, std::move(metadata), nullptr, errorNumber, args);
+}
+
+[[nodiscard]] MOZ_ALWAYS_INLINE bool
+TokenStreamAnyChars::internalUpdateLineInfoForEOL(uint32_t lineStartOffset) {
+ prevLinebase = linebase;
+ linebase = lineStartOffset;
+ lineno++;
+
+ // On overflow, report error.
+ if (MOZ_UNLIKELY(!lineno)) {
+ reportErrorNoOffset(JSMSG_BAD_LINE_NUMBER);
+ return false;
+ }
+
+ return srcCoords.add(lineno, linebase);
+}
+
+#ifdef DEBUG
+
+template <>
+inline void SourceUnits<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>(fc))) {
+ // In case of OOM, just count columns from the start of the line.
+ fc->recoverFromOutOfMemory();
+ return ColumnFromPartial(start, 0, UnitsType::PossiblyMultiUnit);
+ }
+ }
+
+ // Note that adding elements to this vector won't invalidate this pointer.
+ lastChunkVectorForLine_ = &ptr->value();
+ }
+
+ const Unit* const limit = sourceUnits.codeUnitPtrAt(offset);
+
+ auto RetractedOffsetOfChunk = [
+#ifdef DEBUG
+ this,
+#endif
+ start, limit,
+ &sourceUnits](uint32_t index) {
+ MOZ_ASSERT(index < this->lastChunkVectorForLine_->length());
+
+ uint32_t naiveOffset = start + index * ColumnChunkLength;
+ const Unit* naivePtr = sourceUnits.codeUnitPtrAt(naiveOffset);
+
+ const Unit* actualPtr = naivePtr;
+ RetractPointerToCodePointBoundary(&actualPtr, limit);
+
+#ifdef DEBUG
+ if ((*this->lastChunkVectorForLine_)[index].unitsType() ==
+ UnitsType::GuaranteedSingleUnit) {
+ MOZ_ASSERT(naivePtr == actualPtr, "miscomputed unitsType value");
+ }
+#endif
+
+ return naiveOffset - PointerRangeSize(actualPtr, naivePtr);
+ };
+
+ uint32_t partialOffset;
+ uint32_t partialColumn;
+ UnitsType unitsType;
+
+ auto entriesLen = AssertedCast<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.
+ fc->recoverFromOutOfMemory();
+ return ColumnFromPartial(partialOffset, partialColumn,
+ UnitsType::PossiblyMultiUnit);
+ }
+
+ // OOM is no longer possible now. \o/
+
+ // The vector always begins with the column of the line start, i.e. zero,
+ // with chunk units pessimally assumed not single-unit.
+ if (entriesLen == 0) {
+ lastChunkVectorForLine_->infallibleAppend(
+ ChunkInfo(0, UnitsType::PossiblyMultiUnit));
+ entriesLen++;
+ }
+
+ do {
+ const Unit* const begin = sourceUnits.codeUnitPtrAt(partialOffset);
+ const Unit* chunkLimit = sourceUnits.codeUnitPtrAt(
+ start + std::min(entriesLen++ * ColumnChunkLength, offsetInLine));
+
+ MOZ_ASSERT(begin < chunkLimit);
+ MOZ_ASSERT(chunkLimit <= limit);
+
+ static_assert(
+ ColumnChunkLength > SourceUnitTraits<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.fc);
+ break;
+ }
+
+ // The largest encoding of a UTF-8 code point is 4 units. (Encoding an
+ // obsolete 5- or 6-byte code point will complain only about a bad lead
+ // code unit.)
+ constexpr size_t MaxWidth = sizeof("0xHH 0xHH 0xHH 0xHH");
+
+ MOZ_ASSERT(relevantUnits > 0);
+
+ char badUnitsStr[MaxWidth];
+ char* ptr = badUnitsStr;
+ while (relevantUnits > 0) {
+ byteToString(this->sourceUnits.getCodeUnit().toUint8(), ptr);
+ ptr[4] = ' ';
+
+ ptr += 5;
+ relevantUnits--;
+ }
+
+ ptr[-1] = '\0';
+
+ uint32_t line, column;
+ computeLineAndColumn(offset, &line, &column);
+
+ if (!notes->addNoteASCII(anyChars.fc, anyChars.getFilename(), 0, line,
+ column, GetErrorMessage, nullptr,
+ JSMSG_BAD_CODE_UNITS, badUnitsStr)) {
+ break;
+ }
+
+ ReportCompileErrorLatin1(anyChars.fc, std::move(err), std::move(notes),
+ errorNumber, &args);
+ } while (false);
+
+ va_end(args);
+}
+
+template <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(
+ char32_t codePoint, uint8_t codePointLength, const char* reason) {
+ // Construct a string like "0x203D" (including null terminator) to include
+ // in the error message. Write the string end-to-start from end to start
+ // of an adequately sized |char| array, shifting least significant nibbles
+ // off the number and writing the corresponding hex digits until done, then
+ // prefixing with "0x". |codePointStr| points at the incrementally
+ // computed string, within |codePointCharsArray|'s bounds.
+
+ // 0x1F'FFFF is the maximum value that can fit in 3+6+6+6 unconstrained
+ // bits in a four-byte UTF-8 code unit sequence.
+ constexpr size_t MaxHexSize = sizeof(
+ "0x1F"
+ "FFFF"); // including '\0'
+ char codePointCharsArray[MaxHexSize];
+
+ char* codePointStr = std::end(codePointCharsArray);
+ *--codePointStr = '\0';
+
+ // Note that by do-while looping here rather than while-looping, this
+ // writes a '0' when |codePoint == 0|.
+ do {
+ MOZ_ASSERT(codePointCharsArray < codePointStr);
+ *--codePointStr = toHexChar(codePoint & 0xF);
+ codePoint >>= 4;
+ } while (codePoint);
+
+ MOZ_ASSERT(codePointCharsArray + 2 <= codePointStr);
+ *--codePointStr = 'x';
+ *--codePointStr = '0';
+
+ internalEncodingError(codePointLength, JSMSG_FORBIDDEN_UTF8_CODE_POINT,
+ codePointStr, reason);
+}
+
+template <class AnyCharsAccess>
+[[nodiscard]] 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, char32_t* codePoint) {
+ MOZ_ASSERT(lead != EOF);
+ MOZ_ASSERT(!isAsciiCodePoint(lead),
+ "ASCII code unit/point must be handled separately");
+ MOZ_ASSERT(lead == this->sourceUnits.previousCodeUnit(),
+ "getNonAsciiCodePoint called incorrectly");
+
+ // The code point is usually |lead|: overwrite later if needed.
+ *codePoint = AssertedCast<char32_t>(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
+ // Assign to a sentinel value to hopefully cause errors.
+ *codePoint = std::numeric_limits<char32_t>::max();
+#endif
+ MOZ_MAKE_MEM_UNDEFINED(codePoint, sizeof(*codePoint));
+ return false;
+ }
+
+ *codePoint = '\n';
+ } else {
+ MOZ_ASSERT(!IsLineTerminator(*codePoint));
+ }
+
+ return true;
+ }
+
+ // Also handle a lead surrogate not paired with a trailing surrogate.
+ if (MOZ_UNLIKELY(
+ this->sourceUnits.atEnd() ||
+ !unicode::IsTrailSurrogate(this->sourceUnits.peekCodeUnit()))) {
+ MOZ_ASSERT(!IsLineTerminator(*codePoint));
+ return true;
+ }
+
+ // Otherwise we have a multi-unit code point.
+ *codePoint = unicode::UTF16Decode(lead, this->sourceUnits.getCodeUnit());
+ MOZ_ASSERT(!IsLineTerminator(*codePoint));
+ return true;
+}
+
+template <class AnyCharsAccess>
+bool TokenStreamChars<Utf8Unit, AnyCharsAccess>::getNonAsciiCodePoint(
+ int32_t unit, char32_t* codePoint) {
+ MOZ_ASSERT(unit != EOF);
+ MOZ_ASSERT(!isAsciiCodePoint(unit),
+ "ASCII code unit/point must be handled separately");
+
+ Utf8Unit lead = Utf8Unit(static_cast<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
+ // Assign to a sentinel value to hopefully cause errors.
+ *codePoint = std::numeric_limits<char32_t>::max();
+#endif
+ MOZ_MAKE_MEM_UNDEFINED(codePoint, sizeof(*codePoint));
+ return false;
+ }
+
+ *codePoint = '\n';
+ } else {
+ MOZ_ASSERT(!IsLineTerminator(cp));
+ *codePoint = cp;
+ }
+
+ return true;
+}
+
+template <>
+size_t SourceUnits<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) {
+ if (!getCodePoint()) {
+ return false;
+ }
+ }
+
+ TokenStreamAnyChars& anyChars = anyCharsAccess();
+ Token* cur = const_cast<Token*>(&anyChars.currentToken());
+ cur->pos.begin = this->sourceUnits.offset();
+ cur->pos.end = cur->pos.begin;
+#ifdef DEBUG
+ cur->type = TokenKind::Limit;
+#endif
+ MOZ_MAKE_MEM_UNDEFINED(&cur->type, sizeof(cur->type));
+ anyChars.lookahead = 0;
+ return true;
+}
+
+template <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) const {
+ err->isMuted = mutedErrors;
+ err->filename = filename_;
+ err->lineNumber = 0;
+ err->columnNumber = 0;
+
+ MOZ_ASSERT(err->lineOfContext == nullptr);
+}
+
+bool TokenStreamAnyChars::fillExceptingContext(ErrorMetadata* err,
+ uint32_t offset) const {
+ err->isMuted = mutedErrors;
+
+ // If this TokenStreamAnyChars doesn't have location information, try to
+ // get it from the caller.
+ if (!filename_) {
+ JSContext* maybeCx = context()->maybeCurrentJSContext();
+ if (maybeCx && !maybeCx->isHelperThreadContext()) {
+ NonBuiltinFrameIter iter(maybeCx,
+ FrameIter::FOLLOW_DEBUGGER_EVAL_PREV_LINK,
+ maybeCx->realm()->principals());
+ if (!iter.done() && iter.filename()) {
+ err->filename = iter.filename();
+ err->lineNumber = iter.computeLine(&err->columnNumber);
+ return false;
+ }
+ }
+ }
+
+ // Otherwise use this TokenStreamAnyChars's location information.
+ err->filename = filename_;
+ return true;
+}
+
+template <>
+inline void SourceUnits<char16_t>::computeWindowOffsetAndLength(
+ const char16_t* encodedWindow, size_t encodedTokenOffset,
+ size_t* utf16TokenOffset, size_t encodedWindowLength,
+ size_t* utf16WindowLength) const {
+ MOZ_ASSERT_UNREACHABLE("shouldn't need to recompute for UTF-16");
+}
+
+template <>
+inline void SourceUnits<Utf8Unit>::computeWindowOffsetAndLength(
+ const Utf8Unit* encodedWindow, size_t encodedTokenOffset,
+ size_t* utf16TokenOffset, size_t encodedWindowLength,
+ size_t* utf16WindowLength) const {
+ MOZ_ASSERT(encodedTokenOffset <= encodedWindowLength,
+ "token offset must be within the window, and the two lambda "
+ "calls below presume this ordering of values");
+
+ const Utf8Unit* const encodedWindowEnd = encodedWindow + encodedWindowLength;
+
+ size_t i = 0;
+ auto ComputeUtf16Count = [&i, &encodedWindow](const Utf8Unit* limit) {
+ while (encodedWindow < limit) {
+ Utf8Unit lead = *encodedWindow++;
+ if (MOZ_LIKELY(IsAscii(lead))) {
+ // ASCII contributes a single UTF-16 code unit.
+ i++;
+ continue;
+ }
+
+ Maybe<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) const {
+ // Rename the variable to make meaning clearer: an offset into source units
+ // in Unit encoding.
+ size_t encodedOffset = offset;
+
+ // These are also offsets into source units in Unit encoding.
+ size_t encodedWindowStart = sourceUnits.findWindowStart(encodedOffset);
+ size_t encodedWindowEnd = sourceUnits.findWindowEnd(encodedOffset);
+
+ size_t encodedWindowLength = encodedWindowEnd - encodedWindowStart;
+ MOZ_ASSERT(encodedWindowLength <= SourceUnits::WindowRadius * 2);
+
+ // Don't add a useless "line" of context when the window ends up empty
+ // because of an invalid encoding at the start of a line.
+ if (encodedWindowLength == 0) {
+ MOZ_ASSERT(err->lineOfContext == nullptr,
+ "ErrorMetadata::lineOfContext must be null so we don't "
+ "have to set the lineLength/tokenOffset fields");
+ return true;
+ }
+
+ CharBuffer lineOfContext(fc);
+
+ const Unit* encodedWindow = sourceUnits.codeUnitPtrAt(encodedWindowStart);
+ if (!FillCharBufferFromSourceNormalizingAsciiLineBreaks(
+ lineOfContext, encodedWindow, encodedWindow + encodedWindowLength)) {
+ return false;
+ }
+
+ size_t utf16WindowLength = lineOfContext.length();
+
+ // The windowed string is null-terminated.
+ if (!lineOfContext.append('\0')) {
+ return false;
+ }
+
+ err->lineOfContext.reset(lineOfContext.extractOrCopyRawBuffer());
+ if (!err->lineOfContext) {
+ return false;
+ }
+
+ size_t encodedTokenOffset = encodedOffset - encodedWindowStart;
+
+ MOZ_ASSERT(encodedTokenOffset <= encodedWindowLength,
+ "token offset must be inside the window");
+
+ // The length in UTF-8 code units of a code point is always greater than or
+ // equal to the same code point's length in UTF-16 code points. ASCII code
+ // points are 1 unit in either encoding. Code points in [U+0080, U+10000)
+ // are 2-3 UTF-8 code units to 1 UTF-16 code unit. And code points in
+ // [U+10000, U+10FFFF] are 4 UTF-8 code units to 2 UTF-16 code units.
+ //
+ // Therefore, if encoded window length equals the length in UTF-16 (this is
+ // always the case for Unit=char16_t), the UTF-16 offsets are exactly the
+ // encoded offsets. Otherwise we must convert offset/length from UTF-8 to
+ // UTF-16.
+ if constexpr (std::is_same_v<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) const {
+ 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().fc);
+ return;
+ }
+ error(JSMSG_ILLEGAL_CHARACTER, display.get());
+}
+
+// We have encountered a '\': check for a Unicode escape sequence after it.
+// Return the length of the escape sequence and the encoded code point (by
+// value) if we found a Unicode escape sequence, and skip all code units
+// involed. Otherwise, return 0 and don't advance along the buffer.
+template <typename Unit, class AnyCharsAccess>
+uint32_t GeneralTokenStreamChars<Unit, AnyCharsAccess>::matchUnicodeEscape(
+ char32_t* codePoint) {
+ MOZ_ASSERT(this->sourceUnits.previousCodeUnit() == Unit('\\'));
+
+ int32_t unit = getCodeUnit();
+ if (unit != 'u') {
+ // NOTE: |unit| may be EOF here.
+ ungetCodeUnit(unit);
+ MOZ_ASSERT(this->sourceUnits.previousCodeUnit() == Unit('\\'));
+ return 0;
+ }
+
+ char16_t v;
+ unit = getCodeUnit();
+ if (IsAsciiHexDigit(unit) && this->sourceUnits.matchHexDigits(3, &v)) {
+ *codePoint = (AsciiAlphanumericToNumber(unit) << 12) | v;
+ return 5;
+ }
+
+ if (unit == '{') {
+ return matchExtendedUnicodeEscape(codePoint);
+ }
+
+ // NOTE: |unit| may be EOF here, so this ungets either one or two units.
+ ungetCodeUnit(unit);
+ ungetCodeUnit('u');
+ MOZ_ASSERT(this->sourceUnits.previousCodeUnit() == Unit('\\'));
+ return 0;
+}
+
+template <typename Unit, class AnyCharsAccess>
+uint32_t
+GeneralTokenStreamChars<Unit, AnyCharsAccess>::matchExtendedUnicodeEscape(
+ char32_t* codePoint) {
+ MOZ_ASSERT(this->sourceUnits.previousCodeUnit() == Unit('{'));
+
+ int32_t unit = getCodeUnit();
+
+ // Skip leading zeroes.
+ uint32_t leadingZeroes = 0;
+ while (unit == '0') {
+ leadingZeroes++;
+ unit = getCodeUnit();
+ }
+
+ size_t i = 0;
+ uint32_t code = 0;
+ while (IsAsciiHexDigit(unit) && i < 6) {
+ code = (code << 4) | AsciiAlphanumericToNumber(unit);
+ unit = getCodeUnit();
+ i++;
+ }
+
+ uint32_t gotten =
+ 2 + // 'u{'
+ leadingZeroes + i + // significant hexdigits
+ (unit != EOF); // subtract a get if it didn't contribute to length
+
+ if (unit == '}' && (leadingZeroes > 0 || i > 0) &&
+ code <= unicode::NonBMPMax) {
+ *codePoint = code;
+ return gotten;
+ }
+
+ this->sourceUnits.unskipCodeUnits(gotten);
+ MOZ_ASSERT(this->sourceUnits.previousCodeUnit() == Unit('\\'));
+ return 0;
+}
+
+template <typename Unit, class AnyCharsAccess>
+uint32_t
+GeneralTokenStreamChars<Unit, AnyCharsAccess>::matchUnicodeEscapeIdStart(
+ char32_t* codePoint) {
+ uint32_t length = matchUnicodeEscape(codePoint);
+ if (MOZ_LIKELY(length > 0)) {
+ if (MOZ_LIKELY(unicode::IsIdentifierStart(*codePoint))) {
+ return length;
+ }
+
+ this->sourceUnits.unskipCodeUnits(length);
+ }
+
+ MOZ_ASSERT(this->sourceUnits.previousCodeUnit() == Unit('\\'));
+ return 0;
+}
+
+template <typename Unit, class AnyCharsAccess>
+bool GeneralTokenStreamChars<Unit, AnyCharsAccess>::matchUnicodeEscapeIdent(
+ char32_t* codePoint) {
+ uint32_t length = matchUnicodeEscape(codePoint);
+ if (MOZ_LIKELY(length > 0)) {
+ if (MOZ_LIKELY(unicode::IsIdentifierPart(*codePoint))) {
+ return true;
+ }
+
+ this->sourceUnits.unskipCodeUnits(length);
+ }
+
+ MOZ_ASSERT(this->sourceUnits.previousCodeUnit() == Unit('\\'));
+ return false;
+}
+
+template <typename Unit, class AnyCharsAccess>
+[[nodiscard]] bool
+TokenStreamSpecific<Unit, AnyCharsAccess>::matchIdentifierStart(
+ IdentifierEscapes* sawEscape) {
+ int32_t unit = getCodeUnit();
+ if (unit == EOF) {
+ error(JSMSG_MISSING_PRIVATE_NAME);
+ return false;
+ }
+
+ if (MOZ_LIKELY(isAsciiCodePoint(unit))) {
+ if (unicode::IsIdentifierStart(char16_t(unit))) {
+ *sawEscape = IdentifierEscapes::None;
+ return true;
+ }
+
+ if (unit == '\\') {
+ char32_t codePoint;
+ uint32_t escapeLength = matchUnicodeEscapeIdStart(&codePoint);
+ if (escapeLength != 0) {
+ *sawEscape = IdentifierEscapes::SawUnicodeEscape;
+ return true;
+ }
+
+ // We could point "into" a mistyped escape, e.g. for "\u{41H}" we
+ // could point at the 'H'. But we don't do that now, so the code
+ // unit after the '\' isn't necessarily bad, so just point at the
+ // start of the actually-invalid escape.
+ ungetCodeUnit('\\');
+ error(JSMSG_BAD_ESCAPE);
+ return false;
+ }
+ }
+
+ // Unget the lead code unit before peeking at the full code point.
+ ungetCodeUnit(unit);
+
+ PeekedCodePoint<Unit> peeked = this->sourceUnits.peekCodePoint();
+ if (!peeked.isNone() && unicode::IsIdentifierStart(peeked.codePoint())) {
+ this->sourceUnits.consumeKnownCodePoint(peeked);
+
+ *sawEscape = IdentifierEscapes::None;
+ return true;
+ }
+
+ error(JSMSG_MISSING_PRIVATE_NAME);
+ return false;
+}
+
+template <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;
+}
+
+[[nodiscard]] bool TokenStreamCharsShared::copyCharBufferTo(
+ UniquePtr<char16_t[], JS::FreePolicy>* destination) {
+ size_t length = charBuffer.length();
+
+ *destination = fc->getAllocator()->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>
+[[nodiscard]] 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(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, char32_t codePoint) {
+ MOZ_ASSERT(codePoint <= unicode::NonBMPMax,
+ "should only be processing code points validly decoded from UTF-8 "
+ "or WTF-16 source text (surrogate code points permitted)");
+
+ char16_t units[2];
+ unsigned numUnits = 0;
+ unicode::UTF16Encode(codePoint, units, &numUnits);
+
+ MOZ_ASSERT(numUnits == 1 || numUnits == 2,
+ "UTF-16 code points are only encoded in one or two units");
+
+ if (!charBuffer.append(units[0])) {
+ return false;
+ }
+
+ if (numUnits == 1) {
+ return true;
+ }
+
+ return charBuffer.append(units[1]);
+}
+
+template <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;
+ }
+
+ char32_t codePoint;
+ if (MOZ_LIKELY(isAsciiCodePoint(unit))) {
+ if (unicode::IsIdentifierPart(char16_t(unit)) || unit == '#') {
+ if (!this->charBuffer.append(unit)) {
+ return false;
+ }
+
+ continue;
+ }
+
+ if (unit != '\\' || !matchUnicodeEscapeIdent(&codePoint)) {
+ break;
+ }
+ } else {
+ // |restoreNextRawCharAddress| undoes all gets, and this function
+ // doesn't update line/column info.
+ char32_t cp;
+ if (!getNonAsciiCodePointDontNormalize(toUnit(unit), &cp)) {
+ return false;
+ }
+
+ codePoint = cp;
+ if (!unicode::IsIdentifierPart(codePoint)) {
+ break;
+ }
+ }
+
+ if (!AppendCodePointToCharBuffer(this->charBuffer, codePoint)) {
+ return false;
+ }
+ } while (true);
+
+ return true;
+}
+
+template <typename Unit, class AnyCharsAccess>
+[[nodiscard]] 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.
+ char32_t codePoint;
+ if (unit != '\\' || !matchUnicodeEscapeIdent(&codePoint)) {
+ ungetCodeUnit(unit);
+ break;
+ }
+
+ escaping = IdentifierEscapes::SawUnicodeEscape;
+ }
+ } else {
+ // This ignores encoding errors: subsequent caller-side code to
+ // handle source text after the IdentifierName will do so.
+ PeekedCodePoint<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);
+ }
+ }
+
+ TaggedParserAtomIndex atom;
+ if (MOZ_UNLIKELY(escaping == IdentifierEscapes::SawUnicodeEscape)) {
+ // Identifiers containing Unicode escapes have to be converted into
+ // tokenbuf before atomizing.
+ if (!putIdentInCharBuffer(identStart)) {
+ return false;
+ }
+
+ atom = drainCharBufferIntoAtom();
+ } else {
+ // Escape-free identifiers can be created directly from sourceUnits.
+ const Unit* chars = identStart;
+ size_t length = this->sourceUnits.addressOfNextCodeUnit() - identStart;
+
+ // Private identifiers start with a '#', and so cannot be reserved words.
+ if (visibility == NameVisibility::Public) {
+ // Represent reserved words lacking escapes as reserved word tokens.
+ if (const ReservedWordInfo* rw = FindReservedWord(chars, length)) {
+ noteBadToken.release();
+ newSimpleToken(rw->tokentype, start, modifier, out);
+ return true;
+ }
+ }
+
+ atom = atomizeSourceChars(Span(chars, length));
+ }
+ if (!atom) {
+ return false;
+ }
+
+ noteBadToken.release();
+ if (visibility == NameVisibility::Private) {
+ newPrivateNameToken(atom, start, modifier, out);
+ return true;
+ }
+ newNameToken(atom, start, modifier, out);
+ return true;
+}
+
+enum FirstCharKind {
+ // A char16_t has the 'OneChar' kind if it, by itself, constitutes a valid
+ // token that cannot also be a prefix of a longer token. E.g. ';' has the
+ // OneChar kind, but '+' does not, because '++' and '+=' are valid longer
+ // tokens
+ // that begin with '+'.
+ //
+ // The few token kinds satisfying these properties cover roughly 35--45%
+ // of the tokens seen in practice.
+ //
+ // We represent the 'OneChar' kind with any positive value less than
+ // TokenKind::Limit. This representation lets us associate
+ // each one-char token char16_t with a TokenKind and thus avoid
+ // a subsequent char16_t-to-TokenKind conversion.
+ OneChar_Min = 0,
+ OneChar_Max = size_t(TokenKind::Limit) - 1,
+
+ Space = size_t(TokenKind::Limit),
+ Ident,
+ Dec,
+ String,
+ EOL,
+ ZeroDigit,
+ Other,
+
+ LastCharKind = Other
+};
+
+// OneChar: 40, 41, 44, 58, 59, 91, 93, 123, 125, 126:
+// '(', ')', ',', ':', ';', '[', ']', '{', '}', '~'
+// Ident: 36, 65..90, 95, 97..122: '$', 'A'..'Z', '_', 'a'..'z'
+// Dot: 46: '.'
+// Equals: 61: '='
+// String: 34, 39, 96: '"', '\'', '`'
+// Dec: 49..57: '1'..'9'
+// Plus: 43: '+'
+// ZeroDigit: 48: '0'
+// Space: 9, 11, 12, 32: '\t', '\v', '\f', ' '
+// EOL: 10, 13: '\n', '\r'
+//
+#define T_COMMA size_t(TokenKind::Comma)
+#define T_COLON size_t(TokenKind::Colon)
+#define T_BITNOT size_t(TokenKind::BitNot)
+#define T_LP size_t(TokenKind::LeftParen)
+#define T_RP size_t(TokenKind::RightParen)
+#define T_SEMI size_t(TokenKind::Semi)
+#define T_LB size_t(TokenKind::LeftBracket)
+#define T_RB size_t(TokenKind::RightBracket)
+#define T_LC size_t(TokenKind::LeftCurly)
+#define T_RC size_t(TokenKind::RightCurly)
+#define _______ Other
+static const uint8_t firstCharKinds[] = {
+ // clang-format off
+/* 0 1 2 3 4 5 6 7 8 9 */
+/* 0+ */ _______, _______, _______, _______, _______, _______, _______, _______, _______, Space,
+/* 10+ */ EOL, Space, Space, EOL, _______, _______, _______, _______, _______, _______,
+/* 20+ */ _______, _______, _______, _______, _______, _______, _______, _______, _______, _______,
+/* 30+ */ _______, _______, Space, _______, String, _______, Ident, _______, _______, String,
+/* 40+ */ T_LP, T_RP, _______, _______, T_COMMA, _______, _______, _______,ZeroDigit, Dec,
+/* 50+ */ Dec, Dec, Dec, Dec, Dec, Dec, Dec, Dec, T_COLON, T_SEMI,
+/* 60+ */ _______, _______, _______, _______, _______, Ident, Ident, Ident, Ident, Ident,
+/* 70+ */ Ident, Ident, Ident, Ident, Ident, Ident, Ident, Ident, Ident, Ident,
+/* 80+ */ Ident, Ident, Ident, Ident, Ident, Ident, Ident, Ident, Ident, Ident,
+/* 90+ */ Ident, T_LB, _______, T_RB, _______, Ident, String, Ident, Ident, Ident,
+/* 100+ */ Ident, Ident, Ident, Ident, Ident, Ident, Ident, Ident, Ident, Ident,
+/* 110+ */ Ident, Ident, Ident, Ident, Ident, Ident, Ident, Ident, Ident, Ident,
+/* 120+ */ Ident, Ident, Ident, T_LC, _______, T_RC,T_BITNOT, _______
+ // clang-format on
+};
+#undef T_COMMA
+#undef T_COLON
+#undef T_BITNOT
+#undef T_LP
+#undef T_RP
+#undef T_SEMI
+#undef T_LB
+#undef T_RB
+#undef T_LC
+#undef T_RC
+#undef _______
+
+static_assert(LastCharKind < (1 << (sizeof(firstCharKinds[0]) * 8)),
+ "Elements of firstCharKinds[] are too small");
+
+template <>
+void SourceUnits<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>
+[[nodiscard]] 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>
+[[nodiscard]] 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 == '_') {
+ ungetCodeUnit(unit);
+ error(JSMSG_NUMBER_MULTIPLE_ADJACENT_UNDERSCORES);
+ } else {
+ ungetCodeUnit(unit);
+ ungetCodeUnit('_');
+ error(JSMSG_NUMBER_END_WITH_UNDERSCORE);
+ }
+ return false;
+ }
+ }
+
+ *nextUnit = unit;
+ return true;
+}
+
+template <typename Unit, class AnyCharsAccess>
+[[nodiscard]] 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(numStart, this->sourceUnits.addressOfNextCodeUnit(),
+ &dval)) {
+ ReportOutOfMemory(this->fc);
+ return false;
+ }
+ } else if (unit == 'n') {
+ isBigInt = true;
+ unit = peekCodeUnit();
+ } else {
+ // Consume any decimal dot and fractional component.
+ if (unit == '.') {
+ decimalPoint = HasDecimal;
+ if (!matchInteger(IsAsciiDigit, &unit)) {
+ return false;
+ }
+ }
+
+ // Consume any exponential notation.
+ if (unit == 'e' || unit == 'E') {
+ unit = getCodeUnit();
+ if (unit == '+' || unit == '-') {
+ unit = getCodeUnit();
+ }
+
+ // Exponential notation must contain at least one digit.
+ if (!IsAsciiDigit(unit)) {
+ ungetCodeUnit(unit);
+ error(JSMSG_MISSING_EXPONENT);
+ return false;
+ }
+
+ // Consume exponential digits.
+ if (!matchIntegerAfterFirstDigit(IsAsciiDigit, &unit)) {
+ return false;
+ }
+ }
+
+ ungetCodeUnit(unit);
+
+ if (!GetDecimal(numStart, this->sourceUnits.addressOfNextCodeUnit(),
+ &dval)) {
+ ReportOutOfMemory(this->fc);
+ return false;
+ }
+ }
+
+ // Number followed by IdentifierStart is an error. (This is the only place
+ // in ECMAScript where token boundary is inadequate to properly separate
+ // two tokens, necessitating this unaesthetic lookahead.)
+ if (unit != EOF) {
+ if (MOZ_LIKELY(isAsciiCodePoint(unit))) {
+ if (unicode::IsIdentifierStart(char16_t(unit))) {
+ error(JSMSG_IDSTART_AFTER_NUMBER);
+ return false;
+ }
+ } else {
+ // This ignores encoding errors: subsequent caller-side code to
+ // handle source text after the number will do so.
+ PeekedCodePoint<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>
+[[nodiscard]] 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 == 'd') {
+ flag = RegExpFlag::HasIndices;
+ } else if (unit == 'g') {
+ flag = RegExpFlag::Global;
+ } else if (unit == 'i') {
+ flag = RegExpFlag::IgnoreCase;
+ } else if (unit == 'm') {
+ flag = RegExpFlag::Multiline;
+ } else if (unit == 's') {
+ flag = RegExpFlag::DotAll;
+ } else if (unit == 'u') {
+ flag = RegExpFlag::Unicode;
+ } else if (unit == 'y') {
+ flag = RegExpFlag::Sticky;
+ } else if (IsAsciiAlpha(unit)) {
+ flag = RegExpFlag::NoFlags;
+ } else {
+ break;
+ }
+
+ if ((reflags & flag) || flag == RegExpFlag::NoFlags) {
+ ungetCodeUnit(unit);
+ char buf[2] = {char(unit), '\0'};
+ error(JSMSG_BAD_REGEXP_FLAG, buf);
+ return badToken();
+ }
+
+ reflags |= flag;
+ }
+ ungetCodeUnit(unit);
+
+ newRegExpToken(reflags, start, out);
+ return true;
+}
+
+template <typename Unit, class AnyCharsAccess>
+[[nodiscard]] 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>
+[[nodiscard]] 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()) {
+ MOZ_ALWAYS_FALSE(getCodePoint());
+ return badToken();
+ }
+
+ char32_t cp = peeked.codePoint();
+ if (unicode::IsSpace(cp)) {
+ this->sourceUnits.consumeKnownCodePoint(peeked);
+ if (IsLineTerminator(cp)) {
+ if (!updateLineInfoForEOL()) {
+ return badToken();
+ }
+
+ anyCharsAccess().updateFlagsForEOL();
+ }
+
+ continue;
+ }
+
+ static_assert(isAsciiCodePoint('$'),
+ "IdentifierStart contains '$', but as "
+ "!IsUnicodeIDStart('$'), ensure that '$' is never "
+ "handled here");
+ static_assert(isAsciiCodePoint('_'),
+ "IdentifierStart contains '_', but as "
+ "!IsUnicodeIDStart('_'), ensure that '_' is never "
+ "handled here");
+
+ if (MOZ_LIKELY(unicode::IsUnicodeIDStart(cp))) {
+ this->sourceUnits.consumeKnownCodePoint(peeked);
+ MOZ_ASSERT(!IsLineTerminator(cp),
+ "IdentifierStart must guarantee !IsLineTerminator "
+ "or else we'll fail to maintain line-info/flags "
+ "for EOL here");
+
+ return identifierName(start, identStart, IdentifierEscapes::None,
+ modifier, NameVisibility::Public, ttp);
+ }
+
+ reportIllegalCharacter(cp);
+ return badToken();
+ } // !isAsciiCodePoint(unit)
+
+ consumeKnownCodeUnit(unit);
+
+ // Get the token kind, based on the first char. The ordering of c1kind
+ // comparison is based on the frequency of tokens in real code:
+ // Parsemark (which represents typical JS code on the web) and the
+ // Unreal demo (which represents asm.js code).
+ //
+ // Parsemark Unreal
+ // OneChar 32.9% 39.7%
+ // Space 25.0% 0.6%
+ // Ident 19.2% 36.4%
+ // Dec 7.2% 5.1%
+ // String 7.9% 0.0%
+ // EOL 1.7% 0.0%
+ // ZeroDigit 0.4% 4.9%
+ // Other 5.7% 13.3%
+ //
+ // The ordering is based mostly only Parsemark frequencies, with Unreal
+ // frequencies used to break close categories (e.g. |Dec| and
+ // |String|). |Other| is biggish, but no other token kind is common
+ // enough for it to be worth adding extra values to FirstCharKind.
+ FirstCharKind c1kind = FirstCharKind(firstCharKinds[unit]);
+
+ // Look for an unambiguous single-char token.
+ //
+ if (c1kind <= OneChar_Max) {
+ TokenStart start(this->sourceUnits, -1);
+ newSimpleToken(TokenKind(c1kind), start, modifier, ttp);
+ return true;
+ }
+
+ // Skip over non-EOL whitespace chars.
+ //
+ if (c1kind == Space) {
+ continue;
+ }
+
+ // Look for an identifier.
+ //
+ if (c1kind == Ident) {
+ TokenStart start(this->sourceUnits, -1);
+ return identifierName(
+ start, this->sourceUnits.addressOfNextCodeUnit() - 1,
+ IdentifierEscapes::None, modifier, NameVisibility::Public, ttp);
+ }
+
+ // Look for a decimal number.
+ //
+ if (c1kind == Dec) {
+ TokenStart start(this->sourceUnits, -1);
+ const Unit* numStart = this->sourceUnits.addressOfNextCodeUnit() - 1;
+ return decimalNumber(unit, start, numStart, modifier, ttp);
+ }
+
+ // Look for a string or a template string.
+ //
+ if (c1kind == String) {
+ return getStringOrTemplateToken(static_cast<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)) {
+ // Reject octal literals that appear in strict mode code.
+ if (!strictModeError(JSMSG_DEPRECATED_OCTAL_LITERAL)) {
+ return badToken();
+ }
+
+ // The above test doesn't catch a few edge cases; see
+ // |GeneralParser::maybeParseDirective|. Record the violation so that
+ // that function can handle them.
+ anyCharsAccess().setSawDeprecatedOctalLiteral();
+
+ radix = 8;
+ // one past the '0'
+ numStart = this->sourceUnits.addressOfNextCodeUnit() - 1;
+
+ bool nonOctalDecimalIntegerLiteral = false;
+ do {
+ if (unit >= '8') {
+ nonOctalDecimalIntegerLiteral = true;
+ }
+ unit = getCodeUnit();
+ } while (IsAsciiDigit(unit));
+
+ if (unit == '_') {
+ ungetCodeUnit(unit);
+ error(JSMSG_SEPARATOR_IN_ZERO_PREFIXED_NUMBER);
+ return badToken();
+ }
+
+ if (unit == 'n') {
+ ungetCodeUnit(unit);
+ error(JSMSG_BIGINT_INVALID_SYNTAX);
+ return badToken();
+ }
+
+ if (nonOctalDecimalIntegerLiteral) {
+ // Use the decimal scanner for the rest of the number.
+ return decimalNumber(unit, start, numStart, modifier, ttp);
+ }
+ } else if (unit == '_') {
+ // Give a more explicit error message when '_' is used after '0'.
+ ungetCodeUnit(unit);
+ error(JSMSG_SEPARATOR_IN_ZERO_PREFIXED_NUMBER);
+ return badToken();
+ } else {
+ // '0' not followed by [XxBbOo0-9_]; scan as a decimal number.
+ ungetCodeUnit(unit);
+ numStart = this->sourceUnits.addressOfNextCodeUnit() - 1; // The '0'.
+ return decimalNumber('0', start, numStart, modifier, ttp);
+ }
+
+ if (unit == 'n') {
+ isBigInt = true;
+ unit = peekCodeUnit();
+ } else {
+ ungetCodeUnit(unit);
+ }
+
+ // Error if an identifier-start code point appears immediately
+ // after the number. Somewhat surprisingly, if we don't check
+ // here, we'll never check at all.
+ if (MOZ_LIKELY(isAsciiCodePoint(unit))) {
+ if (unicode::IsIdentifierStart(char16_t(unit))) {
+ error(JSMSG_IDSTART_AFTER_NUMBER);
+ return badToken();
+ }
+ } else if (MOZ_LIKELY(unit != EOF)) {
+ // This ignores encoding errors: subsequent caller-side code to
+ // handle source text after the number will do so.
+ PeekedCodePoint<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(numStart, this->sourceUnits.addressOfNextCodeUnit(),
+ radix, IntegerSeparatorHandling::SkipUnderscore,
+ &dval)) {
+ ReportOutOfMemory(this->fc);
+ return badToken();
+ }
+ newNumberToken(dval, NoDecimal, start, modifier, ttp);
+ return true;
+ }
+
+ MOZ_ASSERT(c1kind == Other);
+
+ // This handles everything else. Simple tokens distinguished solely by
+ // TokenKind should set |simpleKind| and break, to share simple-token
+ // creation code for all such tokens. All other tokens must be handled
+ // by returning (or by continuing from the loop enclosing this).
+ //
+ TokenStart start(this->sourceUnits, -1);
+ TokenKind simpleKind;
+#ifdef DEBUG
+ simpleKind = TokenKind::Limit; // sentinel value for code after switch
+#endif
+
+ // The block a ways above eliminated all non-ASCII, so cast to the
+ // smallest type possible to assist the C++ compiler.
+ switch (AssertedCast<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 '#': {
+#ifdef ENABLE_RECORD_TUPLE
+ if (matchCodeUnit('{')) {
+ simpleKind = TokenKind::HashCurly;
+ break;
+ }
+ if (matchCodeUnit('[')) {
+ simpleKind = TokenKind::HashBracket;
+ break;
+ }
+#endif
+
+ TokenStart start(this->sourceUnits, -1);
+ const Unit* identStart = this->sourceUnits.addressOfNextCodeUnit() - 1;
+ IdentifierEscapes sawEscape;
+ if (!matchIdentifierStart(&sawEscape)) {
+ return badToken();
+ }
+ return identifierName(start, identStart, sawEscape, modifier,
+ NameVisibility::Private, ttp);
+ }
+
+ case '=':
+ if (matchCodeUnit('=')) {
+ simpleKind = matchCodeUnit('=') ? TokenKind::StrictEq : TokenKind::Eq;
+ } else if (matchCodeUnit('>')) {
+ simpleKind = TokenKind::Arrow;
+ } else {
+ simpleKind = TokenKind::Assign;
+ }
+ break;
+
+ case '+':
+ if (matchCodeUnit('+')) {
+ simpleKind = TokenKind::Inc;
+ } else {
+ simpleKind =
+ matchCodeUnit('=') ? TokenKind::AddAssign : TokenKind::Add;
+ }
+ break;
+
+ case '\\': {
+ char32_t codePoint;
+ if (uint32_t escapeLength = matchUnicodeEscapeIdStart(&codePoint)) {
+ return identifierName(
+ start,
+ this->sourceUnits.addressOfNextCodeUnit() - escapeLength - 1,
+ IdentifierEscapes::SawUnicodeEscape, modifier,
+ NameVisibility::Public, ttp);
+ }
+
+ // We could point "into" a mistyped escape, e.g. for "\u{41H}" we
+ // could point at the 'H'. But we don't do that now, so the code
+ // unit after the '\' isn't necessarily bad, so just point at the
+ // start of the actually-invalid escape.
+ ungetCodeUnit('\\');
+ error(JSMSG_BAD_ESCAPE);
+ return badToken();
+ }
+
+ case '|':
+ if (matchCodeUnit('|')) {
+ simpleKind = matchCodeUnit('=') ? TokenKind::OrAssign : TokenKind::Or;
+ } else {
+ simpleKind =
+ matchCodeUnit('=') ? TokenKind::BitOrAssign : TokenKind::BitOr;
+ }
+ break;
+
+ case '^':
+ simpleKind =
+ matchCodeUnit('=') ? TokenKind::BitXorAssign : TokenKind::BitXor;
+ break;
+
+ case '&':
+ if (matchCodeUnit('&')) {
+ simpleKind =
+ matchCodeUnit('=') ? TokenKind::AndAssign : TokenKind::And;
+ } else {
+ simpleKind =
+ matchCodeUnit('=') ? TokenKind::BitAndAssign : TokenKind::BitAnd;
+ }
+ break;
+
+ case '?':
+ if (matchCodeUnit('.')) {
+ unit = getCodeUnit();
+ if (IsAsciiDigit(unit)) {
+ // if the code unit is followed by a number, for example it has the
+ // following form `<...> ?.5 <..> then it should be treated as a
+ // ternary rather than as an optional chain
+ simpleKind = TokenKind::Hook;
+ ungetCodeUnit(unit);
+ ungetCodeUnit('.');
+ } else {
+ ungetCodeUnit(unit);
+ simpleKind = TokenKind::OptionalChain;
+ }
+ } else if (matchCodeUnit('?')) {
+ simpleKind = matchCodeUnit('=') ? TokenKind::CoalesceAssign
+ : TokenKind::Coalesce;
+ } else {
+ simpleKind = TokenKind::Hook;
+ }
+ break;
+
+ case '!':
+ if (matchCodeUnit('=')) {
+ simpleKind = matchCodeUnit('=') ? TokenKind::StrictNe : TokenKind::Ne;
+ } else {
+ simpleKind = TokenKind::Not;
+ }
+ break;
+
+ case '<':
+ if (anyCharsAccess().options().allowHTMLComments) {
+ // Treat HTML begin-comment as comment-till-end-of-line.
+ if (matchCodeUnit('!')) {
+ if (matchCodeUnit('-')) {
+ if (matchCodeUnit('-')) {
+ this->sourceUnits.consumeRestOfSingleLineComment();
+ continue;
+ }
+ ungetCodeUnit('-');
+ }
+ ungetCodeUnit('!');
+ }
+ }
+ if (matchCodeUnit('<')) {
+ simpleKind =
+ matchCodeUnit('=') ? TokenKind::LshAssign : TokenKind::Lsh;
+ } else {
+ simpleKind = matchCodeUnit('=') ? TokenKind::Le : TokenKind::Lt;
+ }
+ break;
+
+ case '>':
+ if (matchCodeUnit('>')) {
+ if (matchCodeUnit('>')) {
+ simpleKind =
+ matchCodeUnit('=') ? TokenKind::UrshAssign : TokenKind::Ursh;
+ } else {
+ simpleKind =
+ matchCodeUnit('=') ? TokenKind::RshAssign : TokenKind::Rsh;
+ }
+ } else {
+ simpleKind = matchCodeUnit('=') ? TokenKind::Ge : TokenKind::Gt;
+ }
+ break;
+
+ case '*':
+ if (matchCodeUnit('*')) {
+ simpleKind =
+ matchCodeUnit('=') ? TokenKind::PowAssign : TokenKind::Pow;
+ } else {
+ simpleKind =
+ matchCodeUnit('=') ? TokenKind::MulAssign : TokenKind::Mul;
+ }
+ break;
+
+ case '/':
+ // Look for a single-line comment.
+ if (matchCodeUnit('/')) {
+ unit = getCodeUnit();
+ if (unit == '@' || unit == '#') {
+ bool shouldWarn = unit == '@';
+ if (!getDirectives(false, shouldWarn)) {
+ return false;
+ }
+ } else {
+ // NOTE: |unit| may be EOF here.
+ ungetCodeUnit(unit);
+ }
+
+ this->sourceUnits.consumeRestOfSingleLineComment();
+ continue;
+ }
+
+ // Look for a multi-line comment.
+ if (matchCodeUnit('*')) {
+ TokenStreamAnyChars& anyChars = anyCharsAccess();
+ unsigned linenoBefore = anyChars.lineno;
+
+ do {
+ int32_t unit = getCodeUnit();
+ if (unit == EOF) {
+ error(JSMSG_UNTERMINATED_COMMENT);
+ return badToken();
+ }
+
+ if (unit == '*' && matchCodeUnit('/')) {
+ break;
+ }
+
+ if (unit == '@' || unit == '#') {
+ bool shouldWarn = unit == '@';
+ if (!getDirectives(true, shouldWarn)) {
+ return badToken();
+ }
+ } else if (MOZ_LIKELY(isAsciiCodePoint(unit))) {
+ if (!getFullAsciiCodePoint(unit)) {
+ return badToken();
+ }
+ } else {
+ char32_t codePoint;
+ if (!getNonAsciiCodePoint(unit, &codePoint)) {
+ return badToken();
+ }
+ }
+ } while (true);
+
+ if (linenoBefore != anyChars.lineno) {
+ anyChars.updateFlagsForEOL();
+ }
+
+ continue;
+ }
+
+ // Look for a regexp.
+ if (modifier == SlashIsRegExp) {
+ return regexpLiteral(start, ttp);
+ }
+
+ simpleKind = matchCodeUnit('=') ? TokenKind::DivAssign : TokenKind::Div;
+ break;
+
+ case '%':
+ simpleKind = matchCodeUnit('=') ? TokenKind::ModAssign : TokenKind::Mod;
+ break;
+
+ case '-':
+ if (matchCodeUnit('-')) {
+ if (anyCharsAccess().options().allowHTMLComments &&
+ !anyCharsAccess().flags.isDirtyLine) {
+ if (matchCodeUnit('>')) {
+ this->sourceUnits.consumeRestOfSingleLineComment();
+ continue;
+ }
+ }
+
+ simpleKind = TokenKind::Dec;
+ } else {
+ simpleKind =
+ matchCodeUnit('=') ? TokenKind::SubAssign : TokenKind::Sub;
+ }
+ break;
+
+#ifdef ENABLE_DECORATORS
+ case '@':
+ simpleKind = TokenKind::At;
+ break;
+#endif
+
+ default:
+ // We consumed a bad ASCII code point/unit. Put it back so the
+ // error location is the bad code point.
+ ungetCodeUnit(unit);
+ reportIllegalCharacter(unit);
+ return badToken();
+ } // switch (AssertedCast<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))) {
+ char32_t codePoint;
+ if (!getNonAsciiCodePoint(unit, &codePoint)) {
+ return false;
+ }
+
+ // If we consumed U+2028 LINE SEPARATOR or U+2029 PARAGRAPH
+ // SEPARATOR, they'll be normalized to '\n'. '\' followed by
+ // LineContinuation represents no code points, so don't append
+ // in this case.
+ if (codePoint != '\n') {
+ if (!AppendCodePointToCharBuffer(this->charBuffer, codePoint)) {
+ return false;
+ }
+ }
+
+ continue;
+ }
+
+ // The block above eliminated all non-ASCII, so cast to the
+ // smallest type possible to assist the C++ compiler.
+ switch (AssertedCast<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)) {
+ // \8 or \9 in an untagged template literal is a syntax error,
+ // reported in GeneralParser::noSubstitutionUntaggedTemplate.
+ //
+ // Tagged template literals, however, may contain \8 and \9. The
+ // "cooked" representation of such a part will be |undefined|, and
+ // the "raw" representation will contain the literal characters.
+ //
+ // function f(parts) {
+ // assertEq(parts[0], undefined);
+ // assertEq(parts.raw[0], "\\8");
+ // return "composed";
+ // }
+ // assertEq(f`\8`, "composed");
+ if (unit == '8' || unit == '9') {
+ TokenStreamAnyChars& anyChars = anyCharsAccess();
+ if (parsingTemplate) {
+ anyChars.setInvalidTemplateEscape(
+ this->sourceUnits.offset() - 2,
+ InvalidEscapeType::EightOrNine);
+ continue;
+ }
+
+ // \8 and \9 are forbidden in string literals in strict mode code.
+ if (!strictModeError(JSMSG_DEPRECATED_EIGHT_OR_NINE_ESCAPE)) {
+ return false;
+ }
+
+ // The above test doesn't catch a few edge cases; see
+ // |GeneralParser::maybeParseDirective|. Record the violation so
+ // that that function can handle them.
+ anyChars.setSawDeprecatedEightOrNineEscape();
+ }
+ break;
+ }
+
+ // Octal character specification.
+ int32_t val = AsciiOctalToNumber(unit);
+
+ unit = peekCodeUnit();
+ if (MOZ_UNLIKELY(unit == EOF)) {
+ ReportPrematureEndOfLiteral(JSMSG_EOF_IN_ESCAPE_IN_LITERAL);
+ return false;
+ }
+
+ // Strict mode code allows only \0 followed by a non-digit.
+ if (val != 0 || IsAsciiDigit(unit)) {
+ TokenStreamAnyChars& anyChars = anyCharsAccess();
+ if (parsingTemplate) {
+ anyChars.setInvalidTemplateEscape(this->sourceUnits.offset() - 2,
+ InvalidEscapeType::Octal);
+ continue;
+ }
+
+ if (!strictModeError(JSMSG_DEPRECATED_OCTAL_ESCAPE)) {
+ return false;
+ }
+
+ // The above test doesn't catch a few edge cases; see
+ // |GeneralParser::maybeParseDirective|. Record the violation so
+ // that that function can handle them.
+ anyChars.setSawDeprecatedOctalEscape();
+ }
+
+ if (IsAsciiOctal(unit)) {
+ val = 8 * val + AsciiOctalToNumber(unit);
+ consumeKnownCodeUnit(unit);
+
+ unit = peekCodeUnit();
+ if (MOZ_UNLIKELY(unit == EOF)) {
+ ReportPrematureEndOfLiteral(JSMSG_EOF_IN_ESCAPE_IN_LITERAL);
+ return false;
+ }
+
+ if (IsAsciiOctal(unit)) {
+ int32_t save = val;
+ val = 8 * val + AsciiOctalToNumber(unit);
+ if (val <= 0xFF) {
+ consumeKnownCodeUnit(unit);
+ } else {
+ val = save;
+ }
+ }
+ }
+
+ unit = char16_t(val);
+ break;
+ } // default
+ } // switch (AssertedCast<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;
+ }
+ }
+
+ TaggedParserAtomIndex atom = drainCharBufferIntoAtom();
+ if (!atom) {
+ return false;
+ }
+
+ noteBadToken.release();
+
+ MOZ_ASSERT_IF(!parsingTemplate, !templateHead);
+
+ TokenKind kind = !parsingTemplate ? TokenKind::String
+ : templateHead ? TokenKind::TemplateHead
+ : TokenKind::NoSubsTemplate;
+ newAtomToken(kind, atom, start, modifier, out);
+ return true;
+}
+
+const char* TokenKindToDesc(TokenKind tt) {
+ switch (tt) {
+#define EMIT_CASE(name, desc) \
+ case TokenKind::name: \
+ return desc;
+ FOR_EACH_TOKEN_KIND(EMIT_CASE)
+#undef EMIT_CASE
+ case TokenKind::Limit:
+ MOZ_ASSERT_UNREACHABLE("TokenKind::Limit should not be passed.");
+ break;
+ }
+
+ return "<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
diff --git a/js/src/frontend/TokenStream.h b/js/src/frontend/TokenStream.h
new file mode 100644
index 0000000000..95a8651e05
--- /dev/null
+++ b/js/src/frontend/TokenStream.h
@@ -0,0 +1,2962 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Streaming access to the raw tokens of JavaScript source.
+ *
+ * Because JS tokenization is context-sensitive -- a '/' could be either a
+ * regular expression *or* a division operator depending on context -- the
+ * various token stream classes are mostly not useful outside of the Parser
+ * where they reside. We should probably eventually merge the two concepts.
+ */
+#ifndef frontend_TokenStream_h
+#define frontend_TokenStream_h
+
+/*
+ * [SMDOC] Parser Token Stream
+ *
+ * A token stream exposes the raw tokens -- operators, names, numbers,
+ * keywords, and so on -- of JavaScript source code.
+ *
+ * These are the components of the overall token stream concept:
+ * TokenStreamShared, TokenStreamAnyChars, TokenStreamCharsBase<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/Maybe.h"
+#include "mozilla/MemoryChecking.h"
+#include "mozilla/Span.h"
+#include "mozilla/TextUtils.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" // ParserAtom, ParserAtomsTable, TaggedParserAtomIndex
+#include "frontend/Token.h"
+#include "frontend/TokenKind.h"
+#include "js/CompileOptions.h"
+#include "js/friend/ErrorMessages.h" // JSMSG_*
+#include "js/HashTable.h" // js::HashMap
+#include "js/RegExpFlags.h" // JS::RegExpFlags
+#include "js/UniquePtr.h"
+#include "js/Vector.h"
+#include "util/Unicode.h"
+#include "vm/ErrorReporting.h"
+
+struct KeywordInfo;
+
+namespace js {
+
+class FrontendContext;
+
+namespace frontend {
+
+// Saturate column number at a limit that can be represented in various parts of
+// the engine. Source locations beyond this point will report at the limit
+// column instead.
+//
+// See:
+// - TokenStreamAnyChars::checkOptions
+// - ColSpan::isRepresentable
+// - WasmFrameIter::computeLine
+static constexpr uint32_t ColumnLimit = std::numeric_limits<int32_t>::max() / 2;
+
+// If `name` is reserved word, returns the TokenKind of it.
+// TokenKind::Limit otherwise.
+extern TokenKind ReservedWordTokenKind(TaggedParserAtomIndex name);
+
+// If `name` is reserved word, returns string representation of it.
+// nullptr otherwise.
+extern const char* ReservedWordToCharZ(TaggedParserAtomIndex name);
+
+// If `tt` is reserved word, returns string representation of it.
+// nullptr otherwise.
+extern const char* ReservedWordToCharZ(TokenKind tt);
+
+enum class DeprecatedContent : uint8_t {
+ // No deprecated content was present.
+ None = 0,
+ // Octal literal not prefixed by "0o" but rather by just "0", e.g. 0755.
+ OctalLiteral,
+ // Octal character escape, e.g. "hell\157 world".
+ OctalEscape,
+ // NonOctalDecimalEscape, i.e. "\8" or "\9".
+ EightOrNineEscape,
+};
+
+struct TokenStreamFlags {
+ // Hit end of file.
+ bool isEOF : 1;
+ // Non-whitespace since start of line.
+ bool isDirtyLine : 1;
+ // Hit a syntax error, at start or during a token.
+ bool hadError : 1;
+
+ // The nature of any deprecated content seen since last reset.
+ // We have to uint8_t instead DeprecatedContent to work around a GCC 7 bug.
+ // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=61414
+ uint8_t sawDeprecatedContent : 2;
+
+ TokenStreamFlags()
+ : isEOF(false),
+ isDirtyLine(false),
+ hadError(false),
+ sawDeprecatedContent(uint8_t(DeprecatedContent::None)) {}
+};
+
+template <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(FrontendContext* fc, uint32_t initialLineNumber,
+ uint32_t initialOffset);
+
+ [[nodiscard]] bool add(uint32_t lineNum, uint32_t lineStartOffset);
+ [[nodiscard]] bool fill(const SourceCoords& other);
+
+ bool isOnThisLine(uint32_t offset, uint32_t lineNum, bool* onThisLine) const {
+ uint32_t index = indexFromLineNumber(lineNum);
+ if (index + 1 >= lineStartOffsets_.length()) { // +1 due to sentinel
+ return false;
+ }
+ *onThisLine = lineStartOffsets_[index] <= offset &&
+ offset < lineStartOffsets_[index + 1];
+ return true;
+ }
+
+ /**
+ * A token, computed for an offset in source text, that can be used to
+ * access line number and line-offset information for that offset.
+ *
+ * LineToken *alone* exposes whether the corresponding offset is in the
+ * the first line of source (which may not be 1, depending on
+ * |initialLineNumber|), and whether it's in the same line as
+ * another LineToken.
+ */
+ class LineToken {
+ uint32_t index;
+#ifdef DEBUG
+ uint32_t offset_; // stored for consistency-of-use assertions
+#endif
+
+ friend class SourceCoords;
+
+ public:
+ LineToken(uint32_t index, uint32_t offset)
+ : index(index)
+#ifdef DEBUG
+ ,
+ offset_(offset)
+#endif
+ {
+ }
+
+ bool isFirstLine() const { return index == 0; }
+
+ bool isSameLine(LineToken other) const { return index == other.index; }
+
+ void assertConsistentOffset(uint32_t offset) const {
+ MOZ_ASSERT(offset_ == offset);
+ }
+ };
+
+ /**
+ * Compute a token usable to access information about the line at the
+ * given offset.
+ *
+ * The only information directly accessible in a token is whether it
+ * corresponds to the first line of source text (which may not be line
+ * 1, depending on the |initialLineNumber| value used to construct
+ * this). Use |lineNumber(LineToken)| to compute the actual line
+ * number (incorporating the contribution of |initialLineNumber|).
+ */
+ LineToken lineToken(uint32_t offset) const;
+
+ /** Compute the line number for the given token. */
+ uint32_t lineNumber(LineToken lineToken) const {
+ return lineNumberFromIndex(lineToken.index);
+ }
+
+ /** Return the offset of the start of the line for |lineToken|. */
+ uint32_t lineStart(LineToken lineToken) const {
+ MOZ_ASSERT(lineToken.index + 1 < lineStartOffsets_.length(),
+ "recorded line-start information must be available");
+ return lineStartOffsets_[lineToken.index];
+ }
+};
+
+enum class UnitsType : unsigned char {
+ PossiblyMultiUnit = 0,
+ GuaranteedSingleUnit = 1,
+};
+
+class ChunkInfo {
+ private:
+ // Store everything in |unsigned char|s so everything packs.
+ unsigned char column_[sizeof(uint32_t)];
+ unsigned char unitsType_;
+
+ public:
+ ChunkInfo(uint32_t col, UnitsType type)
+ : unitsType_(static_cast<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,
+ // NonOctalDecimalEscape - \8 or \9.
+ EightOrNine
+};
+
+class TokenStreamAnyChars : public TokenStreamShared {
+ private:
+ // Constant-at-construction fields.
+
+ FrontendContext* const fc;
+
+ /** Options used for parsing/tokenizing. */
+ const JS::ReadOnlyCompileOptions& options_;
+
+ /**
+ * Pointer used internally to test whether in strict mode. Use |strictMode()|
+ * instead of this field.
+ */
+ StrictModeGetter* const strictModeGetter_;
+
+ /** Input filename or null. */
+ const char* const filename_;
+
+ // Column number computation fields.
+
+ /**
+ * A map of (line number => sequence of the column numbers at
+ * |ColumnChunkLength|-unit boundaries rewound [if needed] to the nearest code
+ * point boundary). (|TokenStreamAnyChars::computePartialColumn| is the sole
+ * user of |ColumnChunkLength| and therefore contains its definition.)
+ *
+ * Entries appear in this map only when a column computation of sufficient
+ * distance is performed on a line -- and only when the column is beyond the
+ * first |ColumnChunkLength| units. Each line's vector is lazily filled as
+ * greater offsets require column computations.
+ */
+ mutable HashMap<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(FrontendContext* fc,
+ 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;
+ }
+
+ [[nodiscard]] bool checkOptions();
+
+ private:
+ TaggedParserAtomIndex reservedWordToPropertyName(TokenKind tt) const;
+
+ public:
+ TaggedParserAtomIndex currentName() const {
+ if (isCurrentTokenType(TokenKind::Name) ||
+ isCurrentTokenType(TokenKind::PrivateName)) {
+ return currentToken().name();
+ }
+
+ MOZ_ASSERT(TokenKindIsPossibleIdentifierName(currentToken().type));
+ return reservedWordToPropertyName(currentToken().type);
+ }
+
+ bool currentNameHasEscapes(ParserAtomsTable& parserAtoms) const {
+ if (isCurrentTokenType(TokenKind::Name) ||
+ isCurrentTokenType(TokenKind::PrivateName)) {
+ TokenPos pos = currentToken().pos;
+ return (pos.end - pos.begin) != parserAtoms.length(currentToken().name());
+ }
+
+ MOZ_ASSERT(TokenKindIsPossibleIdentifierName(currentToken().type));
+ return false;
+ }
+
+ bool isCurrentTokenAssignment() const {
+ return TokenKindIsAssignment(currentToken().type);
+ }
+
+ // Flag methods.
+ bool isEOF() const { return flags.isEOF; }
+ bool hadError() const { return flags.hadError; }
+
+ DeprecatedContent sawDeprecatedContent() const {
+ return static_cast<DeprecatedContent>(flags.sawDeprecatedContent);
+ }
+
+ private:
+ // Workaround GCC 7 sadness.
+ void setSawDeprecatedContent(DeprecatedContent content) {
+ flags.sawDeprecatedContent = static_cast<uint8_t>(content);
+ }
+
+ public:
+ void clearSawDeprecatedContent() {
+ setSawDeprecatedContent(DeprecatedContent::None);
+ }
+ void setSawDeprecatedOctalLiteral() {
+ setSawDeprecatedContent(DeprecatedContent::OctalLiteral);
+ }
+ void setSawDeprecatedOctalEscape() {
+ setSawDeprecatedContent(DeprecatedContent::OctalEscape);
+ }
+ void setSawDeprecatedEightOrNineEscape() {
+ setSawDeprecatedContent(DeprecatedContent::EightOrNineEscape);
+ }
+
+ bool hasInvalidTemplateEscape() const {
+ return invalidTemplateEscapeType != InvalidEscapeType::None;
+ }
+ void clearInvalidTemplateEscape() {
+ invalidTemplateEscapeType = InvalidEscapeType::None;
+ }
+
+ private:
+ // This is private because it should only be called by the tokenizer while
+ // tokenizing not by, for example, BytecodeEmitter.
+ bool strictMode() const {
+ return strictModeGetter_ && strictModeGetter_->strictMode();
+ }
+
+ void setInvalidTemplateEscape(uint32_t offset, InvalidEscapeType type) {
+ MOZ_ASSERT(type != InvalidEscapeType::None);
+ if (invalidTemplateEscapeType != InvalidEscapeType::None) {
+ return;
+ }
+ invalidTemplateEscapeOffset = offset;
+ invalidTemplateEscapeType = type;
+ }
+
+ public:
+ // Call this immediately after parsing an OrExpression to allow scanning the
+ // next token with SlashIsRegExp without asserting (even though we just
+ // peeked at it in SlashIsDiv mode).
+ //
+ // It's OK to disable the assertion because the places where this is called
+ // have peeked at the next token in SlashIsDiv mode, and checked that it is
+ // *not* a Div token.
+ //
+ // To see why it is necessary to disable the assertion, consider these two
+ // programs:
+ //
+ // x = arg => q // per spec, this is all one statement, and the
+ // /a/g; // slashes are division operators
+ //
+ // x = arg => {} // per spec, ASI at the end of this line
+ // /a/g; // and that's a regexp literal
+ //
+ // The first program shows why orExpr() has use SlashIsDiv mode when peeking
+ // ahead for the next operator after parsing `q`. The second program shows
+ // why matchOrInsertSemicolon() must use SlashIsRegExp mode when scanning
+ // ahead for a semicolon.
+ void allowGettingNextTokenWithSlashIsRegExp() {
+#ifdef DEBUG
+ // Check the precondition: Caller already peeked ahead at the next token,
+ // in SlashIsDiv mode, and it is *not* a Div token.
+ MOZ_ASSERT(hasLookahead());
+ const Token& next = nextToken();
+ MOZ_ASSERT(next.modifier == SlashIsDiv);
+ MOZ_ASSERT(next.type != TokenKind::Div);
+ tokens[nextCursor()].modifier = SlashIsRegExp;
+#endif
+ }
+
+#ifdef DEBUG
+ inline bool debugHasNoLookahead() const { return lookahead == 0; }
+#endif
+
+ bool hasDisplayURL() const { return displayURL_ != nullptr; }
+
+ char16_t* displayURL() { return displayURL_.get(); }
+
+ bool hasSourceMapURL() const { return sourceMapURL_ != nullptr; }
+
+ char16_t* sourceMapURL() { return sourceMapURL_.get(); }
+
+ FrontendContext* context() const { return fc; }
+
+ using LineToken = SourceCoords::LineToken;
+
+ LineToken lineToken(uint32_t offset) const {
+ return srcCoords.lineToken(offset);
+ }
+
+ uint32_t lineNumber(LineToken lineToken) const {
+ return srcCoords.lineNumber(lineToken);
+ }
+
+ uint32_t lineStart(LineToken lineToken) const {
+ return srcCoords.lineStart(lineToken);
+ }
+
+ /**
+ * Fill in |err|.
+ *
+ * If the token stream doesn't have location info for this error, use the
+ * caller's location (including line/column number) and return false. (No
+ * line of context is set.)
+ *
+ * Otherwise fill in everything in |err| except 1) line/column numbers and
+ * 2) line-of-context-related fields and return true. The caller *must*
+ * fill in the line/column number; filling the line of context is optional.
+ */
+ bool fillExceptingContext(ErrorMetadata* err, uint32_t offset) const;
+
+ MOZ_ALWAYS_INLINE void updateFlagsForEOL() { flags.isDirtyLine = false; }
+
+ private:
+ /**
+ * Compute the "partial" column number in Unicode code points of the absolute
+ * |offset| within source text on the line of |lineToken| (which must have
+ * been computed from |offset|).
+ *
+ * A partial column number on a line that isn't the first line is just the
+ * actual column number. But a partial column number on the first line is the
+ * column number *ignoring the initial line/column of the script*. For
+ * example, consider this HTML with line/column number keys:
+ *
+ * 1 2 3
+ * 0123456789012345678901234 567890
+ * ------------------------------------
+ * 1 | <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|.
+ */
+ [[nodiscard]] MOZ_ALWAYS_INLINE bool internalUpdateLineInfoForEOL(
+ uint32_t lineStartOffset);
+
+ public:
+ const Token& nextToken() const {
+ MOZ_ASSERT(hasLookahead());
+ return tokens[nextCursor()];
+ }
+
+ bool hasLookahead() const { return lookahead > 0; }
+
+ void advanceCursor() { cursor_ = (cursor_ + 1) & ntokensMask; }
+
+ void retractCursor() { cursor_ = (cursor_ - 1) & ntokensMask; }
+
+ Token* allocateToken() {
+ advanceCursor();
+
+ Token* tp = &tokens[cursor()];
+ MOZ_MAKE_MEM_UNDEFINED(tp, sizeof(*tp));
+
+ return tp;
+ }
+
+ // Push the last scanned token back into the stream.
+ void ungetToken() {
+ MOZ_ASSERT(lookahead < maxLookahead);
+ lookahead++;
+ retractCursor();
+ }
+
+ public:
+ void adoptState(TokenStreamAnyChars& other) {
+ // If |other| has fresh information from directives, overwrite any
+ // previously recorded directives. (There is no specification directing
+ // that last-in-source-order directive controls, sadly. We behave this way
+ // in the ordinary case, so we ought do so here too.)
+ if (auto& url = other.displayURL_) {
+ displayURL_ = std::move(url);
+ }
+ if (auto& url = other.sourceMapURL_) {
+ sourceMapURL_ = std::move(url);
+ }
+ }
+
+ // Compute error metadata for an error at no offset.
+ void computeErrorMetadataNoOffset(ErrorMetadata* err) const;
+
+ // ErrorReporter API Helpers
+
+ // Provide minimal set of error reporting API given we cannot use
+ // ErrorReportMixin here. "report" prefix is added to avoid conflict with
+ // ErrorReportMixin methods in TokenStream class.
+ void reportErrorNoOffset(unsigned errorNumber, ...) const;
+ void reportErrorNoOffsetVA(unsigned errorNumber, va_list* args) const;
+
+ const JS::ReadOnlyCompileOptions& options() const { return options_; }
+
+ const char* getFilename() const { return filename_; }
+};
+
+constexpr char16_t CodeUnitValue(char16_t unit) { return unit; }
+
+constexpr uint8_t CodeUnitValue(mozilla::Utf8Unit unit) {
+ return unit.toUint8();
+}
+
+template <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);
+ }
+
+ MOZ_ALWAYS_INLINE Unit getCodeUnit() {
+ return *ptr++; // this will nullptr-crash if poisoned
+ }
+
+ Unit peekCodeUnit() const {
+ return *ptr; // this will nullptr-crash if poisoned
+ }
+
+ /**
+ * Determine the next code point in source text. The code point is not
+ * normalized: '\r', '\n', '\u2028', and '\u2029' are returned literally.
+ * If there is no next code point because |atEnd()|, or if an encoding
+ * error is encountered, return a |PeekedCodePoint| that |isNone()|.
+ *
+ * This function does not report errors: code that attempts to get the next
+ * code point must report any error.
+ *
+ * If a next code point is found, it may be consumed by passing it to
+ * |consumeKnownCodePoint|.
+ */
+ PeekedCodePoint<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 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) const;
+};
+
+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.
+ */
+[[nodiscard]] extern bool AppendCodePointToCharBuffer(CharBuffer& charBuffer,
+ char32_t codePoint);
+
+/**
+ * Accumulate the range of UTF-16 text (lone surrogates permitted, because JS
+ * allows them in source text) into |charBuffer|. Normalize '\r', '\n', and
+ * "\r\n" into '\n'.
+ */
+[[nodiscard]] extern bool FillCharBufferFromSourceNormalizingAsciiLineBreaks(
+ CharBuffer& charBuffer, const char16_t* cur, const char16_t* end);
+
+/**
+ * Accumulate the range of previously-validated UTF-8 text into |charBuffer|.
+ * Normalize '\r', '\n', and "\r\n" into '\n'.
+ */
+[[nodiscard]] extern bool FillCharBufferFromSourceNormalizingAsciiLineBreaks(
+ CharBuffer& charBuffer, const mozilla::Utf8Unit* cur,
+ const mozilla::Utf8Unit* end);
+
+class TokenStreamCharsShared {
+ protected:
+ FrontendContext* fc;
+
+ /**
+ * Buffer transiently used to store sequences of identifier or string code
+ * points when such can't be directly processed from the original source
+ * text (e.g. because it contains escapes).
+ */
+ CharBuffer charBuffer;
+
+ /** Information for parsing with a lifetime longer than the parser itself. */
+ ParserAtomsTable* parserAtoms;
+
+ protected:
+ explicit TokenStreamCharsShared(FrontendContext* fc,
+ ParserAtomsTable* parserAtoms)
+ : fc(fc), charBuffer(fc), parserAtoms(parserAtoms) {}
+
+ [[nodiscard]] bool copyCharBufferTo(
+ UniquePtr<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.)
+ */
+ [[nodiscard]] static constexpr MOZ_ALWAYS_INLINE bool isAsciiCodePoint(
+ int32_t unit) {
+ return mozilla::IsAscii(static_cast<char32_t>(unit));
+ }
+
+ TaggedParserAtomIndex drainCharBufferIntoAtom() {
+ // Add to parser atoms table.
+ auto atom = this->parserAtoms->internChar16(fc, charBuffer.begin(),
+ charBuffer.length());
+ charBuffer.clear();
+ return atom;
+ }
+
+ protected:
+ void adoptState(TokenStreamCharsShared& other) {
+ // The other stream's buffer may contain information for a
+ // gotten-then-ungotten token, that we must transfer into this stream so
+ // that token's final get behaves as desired.
+ charBuffer = std::move(other.charBuffer);
+ }
+
+ public:
+ CharBuffer& getCharBuffer() { return charBuffer; }
+};
+
+template <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(FrontendContext* fc, ParserAtomsTable* parserAtoms,
+ const Unit* units, size_t length, size_t startOffset);
+
+ /**
+ * Convert a non-EOF code unit returned by |getCodeUnit()| or
+ * |peekCodeUnit()| to a Unit code unit.
+ */
+ inline Unit toUnit(int32_t codeUnitValue);
+
+ void ungetCodeUnit(int32_t c) {
+ if (c == EOF) {
+ MOZ_ASSERT(sourceUnits.atEnd());
+ return;
+ }
+
+ MOZ_ASSERT(sourceUnits.previousCodeUnit() == toUnit(c));
+ sourceUnits.ungetCodeUnit();
+ }
+
+ MOZ_ALWAYS_INLINE TaggedParserAtomIndex
+ atomizeSourceChars(mozilla::Span<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.
+ */
+ MOZ_NEVER_INLINE 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.
+ */
+ [[nodiscard]] bool addLineOfContext(ErrorMetadata* err,
+ uint32_t offset) const;
+};
+
+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 TaggedParserAtomIndex
+TokenStreamCharsBase<char16_t>::atomizeSourceChars(
+ mozilla::Span<const char16_t> units) {
+ return this->parserAtoms->internChar16(fc, units.data(), units.size());
+}
+
+template <>
+/* static */ MOZ_ALWAYS_INLINE TaggedParserAtomIndex
+TokenStreamCharsBase<mozilla::Utf8Unit>::atomizeSourceChars(
+ mozilla::Span<const mozilla::Utf8Unit> units) {
+ return this->parserAtoms->internUtf8(fc, 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(char32_t* codePoint);
+ uint32_t matchExtendedUnicodeEscape(char32_t* codePoint);
+
+ protected:
+ using CharsBase::addLineOfContext;
+ using CharsBase::matchCodeUnit;
+ using CharsBase::matchLineTerminator;
+ using TokenStreamCharsShared::drainCharBufferIntoAtom;
+ using TokenStreamCharsShared::isAsciiCodePoint;
+ // Deliberately don't |using CharsBase::sourceUnits| because of bug 1472569.
+ // :-(
+ using CharsBase::toUnit;
+
+ using typename CharsBase::SourceUnits;
+
+ protected:
+ using SpecializedCharsBase::SpecializedCharsBase;
+
+ TokenStreamAnyChars& anyCharsAccess() {
+ return AnyCharsAccess::anyChars(this);
+ }
+
+ const TokenStreamAnyChars& anyCharsAccess() const {
+ return AnyCharsAccess::anyChars(this);
+ }
+
+ using TokenStreamSpecific =
+ frontend::TokenStreamSpecific<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.
+ */
+ [[nodiscard]] bool fillExceptingContext(ErrorMetadata* err,
+ uint32_t offset) const {
+ if (anyCharsAccess().fillExceptingContext(err, offset)) {
+ computeLineAndColumn(offset, &err->lineNumber, &err->columnNumber);
+ return true;
+ }
+ return false;
+ }
+
+ void newSimpleToken(TokenKind kind, TokenStart start,
+ TokenStreamShared::Modifier modifier, TokenKind* out) {
+ newToken(kind, start, modifier, out);
+ }
+
+ void newNumberToken(double dval, DecimalPoint decimalPoint, TokenStart start,
+ TokenStreamShared::Modifier modifier, TokenKind* out) {
+ Token* token = newToken(TokenKind::Number, start, modifier, out);
+ token->setNumber(dval, decimalPoint);
+ }
+
+ void newBigIntToken(TokenStart start, TokenStreamShared::Modifier modifier,
+ TokenKind* out) {
+ newToken(TokenKind::BigInt, start, modifier, out);
+ }
+
+ void newAtomToken(TokenKind kind, TaggedParserAtomIndex atom,
+ TokenStart start, TokenStreamShared::Modifier modifier,
+ TokenKind* out) {
+ MOZ_ASSERT(kind == TokenKind::String || kind == TokenKind::TemplateHead ||
+ kind == TokenKind::NoSubsTemplate);
+
+ Token* token = newToken(kind, start, modifier, out);
+ token->setAtom(atom);
+ }
+
+ void newNameToken(TaggedParserAtomIndex name, TokenStart start,
+ TokenStreamShared::Modifier modifier, TokenKind* out) {
+ Token* token = newToken(TokenKind::Name, start, modifier, out);
+ token->setName(name);
+ }
+
+ void newPrivateNameToken(TaggedParserAtomIndex name, TokenStart start,
+ TokenStreamShared::Modifier modifier,
+ TokenKind* out) {
+ Token* token = newToken(TokenKind::PrivateName, start, modifier, out);
+ token->setName(name);
+ }
+
+ void newRegExpToken(JS::RegExpFlags reflags, TokenStart start,
+ TokenKind* out) {
+ Token* token = newToken(TokenKind::RegExp, start,
+ TokenStreamShared::SlashIsRegExp, out);
+ token->setRegExpFlags(reflags);
+ }
+
+ MOZ_COLD bool badToken();
+
+ /**
+ * Get the next code unit -- the next numeric sub-unit of source text,
+ * possibly smaller than a full code point -- without updating line/column
+ * counters or consuming LineTerminatorSequences.
+ *
+ * Because of these limitations, only use this if (a) the resulting code
+ * unit is guaranteed to be ungotten (by ungetCodeUnit()) if it's an EOL,
+ * and (b) the line-related state (lineno, linebase) is not used before
+ * it's ungotten.
+ */
+ int32_t getCodeUnit() {
+ if (MOZ_LIKELY(!this->sourceUnits.atEnd())) {
+ return CodeUnitValue(this->sourceUnits.getCodeUnit());
+ }
+
+ anyCharsAccess().flags.isEOF = true;
+ return EOF;
+ }
+
+ void ungetCodeUnit(int32_t c) {
+ MOZ_ASSERT_IF(c == EOF, anyCharsAccess().flags.isEOF);
+
+ CharsBase::ungetCodeUnit(c);
+ }
+
+ /**
+ * Given a just-consumed ASCII code unit/point |lead|, consume a full code
+ * point or LineTerminatorSequence (normalizing it to '\n'). Return true on
+ * success, otherwise return false.
+ *
+ * If a LineTerminatorSequence was consumed, also update line/column info.
+ *
+ * This may change the current |sourceUnits| offset.
+ */
+ [[nodiscard]] MOZ_ALWAYS_INLINE bool getFullAsciiCodePoint(int32_t lead) {
+ MOZ_ASSERT(isAsciiCodePoint(lead),
+ "non-ASCII code units must be handled separately");
+ MOZ_ASSERT(toUnit(lead) == this->sourceUnits.previousCodeUnit(),
+ "getFullAsciiCodePoint called incorrectly");
+
+ if (MOZ_UNLIKELY(lead == '\r')) {
+ matchLineTerminator('\n');
+ } else if (MOZ_LIKELY(lead != '\n')) {
+ return true;
+ }
+ return updateLineInfoForEOL();
+ }
+
+ [[nodiscard]] MOZ_NEVER_INLINE bool updateLineInfoForEOL() {
+ return anyCharsAccess().internalUpdateLineInfoForEOL(
+ this->sourceUnits.offset());
+ }
+
+ uint32_t matchUnicodeEscapeIdStart(char32_t* codePoint);
+ bool matchUnicodeEscapeIdent(char32_t* codePoint);
+ bool matchIdentifierStart();
+
+ /**
+ * If possible, compute a line of context for an otherwise-filled-in |err|
+ * at the given offset in this token stream.
+ *
+ * This function is very-internal: almost certainly you should use one of
+ * its callers instead. It basically exists only to make those callers
+ * more readable.
+ */
+ [[nodiscard]] bool internalComputeLineOfContext(ErrorMetadata* err,
+ uint32_t offset) const {
+ // We only have line-start information for the current line. If the error
+ // is on a different line, we can't easily provide context. (This means
+ // any error in a multi-line token, e.g. an unterminated multiline string
+ // literal, won't have context.)
+ if (err->lineNumber != anyCharsAccess().lineno) {
+ return true;
+ }
+
+ return addLineOfContext(err, offset);
+ }
+
+ public:
+ /**
+ * Consume any hashbang comment at the start of a Script or Module, if one is
+ * present. Stops consuming just before any terminating LineTerminator or
+ * before an encoding error is encountered.
+ */
+ void consumeOptionalHashbangComment();
+
+ TaggedParserAtomIndex getRawTemplateStringAtom() {
+ TokenStreamAnyChars& anyChars = anyCharsAccess();
+
+ MOZ_ASSERT(anyChars.currentToken().type == TokenKind::TemplateHead ||
+ anyChars.currentToken().type == TokenKind::NoSubsTemplate);
+ const Unit* cur =
+ this->sourceUnits.codeUnitPtrAt(anyChars.currentToken().pos.begin + 1);
+ const Unit* end;
+ if (anyChars.currentToken().type == TokenKind::TemplateHead) {
+ // Of the form |`...${| or |}...${|
+ end =
+ this->sourceUnits.codeUnitPtrAt(anyChars.currentToken().pos.end - 2);
+ } else {
+ // NO_SUBS_TEMPLATE is of the form |`...`| or |}...`|
+ end =
+ this->sourceUnits.codeUnitPtrAt(anyChars.currentToken().pos.end - 1);
+ }
+
+ // |charBuffer| should be empty here, but we may as well code defensively.
+ MOZ_ASSERT(this->charBuffer.length() == 0);
+ this->charBuffer.clear();
+
+ // Template literals normalize only '\r' and "\r\n" to '\n'; Unicode
+ // separators don't need special handling.
+ // https://tc39.github.io/ecma262/#sec-static-semantics-tv-and-trv
+ if (!FillCharBufferFromSourceNormalizingAsciiLineBreaks(this->charBuffer,
+ cur, end)) {
+ return TaggedParserAtomIndex::null();
+ }
+
+ return drainCharBufferIntoAtom();
+ }
+};
+
+template <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.
+ */
+ [[nodiscard]] bool getNonAsciiCodePointDontNormalize(char16_t lead,
+ char32_t* codePoint) {
+ // There are no encoding errors in 16-bit JS, so implement this so that
+ // the compiler knows it, too.
+ *codePoint = infallibleGetNonAsciiCodePointDontNormalize(lead);
+ return true;
+ }
+
+ /**
+ * Given a just-consumed non-ASCII code unit |lead| (which may also be a
+ * full code point, for UTF-16), consume a full code point or
+ * LineTerminatorSequence (normalizing it to '\n') and store it in
+ * |*codePoint|. Return true on success, otherwise return false and leave
+ * |*codePoint| undefined on failure.
+ *
+ * If a LineTerminatorSequence was consumed, also update line/column info.
+ *
+ * This may change the current |sourceUnits| offset.
+ */
+ [[nodiscard]] bool getNonAsciiCodePoint(int32_t lead, char32_t* codePoint);
+};
+
+template <class 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(char32_t codePoint,
+ uint8_t codePointLength,
+ const char* reason);
+
+ /**
+ * Report an error for UTF-8 that encodes a UTF-16 surrogate or a number
+ * outside the Unicode range.
+ */
+ MOZ_COLD void badCodePoint(char32_t codePoint, uint8_t codePointLength) {
+ MOZ_ASSERT(unicode::IsSurrogate(codePoint) ||
+ codePoint > unicode::NonBMPMax);
+
+ badStructurallyValidCodePoint(codePoint, codePointLength,
+ unicode::IsSurrogate(codePoint)
+ ? "it's a UTF-16 surrogate"
+ : "the maximum code point is U+10FFFF");
+ }
+
+ /**
+ * Report an error for UTF-8 that encodes a code point not in its shortest
+ * form.
+ */
+ MOZ_COLD void notShortestForm(char32_t codePoint, uint8_t codePointLength) {
+ MOZ_ASSERT(!unicode::IsSurrogate(codePoint));
+ MOZ_ASSERT(codePoint <= unicode::NonBMPMax);
+
+ badStructurallyValidCodePoint(
+ codePoint, codePointLength,
+ "it wasn't encoded in shortest possible form");
+ }
+
+ protected:
+ using GeneralCharsBase::GeneralCharsBase;
+
+ /**
+ * Given the non-ASCII |lead| code unit just consumed, consume the rest of
+ * a non-ASCII code point. The code point is not normalized: on success
+ * |*codePoint| may be U+2028 LINE SEPARATOR or U+2029 PARAGRAPH SEPARATOR.
+ *
+ * Report an error if an invalid code point is encountered.
+ */
+ [[nodiscard]] bool getNonAsciiCodePointDontNormalize(mozilla::Utf8Unit lead,
+ char32_t* codePoint);
+
+ /**
+ * Given a just-consumed non-ASCII code unit |lead|, consume a full code
+ * point or LineTerminatorSequence (normalizing it to '\n') and store it in
+ * |*codePoint|. Return true on success, otherwise return false and leave
+ * |*codePoint| undefined on failure.
+ *
+ * If a LineTerminatorSequence was consumed, also update line/column info.
+ *
+ * This function will change the current |sourceUnits| offset.
+ */
+ [[nodiscard]] bool getNonAsciiCodePoint(int32_t lead, char32_t* codePoint);
+};
+
+// TokenStream is the lexical scanner for JavaScript source text.
+//
+// It takes a buffer of Unit code units (currently only char16_t encoding
+// UTF-16, but we're adding either UTF-8 or Latin-1 single-byte text soon) and
+// linearly scans it into |Token|s.
+//
+// Internally the class uses a four element circular buffer |tokens| of
+// |Token|s. As an index for |tokens|, the member |cursor_| points to the
+// current token. Calls to getToken() increase |cursor_| by one and return the
+// new current token. If a TokenStream was just created, the current token is
+// uninitialized. It's therefore important that one of the first four member
+// functions listed below is called first. The circular buffer lets us go back
+// up to two tokens from the last scanned token. Internally, the relative
+// number of backward steps that were taken (via ungetToken()) after the last
+// token was scanned is stored in |lookahead|.
+//
+// The following table lists in which situations it is safe to call each listed
+// function. No checks are made by the functions in non-debug builds.
+//
+// Function Name | Precondition; changes to |lookahead|
+// ------------------+---------------------------------------------------------
+// getToken | none; if |lookahead > 0| then |lookahead--|
+// peekToken | none; if |lookahead == 0| then |lookahead == 1|
+// peekTokenSameLine | none; if |lookahead == 0| then |lookahead == 1|
+// matchToken | none; if |lookahead > 0| and the match succeeds then
+// | |lookahead--|
+// consumeKnownToken | none; if |lookahead > 0| then |lookahead--|
+// ungetToken | 0 <= |lookahead| <= |maxLookahead - 1|; |lookahead++|
+//
+// The behavior of the token scanning process (see getTokenInternal()) can be
+// modified by calling one of the first four above listed member functions with
+// an optional argument of type Modifier. However, the modifier will be
+// ignored unless |lookahead == 0| holds. Due to constraints of the grammar,
+// this turns out not to be a problem in practice. See the
+// mozilla.dev.tech.js-engine.internals thread entitled 'Bug in the scanner?'
+// for more details:
+// https://groups.google.com/forum/?fromgroups=#!topic/mozilla.dev.tech.js-engine.internals/2JLH5jRcr7E).
+//
+// The method seek() allows rescanning from a previously visited location of
+// the buffer, initially computed by constructing a Position local variable.
+//
+template <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(FrontendContext* fc, ParserAtomsTable* parserAtoms,
+ const JS::ReadOnlyCompileOptions& options,
+ const Unit* units, size_t length);
+
+ /**
+ * Get the next code point, converting LineTerminatorSequences to '\n' and
+ * updating internal line-counter state if needed. Return true on success.
+ * Return false on failure.
+ */
+ [[nodiscard]] MOZ_ALWAYS_INLINE bool getCodePoint() {
+ int32_t unit = getCodeUnit();
+ if (MOZ_UNLIKELY(unit == EOF)) {
+ MOZ_ASSERT(anyCharsAccess().flags.isEOF,
+ "flags.isEOF should have been set by getCodeUnit()");
+ return true;
+ }
+
+ if (isAsciiCodePoint(unit)) {
+ return getFullAsciiCodePoint(unit);
+ }
+
+ char32_t cp;
+ return getNonAsciiCodePoint(unit, &cp);
+ }
+
+ // If there is an invalid escape in a template, report it and return false,
+ // otherwise return true.
+ bool checkForInvalidTemplateEscapeError() {
+ if (anyCharsAccess().invalidTemplateEscapeType == InvalidEscapeType::None) {
+ return true;
+ }
+
+ reportInvalidEscapeError(anyCharsAccess().invalidTemplateEscapeOffset,
+ anyCharsAccess().invalidTemplateEscapeType);
+ return false;
+ }
+
+ public:
+ // Implement ErrorReporter.
+
+ bool isOnThisLine(size_t offset, uint32_t lineNum,
+ bool* onThisLine) const final {
+ return anyCharsAccess().srcCoords.isOnThisLine(offset, lineNum, onThisLine);
+ }
+
+ uint32_t lineAt(size_t offset) const final {
+ const auto& anyChars = anyCharsAccess();
+ auto lineToken = anyChars.lineToken(offset);
+ return anyChars.lineNumber(lineToken);
+ }
+
+ uint32_t columnAt(size_t offset) const final {
+ return computeColumn(anyCharsAccess().lineToken(offset), offset);
+ }
+
+ private:
+ // Implement ErrorReportMixin.
+
+ FrontendContext* getContext() const override {
+ return anyCharsAccess().context();
+ }
+
+ [[nodiscard]] bool strictMode() const override {
+ return anyCharsAccess().strictMode();
+ }
+
+ public:
+ // Implement ErrorReportMixin.
+
+ const JS::ReadOnlyCompileOptions& options() const final {
+ return anyCharsAccess().options();
+ }
+
+ [[nodiscard]] bool computeErrorMetadata(
+ ErrorMetadata* err, const ErrorOffset& errorOffset) const override;
+
+ private:
+ void reportInvalidEscapeError(uint32_t offset, InvalidEscapeType type) {
+ switch (type) {
+ case InvalidEscapeType::None:
+ MOZ_ASSERT_UNREACHABLE("unexpected InvalidEscapeType");
+ return;
+ case InvalidEscapeType::Hexadecimal:
+ errorAt(offset, JSMSG_MALFORMED_ESCAPE, "hexadecimal");
+ return;
+ case InvalidEscapeType::Unicode:
+ errorAt(offset, JSMSG_MALFORMED_ESCAPE, "Unicode");
+ return;
+ case InvalidEscapeType::UnicodeOverflow:
+ errorAt(offset, JSMSG_UNICODE_OVERFLOW, "escape sequence");
+ return;
+ case InvalidEscapeType::Octal:
+ errorAt(offset, JSMSG_DEPRECATED_OCTAL_ESCAPE);
+ return;
+ case InvalidEscapeType::EightOrNine:
+ errorAt(offset, JSMSG_DEPRECATED_EIGHT_OR_NINE_ESCAPE);
+ return;
+ }
+ }
+
+ void reportIllegalCharacter(int32_t cp);
+
+ [[nodiscard]] bool putIdentInCharBuffer(const Unit* identStart);
+
+ using IsIntegerUnit = bool (*)(int32_t);
+ [[nodiscard]] MOZ_ALWAYS_INLINE bool matchInteger(IsIntegerUnit isIntegerUnit,
+ int32_t* nextUnit);
+ [[nodiscard]] MOZ_ALWAYS_INLINE bool matchIntegerAfterFirstDigit(
+ IsIntegerUnit isIntegerUnit, int32_t* nextUnit);
+
+ /**
+ * Tokenize a decimal number that begins at |numStart| into the provided
+ * token.
+ *
+ * |unit| must be one of these values:
+ *
+ * 1. The first decimal digit in the integral part of a decimal number
+ * not starting with '.', e.g. '1' for "17", '0' for "0.14", or
+ * '8' for "8.675309e6".
+ *
+ * In this case, the next |getCodeUnit()| must return the code unit after
+ * |unit| in the overall number.
+ *
+ * 2. The '.' in a "."-prefixed decimal number, e.g. ".17" or ".1e3".
+ *
+ * In this case, the next |getCodeUnit()| must return the code unit
+ * *after* the '.'.
+ *
+ * 3. (Non-strict mode code only) The first non-ASCII-digit unit for a
+ * "noctal" number that begins with a '0' but contains a non-octal digit
+ * in its integer part so is interpreted as decimal, e.g. '.' in "09.28"
+ * or EOF for "0386" or '+' in "09+7" (three separate tokens).
+ *
+ * In this case, the next |getCodeUnit()| returns the code unit after
+ * |unit|: '2', 'EOF', or '7' in the examples above.
+ *
+ * This interface is super-hairy and horribly stateful. Unfortunately, its
+ * hair merely reflects the intricacy of ECMAScript numeric literal syntax.
+ * And incredibly, it *improves* on the goto-based horror that predated it.
+ */
+ [[nodiscard]] bool decimalNumber(int32_t unit, TokenStart start,
+ const Unit* numStart, Modifier modifier,
+ TokenKind* out);
+
+ /** Tokenize a regular expression literal beginning at |start|. */
+ [[nodiscard]] bool regexpLiteral(TokenStart start, TokenKind* out);
+
+ /**
+ * Slurp characters between |start| and sourceUnits.current() into
+ * charBuffer, to later parse into a bigint.
+ */
+ [[nodiscard]] bool bigIntLiteral(TokenStart start, Modifier modifier,
+ TokenKind* out);
+
+ public:
+ // Advance to the next token. If the token stream encountered an error,
+ // return false. Otherwise return true and store the token kind in |*ttp|.
+ [[nodiscard]] bool getToken(TokenKind* ttp, Modifier modifier = SlashIsDiv) {
+ // Check for a pushed-back token resulting from mismatching lookahead.
+ TokenStreamAnyChars& anyChars = anyCharsAccess();
+ if (anyChars.lookahead != 0) {
+ MOZ_ASSERT(!anyChars.flags.hadError);
+ anyChars.lookahead--;
+ anyChars.advanceCursor();
+ TokenKind tt = anyChars.currentToken().type;
+ MOZ_ASSERT(tt != TokenKind::Eol);
+ verifyConsistentModifier(modifier, anyChars.currentToken());
+ *ttp = tt;
+ return true;
+ }
+
+ return getTokenInternal(ttp, modifier);
+ }
+
+ [[nodiscard]] bool peekToken(TokenKind* ttp, Modifier modifier = SlashIsDiv) {
+ TokenStreamAnyChars& anyChars = anyCharsAccess();
+ if (anyChars.lookahead > 0) {
+ MOZ_ASSERT(!anyChars.flags.hadError);
+ verifyConsistentModifier(modifier, anyChars.nextToken());
+ *ttp = anyChars.nextToken().type;
+ return true;
+ }
+ if (!getTokenInternal(ttp, modifier)) {
+ return false;
+ }
+ anyChars.ungetToken();
+ return true;
+ }
+
+ [[nodiscard]] bool peekTokenPos(TokenPos* posp,
+ Modifier modifier = SlashIsDiv) {
+ TokenStreamAnyChars& anyChars = anyCharsAccess();
+ if (anyChars.lookahead == 0) {
+ TokenKind tt;
+ if (!getTokenInternal(&tt, modifier)) {
+ return false;
+ }
+ anyChars.ungetToken();
+ MOZ_ASSERT(anyChars.hasLookahead());
+ } else {
+ MOZ_ASSERT(!anyChars.flags.hadError);
+ verifyConsistentModifier(modifier, anyChars.nextToken());
+ }
+ *posp = anyChars.nextToken().pos;
+ return true;
+ }
+
+ [[nodiscard]] bool peekOffset(uint32_t* offset,
+ Modifier modifier = SlashIsDiv) {
+ TokenPos pos;
+ if (!peekTokenPos(&pos, modifier)) {
+ return false;
+ }
+ *offset = pos.begin;
+ return true;
+ }
+
+ // This is like peekToken(), with one exception: if there is an EOL
+ // between the end of the current token and the start of the next token, it
+ // return true and store Eol in |*ttp|. In that case, no token with
+ // Eol is actually created, just a Eol TokenKind is returned, and
+ // currentToken() shouldn't be consulted. (This is the only place Eol
+ // is produced.)
+ [[nodiscard]] MOZ_ALWAYS_INLINE bool peekTokenSameLine(
+ TokenKind* ttp, Modifier modifier = SlashIsDiv) {
+ TokenStreamAnyChars& anyChars = anyCharsAccess();
+ const Token& curr = anyChars.currentToken();
+
+ // If lookahead != 0, we have scanned ahead at least one token, and
+ // |lineno| is the line that the furthest-scanned token ends on. If
+ // it's the same as the line that the current token ends on, that's a
+ // stronger condition than what we are looking for, and we don't need
+ // to return Eol.
+ if (anyChars.lookahead != 0) {
+ bool onThisLine;
+ if (!anyChars.srcCoords.isOnThisLine(curr.pos.end, anyChars.lineno,
+ &onThisLine)) {
+ error(JSMSG_OUT_OF_MEMORY);
+ return false;
+ }
+
+ if (onThisLine) {
+ MOZ_ASSERT(!anyChars.flags.hadError);
+ verifyConsistentModifier(modifier, anyChars.nextToken());
+ *ttp = anyChars.nextToken().type;
+ return true;
+ }
+ }
+
+ // The above check misses two cases where we don't have to return
+ // Eol.
+ // - The next token starts on the same line, but is a multi-line token.
+ // - The next token starts on the same line, but lookahead==2 and there
+ // is a newline between the next token and the one after that.
+ // The following test is somewhat expensive but gets these cases (and
+ // all others) right.
+ TokenKind tmp;
+ if (!getToken(&tmp, modifier)) {
+ return false;
+ }
+
+ const Token& next = anyChars.currentToken();
+ anyChars.ungetToken();
+
+ // Careful, |next| points to an initialized-but-not-allocated Token!
+ // This is safe because we don't modify token data below.
+
+ auto currentEndToken = anyChars.lineToken(curr.pos.end);
+ auto nextBeginToken = anyChars.lineToken(next.pos.begin);
+
+ *ttp =
+ currentEndToken.isSameLine(nextBeginToken) ? next.type : TokenKind::Eol;
+ return true;
+ }
+
+ // Get the next token from the stream if its kind is |tt|.
+ [[nodiscard]] bool matchToken(bool* matchedp, TokenKind tt,
+ Modifier modifier = SlashIsDiv) {
+ TokenKind token;
+ if (!getToken(&token, modifier)) {
+ return false;
+ }
+ if (token == tt) {
+ *matchedp = true;
+ } else {
+ anyCharsAccess().ungetToken();
+ *matchedp = false;
+ }
+ return true;
+ }
+
+ void consumeKnownToken(TokenKind tt, Modifier modifier = SlashIsDiv) {
+ bool matched;
+ MOZ_ASSERT(anyCharsAccess().hasLookahead());
+ MOZ_ALWAYS_TRUE(matchToken(&matched, tt, modifier));
+ MOZ_ALWAYS_TRUE(matched);
+ }
+
+ [[nodiscard]] bool nextTokenEndsExpr(bool* endsExpr) {
+ TokenKind tt;
+ if (!peekToken(&tt)) {
+ return false;
+ }
+
+ *endsExpr = anyCharsAccess().isExprEnding[size_t(tt)];
+ if (*endsExpr) {
+ // If the next token ends an overall Expression, we'll parse this
+ // Expression without ever invoking Parser::orExpr(). But we need that
+ // function's DEBUG-only side effect of marking this token as safe to get
+ // with SlashIsRegExp, so we have to do it manually here.
+ anyCharsAccess().allowGettingNextTokenWithSlashIsRegExp();
+ }
+ return true;
+ }
+
+ [[nodiscard]] bool advance(size_t position);
+
+ void seekTo(const Position& pos);
+ [[nodiscard]] bool seekTo(const Position& pos,
+ const TokenStreamAnyChars& other);
+
+ void rewind(const Position& pos) {
+ MOZ_ASSERT(pos.buf <= this->sourceUnits.addressOfNextCodeUnit(),
+ "should be rewinding here");
+ seekTo(pos);
+ }
+
+ [[nodiscard]] bool rewind(const Position& pos,
+ const TokenStreamAnyChars& other) {
+ MOZ_ASSERT(pos.buf <= this->sourceUnits.addressOfNextCodeUnit(),
+ "should be rewinding here");
+ return seekTo(pos, other);
+ }
+
+ void fastForward(const Position& pos) {
+ MOZ_ASSERT(this->sourceUnits.addressOfNextCodeUnit() <= pos.buf,
+ "should be moving forward here");
+ seekTo(pos);
+ }
+
+ [[nodiscard]] bool fastForward(const Position& pos,
+ const TokenStreamAnyChars& other) {
+ MOZ_ASSERT(this->sourceUnits.addressOfNextCodeUnit() <= pos.buf,
+ "should be moving forward here");
+ return seekTo(pos, other);
+ }
+
+ const Unit* codeUnitPtrAt(size_t offset) const {
+ return this->sourceUnits.codeUnitPtrAt(offset);
+ }
+
+ [[nodiscard]] bool identifierName(TokenStart start, const Unit* identStart,
+ IdentifierEscapes escaping,
+ Modifier modifier,
+ NameVisibility visibility, TokenKind* out);
+
+ [[nodiscard]] bool matchIdentifierStart(IdentifierEscapes* sawEscape);
+
+ [[nodiscard]] bool getTokenInternal(TokenKind* const ttp,
+ const Modifier modifier);
+
+ [[nodiscard]] bool getStringOrTemplateToken(char untilChar, Modifier modifier,
+ TokenKind* out);
+
+ // Parse a TemplateMiddle or TemplateTail token (one of the string-like parts
+ // of a template string) after already consuming the leading `RightCurly`.
+ // (The spec says the `}` is the first character of the TemplateMiddle/
+ // TemplateTail, but we treat it as a separate token because that's much
+ // easier to implement in both TokenStream and the parser.)
+ //
+ // This consumes a token and sets the current token, like `getToken()`. It
+ // doesn't take a Modifier because there's no risk of encountering a division
+ // operator or RegExp literal.
+ //
+ // On success, `*ttp` is either `TokenKind::TemplateHead` (if we got a
+ // TemplateMiddle token) or `TokenKind::NoSubsTemplate` (if we got a
+ // TemplateTail). That may seem strange; there are four different template
+ // token types in the spec, but we only use two. We use `TemplateHead` for
+ // TemplateMiddle because both end with `...${`, and `NoSubsTemplate` for
+ // TemplateTail because both contain the end of the template, including the
+ // closing quote mark. They're not treated differently, either in the parser
+ // or in the tokenizer.
+ [[nodiscard]] bool getTemplateToken(TokenKind* ttp) {
+ MOZ_ASSERT(anyCharsAccess().currentToken().type == TokenKind::RightCurly);
+ return getStringOrTemplateToken('`', SlashIsInvalid, ttp);
+ }
+
+ [[nodiscard]] bool getDirectives(bool isMultiline, bool shouldWarnDeprecated);
+ [[nodiscard]] bool getDirective(
+ bool isMultiline, bool shouldWarnDeprecated, const char* directive,
+ uint8_t directiveLength, const char* errorMsgPragma,
+ UniquePtr<char16_t[], JS::FreePolicy>* destination);
+ [[nodiscard]] bool getDisplayURL(bool isMultiline, bool shouldWarnDeprecated);
+ [[nodiscard]] bool getSourceMappingURL(bool isMultiline,
+ bool shouldWarnDeprecated);
+};
+
+// It's preferable to define this in TokenStream.cpp, but its template-ness
+// means we'd then have to *instantiate* this constructor for all possible
+// (Unit, AnyCharsAccess) pairs -- and that gets super-messy as AnyCharsAccess
+// *itself* is templated. This symbol really isn't that huge compared to some
+// defined inline in TokenStreamSpecific, so just rely on the linker commoning
+// stuff up.
+template <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(FrontendContext* fc, ParserAtomsTable* parserAtoms,
+ const JS::ReadOnlyCompileOptions& options, const Unit* units,
+ size_t length, StrictModeGetter* smg)
+ : TokenStreamAnyChars(fc, options, smg),
+ TokenStreamSpecific<Unit, TokenStreamAnyCharsAccess>(
+ fc, parserAtoms, options, units, length) {}
+};
+
+class MOZ_STACK_CLASS DummyTokenStream final : public TokenStream {
+ public:
+ DummyTokenStream(FrontendContext* fc,
+ const JS::ReadOnlyCompileOptions& options)
+ : TokenStream(fc, nullptr, options, nullptr, 0, nullptr) {}
+};
+
+template <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
+
+#ifdef DEBUG
+extern const char* TokenKindToString(js::frontend::TokenKind tt);
+#endif
+
+#endif /* frontend_TokenStream_h */
diff --git a/js/src/frontend/TryEmitter.cpp b/js/src/frontend/TryEmitter.cpp
new file mode 100644
index 0000000000..63c8a6723e
--- /dev/null
+++ b/js/src/frontend/TryEmitter.cpp
@@ -0,0 +1,317 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "frontend/TryEmitter.h"
+
+#include "mozilla/Assertions.h" // MOZ_ASSERT
+
+#include "frontend/BytecodeEmitter.h" // BytecodeEmitter
+#include "frontend/IfEmitter.h" // BytecodeEmitter
+#include "frontend/SharedContext.h" // StatementKind
+#include "vm/Opcodes.h" // JSOp
+
+using namespace js;
+using namespace js::frontend;
+
+using mozilla::Maybe;
+
+TryEmitter::TryEmitter(BytecodeEmitter* bce, Kind kind, ControlKind controlKind)
+ : bce_(bce),
+ kind_(kind),
+ controlKind_(controlKind),
+ depth_(0),
+ tryOpOffset_(0)
+#ifdef DEBUG
+ ,
+ state_(State::Start)
+#endif
+{
+ if (controlKind_ == ControlKind::Syntactic) {
+ controlInfo_.emplace(
+ bce_, hasFinally() ? StatementKind::Finally : StatementKind::Try);
+ }
+}
+
+bool TryEmitter::emitTry() {
+ MOZ_ASSERT(state_ == State::Start);
+
+ // Since an exception can be thrown at any place inside the try block,
+ // we need to restore the stack and the scope chain before we transfer
+ // the control to the exception handler.
+ //
+ // For that we store in a try note associated with the catch or
+ // finally block the stack depth upon the try entry. The interpreter
+ // uses this depth to properly unwind the stack and the scope chain.
+ depth_ = bce_->bytecodeSection().stackDepth();
+
+ tryOpOffset_ = bce_->bytecodeSection().offset();
+ if (!bce_->emit1(JSOp::Try)) {
+ return false;
+ }
+
+#ifdef DEBUG
+ state_ = State::Try;
+#endif
+ return true;
+}
+
+bool TryEmitter::emitJumpToFinallyWithFallthrough() {
+ uint32_t stackDepthForNextBlock = bce_->bytecodeSection().stackDepth();
+
+ // The fallthrough continuation is special-cased with index 0.
+ uint32_t idx = TryFinallyControl::SpecialContinuations::Fallthrough;
+ if (!bce_->emitJumpToFinally(&controlInfo_->finallyJumps_, idx)) {
+ return false;
+ }
+
+ // Reset the stack depth for the following catch or finally block.
+ bce_->bytecodeSection().setStackDepth(stackDepthForNextBlock);
+ return true;
+}
+
+bool TryEmitter::emitTryEnd() {
+ MOZ_ASSERT(state_ == State::Try);
+ MOZ_ASSERT(depth_ == bce_->bytecodeSection().stackDepth());
+
+ if (hasFinally() && controlInfo_) {
+ if (!emitJumpToFinallyWithFallthrough()) {
+ return false;
+ }
+ } else {
+ // Emit jump over catch
+ if (!bce_->emitJump(JSOp::Goto, &catchAndFinallyJump_)) {
+ return false;
+ }
+ }
+
+ if (!bce_->emitJumpTarget(&tryEnd_)) {
+ return false;
+ }
+
+ return true;
+}
+
+bool TryEmitter::emitCatch() {
+ MOZ_ASSERT(state_ == State::Try);
+ if (!emitTryEnd()) {
+ return false;
+ }
+
+ MOZ_ASSERT(bce_->bytecodeSection().stackDepth() == depth_);
+
+ if (shouldUpdateRval()) {
+ // Clear the frame's return value that might have been set by the
+ // try block:
+ //
+ // eval("try { 1; throw 2 } catch(e) {}"); // undefined, not 1
+ if (!bce_->emit1(JSOp::Undefined)) {
+ return false;
+ }
+ if (!bce_->emit1(JSOp::SetRval)) {
+ return false;
+ }
+ }
+
+ if (!bce_->emit1(JSOp::Exception)) {
+ return false;
+ }
+
+#ifdef DEBUG
+ state_ = State::Catch;
+#endif
+ return true;
+}
+
+bool TryEmitter::emitCatchEnd() {
+ MOZ_ASSERT(state_ == State::Catch);
+
+ if (!controlInfo_) {
+ return true;
+ }
+
+ // Jump to <finally>, if required.
+ if (hasFinally()) {
+ if (!emitJumpToFinallyWithFallthrough()) {
+ 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_);
+
+ // Upon entry to the finally, there are two additional values on the stack:
+ // a boolean value to indicate whether we're throwing an exception, and
+ // either that exception (if we're throwing) or a resume index to which we
+ // will return (if we're not throwing).
+ bce_->bytecodeSection().setStackDepth(depth_ + 2);
+
+ if (!bce_->emitJumpTarget(&finallyStart_)) {
+ return false;
+ }
+
+ if (controlInfo_) {
+ // Fix up the jumps to the finally code.
+ bce_->patchJumpsToTarget(controlInfo_->finallyJumps_, finallyStart_);
+
+ // Indicate that we're emitting a subroutine body.
+ controlInfo_->setEmittingSubroutine();
+ }
+ if (finallyPos) {
+ if (!bce_->updateSourceCoordNotes(finallyPos.value())) {
+ return false;
+ }
+ }
+ if (!bce_->emit1(JSOp::Finally)) {
+ return false;
+ }
+
+ if (shouldUpdateRval()) {
+ if (!bce_->emit1(JSOp::GetRval)) {
+ return false;
+ }
+
+ // Clear the frame's return value to make break/continue return
+ // correct value even if there's no other statement before them:
+ //
+ // eval("x: try { 1 } finally { break x; }"); // undefined, not 1
+ if (!bce_->emit1(JSOp::Undefined)) {
+ return false;
+ }
+ if (!bce_->emit1(JSOp::SetRval)) {
+ return false;
+ }
+ }
+
+#ifdef DEBUG
+ state_ = State::Finally;
+#endif
+ return true;
+}
+
+bool TryEmitter::emitFinallyEnd() {
+ MOZ_ASSERT(state_ == State::Finally);
+
+ if (shouldUpdateRval()) {
+ if (!bce_->emit1(JSOp::SetRval)) {
+ return false;
+ }
+ }
+
+ InternalIfEmitter ifThrowing(bce_);
+ if (!ifThrowing.emitThenElse()) {
+ return false;
+ }
+
+ if (!bce_->emit1(JSOp::Throw)) {
+ return false;
+ }
+
+ if (!ifThrowing.emitElse()) {
+ return false;
+ }
+
+ if (controlInfo_ && !controlInfo_->continuations_.empty()) {
+ if (!controlInfo_->emitContinuations(bce_)) {
+ return false;
+ }
+ } else {
+ // If there are no non-local jumps, then the only possible jump target
+ // is the code immediately following this finally block. Instead of
+ // emitting a tableswitch, we can simply pop the continuation index
+ // and fall through.
+ if (!bce_->emit1(JSOp::Pop)) {
+ return false;
+ }
+ }
+
+ if (!ifThrowing.emitEnd()) {
+ return false;
+ }
+
+ bce_->hasTryFinally = true;
+ return true;
+}
+
+bool TryEmitter::emitEnd() {
+ if (!hasFinally()) {
+ MOZ_ASSERT(state_ == State::Catch);
+ if (!emitCatchEnd()) {
+ return false;
+ }
+ } else {
+ MOZ_ASSERT(state_ == State::Finally);
+ if (!emitFinallyEnd()) {
+ return false;
+ }
+ }
+
+ MOZ_ASSERT(bce_->bytecodeSection().stackDepth() == depth_);
+
+ if (catchAndFinallyJump_.offset.valid()) {
+ // Fix up the end-of-try/catch jumps to come here.
+ if (!bce_->emitJumpTargetAndPatch(catchAndFinallyJump_)) {
+ return false;
+ }
+ }
+
+ // Add the try note last, to let post-order give us the right ordering
+ // (first to last for a given nesting level, inner to outer by level).
+ if (hasCatch()) {
+ if (!bce_->addTryNote(TryNoteKind::Catch, depth_, offsetAfterTryOp(),
+ tryEnd_.offset)) {
+ return false;
+ }
+ }
+
+ // If we've got a finally, mark try+catch region with additional
+ // trynote to catch exceptions (re)thrown from a catch block or
+ // for the try{}finally{} case.
+ if (hasFinally()) {
+ if (!bce_->addTryNote(TryNoteKind::Finally, depth_, offsetAfterTryOp(),
+ finallyStart_.offset)) {
+ return false;
+ }
+ }
+
+#ifdef DEBUG
+ state_ = State::End;
+#endif
+ return true;
+}
+
+bool TryEmitter::shouldUpdateRval() const {
+ return controlKind_ == ControlKind::Syntactic && !bce_->sc->noScriptRval();
+}
diff --git a/js/src/frontend/TryEmitter.h b/js/src/frontend/TryEmitter.h
new file mode 100644
index 0000000000..f32d477889
--- /dev/null
+++ b/js/src/frontend/TryEmitter.h
@@ -0,0 +1,226 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef frontend_TryEmitter_h
+#define frontend_TryEmitter_h
+
+#include "mozilla/Attributes.h" // MOZ_STACK_CLASS
+#include "mozilla/Maybe.h" // mozilla::Maybe, mozilla::Nothing
+
+#include <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. Note that these instructions are not
+ // emitted for noScriptRval scripts that don't track the return value.
+ //
+ // JSOp::Try offsetOf(jumpToEnd)
+ //
+ // try_body...
+ //
+ // JSOp::Goto finally
+ // JSOp::JumpTarget
+ // jumpToEnd:
+ // JSOp::Goto end:
+ //
+ // catch:
+ // JSOp::JumpTarget
+ // * JSOp::Undefined
+ // * JSOp::SetRval
+ //
+ // catch_body...
+ //
+ // JSOp::Goto finally
+ // JSOp::JumpTarget
+ // JSOp::Goto end
+ //
+ // finally:
+ // JSOp::JumpTarget
+ // * JSOp::GetRval
+ // * JSOp::Undefined
+ // * JSOp::SetRval
+ //
+ // finally_body...
+ //
+ // * JSOp::SetRval
+ // JSOp::Nop
+ //
+ // end:
+ // JSOp::JumpTarget
+ //
+ // For syntactic try-catch-finally, Syntactic should be used.
+ // For non-syntactic try-catch-finally, NonSyntactic should be used.
+ enum class ControlKind { Syntactic, NonSyntactic };
+
+ private:
+ BytecodeEmitter* bce_;
+ Kind kind_;
+ ControlKind controlKind_;
+
+ // Tracks jumps to the finally block for later fixup.
+ //
+ // When a finally block is active, non-local jumps (including
+ // jumps-over-catches) result in a goto being written into the bytecode
+ // stream and fixed-up later.
+ //
+ // For non-syntactic try-catch-finally, all that handling is skipped.
+ // The non-syntactic try-catch-finally must:
+ // * have only one catch block
+ // * have JSOp::Goto at the end of catch-block
+ // * have no non-local-jump
+ // * don't use finally block for normal completion of try-block and
+ // catch-block
+ //
+ // Additionally, a finally block may be emitted for non-syntactic
+ // try-catch-finally, even if the kind is TryCatch, because GOSUBs are not
+ // emitted.
+ mozilla::Maybe<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);
+ }
+
+ // Returns true if catch and finally blocks should handle the frame's
+ // return value.
+ bool shouldUpdateRval() const;
+
+ // Jump to the finally block. After the finally block executes,
+ // fall through to the code following the finally block.
+ [[nodiscard]] bool emitJumpToFinallyWithFallthrough();
+
+ public:
+ TryEmitter(BytecodeEmitter* bce, Kind kind, ControlKind controlKind);
+
+ [[nodiscard]] bool emitTry();
+ [[nodiscard]] bool emitCatch();
+
+ // If `finallyPos` is specified, it's an offset of the finally block's
+ // "{" character in the source code text, to improve line:column number in
+ // the error reporting.
+ // For non-syntactic try-catch-finally, `finallyPos` can be omitted.
+ [[nodiscard]] bool emitFinally(
+ const mozilla::Maybe<uint32_t>& finallyPos = mozilla::Nothing());
+
+ [[nodiscard]] bool emitEnd();
+
+ private:
+ [[nodiscard]] bool emitTryEnd();
+ [[nodiscard]] bool emitCatchEnd();
+ [[nodiscard]] bool emitFinallyEnd();
+};
+
+} /* namespace frontend */
+} /* namespace js */
+
+#endif /* frontend_TryEmitter_h */
diff --git a/js/src/frontend/TypedIndex.h b/js/src/frontend/TypedIndex.h
new file mode 100644
index 0000000000..c75d59ae3d
--- /dev/null
+++ b/js/src/frontend/TypedIndex.h
@@ -0,0 +1,42 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef frontend_TypedIndex_h
+#define frontend_TypedIndex_h
+
+#include <cstdint>
+#include <stddef.h>
+
+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) const { return index < other.index; }
+ bool operator<=(TypedIndex other) const { return index <= other.index; }
+ bool operator>(TypedIndex other) const { return index > other.index; }
+ bool operator>=(TypedIndex other) const { return index >= other.index; }
+};
+
+} // namespace frontend
+} // namespace js
+
+#endif
diff --git a/js/src/frontend/UsedNameTracker.h b/js/src/frontend/UsedNameTracker.h
new file mode 100644
index 0000000000..50245f0ce2
--- /dev/null
+++ b/js/src/frontend/UsedNameTracker.h
@@ -0,0 +1,259 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef frontend_UsedNameTracker_h
+#define frontend_UsedNameTracker_h
+
+#include "mozilla/Assertions.h"
+#include "mozilla/Maybe.h"
+
+#include <stdint.h>
+
+#include "frontend/ParserAtom.h" // TaggedParserAtomIndex
+#include "frontend/TaggedParserAtomIndexHasher.h" // TaggedParserAtomIndexHasher
+#include "frontend/Token.h"
+#include "js/AllocPolicy.h"
+#include "js/HashTable.h"
+#include "js/Vector.h"
+
+namespace js {
+namespace frontend {
+
+// A data structure for tracking used names per parsing session in order to
+// compute which bindings are closed over. Scripts and scopes are numbered
+// monotonically in textual order and unresolved uses of a name are tracked by
+// lists of identifier uses, which are a pair of (ScriptId,ScopeId).
+//
+// For an identifier `i` with a use (ScriptId,ScopeId) in the Used list,
+// ScriptId tracks the most nested script that has a use of u, and ScopeId
+// tracks the most nested scope that is still being parsed (as the lists will be
+// filtered as we finish processing a particular scope).
+//
+// ScriptId is used to answer the question "is `i` used by a nested function?"
+// ScopeId is used to answer the question "is `i` used in any scopes currently
+// being parsed?"
+//
+// The algorithm:
+//
+// Let Used be a map of names to lists.
+// Let Declared(ScopeId) be a list of declarations for a scope numbered with
+// ScopeId
+//
+// 1. Number all scopes in monotonic increasing order in textual order.
+// 2. Number all scripts in monotonic increasing order in textual order.
+// 3. When an identifier `i` is used in (ScriptId,ScopeId), append that use to
+// the list Used[i] (creating the list and table entry if necessary).
+// 4. When an identifier `i` is declared in a scope numbered ScopeId, append `i`
+// to Declared(ScopeId).
+// 5. When we finish parsing a scope numbered with ScopeId, in script numbered
+// ScriptId, for each declared name d in Declared(ScopeId):
+// a. If d is found in Used, mark d as closed over if there is a value
+// (UsedScriptId, UsedScopeId) in Used[d] such that UsedScriptId > ScriptId
+// and UsedScopeId > ScopeId.
+// b. Remove all values uses in Used[d] where UsedScopeId > ScopeId.
+//
+// Steps 1 and 2 are implemented by UsedNameTracker::next{Script,Scope}Id.
+// Step 3 is implemented by UsedNameTracker::noteUsedInScope.
+// Step 4 is implemented by ParseContext::Scope::addDeclaredName.
+// Step 5 is implemented by UsedNameTracker::noteBoundInScope and
+// Parser::propagateFreeNamesAndMarkClosedOverBindings
+//
+// The following is a worked example to show how the algorithm works on a
+// relatively simple piece of code. (clang-format is disabled due to the width
+// of the example).
+
+// clang-format off
+//
+// // Script 1, Scope 1
+// var x = 1; // Declared(1) = [x];
+// function f() {// Script 2, Scope 2
+// if (x > 10) { //Scope 3 // Used[x] = [(2,2)];
+// var x = 12; // Declared(3) = [x];
+// function g() // Script 3
+// { // Scope 4
+// return x; // Used[x] = [(2,2), (3,4)]
+// } // Leaving Script 3, Scope 4: No declared variables.
+// } // Leaving Script 2, Scope 3: Declared(3) = [x];
+// // - Used[x][1] = (2,2) is not > (2,3)
+// // - Used[x][2] = (3,4) is > (2,3), so mark x as closed over.
+// // - Update Used[x]: [] -- Makes sense, as at this point we have
+// // bound all the unbound x to a particlar
+// // declaration..
+//
+// else { // Scope 5
+// var x = 14; // Declared(5) = [x]
+// function g() // Script 4
+// { // Scope 6
+// return y; // Used[y] = [(4,6)]
+// } // Leaving Script 4, Scope 6: No declarations.
+// } // Leaving Script 2, Scope 5: Declared(5) = [x]
+// // - Used[x] = [], so don't mark x as closed over.
+// var y = 12; // Declared(2) = [y]
+// } // Leaving Script 2, Scope 2: Declared(2) = [y]
+// // - Used[y][1] = (4,6) is > (2,2), so mark y as closed over.
+// // - Update Used[y]: [].
+
+// clang-format on
+
+struct UnboundPrivateName {
+ TaggedParserAtomIndex atom;
+ TokenPos position;
+
+ UnboundPrivateName(TaggedParserAtomIndex atom, TokenPos position)
+ : atom(atom), position(position) {}
+};
+
+class UsedNameTracker {
+ public:
+ struct Use {
+ uint32_t scriptId;
+ uint32_t scopeId;
+ };
+
+ class UsedNameInfo {
+ friend class UsedNameTracker;
+
+ Vector<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(FrontendContext* fc, NameVisibility visibility,
+ mozilla::Maybe<TokenPos> position)
+ : uses_(fc), visibility_(visibility), firstUsePos_(position) {}
+
+ UsedNameInfo(UsedNameInfo&& other) = default;
+
+ bool noteUsedInScope(uint32_t scriptId, uint32_t scopeId) {
+ if (uses_.empty() || uses_.back().scopeId < scopeId) {
+ return uses_.append(Use{scriptId, scopeId});
+ }
+ return true;
+ }
+
+ void noteBoundInScope(uint32_t scriptId, uint32_t scopeId,
+ bool* closedOver) {
+ *closedOver = false;
+ while (!uses_.empty()) {
+ Use& innermost = uses_.back();
+ if (innermost.scopeId < scopeId) {
+ break;
+ }
+ if (innermost.scriptId > scriptId) {
+ *closedOver = true;
+ }
+ uses_.popBack();
+ }
+ }
+
+ bool isUsedInScript(uint32_t scriptId) const {
+ return !uses_.empty() && uses_.back().scriptId >= scriptId;
+ }
+
+ // To allow disambiguating public and private symbols
+ bool isPublic() { return visibility_ == NameVisibility::Public; }
+
+ bool empty() { return uses_.empty(); }
+
+ mozilla::Maybe<TokenPos> pos() { return firstUsePos_; }
+
+ // When we leave a scope, and subsequently find a new private name
+ // reference, we don't want our error messages to be attributed to an old
+ // scope, so we update the position in that scenario.
+ void maybeUpdatePos(mozilla::Maybe<TokenPos> p) {
+ MOZ_ASSERT_IF(!isPublic(), p.isSome());
+
+ if (empty() && !isPublic()) {
+ firstUsePos_ = p;
+ }
+ }
+ };
+
+ using UsedNameMap =
+ HashMap<TaggedParserAtomIndex, UsedNameInfo, TaggedParserAtomIndexHasher>;
+
+ private:
+ // The map of names to chains of uses.
+ UsedNameMap map_;
+
+ // Monotonically increasing id for all nested scripts.
+ uint32_t scriptCounter_;
+
+ // Monotonically increasing id for all nested scopes.
+ uint32_t scopeCounter_;
+
+ // Set if a private name was encountered.
+ // Used to short circuit some private field early error checks
+ bool hasPrivateNames_;
+
+ public:
+ explicit UsedNameTracker(FrontendContext* fc)
+ : map_(fc),
+ scriptCounter_(0),
+ scopeCounter_(0),
+ hasPrivateNames_(false) {}
+
+ uint32_t nextScriptId() {
+ MOZ_ASSERT(scriptCounter_ != UINT32_MAX,
+ "ParseContext::Scope::init should have prevented wraparound");
+ return scriptCounter_++;
+ }
+
+ uint32_t nextScopeId() {
+ MOZ_ASSERT(scopeCounter_ != UINT32_MAX);
+ return scopeCounter_++;
+ }
+
+ UsedNameMap::Ptr lookup(TaggedParserAtomIndex name) const {
+ return map_.lookup(name);
+ }
+
+ [[nodiscard]] bool noteUse(
+ FrontendContext* fc, TaggedParserAtomIndex name,
+ NameVisibility visibility, uint32_t scriptId, uint32_t scopeId,
+ mozilla::Maybe<TokenPos> tokenPosition = mozilla::Nothing());
+
+ // Fill maybeUnboundName with the first (source order) unbound name, or
+ // Nothing() if there are no unbound names.
+ [[nodiscard]] bool hasUnboundPrivateNames(
+ FrontendContext* fc,
+ mozilla::Maybe<UnboundPrivateName>& maybeUnboundName);
+
+ // Return a list of unbound private names, sorted by increasing location in
+ // the source.
+ [[nodiscard]] 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..562272c5f8
--- /dev/null
+++ b/js/src/frontend/ValueUsage.h
@@ -0,0 +1,30 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef frontend_ValueUsage_h
+#define frontend_ValueUsage_h
+
+namespace js {
+namespace frontend {
+
+// Used to control whether the expression result is used. This enables various
+// optimizations, for example it allows to emit JSOp::CallIgnoresRv for function
+// calls.
+enum class ValueUsage {
+ // Assume the value of the current expression may be used. This is always
+ // correct but prohibits optimizations like JSOp::CallIgnoresRv.
+ WantValue,
+
+ // Pass this when emitting an expression if the expression's value is
+ // definitely unused by later instructions. You must make sure the next
+ // instruction is JSOp::Pop, a jump to a JSOp::Pop, or something similar.
+ IgnoreValue
+};
+
+} /* namespace frontend */
+} /* namespace js */
+
+#endif /* frontend_ValueUsage_h */
diff --git a/js/src/frontend/WhileEmitter.cpp b/js/src/frontend/WhileEmitter.cpp
new file mode 100644
index 0000000000..bc002b2ea6
--- /dev/null
+++ b/js/src/frontend/WhileEmitter.cpp
@@ -0,0 +1,85 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "frontend/WhileEmitter.h"
+
+#include "frontend/BytecodeEmitter.h"
+#include "vm/Opcodes.h"
+#include "vm/StencilEnums.h" // TryNoteKind
+
+using namespace js;
+using namespace js::frontend;
+
+WhileEmitter::WhileEmitter(BytecodeEmitter* bce) : bce_(bce) {}
+
+bool WhileEmitter::emitCond(uint32_t whilePos, uint32_t condPos,
+ uint32_t endPos) {
+ MOZ_ASSERT(state_ == State::Start);
+
+ // If we have a single-line while, like "while (x) ;", we want to emit the
+ // line note before the loop, so that the debugger sees a single entry point.
+ // This way, if there is a breakpoint on the line, it will only fire once; and
+ // "next"ing will skip the whole loop. However, for the multi-line case we
+ // want to emit the line note for the JSOp::LoopHead, so that "cont" stops on
+ // each iteration -- but without a stop before the first iteration.
+ if (bce_->errorReporter().lineAt(whilePos) ==
+ bce_->errorReporter().lineAt(endPos)) {
+ if (!bce_->updateSourceCoordNotes(whilePos)) {
+ return false;
+ }
+ // Emit a Nop to ensure the source position is not part of the loop.
+ if (!bce_->emit1(JSOp::Nop)) {
+ return false;
+ }
+ }
+
+ loopInfo_.emplace(bce_, StatementKind::WhileLoop);
+
+ if (!loopInfo_->emitLoopHead(bce_, mozilla::Some(condPos))) {
+ return false;
+ }
+
+#ifdef DEBUG
+ state_ = State::Cond;
+#endif
+ return true;
+}
+
+bool WhileEmitter::emitBody() {
+ MOZ_ASSERT(state_ == State::Cond);
+
+ if (!bce_->emitJump(JSOp::JumpIfFalse, &loopInfo_->breaks)) {
+ return false;
+ }
+
+ tdzCacheForBody_.emplace(bce_);
+
+#ifdef DEBUG
+ state_ = State::Body;
+#endif
+ return true;
+}
+
+bool WhileEmitter::emitEnd() {
+ MOZ_ASSERT(state_ == State::Body);
+
+ tdzCacheForBody_.reset();
+
+ if (!loopInfo_->emitContinueTarget(bce_)) {
+ return false;
+ }
+
+ if (!loopInfo_->emitLoopEnd(bce_, JSOp::Goto, TryNoteKind::Loop)) {
+ return false;
+ }
+
+ loopInfo_.reset();
+
+#ifdef DEBUG
+ state_ = State::End;
+#endif
+ return true;
+}
diff --git a/js/src/frontend/WhileEmitter.h b/js/src/frontend/WhileEmitter.h
new file mode 100644
index 0000000000..5bc7fecccb
--- /dev/null
+++ b/js/src/frontend/WhileEmitter.h
@@ -0,0 +1,90 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef frontend_WhileEmitter_h
+#define frontend_WhileEmitter_h
+
+#include "mozilla/Attributes.h"
+#include "mozilla/Maybe.h"
+
+#include <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(offset_of_while,
+// offset_of_body,
+// offset_of_end);
+// emit(cond);
+// wh.emitBody();
+// emit(body);
+// wh.emitEnd();
+//
+class MOZ_STACK_CLASS WhileEmitter {
+ BytecodeEmitter* bce_;
+
+ mozilla::Maybe<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_
+ [[nodiscard]] bool emitCond(uint32_t whilePos, uint32_t condPos,
+ uint32_t endPos);
+ [[nodiscard]] bool emitBody();
+ [[nodiscard]] bool emitEnd();
+};
+
+} /* namespace frontend */
+} /* namespace js */
+
+#endif /* frontend_WhileEmitter_h */
diff --git a/js/src/frontend/align_stack_comment.py b/js/src/frontend/align_stack_comment.py
new file mode 100755
index 0000000000..28d5d8cf7f
--- /dev/null
+++ b/js/src/frontend/align_stack_comment.py
@@ -0,0 +1,108 @@
+#!/usr/bin/python -B
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this file,
+# You can obtain one at http://mozilla.org/MPL/2.0/.
+
+
+""" Usage: align_stack_comment.py FILE
+
+ This script aligns the stack transition comment in BytecodeEmitter and
+ its helper classes.
+
+ The stack transition comment looks like the following:
+ // [stack] VAL1 VAL2 VAL3
+"""
+
+import re
+import sys
+
+# The column index of '[' of '[stack]'
+ALIGNMENT_COLUMN = 20
+
+# The maximum column for comment
+MAX_CHARS_PER_LINE = 80
+
+stack_comment_pat = re.compile("^( *//) *(\[stack\].*)$")
+
+
+def align_stack_comment(path):
+ lines = []
+ changed = False
+
+ with open(path) as f:
+ max_head_len = 0
+ max_comment_len = 0
+
+ line_num = 0
+
+ for line in f:
+ line_num += 1
+ # Python includes \n in lines.
+ line = line.rstrip("\n")
+
+ m = stack_comment_pat.search(line)
+ if m:
+ head = m.group(1) + " "
+ head_len = len(head)
+ comment = m.group(2)
+ comment_len = len(comment)
+
+ if head_len > ALIGNMENT_COLUMN:
+ print(
+ "Warning: line {} overflows from alignment column {}: {}".format(
+ line_num, ALIGNMENT_COLUMN, head_len
+ ),
+ file=sys.stderr,
+ )
+
+ line_len = max(head_len, ALIGNMENT_COLUMN) + comment_len
+ if line_len > MAX_CHARS_PER_LINE:
+ print(
+ "Warning: line {} overflows from {} chars: {}".format(
+ line_num, MAX_CHARS_PER_LINE, line_len
+ ),
+ file=sys.stderr,
+ )
+
+ max_head_len = max(max_head_len, head_len)
+ max_comment_len = max(max_comment_len, comment_len)
+
+ spaces = max(ALIGNMENT_COLUMN - head_len, 0)
+ formatted = head + " " * spaces + comment
+
+ if formatted != line:
+ changed = True
+
+ lines.append(formatted)
+ else:
+ lines.append(line)
+
+ print(
+ "Info: Minimum column number for [stack]: {}".format(max_head_len),
+ file=sys.stderr,
+ )
+ print(
+ "Info: Alignment column number for [stack]: {}".format(ALIGNMENT_COLUMN),
+ file=sys.stderr,
+ )
+ print(
+ "Info: Max length of stack transition comments: {}".format(max_comment_len),
+ file=sys.stderr,
+ )
+
+ if changed:
+ with open(path, "w") as f:
+ for line in lines:
+ print(line, file=f)
+ else:
+ print("No change.")
+
+
+if __name__ == "__main__":
+ if len(sys.argv) < 2:
+ print("Usage: align_stack_comment.py FILE", file=sys.stderr)
+ sys.exit(1)
+
+ for path in sys.argv[1:]:
+ print(path)
+ align_stack_comment(path)
diff --git a/js/src/frontend/moz.build b/js/src/frontend/moz.build
new file mode 100644
index 0000000000..0a820086a5
--- /dev/null
+++ b/js/src/frontend/moz.build
@@ -0,0 +1,98 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+FINAL_LIBRARY = "js"
+
+# Includes should be relative to parent path
+LOCAL_INCLUDES += ["!..", ".."]
+
+include("../js-config.mozbuild")
+include("../js-cxxflags.mozbuild")
+
+
+# Generate frontend/ReservedWordsGenerated.h from frontend/ReservedWords.h
+if CONFIG["ENABLE_DECORATORS"]:
+ GeneratedFile(
+ "ReservedWordsGenerated.h",
+ script="GenerateReservedWords.py",
+ inputs=["ReservedWords.h"],
+ flags=["--enable-decorators"],
+ )
+else:
+ GeneratedFile(
+ "ReservedWordsGenerated.h",
+ script="GenerateReservedWords.py",
+ inputs=["ReservedWords.h"],
+ )
+
+if CONFIG["JS_ENABLE_SMOOSH"]:
+ CbindgenHeader("smoosh_generated.h", inputs=["/js/src/frontend/smoosh"])
+
+UNIFIED_SOURCES += [
+ "AbstractScopePtr.cpp",
+ "AsyncEmitter.cpp",
+ "BytecodeCompiler.cpp",
+ "BytecodeControlStructures.cpp",
+ "BytecodeEmitter.cpp",
+ "BytecodeSection.cpp",
+ "CallOrNewEmitter.cpp",
+ "CForEmitter.cpp",
+ "CompileScript.cpp",
+ "DefaultEmitter.cpp",
+ "DoWhileEmitter.cpp",
+ "ElemOpEmitter.cpp",
+ "EmitterScope.cpp",
+ "ExpressionStatementEmitter.cpp",
+ "FoldConstants.cpp",
+ "ForInEmitter.cpp",
+ "ForOfEmitter.cpp",
+ "ForOfLoopControl.cpp",
+ "FrontendContext.cpp",
+ "FunctionEmitter.cpp",
+ "IfEmitter.cpp",
+ "JumpList.cpp",
+ "LabelEmitter.cpp",
+ "LexicalScopeEmitter.cpp",
+ "NameFunctions.cpp",
+ "NameOpEmitter.cpp",
+ "ObjectEmitter.cpp",
+ "ObjLiteral.cpp",
+ "OptionalEmitter.cpp",
+ "ParseContext.cpp",
+ "ParseNode.cpp",
+ "ParseNodeVerify.cpp",
+ "ParserAtom.cpp",
+ "PrivateOpEmitter.cpp",
+ "PropOpEmitter.cpp",
+ "SharedContext.cpp",
+ "SourceNotes.cpp",
+ "Stencil.cpp",
+ "StencilXdr.cpp",
+ "SwitchEmitter.cpp",
+ "TDZCheckCache.cpp",
+ "TokenStream.cpp",
+ "TryEmitter.cpp",
+ "WhileEmitter.cpp",
+]
+
+if CONFIG["JS_ENABLE_SMOOSH"]:
+ UNIFIED_SOURCES += [
+ "Frontend2.cpp",
+ ]
+
+if CONFIG["ENABLE_DECORATORS"]:
+ UNIFIED_SOURCES += [
+ "DecoratorEmitter.cpp",
+ ]
+
+# Parser.cpp cannot be built in unified mode because of explicit
+# template instantiations.
+SOURCES += [
+ "Parser.cpp",
+]
+
+if CONFIG["FUZZING_INTERFACES"] and CONFIG["LIBFUZZER"]:
+ include("/tools/fuzzing/libfuzzer-config.mozbuild")
diff --git a/js/src/frontend/smoosh/Cargo.toml b/js/src/frontend/smoosh/Cargo.toml
new file mode 100644
index 0000000000..260d62bcc9
--- /dev/null
+++ b/js/src/frontend/smoosh/Cargo.toml
@@ -0,0 +1,23 @@
+[package]
+name = "smoosh"
+version = "0.1.0"
+authors = ["The jsparagus Project Developers"]
+edition = "2018"
+license = "MIT/Apache-2.0"
+
+[dependencies]
+bumpalo = "3.4.0"
+log = "0.4"
+# Setup RUST_LOG logging.
+# Disable regex feature for code size.
+env_logger = {version = "0.10", default-features = false}
+# For non-jsparagus developers.
+jsparagus = { git = "https://github.com/mozilla-spidermonkey/jsparagus", rev = "64ba08e24749616de2344112f226d1ef4ba893ae" }
+# For local development, replace above with
+# jsparagus = { path = "{path to jsparagus}" }
+
+[build-dependencies]
+# For non-jsparagus developers.
+jsparagus = { git = "https://github.com/mozilla-spidermonkey/jsparagus", rev = "64ba08e24749616de2344112f226d1ef4ba893ae" }
+# For local development, replace above with
+# jsparagus = { path = "{path to jsparagus}" }
diff --git a/js/src/frontend/smoosh/build.rs b/js/src/frontend/smoosh/build.rs
new file mode 100644
index 0000000000..367c682f86
--- /dev/null
+++ b/js/src/frontend/smoosh/build.rs
@@ -0,0 +1,27 @@
+use jsparagus::stencil::opcode_info;
+
+fn compare(name: &str, orig: &str, copied: &str) {
+ if copied != orig {
+ panic!(
+ "{} is out of sync. \
+ It's possible that the bytecode generated by jsparagus is \
+ based on older opcodes. Please run \
+ update_stencil.py in jsparagus. \
+ You can disable this check by setting \
+ JS_SMOOSH_DISABLE_OPCODE_CHECK environment variable.",
+ name
+ );
+ }
+}
+
+fn main() {
+ if std::env::var("JS_SMOOSH_DISABLE_OPCODE_CHECK").is_ok() {
+ return;
+ }
+
+ compare(
+ "Opcodes.h",
+ include_str!("../../vm/Opcodes.h"),
+ opcode_info::get_opcodes_source(),
+ );
+}
diff --git a/js/src/frontend/smoosh/cbindgen.toml b/js/src/frontend/smoosh/cbindgen.toml
new file mode 100644
index 0000000000..71a9568b35
--- /dev/null
+++ b/js/src/frontend/smoosh/cbindgen.toml
@@ -0,0 +1,19 @@
+header = """/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */"""
+autogen_warning = """/* DO NOT MODIFY THIS MANUALLY! This file was generated using cbindgen. See RunCbindgen.py */
+
+#include "mozilla/Assertions.h" // MOZ_ASSERT
+"""
+include_version = true
+braces = "SameLine"
+line_length = 100
+tab_width = 2
+language = "C++"
+
+[enum]
+derive_helper_methods = true
+derive_const_casts = true
+derive_mut_casts = true
+
+cast_assert_name = "MOZ_ASSERT"
diff --git a/js/src/frontend/smoosh/moz.build b/js/src/frontend/smoosh/moz.build
new file mode 100644
index 0000000000..d75c4c18ba
--- /dev/null
+++ b/js/src/frontend/smoosh/moz.build
@@ -0,0 +1,15 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+FINAL_LIBRARY = "js"
+
+# Includes should be relative to parent path
+LOCAL_INCLUDES += ["!../..", "../.."]
+
+include("../../js-config.mozbuild")
+include("../../js-cxxflags.mozbuild")
+
+DIRS += ["../../rust"]
diff --git a/js/src/frontend/smoosh/src/lib.rs b/js/src/frontend/smoosh/src/lib.rs
new file mode 100644
index 0000000000..f663706533
--- /dev/null
+++ b/js/src/frontend/smoosh/src/lib.rs
@@ -0,0 +1,769 @@
+/* Copyright Mozilla Foundation
+ *
+ * Licensed under the Apache License, Version 2.0, or the MIT license,
+ * (the "Licenses") at your option. You may not use this file except in
+ * compliance with one of the Licenses. You may obtain copies of the
+ * Licenses at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * http://opensource.org/licenses/MIT
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the Licenses is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the Licenses for the specific language governing permissions and
+ * limitations under the Licenses.
+ */
+
+use bumpalo;
+use env_logger;
+use jsparagus::ast::source_atom_set::SourceAtomSet;
+use jsparagus::ast::source_slice_list::SourceSliceList;
+use jsparagus::ast::types::Program;
+use jsparagus::emitter::{emit, EmitError, EmitOptions};
+use jsparagus::parser::{parse_module, parse_script, ParseError, ParseOptions};
+use jsparagus::stencil::gcthings::GCThing;
+use jsparagus::stencil::regexp::RegExpItem;
+use jsparagus::stencil::result::EmitResult;
+use jsparagus::stencil::scope::{BindingName, ScopeData};
+use jsparagus::stencil::scope_notes::ScopeNote;
+use jsparagus::stencil::script::{ImmutableScriptData, ScriptStencil, SourceExtent};
+use std::boxed::Box;
+use std::cell::RefCell;
+use std::collections::HashMap;
+use std::convert::TryInto;
+use std::os::raw::{c_char, c_void};
+use std::rc::Rc;
+use std::{mem, slice, str};
+
+#[repr(C)]
+pub struct CVec<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);
+ }
+}