summaryrefslogtreecommitdiff
path: root/js/src/frontend
diff options
context:
space:
mode:
authorMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
committerMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
commit5f8de423f190bbb79a62f804151bc24824fa32d8 (patch)
tree10027f336435511475e392454359edea8e25895d /js/src/frontend
parent49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff)
downloaduxp-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz
Add m-esr52 at 52.6.0
Diffstat (limited to 'js/src/frontend')
-rw-r--r--js/src/frontend/BytecodeCompiler.cpp738
-rw-r--r--js/src/frontend/BytecodeCompiler.h108
-rw-r--r--js/src/frontend/BytecodeEmitter.cpp10170
-rw-r--r--js/src/frontend/BytecodeEmitter.h763
-rw-r--r--js/src/frontend/FoldConstants.cpp1928
-rw-r--r--js/src/frontend/FoldConstants.h41
-rw-r--r--js/src/frontend/FullParseHandler.h977
-rw-r--r--js/src/frontend/NameAnalysisTypes.h366
-rw-r--r--js/src/frontend/NameCollections.h338
-rw-r--r--js/src/frontend/NameFunctions.cpp838
-rw-r--r--js/src/frontend/NameFunctions.h28
-rw-r--r--js/src/frontend/ParseNode-inl.h35
-rw-r--r--js/src/frontend/ParseNode.cpp904
-rw-r--r--js/src/frontend/ParseNode.h1450
-rw-r--r--js/src/frontend/Parser.cpp9627
-rw-r--r--js/src/frontend/Parser.h1430
-rw-r--r--js/src/frontend/SharedContext.h639
-rw-r--r--js/src/frontend/SourceNotes.h207
-rw-r--r--js/src/frontend/SyntaxParseHandler.h599
-rw-r--r--js/src/frontend/TokenKind.h245
-rw-r--r--js/src/frontend/TokenStream.cpp1962
-rw-r--r--js/src/frontend/TokenStream.h1057
22 files changed, 34450 insertions, 0 deletions
diff --git a/js/src/frontend/BytecodeCompiler.cpp b/js/src/frontend/BytecodeCompiler.cpp
new file mode 100644
index 0000000000..d4c758b6c2
--- /dev/null
+++ b/js/src/frontend/BytecodeCompiler.cpp
@@ -0,0 +1,738 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sts=4 et sw=4 tw=99:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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/IntegerPrintfMacros.h"
+
+#include "jscntxt.h"
+#include "jsscript.h"
+
+#include "builtin/ModuleObject.h"
+#include "frontend/BytecodeEmitter.h"
+#include "frontend/FoldConstants.h"
+#include "frontend/NameFunctions.h"
+#include "frontend/Parser.h"
+#include "vm/GlobalObject.h"
+#include "vm/TraceLogging.h"
+#include "wasm/AsmJS.h"
+
+#include "jsobjinlines.h"
+#include "jsscriptinlines.h"
+
+#include "vm/EnvironmentObject-inl.h"
+
+using namespace js;
+using namespace js::frontend;
+using mozilla::Maybe;
+
+class MOZ_STACK_CLASS AutoCompilationTraceLogger
+{
+ public:
+ AutoCompilationTraceLogger(ExclusiveContext* cx, const TraceLoggerTextId id,
+ const ReadOnlyCompileOptions& options);
+
+ private:
+ TraceLoggerThread* logger;
+ TraceLoggerEvent event;
+ AutoTraceLog scriptLogger;
+ AutoTraceLog typeLogger;
+};
+
+// The BytecodeCompiler class contains resources common to compiling scripts and
+// function bodies.
+class MOZ_STACK_CLASS BytecodeCompiler
+{
+ public:
+ // Construct an object passing mandatory arguments.
+ BytecodeCompiler(ExclusiveContext* cx,
+ LifoAlloc& alloc,
+ const ReadOnlyCompileOptions& options,
+ SourceBufferHolder& sourceBuffer,
+ HandleScope enclosingScope,
+ TraceLoggerTextId logId);
+
+ // Call setters for optional arguments.
+ void maybeSetSourceCompressor(SourceCompressionTask* sourceCompressor);
+ void setSourceArgumentsNotIncluded();
+
+ JSScript* compileGlobalScript(ScopeKind scopeKind);
+ JSScript* compileEvalScript(HandleObject environment, HandleScope enclosingScope);
+ ModuleObject* compileModule();
+ bool compileFunctionBody(MutableHandleFunction fun, Handle<PropertyNameVector> formals,
+ GeneratorKind generatorKind, FunctionAsyncKind asyncKind);
+
+ ScriptSourceObject* sourceObjectPtr() const;
+
+ private:
+ JSScript* compileScript(HandleObject environment, SharedContext* sc);
+ bool checkLength();
+ bool createScriptSource();
+ bool maybeCompressSource();
+ bool canLazilyParse();
+ bool createParser();
+ bool createSourceAndParser();
+ bool createScript();
+ bool emplaceEmitter(Maybe<BytecodeEmitter>& emitter, SharedContext* sharedContext);
+ bool handleParseFailure(const Directives& newDirectives);
+ bool deoptimizeArgumentsInEnclosingScripts(JSContext* cx, HandleObject environment);
+ bool maybeCompleteCompressSource();
+
+ AutoCompilationTraceLogger traceLogger;
+ AutoKeepAtoms keepAtoms;
+
+ ExclusiveContext* cx;
+ LifoAlloc& alloc;
+ const ReadOnlyCompileOptions& options;
+ SourceBufferHolder& sourceBuffer;
+
+ RootedScope enclosingScope;
+ bool sourceArgumentsNotIncluded;
+
+ RootedScriptSource sourceObject;
+ ScriptSource* scriptSource;
+
+ Maybe<SourceCompressionTask> maybeSourceCompressor;
+ SourceCompressionTask* sourceCompressor;
+
+ Maybe<UsedNameTracker> usedNames;
+ Maybe<Parser<SyntaxParseHandler>> syntaxParser;
+ Maybe<Parser<FullParseHandler>> parser;
+
+ Directives directives;
+ TokenStream::Position startPosition;
+
+ RootedScript script;
+};
+
+AutoCompilationTraceLogger::AutoCompilationTraceLogger(ExclusiveContext* cx,
+ const TraceLoggerTextId id, const ReadOnlyCompileOptions& options)
+ : logger(cx->isJSContext() ? TraceLoggerForMainThread(cx->asJSContext()->runtime())
+ : TraceLoggerForCurrentThread()),
+ event(logger, TraceLogger_AnnotateScripts, options),
+ scriptLogger(logger, event),
+ typeLogger(logger, id)
+{}
+
+BytecodeCompiler::BytecodeCompiler(ExclusiveContext* cx,
+ LifoAlloc& alloc,
+ const ReadOnlyCompileOptions& options,
+ SourceBufferHolder& sourceBuffer,
+ HandleScope enclosingScope,
+ TraceLoggerTextId logId)
+ : traceLogger(cx, logId, options),
+ keepAtoms(cx->perThreadData),
+ cx(cx),
+ alloc(alloc),
+ options(options),
+ sourceBuffer(sourceBuffer),
+ enclosingScope(cx, enclosingScope),
+ sourceArgumentsNotIncluded(false),
+ sourceObject(cx),
+ scriptSource(nullptr),
+ sourceCompressor(nullptr),
+ directives(options.strictOption),
+ startPosition(keepAtoms),
+ script(cx)
+{
+ MOZ_ASSERT(sourceBuffer.get());
+}
+
+void
+BytecodeCompiler::maybeSetSourceCompressor(SourceCompressionTask* sourceCompressor)
+{
+ this->sourceCompressor = sourceCompressor;
+}
+
+void
+BytecodeCompiler::setSourceArgumentsNotIncluded()
+{
+ sourceArgumentsNotIncluded = true;
+}
+
+bool
+BytecodeCompiler::checkLength()
+{
+ // Note this limit is simply so we can store sourceStart and sourceEnd in
+ // JSScript as 32-bits. It could be lifted fairly easily, since the compiler
+ // is using size_t internally already.
+ if (sourceBuffer.length() > UINT32_MAX) {
+ if (cx->isJSContext())
+ JS_ReportErrorNumberASCII(cx->asJSContext(), GetErrorMessage, nullptr,
+ JSMSG_SOURCE_TOO_LONG);
+ return false;
+ }
+ return true;
+}
+
+bool
+BytecodeCompiler::createScriptSource()
+{
+ if (!checkLength())
+ return false;
+
+ sourceObject = CreateScriptSourceObject(cx, options);
+ if (!sourceObject)
+ return false;
+
+ scriptSource = sourceObject->source();
+ return true;
+}
+
+bool
+BytecodeCompiler::maybeCompressSource()
+{
+ if (!sourceCompressor) {
+ maybeSourceCompressor.emplace(cx);
+ sourceCompressor = maybeSourceCompressor.ptr();
+ }
+
+ if (!cx->compartment()->behaviors().discardSource()) {
+ if (options.sourceIsLazy) {
+ scriptSource->setSourceRetrievable();
+ } else if (!scriptSource->setSourceCopy(cx, sourceBuffer, sourceArgumentsNotIncluded,
+ sourceCompressor))
+ {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool
+BytecodeCompiler::canLazilyParse()
+{
+ return options.canLazilyParse &&
+ !(enclosingScope && enclosingScope->hasOnChain(ScopeKind::NonSyntactic)) &&
+ !cx->compartment()->behaviors().disableLazyParsing() &&
+ !cx->compartment()->behaviors().discardSource() &&
+ !options.sourceIsLazy &&
+ !cx->lcovEnabled();
+}
+
+bool
+BytecodeCompiler::createParser()
+{
+ usedNames.emplace(cx);
+ if (!usedNames->init())
+ return false;
+
+ if (canLazilyParse()) {
+ syntaxParser.emplace(cx, alloc, options, sourceBuffer.get(), sourceBuffer.length(),
+ /* foldConstants = */ false, *usedNames,
+ (Parser<SyntaxParseHandler>*) nullptr, (LazyScript*) nullptr);
+
+ if (!syntaxParser->checkOptions())
+ return false;
+ }
+
+ parser.emplace(cx, alloc, options, sourceBuffer.get(), sourceBuffer.length(),
+ /* foldConstants = */ true, *usedNames, syntaxParser.ptrOr(nullptr), nullptr);
+ parser->sct = sourceCompressor;
+ parser->ss = scriptSource;
+ if (!parser->checkOptions())
+ return false;
+
+ parser->tokenStream.tell(&startPosition);
+ return true;
+}
+
+bool
+BytecodeCompiler::createSourceAndParser()
+{
+ return createScriptSource() &&
+ maybeCompressSource() &&
+ createParser();
+}
+
+bool
+BytecodeCompiler::createScript()
+{
+ script = JSScript::Create(cx, options,
+ sourceObject, /* sourceStart = */ 0, sourceBuffer.length());
+ return script != nullptr;
+}
+
+bool
+BytecodeCompiler::emplaceEmitter(Maybe<BytecodeEmitter>& emitter, SharedContext* sharedContext)
+{
+ BytecodeEmitter::EmitterMode emitterMode =
+ options.selfHostingMode ? BytecodeEmitter::SelfHosting : BytecodeEmitter::Normal;
+ emitter.emplace(/* parent = */ nullptr, parser.ptr(), sharedContext, script,
+ /* lazyScript = */ nullptr, options.lineno, emitterMode);
+ return emitter->init();
+}
+
+bool
+BytecodeCompiler::handleParseFailure(const Directives& newDirectives)
+{
+ if (parser->hadAbortedSyntaxParse()) {
+ // Hit some unrecoverable ambiguity during an inner syntax parse.
+ // Syntax parsing has now been disabled in the parser, so retry
+ // the parse.
+ parser->clearAbortedSyntaxParse();
+ } else if (parser->tokenStream.hadError() || directives == newDirectives) {
+ return false;
+ }
+
+ parser->tokenStream.seek(startPosition);
+
+ // Assignment must be monotonic to prevent reparsing iloops
+ MOZ_ASSERT_IF(directives.strict(), newDirectives.strict());
+ MOZ_ASSERT_IF(directives.asmJS(), newDirectives.asmJS());
+ directives = newDirectives;
+ return true;
+}
+
+bool
+BytecodeCompiler::deoptimizeArgumentsInEnclosingScripts(JSContext* cx, HandleObject environment)
+{
+ RootedObject env(cx, environment);
+ while (env->is<EnvironmentObject>() || env->is<DebugEnvironmentProxy>()) {
+ if (env->is<CallObject>()) {
+ RootedScript script(cx, env->as<CallObject>().callee().getOrCreateScript(cx));
+ if (!script)
+ return false;
+ if (script->argumentsHasVarBinding()) {
+ if (!JSScript::argumentsOptimizationFailed(cx, script))
+ return false;
+ }
+ }
+ env = env->enclosingEnvironment();
+ }
+
+ return true;
+}
+
+bool
+BytecodeCompiler::maybeCompleteCompressSource()
+{
+ return !maybeSourceCompressor || maybeSourceCompressor->complete();
+}
+
+JSScript*
+BytecodeCompiler::compileScript(HandleObject environment, SharedContext* sc)
+{
+ if (!createSourceAndParser())
+ return nullptr;
+
+ if (!createScript())
+ return nullptr;
+
+ Maybe<BytecodeEmitter> emitter;
+ if (!emplaceEmitter(emitter, sc))
+ return nullptr;
+
+ for (;;) {
+ ParseNode* pn;
+ if (sc->isEvalContext())
+ pn = parser->evalBody(sc->asEvalContext());
+ else
+ pn = parser->globalBody(sc->asGlobalContext());
+
+ // Successfully parsed. Emit the script.
+ if (pn) {
+ if (sc->isEvalContext() && sc->hasDebuggerStatement() && cx->isJSContext()) {
+ // If the eval'ed script contains any debugger statement, force construction
+ // of arguments objects for the caller script and any other scripts it is
+ // transitively nested inside. The debugger can access any variable on the
+ // scope chain.
+ if (!deoptimizeArgumentsInEnclosingScripts(cx->asJSContext(), environment))
+ return nullptr;
+ }
+ if (!NameFunctions(cx, pn))
+ return nullptr;
+ if (!emitter->emitScript(pn))
+ return nullptr;
+ parser->handler.freeTree(pn);
+
+ break;
+ }
+
+ // Maybe we aborted a syntax parse. See if we can try again.
+ if (!handleParseFailure(directives))
+ return nullptr;
+
+ // Reset UsedNameTracker state before trying again.
+ usedNames->reset();
+ }
+
+ if (!maybeCompleteCompressSource())
+ return nullptr;
+
+ MOZ_ASSERT_IF(cx->isJSContext(), !cx->asJSContext()->isExceptionPending());
+
+ return script;
+}
+
+JSScript*
+BytecodeCompiler::compileGlobalScript(ScopeKind scopeKind)
+{
+ GlobalSharedContext globalsc(cx, scopeKind, directives, options.extraWarningsOption);
+ return compileScript(nullptr, &globalsc);
+}
+
+JSScript*
+BytecodeCompiler::compileEvalScript(HandleObject environment, HandleScope enclosingScope)
+{
+ EvalSharedContext evalsc(cx, environment, enclosingScope,
+ directives, options.extraWarningsOption);
+ return compileScript(environment, &evalsc);
+}
+
+ModuleObject*
+BytecodeCompiler::compileModule()
+{
+ if (!createSourceAndParser())
+ return nullptr;
+
+ Rooted<ModuleObject*> module(cx, ModuleObject::create(cx));
+ if (!module)
+ return nullptr;
+
+ if (!createScript())
+ return nullptr;
+
+ module->init(script);
+
+ ModuleBuilder builder(cx, module);
+ ModuleSharedContext modulesc(cx, module, enclosingScope, builder);
+ ParseNode* pn = parser->moduleBody(&modulesc);
+ if (!pn)
+ return nullptr;
+
+ if (!NameFunctions(cx, pn))
+ return nullptr;
+
+ Maybe<BytecodeEmitter> emitter;
+ if (!emplaceEmitter(emitter, &modulesc))
+ return nullptr;
+ if (!emitter->emitScript(pn->pn_body))
+ return nullptr;
+
+ parser->handler.freeTree(pn);
+
+ if (!builder.initModule())
+ return nullptr;
+
+ RootedModuleEnvironmentObject env(cx, ModuleEnvironmentObject::create(cx, module));
+ if (!env)
+ return nullptr;
+
+ module->setInitialEnvironment(env);
+
+ if (!maybeCompleteCompressSource())
+ return nullptr;
+
+ MOZ_ASSERT_IF(cx->isJSContext(), !cx->asJSContext()->isExceptionPending());
+ return module;
+}
+
+bool
+BytecodeCompiler::compileFunctionBody(MutableHandleFunction fun,
+ Handle<PropertyNameVector> formals,
+ GeneratorKind generatorKind,
+ FunctionAsyncKind asyncKind)
+{
+ MOZ_ASSERT(fun);
+ MOZ_ASSERT(fun->isTenured());
+
+ fun->setArgCount(formals.length());
+
+ if (!createSourceAndParser())
+ return false;
+
+ // 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.
+
+ ParseNode* fn;
+ do {
+ Directives newDirectives = directives;
+ fn = parser->standaloneFunctionBody(fun, enclosingScope, formals, generatorKind, asyncKind,
+ directives, &newDirectives);
+ if (!fn && !handleParseFailure(newDirectives))
+ return false;
+ } while (!fn);
+
+ if (!NameFunctions(cx, fn))
+ return false;
+
+ if (fn->pn_funbox->function()->isInterpreted()) {
+ MOZ_ASSERT(fun == fn->pn_funbox->function());
+
+ if (!createScript())
+ return false;
+
+ Maybe<BytecodeEmitter> emitter;
+ if (!emplaceEmitter(emitter, fn->pn_funbox))
+ return false;
+ if (!emitter->emitFunctionScript(fn->pn_body))
+ return false;
+ } else {
+ fun.set(fn->pn_funbox->function());
+ MOZ_ASSERT(IsAsmJSModule(fun));
+ }
+
+ if (!maybeCompleteCompressSource())
+ return false;
+
+ return true;
+}
+
+ScriptSourceObject*
+BytecodeCompiler::sourceObjectPtr() const
+{
+ return sourceObject.get();
+}
+
+ScriptSourceObject*
+frontend::CreateScriptSourceObject(ExclusiveContext* cx, const ReadOnlyCompileOptions& options)
+{
+ ScriptSource* ss = cx->new_<ScriptSource>();
+ if (!ss)
+ return nullptr;
+ ScriptSourceHolder ssHolder(ss);
+
+ if (!ss->initFromOptions(cx, options))
+ return nullptr;
+
+ RootedScriptSource sso(cx, ScriptSourceObject::create(cx, ss));
+ if (!sso)
+ return nullptr;
+
+ // Off-thread compilations do all their GC heap allocation, including the
+ // SSO, in a temporary compartment. Hence, for the SSO to refer to the
+ // gc-heap-allocated values in |options|, it would need cross-compartment
+ // wrappers from the temporary compartment to the real compartment --- which
+ // would then be inappropriate once we merged the temporary and real
+ // compartments.
+ //
+ // Instead, we put off populating those SSO slots in off-thread compilations
+ // until after we've merged compartments.
+ if (cx->isJSContext()) {
+ if (!ScriptSourceObject::initFromOptions(cx->asJSContext(), sso, options))
+ return nullptr;
+ }
+
+ return sso;
+}
+
+// CompileScript independently returns the ScriptSourceObject (SSO) for the
+// compile. This is used by off-main-thread script compilation (OMT-SC).
+//
+// OMT-SC cannot initialize the SSO when it is first constructed because the
+// SSO is allocated initially in a separate compartment.
+//
+// After OMT-SC, the separate compartment is merged with the main compartment,
+// at which point the JSScripts created become observable by the debugger via
+// memory-space scanning.
+//
+// Whatever happens to the top-level script compilation (even if it fails and
+// returns null), we must finish initializing the SSO. This is because there
+// may be valid inner scripts observable by the debugger which reference the
+// partially-initialized SSO.
+class MOZ_STACK_CLASS AutoInitializeSourceObject
+{
+ BytecodeCompiler& compiler_;
+ ScriptSourceObject** sourceObjectOut_;
+
+ public:
+ AutoInitializeSourceObject(BytecodeCompiler& compiler, ScriptSourceObject** sourceObjectOut)
+ : compiler_(compiler),
+ sourceObjectOut_(sourceObjectOut)
+ { }
+
+ ~AutoInitializeSourceObject() {
+ if (sourceObjectOut_)
+ *sourceObjectOut_ = compiler_.sourceObjectPtr();
+ }
+};
+
+JSScript*
+frontend::CompileGlobalScript(ExclusiveContext* cx, LifoAlloc& alloc, ScopeKind scopeKind,
+ const ReadOnlyCompileOptions& options,
+ SourceBufferHolder& srcBuf,
+ SourceCompressionTask* extraSct,
+ ScriptSourceObject** sourceObjectOut)
+{
+ MOZ_ASSERT(scopeKind == ScopeKind::Global || scopeKind == ScopeKind::NonSyntactic);
+ BytecodeCompiler compiler(cx, alloc, options, srcBuf, /* enclosingScope = */ nullptr,
+ TraceLogger_ParserCompileScript);
+ AutoInitializeSourceObject autoSSO(compiler, sourceObjectOut);
+ compiler.maybeSetSourceCompressor(extraSct);
+ return compiler.compileGlobalScript(scopeKind);
+}
+
+JSScript*
+frontend::CompileEvalScript(ExclusiveContext* cx, LifoAlloc& alloc,
+ HandleObject environment, HandleScope enclosingScope,
+ const ReadOnlyCompileOptions& options,
+ SourceBufferHolder& srcBuf,
+ SourceCompressionTask* extraSct,
+ ScriptSourceObject** sourceObjectOut)
+{
+ BytecodeCompiler compiler(cx, alloc, options, srcBuf, enclosingScope,
+ TraceLogger_ParserCompileScript);
+ AutoInitializeSourceObject autoSSO(compiler, sourceObjectOut);
+ compiler.maybeSetSourceCompressor(extraSct);
+ return compiler.compileEvalScript(environment, enclosingScope);
+}
+
+ModuleObject*
+frontend::CompileModule(ExclusiveContext* cx, const ReadOnlyCompileOptions& optionsInput,
+ SourceBufferHolder& srcBuf, LifoAlloc& alloc,
+ ScriptSourceObject** sourceObjectOut /* = nullptr */)
+{
+ MOZ_ASSERT(srcBuf.get());
+ MOZ_ASSERT_IF(sourceObjectOut, *sourceObjectOut == nullptr);
+
+ CompileOptions options(cx, optionsInput);
+ options.maybeMakeStrictMode(true); // ES6 10.2.1 Module code is always strict mode code.
+ options.setIsRunOnce(true);
+
+ RootedScope emptyGlobalScope(cx, &cx->global()->emptyGlobalScope());
+ BytecodeCompiler compiler(cx, alloc, options, srcBuf, emptyGlobalScope,
+ TraceLogger_ParserCompileModule);
+ AutoInitializeSourceObject autoSSO(compiler, sourceObjectOut);
+ return compiler.compileModule();
+}
+
+ModuleObject*
+frontend::CompileModule(JSContext* cx, const ReadOnlyCompileOptions& options,
+ SourceBufferHolder& srcBuf)
+{
+ if (!GlobalObject::ensureModulePrototypesCreated(cx, cx->global()))
+ return nullptr;
+
+ LifoAlloc& alloc = cx->asJSContext()->tempLifoAlloc();
+ RootedModuleObject module(cx, CompileModule(cx, options, srcBuf, alloc));
+ if (!module)
+ return nullptr;
+
+ // This happens in GlobalHelperThreadState::finishModuleParseTask() when a
+ // module is compiled off main thread.
+ if (!ModuleObject::Freeze(cx->asJSContext(), module))
+ return nullptr;
+
+ return module;
+}
+
+bool
+frontend::CompileLazyFunction(JSContext* cx, Handle<LazyScript*> lazy, const char16_t* chars, size_t length)
+{
+ MOZ_ASSERT(cx->compartment() == lazy->functionNonDelazifying()->compartment());
+
+ CompileOptions options(cx, lazy->version());
+ options.setMutedErrors(lazy->mutedErrors())
+ .setFileAndLine(lazy->filename(), lazy->lineno())
+ .setColumn(lazy->column())
+ .setNoScriptRval(false)
+ .setSelfHostingMode(false);
+
+ AutoCompilationTraceLogger traceLogger(cx, TraceLogger_ParserCompileLazy, options);
+
+ UsedNameTracker usedNames(cx);
+ if (!usedNames.init())
+ return false;
+ Parser<FullParseHandler> parser(cx, cx->tempLifoAlloc(), options, chars, length,
+ /* foldConstants = */ true, usedNames, nullptr, lazy);
+ if (!parser.checkOptions())
+ return false;
+
+ Rooted<JSFunction*> fun(cx, lazy->functionNonDelazifying());
+ MOZ_ASSERT(!lazy->isLegacyGenerator());
+ ParseNode* pn = parser.standaloneLazyFunction(fun, lazy->strict(), lazy->generatorKind(),
+ lazy->asyncKind());
+ if (!pn)
+ return false;
+
+ if (!NameFunctions(cx, pn))
+ return false;
+
+ RootedScriptSource sourceObject(cx, lazy->sourceObject());
+ MOZ_ASSERT(sourceObject);
+
+ Rooted<JSScript*> script(cx, JSScript::Create(cx, options, sourceObject,
+ lazy->begin(), lazy->end()));
+ if (!script)
+ return false;
+
+ if (lazy->isLikelyConstructorWrapper())
+ script->setLikelyConstructorWrapper();
+ if (lazy->hasBeenCloned())
+ script->setHasBeenCloned();
+
+ BytecodeEmitter bce(/* parent = */ nullptr, &parser, pn->pn_funbox, script, lazy,
+ pn->pn_pos, BytecodeEmitter::LazyFunction);
+ if (!bce.init())
+ return false;
+
+ return bce.emitFunctionScript(pn->pn_body);
+}
+
+// Compile a JS function body, which might appear as the value of an event
+// handler attribute in an HTML <INPUT> tag, or in a Function() constructor.
+static bool
+CompileFunctionBody(JSContext* cx, MutableHandleFunction fun, const ReadOnlyCompileOptions& options,
+ Handle<PropertyNameVector> formals, SourceBufferHolder& srcBuf,
+ HandleScope enclosingScope, GeneratorKind generatorKind,
+ FunctionAsyncKind asyncKind)
+{
+ MOZ_ASSERT(!options.isRunOnce);
+
+ // FIXME: make Function pass in two strings and parse them as arguments and
+ // ProgramElements respectively.
+
+ BytecodeCompiler compiler(cx, cx->tempLifoAlloc(), options, srcBuf, enclosingScope,
+ TraceLogger_ParserCompileFunction);
+ compiler.setSourceArgumentsNotIncluded();
+ return compiler.compileFunctionBody(fun, formals, generatorKind, asyncKind);
+}
+
+bool
+frontend::CompileFunctionBody(JSContext* cx, MutableHandleFunction fun,
+ const ReadOnlyCompileOptions& options,
+ Handle<PropertyNameVector> formals, JS::SourceBufferHolder& srcBuf,
+ HandleScope enclosingScope)
+{
+ return CompileFunctionBody(cx, fun, options, formals, srcBuf, enclosingScope, NotGenerator,
+ SyncFunction);
+}
+
+bool
+frontend::CompileFunctionBody(JSContext* cx, MutableHandleFunction fun,
+ const ReadOnlyCompileOptions& options,
+ Handle<PropertyNameVector> formals, JS::SourceBufferHolder& srcBuf)
+{
+ RootedScope emptyGlobalScope(cx, &cx->global()->emptyGlobalScope());
+ return CompileFunctionBody(cx, fun, options, formals, srcBuf, emptyGlobalScope,
+ NotGenerator, SyncFunction);
+}
+
+bool
+frontend::CompileStarGeneratorBody(JSContext* cx, MutableHandleFunction fun,
+ const ReadOnlyCompileOptions& options,
+ Handle<PropertyNameVector> formals,
+ JS::SourceBufferHolder& srcBuf)
+{
+ RootedScope emptyGlobalScope(cx, &cx->global()->emptyGlobalScope());
+ return CompileFunctionBody(cx, fun, options, formals, srcBuf, emptyGlobalScope,
+ StarGenerator, SyncFunction);
+}
+
+bool
+frontend::CompileAsyncFunctionBody(JSContext* cx, MutableHandleFunction fun,
+ const ReadOnlyCompileOptions& options,
+ Handle<PropertyNameVector> formals,
+ JS::SourceBufferHolder& srcBuf)
+{
+ RootedScope emptyGlobalScope(cx, &cx->global()->emptyGlobalScope());
+ return CompileFunctionBody(cx, fun, options, formals, srcBuf, emptyGlobalScope,
+ StarGenerator, AsyncFunction);
+}
diff --git a/js/src/frontend/BytecodeCompiler.h b/js/src/frontend/BytecodeCompiler.h
new file mode 100644
index 0000000000..1d86f11607
--- /dev/null
+++ b/js/src/frontend/BytecodeCompiler.h
@@ -0,0 +1,108 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sts=4 et sw=4 tw=99:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "NamespaceImports.h"
+
+#include "vm/Scope.h"
+#include "vm/String.h"
+
+class JSLinearString;
+
+namespace js {
+
+class LazyScript;
+class LifoAlloc;
+class ModuleObject;
+class ScriptSourceObject;
+struct SourceCompressionTask;
+
+namespace frontend {
+
+JSScript*
+CompileGlobalScript(ExclusiveContext* cx, LifoAlloc& alloc, ScopeKind scopeKind,
+ const ReadOnlyCompileOptions& options,
+ SourceBufferHolder& srcBuf,
+ SourceCompressionTask* extraSct = nullptr,
+ ScriptSourceObject** sourceObjectOut = nullptr);
+
+JSScript*
+CompileEvalScript(ExclusiveContext* cx, LifoAlloc& alloc,
+ HandleObject scopeChain, HandleScope enclosingScope,
+ const ReadOnlyCompileOptions& options,
+ SourceBufferHolder& srcBuf,
+ SourceCompressionTask* extraSct = nullptr,
+ ScriptSourceObject** sourceObjectOut = nullptr);
+
+ModuleObject*
+CompileModule(JSContext* cx, const ReadOnlyCompileOptions& options,
+ SourceBufferHolder& srcBuf);
+
+ModuleObject*
+CompileModule(ExclusiveContext* cx, const ReadOnlyCompileOptions& options,
+ SourceBufferHolder& srcBuf, LifoAlloc& alloc,
+ ScriptSourceObject** sourceObjectOut = nullptr);
+
+MOZ_MUST_USE bool
+CompileLazyFunction(JSContext* cx, Handle<LazyScript*> lazy, const char16_t* chars, size_t length);
+
+MOZ_MUST_USE bool
+CompileFunctionBody(JSContext* cx, MutableHandleFunction fun,
+ const ReadOnlyCompileOptions& options,
+ Handle<PropertyNameVector> formals, JS::SourceBufferHolder& srcBuf,
+ HandleScope enclosingScope);
+
+// As above, but defaults to the global lexical scope as the enclosing scope.
+MOZ_MUST_USE bool
+CompileFunctionBody(JSContext* cx, MutableHandleFunction fun,
+ const ReadOnlyCompileOptions& options,
+ Handle<PropertyNameVector> formals, JS::SourceBufferHolder& srcBuf);
+
+MOZ_MUST_USE bool
+CompileStarGeneratorBody(JSContext* cx, MutableHandleFunction fun,
+ const ReadOnlyCompileOptions& options,
+ Handle<PropertyNameVector> formals, JS::SourceBufferHolder& srcBuf);
+
+MOZ_MUST_USE bool
+CompileAsyncFunctionBody(JSContext* cx, MutableHandleFunction fun,
+ const ReadOnlyCompileOptions& options,
+ Handle<PropertyNameVector> formals, JS::SourceBufferHolder& srcBuf);
+
+ScriptSourceObject*
+CreateScriptSourceObject(ExclusiveContext* cx, const ReadOnlyCompileOptions& options);
+
+/*
+ * 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);
+
+/*
+ * As above, but taking chars + length.
+ */
+bool
+IsIdentifier(const char16_t* chars, size_t length);
+
+/* True if str is a keyword. Defined in TokenStream.cpp. */
+bool
+IsKeyword(JSLinearString* str);
+
+/* GC marking. Defined in Parser.cpp. */
+void
+MarkParser(JSTracer* trc, JS::AutoGCRooter* parser);
+
+} /* namespace frontend */
+} /* namespace js */
+
+#endif /* frontend_BytecodeCompiler_h */
diff --git a/js/src/frontend/BytecodeEmitter.cpp b/js/src/frontend/BytecodeEmitter.cpp
new file mode 100644
index 0000000000..1e9d8f2243
--- /dev/null
+++ b/js/src/frontend/BytecodeEmitter.cpp
@@ -0,0 +1,10170 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sts=4 et sw=4 tw=99:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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/ArrayUtils.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/FloatingPoint.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/PodOperations.h"
+
+#include <string.h>
+
+#include "jsapi.h"
+#include "jsatom.h"
+#include "jscntxt.h"
+#include "jsfun.h"
+#include "jsnum.h"
+#include "jsopcode.h"
+#include "jsscript.h"
+#include "jstypes.h"
+#include "jsutil.h"
+
+#include "frontend/Parser.h"
+#include "frontend/TokenStream.h"
+#include "vm/Debugger.h"
+#include "vm/GeneratorObject.h"
+#include "vm/Stack.h"
+#include "wasm/AsmJS.h"
+
+#include "jsatominlines.h"
+#include "jsobjinlines.h"
+#include "jsscriptinlines.h"
+
+#include "frontend/ParseNode-inl.h"
+#include "vm/EnvironmentObject-inl.h"
+#include "vm/NativeObject-inl.h"
+
+using namespace js;
+using namespace js::gc;
+using namespace js::frontend;
+
+using mozilla::AssertedCast;
+using mozilla::DebugOnly;
+using mozilla::Maybe;
+using mozilla::Nothing;
+using mozilla::NumberIsInt32;
+using mozilla::PodCopy;
+using mozilla::Some;
+
+class BreakableControl;
+class LabelControl;
+class LoopControl;
+class TryFinallyControl;
+
+static bool
+ParseNodeRequiresSpecialLineNumberNotes(ParseNode* pn)
+{
+ return pn->getKind() == PNK_WHILE || pn->getKind() == PNK_FOR;
+}
+
+// A cache that tracks superfluous TDZ checks.
+//
+// Each basic block should have a TDZCheckCache in scope. Some NestableControl
+// subclasses contain a TDZCheckCache.
+class BytecodeEmitter::TDZCheckCache : public Nestable<BytecodeEmitter::TDZCheckCache>
+{
+ PooledMapPtr<CheckTDZMap> cache_;
+
+ MOZ_MUST_USE bool ensureCache(BytecodeEmitter* bce) {
+ return cache_ || cache_.acquire(bce->cx);
+ }
+
+ public:
+ explicit TDZCheckCache(BytecodeEmitter* bce)
+ : Nestable<TDZCheckCache>(&bce->innermostTDZCheckCache),
+ cache_(bce->cx->frontendCollectionPool())
+ { }
+
+ Maybe<MaybeCheckTDZ> needsTDZCheck(BytecodeEmitter* bce, JSAtom* name);
+ MOZ_MUST_USE bool noteTDZCheck(BytecodeEmitter* bce, JSAtom* name, MaybeCheckTDZ check);
+};
+
+class BytecodeEmitter::NestableControl : public Nestable<BytecodeEmitter::NestableControl>
+{
+ StatementKind kind_;
+
+ // The innermost scope when this was pushed.
+ EmitterScope* emitterScope_;
+
+ protected:
+ NestableControl(BytecodeEmitter* bce, StatementKind kind)
+ : Nestable<NestableControl>(&bce->innermostNestableControl),
+ kind_(kind),
+ emitterScope_(bce->innermostEmitterScope)
+ { }
+
+ 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);
+ }
+};
+
+// Template specializations are disallowed in different namespaces; specialize
+// all the NestableControl subtypes up front.
+namespace js {
+namespace frontend {
+
+template <>
+bool
+BytecodeEmitter::NestableControl::is<BreakableControl>() const
+{
+ return StatementKindIsUnlabeledBreakTarget(kind_) || kind_ == StatementKind::Label;
+}
+
+template <>
+bool
+BytecodeEmitter::NestableControl::is<LabelControl>() const
+{
+ return kind_ == StatementKind::Label;
+}
+
+template <>
+bool
+BytecodeEmitter::NestableControl::is<LoopControl>() const
+{
+ return StatementKindIsLoop(kind_);
+}
+
+template <>
+bool
+BytecodeEmitter::NestableControl::is<TryFinallyControl>() const
+{
+ return kind_ == StatementKind::Try || kind_ == StatementKind::Finally;
+}
+
+} // namespace frontend
+} // namespace js
+
+class BreakableControl : public BytecodeEmitter::NestableControl
+{
+ public:
+ // Offset of the last break.
+ JumpList breaks;
+
+ BreakableControl(BytecodeEmitter* bce, StatementKind kind)
+ : NestableControl(bce, kind)
+ {
+ MOZ_ASSERT(is<BreakableControl>());
+ }
+
+ MOZ_MUST_USE bool patchBreaks(BytecodeEmitter* bce) {
+ return bce->emitJumpTargetAndPatch(breaks);
+ }
+};
+
+class LabelControl : public BreakableControl
+{
+ RootedAtom label_;
+
+ // The code offset when this was pushed. Used for effectfulness checking.
+ ptrdiff_t startOffset_;
+
+ public:
+ LabelControl(BytecodeEmitter* bce, JSAtom* label, ptrdiff_t startOffset)
+ : BreakableControl(bce, StatementKind::Label),
+ label_(bce->cx, label),
+ startOffset_(startOffset)
+ { }
+
+ HandleAtom label() const {
+ return label_;
+ }
+
+ ptrdiff_t startOffset() const {
+ return startOffset_;
+ }
+};
+
+class LoopControl : public BreakableControl
+{
+ // Loops' children are emitted in dominance order, so they can always
+ // have a TDZCheckCache.
+ BytecodeEmitter::TDZCheckCache tdzCache_;
+
+ // 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_;
+
+ // Can we OSR into Ion from here? True unless there is non-loop state on the stack.
+ bool canIonOsr_;
+
+ public:
+ // The target of continue statement jumps, e.g., the update portion of a
+ // for(;;) loop.
+ JumpTarget continueTarget;
+
+ // Offset of the last continue in the loop.
+ JumpList continues;
+
+ LoopControl(BytecodeEmitter* bce, StatementKind loopKind)
+ : BreakableControl(bce, loopKind),
+ tdzCache_(bce),
+ continueTarget({ -1 })
+ {
+ MOZ_ASSERT(is<LoopControl>());
+
+ LoopControl* enclosingLoop = findNearest<LoopControl>(enclosing());
+
+ stackDepth_ = bce->stackDepth;
+ loopDepth_ = enclosingLoop ? enclosingLoop->loopDepth_ + 1 : 1;
+
+ int loopSlots;
+ if (loopKind == StatementKind::Spread)
+ loopSlots = 3;
+ else if (loopKind == StatementKind::ForInLoop || loopKind == StatementKind::ForOfLoop)
+ loopSlots = 2;
+ else
+ loopSlots = 0;
+
+ MOZ_ASSERT(loopSlots <= stackDepth_);
+
+ if (enclosingLoop) {
+ canIonOsr_ = (enclosingLoop->canIonOsr_ &&
+ stackDepth_ == enclosingLoop->stackDepth_ + loopSlots);
+ } else {
+ canIonOsr_ = stackDepth_ == loopSlots;
+ }
+ }
+
+ uint32_t loopDepth() const {
+ return loopDepth_;
+ }
+
+ bool canIonOsr() const {
+ return canIonOsr_;
+ }
+
+ MOZ_MUST_USE bool patchBreaksAndContinues(BytecodeEmitter* bce) {
+ MOZ_ASSERT(continueTarget.offset != -1);
+ if (!patchBreaks(bce))
+ return false;
+ bce->patchJumpsToTarget(continues, continueTarget);
+ return true;
+ }
+};
+
+class TryFinallyControl : public BytecodeEmitter::NestableControl
+{
+ bool emittingSubroutine_;
+
+ public:
+ // The subroutine when emitting a finally block.
+ JumpList gosubs;
+
+ // Offset of the last catch guard, if any.
+ JumpList guardJump;
+
+ TryFinallyControl(BytecodeEmitter* bce, StatementKind kind)
+ : NestableControl(bce, kind),
+ emittingSubroutine_(false)
+ {
+ MOZ_ASSERT(is<TryFinallyControl>());
+ }
+
+ void setEmittingSubroutine() {
+ emittingSubroutine_ = true;
+ }
+
+ bool emittingSubroutine() const {
+ return emittingSubroutine_;
+ }
+};
+
+static bool
+ScopeKindIsInBody(ScopeKind kind)
+{
+ return kind == ScopeKind::Lexical ||
+ kind == ScopeKind::SimpleCatch ||
+ kind == ScopeKind::Catch ||
+ kind == ScopeKind::With ||
+ kind == ScopeKind::FunctionBodyVar ||
+ kind == ScopeKind::ParameterExpressionVar;
+}
+
+static inline void
+MarkAllBindingsClosedOver(LexicalScope::Data& data)
+{
+ BindingName* names = data.names;
+ for (uint32_t i = 0; i < data.length; i++)
+ names[i] = BindingName(names[i].name(), true);
+}
+
+// A scope that introduces bindings.
+class BytecodeEmitter::EmitterScope : public Nestable<BytecodeEmitter::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.
+ 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.
+ uint32_t scopeIndex_;
+
+ // If kind is Lexical, Catch, or With, the index in the BytecodeEmitter's
+ // block scope note list. Otherwise ScopeNote::NoScopeNote.
+ uint32_t noteIndex_;
+
+ MOZ_MUST_USE bool ensureCache(BytecodeEmitter* bce) {
+ return nameCache_.acquire(bce->cx);
+ }
+
+ template <typename BindingIter>
+ MOZ_MUST_USE bool checkSlotLimits(BytecodeEmitter* bce, const BindingIter& bi) {
+ if (bi.nextFrameSlot() >= LOCALNO_LIMIT ||
+ bi.nextEnvironmentSlot() >= ENVCOORD_SLOT_LIMIT)
+ {
+ return bce->reportError(nullptr, JSMSG_TOO_MANY_LOCALS);
+ }
+ return true;
+ }
+
+ MOZ_MUST_USE bool checkEnvironmentChainLength(BytecodeEmitter* bce) {
+ uint32_t hops;
+ if (EmitterScope* emitterScope = enclosing(&bce))
+ hops = emitterScope->environmentChainLength_;
+ else
+ hops = bce->sc->compilationEnclosingScope()->environmentChainLength();
+ if (hops >= ENVCOORD_HOPS_LIMIT - 1)
+ return bce->reportError(nullptr, JSMSG_TOO_DEEP, js_function_str);
+ environmentChainLength_ = mozilla::AssertedCast<uint8_t>(hops + 1);
+ return true;
+ }
+
+ void updateFrameFixedSlots(BytecodeEmitter* bce, const BindingIter& bi) {
+ nextFrameSlot_ = bi.nextFrameSlot();
+ if (nextFrameSlot_ > bce->maxFixedSlots)
+ bce->maxFixedSlots = nextFrameSlot_;
+ MOZ_ASSERT_IF(bce->sc->isFunctionBox() && bce->sc->asFunctionBox()->isGenerator(),
+ bce->maxFixedSlots == 0);
+ }
+
+ MOZ_MUST_USE bool putNameInCache(BytecodeEmitter* bce, JSAtom* name, NameLocation loc) {
+ NameLocationMap& cache = *nameCache_;
+ NameLocationMap::AddPtr p = cache.lookupForAdd(name);
+ MOZ_ASSERT(!p);
+ if (!cache.add(p, name, loc)) {
+ ReportOutOfMemory(bce->cx);
+ return false;
+ }
+ return true;
+ }
+
+ Maybe<NameLocation> lookupInCache(BytecodeEmitter* bce, JSAtom* name) {
+ if (NameLocationMap::Ptr p = nameCache_->lookup(name))
+ return Some(p->value().wrapped);
+ if (fallbackFreeNameLocation_ && nameCanBeFree(bce, name))
+ return fallbackFreeNameLocation_;
+ return Nothing();
+ }
+
+ friend bool BytecodeEmitter::needsImplicitThis();
+
+ 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)->innermostEmitterScope;
+ }
+
+ return nullptr;
+ }
+
+ Scope* enclosingScope(BytecodeEmitter* bce) const {
+ if (EmitterScope* es = enclosing(&bce))
+ return es->scope(bce);
+
+ // The enclosing script is already compiled or the current script is the
+ // global script.
+ return bce->sc->compilationEnclosingScope();
+ }
+
+ static bool nameCanBeFree(BytecodeEmitter* bce, JSAtom* name) {
+ // '.generator' cannot be accessed by name.
+ return name != bce->cx->names().dotGenerator;
+ }
+
+ static NameLocation searchInEnclosingScope(JSAtom* name, Scope* scope, uint8_t hops);
+ NameLocation searchAndCache(BytecodeEmitter* bce, JSAtom* name);
+
+ template <typename ScopeCreator>
+ MOZ_MUST_USE bool internScope(BytecodeEmitter* bce, ScopeCreator createScope);
+ template <typename ScopeCreator>
+ MOZ_MUST_USE bool internBodyScope(BytecodeEmitter* bce, ScopeCreator createScope);
+ MOZ_MUST_USE bool appendScopeNote(BytecodeEmitter* bce);
+
+ MOZ_MUST_USE bool deadZoneFrameSlotRange(BytecodeEmitter* bce, uint32_t slotStart,
+ uint32_t slotEnd);
+
+ public:
+ explicit EmitterScope(BytecodeEmitter* bce)
+ : Nestable<EmitterScope>(&bce->innermostEmitterScope),
+ nameCache_(bce->cx->frontendCollectionPool()),
+ hasEnvironment_(false),
+ environmentChainLength_(0),
+ nextFrameSlot_(0),
+ scopeIndex_(ScopeNote::NoScopeIndex),
+ noteIndex_(ScopeNote::NoScopeNoteIndex)
+ { }
+
+ void dump(BytecodeEmitter* bce);
+
+ MOZ_MUST_USE bool enterLexical(BytecodeEmitter* bce, ScopeKind kind,
+ Handle<LexicalScope::Data*> bindings);
+ MOZ_MUST_USE bool enterNamedLambda(BytecodeEmitter* bce, FunctionBox* funbox);
+ MOZ_MUST_USE bool enterComprehensionFor(BytecodeEmitter* bce,
+ Handle<LexicalScope::Data*> bindings);
+ MOZ_MUST_USE bool enterFunction(BytecodeEmitter* bce, FunctionBox* funbox);
+ MOZ_MUST_USE bool enterFunctionExtraBodyVar(BytecodeEmitter* bce, FunctionBox* funbox);
+ MOZ_MUST_USE bool enterParameterExpressionVar(BytecodeEmitter* bce);
+ MOZ_MUST_USE bool enterGlobal(BytecodeEmitter* bce, GlobalSharedContext* globalsc);
+ MOZ_MUST_USE bool enterEval(BytecodeEmitter* bce, EvalSharedContext* evalsc);
+ MOZ_MUST_USE bool enterModule(BytecodeEmitter* module, ModuleSharedContext* modulesc);
+ MOZ_MUST_USE bool enterWith(BytecodeEmitter* bce);
+ MOZ_MUST_USE bool deadZoneFrameSlots(BytecodeEmitter* bce);
+
+ MOZ_MUST_USE bool leave(BytecodeEmitter* bce, bool nonLocal = false);
+
+ uint32_t index() const {
+ MOZ_ASSERT(scopeIndex_ != ScopeNote::NoScopeIndex, "Did you forget to intern a Scope?");
+ return scopeIndex_;
+ }
+
+ uint32_t noteIndex() const {
+ return noteIndex_;
+ }
+
+ Scope* scope(const BytecodeEmitter* bce) const {
+ return bce->scopeList.vector[index()];
+ }
+
+ 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_;
+ }
+
+ uint32_t numFrameSlots() const {
+ return frameSlotEnd() - frameSlotStart();
+ }
+
+ EmitterScope* enclosingInFrame() const {
+ return Nestable<EmitterScope>::enclosing();
+ }
+
+ NameLocation lookup(BytecodeEmitter* bce, JSAtom* name) {
+ if (Maybe<NameLocation> loc = lookupInCache(bce, name))
+ return *loc;
+ return searchAndCache(bce, name);
+ }
+
+ Maybe<NameLocation> locationBoundInScope(BytecodeEmitter* bce, JSAtom* name,
+ EmitterScope* target);
+};
+
+void
+BytecodeEmitter::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();
+
+ JSAutoByteString bytes;
+ if (!AtomToPrintableString(bce->cx, r.front().key(), &bytes))
+ return;
+ if (l.kind() != NameLocation::Kind::Dynamic)
+ fprintf(stdout, " %s %s ", BindingKindString(l.bindingKind()), bytes.ptr());
+ else
+ fprintf(stdout, " %s ", bytes.ptr());
+
+ switch (l.kind()) {
+ case NameLocation::Kind::Dynamic:
+ fprintf(stdout, "dynamic\n");
+ break;
+ case NameLocation::Kind::Global:
+ fprintf(stdout, "global\n");
+ break;
+ case NameLocation::Kind::Intrinsic:
+ fprintf(stdout, "intrinsic\n");
+ break;
+ case NameLocation::Kind::NamedLambdaCallee:
+ fprintf(stdout, "named lambda callee\n");
+ break;
+ case NameLocation::Kind::Import:
+ fprintf(stdout, "import\n");
+ break;
+ case NameLocation::Kind::ArgumentSlot:
+ fprintf(stdout, "arg slot=%u\n", l.argumentSlot());
+ break;
+ case NameLocation::Kind::FrameSlot:
+ fprintf(stdout, "frame slot=%u\n", l.frameSlot());
+ break;
+ case NameLocation::Kind::EnvironmentCoordinate:
+ fprintf(stdout, "environment hops=%u slot=%u\n",
+ l.environmentCoordinate().hops(), l.environmentCoordinate().slot());
+ break;
+ case NameLocation::Kind::DynamicAnnexBVar:
+ fprintf(stdout, "dynamic annex b var\n");
+ break;
+ }
+ }
+
+ fprintf(stdout, "\n");
+}
+
+template <typename ScopeCreator>
+bool
+BytecodeEmitter::EmitterScope::internScope(BytecodeEmitter* bce, ScopeCreator createScope)
+{
+ RootedScope enclosing(bce->cx, enclosingScope(bce));
+ Scope* scope = createScope(bce->cx, enclosing);
+ if (!scope)
+ return false;
+ hasEnvironment_ = scope->hasEnvironment();
+ scopeIndex_ = bce->scopeList.length();
+ return bce->scopeList.append(scope);
+}
+
+template <typename ScopeCreator>
+bool
+BytecodeEmitter::EmitterScope::internBodyScope(BytecodeEmitter* bce, ScopeCreator createScope)
+{
+ MOZ_ASSERT(bce->bodyScopeIndex == UINT32_MAX, "There can be only one body scope");
+ bce->bodyScopeIndex = bce->scopeList.length();
+ return internScope(bce, createScope);
+}
+
+bool
+BytecodeEmitter::EmitterScope::appendScopeNote(BytecodeEmitter* bce)
+{
+ MOZ_ASSERT(ScopeKindIsInBody(scope(bce)->kind()) && enclosingInFrame(),
+ "Scope notes are not needed for body-level scopes.");
+ noteIndex_ = bce->scopeNoteList.length();
+ return bce->scopeNoteList.append(index(), bce->offset(), bce->inPrologue(),
+ enclosingInFrame() ? enclosingInFrame()->noteIndex()
+ : ScopeNote::NoScopeNoteIndex);
+}
+
+#ifdef DEBUG
+static bool
+NameIsOnEnvironment(Scope* scope, JSAtom* name)
+{
+ for (BindingIter bi(scope); bi; bi++) {
+ // If found, the name must already be on the environment or an import,
+ // or else there is a bug in the closed-over name analysis in the
+ // Parser.
+ if (bi.name() == name) {
+ BindingLocation::Kind kind = bi.location().kind();
+
+ if (bi.hasArgumentSlot()) {
+ JSScript* script = scope->as<FunctionScope>().script();
+ if (!script->strict() && !script->functionHasParameterExprs()) {
+ // Check for duplicate positional formal parameters.
+ for (BindingIter bi2(bi); bi2 && bi2.hasArgumentSlot(); bi2++) {
+ if (bi2.name() == name)
+ kind = bi2.location().kind();
+ }
+ }
+ }
+
+ return kind == BindingLocation::Kind::Global ||
+ kind == BindingLocation::Kind::Environment ||
+ kind == BindingLocation::Kind::Import;
+ }
+ }
+
+ // If not found, assume it's on the global or dynamically accessed.
+ return true;
+}
+#endif
+
+/* static */ NameLocation
+BytecodeEmitter::EmitterScope::searchInEnclosingScope(JSAtom* name, Scope* scope, uint8_t hops)
+{
+ for (ScopeIter si(scope); si; si++) {
+ MOZ_ASSERT(NameIsOnEnvironment(si.scope(), name));
+
+ bool hasEnv = si.hasSyntacticEnvironment();
+
+ switch (si.kind()) {
+ case ScopeKind::Function:
+ if (hasEnv) {
+ JSScript* script = si.scope()->as<FunctionScope>().script();
+ if (script->funHasExtensibleScope())
+ return NameLocation::Dynamic();
+
+ for (BindingIter bi(si.scope()); bi; bi++) {
+ if (bi.name() != name)
+ continue;
+
+ BindingLocation bindLoc = bi.location();
+ if (bi.hasArgumentSlot() &&
+ !script->strict() &&
+ !script->functionHasParameterExprs())
+ {
+ // Check for duplicate positional formal parameters.
+ for (BindingIter bi2(bi); bi2 && bi2.hasArgumentSlot(); bi2++) {
+ if (bi2.name() == name)
+ bindLoc = bi2.location();
+ }
+ }
+
+ MOZ_ASSERT(bindLoc.kind() == BindingLocation::Kind::Environment);
+ return NameLocation::EnvironmentCoordinate(bi.kind(), hops, bindLoc.slot());
+ }
+ }
+ break;
+
+ case ScopeKind::FunctionBodyVar:
+ case ScopeKind::ParameterExpressionVar:
+ case ScopeKind::Lexical:
+ case ScopeKind::NamedLambda:
+ case ScopeKind::StrictNamedLambda:
+ case ScopeKind::SimpleCatch:
+ case ScopeKind::Catch:
+ if (hasEnv) {
+ for (BindingIter bi(si.scope()); bi; bi++) {
+ if (bi.name() != name)
+ continue;
+
+ // The name must already have been marked as closed
+ // over. If this assertion is hit, there is a bug in the
+ // name analysis.
+ BindingLocation bindLoc = bi.location();
+ MOZ_ASSERT(bindLoc.kind() == BindingLocation::Kind::Environment);
+ return NameLocation::EnvironmentCoordinate(bi.kind(), hops, bindLoc.slot());
+ }
+ }
+ break;
+
+ case ScopeKind::Module:
+ if (hasEnv) {
+ for (BindingIter bi(si.scope()); bi; bi++) {
+ if (bi.name() != name)
+ continue;
+
+ BindingLocation bindLoc = bi.location();
+
+ // Imports are on the environment but are indirect
+ // bindings and must be accessed dynamically instead of
+ // using an EnvironmentCoordinate.
+ if (bindLoc.kind() == BindingLocation::Kind::Import) {
+ MOZ_ASSERT(si.kind() == ScopeKind::Module);
+ return NameLocation::Import();
+ }
+
+ MOZ_ASSERT(bindLoc.kind() == BindingLocation::Kind::Environment);
+ return NameLocation::EnvironmentCoordinate(bi.kind(), hops, bindLoc.slot());
+ }
+ }
+ break;
+
+ case ScopeKind::Eval:
+ case ScopeKind::StrictEval:
+ // As an optimization, if the eval doesn't have its own var
+ // environment and its immediate enclosing scope is a global
+ // scope, all accesses are global.
+ if (!hasEnv && si.scope()->enclosing()->is<GlobalScope>())
+ return NameLocation::Global(BindingKind::Var);
+ return NameLocation::Dynamic();
+
+ case ScopeKind::Global:
+ return NameLocation::Global(BindingKind::Var);
+
+ case ScopeKind::With:
+ case ScopeKind::NonSyntactic:
+ return NameLocation::Dynamic();
+ }
+
+ if (hasEnv) {
+ MOZ_ASSERT(hops < ENVCOORD_HOPS_LIMIT - 1);
+ hops++;
+ }
+ }
+
+ MOZ_CRASH("Malformed scope chain");
+}
+
+NameLocation
+BytecodeEmitter::EmitterScope::searchAndCache(BytecodeEmitter* bce, JSAtom* 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) {
+ inCurrentScript = false;
+ loc = Some(searchInEnclosingScope(name, bce->sc->compilationEnclosingScope(), hops));
+ }
+
+ // Each script has its own frame. A free name that is accessed
+ // from an inner script must not be a frame slot access. If this
+ // assertion is hit, it is a bug in the free name analysis in the
+ // parser.
+ MOZ_ASSERT_IF(!inCurrentScript, loc->kind() != NameLocation::Kind::FrameSlot);
+
+ // It is always correct to not cache the location. Ignore OOMs to make
+ // lookups infallible.
+ if (!putNameInCache(bce, name, *loc))
+ bce->cx->recoverFromOutOfMemory();
+
+ return *loc;
+}
+
+Maybe<NameLocation>
+BytecodeEmitter::EmitterScope::locationBoundInScope(BytecodeEmitter* bce, JSAtom* 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;
+}
+
+bool
+BytecodeEmitter::EmitterScope::deadZoneFrameSlotRange(BytecodeEmitter* bce, uint32_t slotStart,
+ uint32_t slotEnd)
+{
+ // 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.
+ if (slotStart != slotEnd) {
+ if (!bce->emit1(JSOP_UNINITIALIZED))
+ 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;
+}
+
+bool
+BytecodeEmitter::EmitterScope::deadZoneFrameSlots(BytecodeEmitter* bce)
+{
+ return deadZoneFrameSlotRange(bce, frameSlotStart(), frameSlotEnd());
+}
+
+bool
+BytecodeEmitter::EmitterScope::enterLexical(BytecodeEmitter* bce, ScopeKind kind,
+ Handle<LexicalScope::Data*> bindings)
+{
+ MOZ_ASSERT(kind != ScopeKind::NamedLambda && kind != ScopeKind::StrictNamedLambda);
+ MOZ_ASSERT(this == bce->innermostEmitterScope);
+
+ if (!ensureCache(bce))
+ return false;
+
+ // Marks all names as closed over if the the context requires it. This
+ // cannot be done in the Parser as we may not know if the context requires
+ // all bindings to be closed over until after parsing is finished. For
+ // example, legacy generators require all bindings to be closed over but
+ // it is unknown if a function is a legacy generator until the first
+ // 'yield' expression is parsed.
+ //
+ // This is not a problem with other scopes, as all other scopes with
+ // bindings are body-level. At the time of their creation, whether or not
+ // the context requires all bindings to be closed over is already known.
+ if (bce->sc->allBindingsClosedOver())
+ MarkAllBindingsClosedOver(*bindings);
+
+ // Resolve bindings.
+ TDZCheckCache* tdzCache = bce->innermostTDZCheckCache;
+ uint32_t firstFrameSlot = frameSlotStart();
+ BindingIter bi(*bindings, firstFrameSlot, /* isNamedLambda = */ false);
+ for (; bi; bi++) {
+ if (!checkSlotLimits(bce, bi))
+ return false;
+
+ NameLocation loc = NameLocation::fromBinding(bi.kind(), bi.location());
+ if (!putNameInCache(bce, bi.name(), loc))
+ return false;
+
+ if (!tdzCache->noteTDZCheck(bce, bi.name(), CheckTDZ))
+ return false;
+ }
+
+ updateFrameFixedSlots(bce, bi);
+
+ // Create and intern the VM scope.
+ auto createScope = [kind, bindings, firstFrameSlot](ExclusiveContext* cx,
+ HandleScope enclosing)
+ {
+ return LexicalScope::create(cx, kind, bindings, firstFrameSlot, enclosing);
+ };
+ if (!internScope(bce, createScope))
+ return false;
+
+ if (ScopeKindIsInBody(kind) && hasEnvironment()) {
+ // After interning the VM scope we can get the scope index.
+ if (!bce->emitInternedScopeOp(index(), JSOP_PUSHLEXICALENV))
+ return false;
+ }
+
+ // Lexical scopes need notes to be mapped from a pc.
+ if (!appendScopeNote(bce))
+ return false;
+
+ // Put frame slots in TDZ. Environment slots are poisoned during
+ // environment creation.
+ //
+ // This must be done after appendScopeNote to be considered in the extent
+ // of the scope.
+ if (!deadZoneFrameSlotRange(bce, firstFrameSlot, frameSlotEnd()))
+ return false;
+
+ return checkEnvironmentChainLength(bce);
+}
+
+bool
+BytecodeEmitter::EmitterScope::enterNamedLambda(BytecodeEmitter* bce, FunctionBox* funbox)
+{
+ MOZ_ASSERT(this == bce->innermostEmitterScope);
+ MOZ_ASSERT(funbox->namedLambdaBindings());
+
+ if (!ensureCache(bce))
+ return false;
+
+ // See comment in enterLexical about allBindingsClosedOver.
+ if (funbox->allBindingsClosedOver())
+ MarkAllBindingsClosedOver(*funbox->namedLambdaBindings());
+
+ BindingIter bi(*funbox->namedLambdaBindings(), LOCALNO_LIMIT, /* isNamedLambda = */ true);
+ MOZ_ASSERT(bi.kind() == BindingKind::NamedLambdaCallee);
+
+ // The lambda name, if not closed over, is accessed via JSOP_CALLEE and
+ // not a frame slot. Do not update frame slot information.
+ NameLocation loc = NameLocation::fromBinding(bi.kind(), bi.location());
+ if (!putNameInCache(bce, bi.name(), loc))
+ return false;
+
+ bi++;
+ MOZ_ASSERT(!bi, "There should be exactly one binding in a NamedLambda scope");
+
+ auto createScope = [funbox](ExclusiveContext* cx, HandleScope enclosing) {
+ ScopeKind scopeKind =
+ funbox->strict() ? ScopeKind::StrictNamedLambda : ScopeKind::NamedLambda;
+ return LexicalScope::create(cx, scopeKind, funbox->namedLambdaBindings(),
+ LOCALNO_LIMIT, enclosing);
+ };
+ if (!internScope(bce, createScope))
+ return false;
+
+ return checkEnvironmentChainLength(bce);
+}
+
+bool
+BytecodeEmitter::EmitterScope::enterComprehensionFor(BytecodeEmitter* bce,
+ Handle<LexicalScope::Data*> bindings)
+{
+ if (!enterLexical(bce, ScopeKind::Lexical, bindings))
+ return false;
+
+ // For comprehensions, initialize all lexical names up front to undefined
+ // because they're now a dead feature and don't interact properly with
+ // TDZ.
+ auto nop = [](BytecodeEmitter*, const NameLocation&, bool) {
+ return true;
+ };
+
+ if (!bce->emit1(JSOP_UNDEFINED))
+ return false;
+
+ RootedAtom name(bce->cx);
+ for (BindingIter bi(*bindings, frameSlotStart(), /* isNamedLambda = */ false); bi; bi++) {
+ name = bi.name();
+ if (!bce->emitInitializeName(name, nop))
+ return false;
+ }
+
+ if (!bce->emit1(JSOP_POP))
+ return false;
+
+ return true;
+}
+
+bool
+BytecodeEmitter::EmitterScope::enterParameterExpressionVar(BytecodeEmitter* bce)
+{
+ MOZ_ASSERT(this == bce->innermostEmitterScope);
+
+ if (!ensureCache(bce))
+ return false;
+
+ // Parameter expressions var scopes have no pre-set bindings and are
+ // always extensible, as they are needed for eval.
+ fallbackFreeNameLocation_ = Some(NameLocation::Dynamic());
+
+ // Create and intern the VM scope.
+ uint32_t firstFrameSlot = frameSlotStart();
+ auto createScope = [firstFrameSlot](ExclusiveContext* cx, HandleScope enclosing) {
+ return VarScope::create(cx, ScopeKind::ParameterExpressionVar,
+ /* data = */ nullptr, firstFrameSlot,
+ /* needsEnvironment = */ true, enclosing);
+ };
+ if (!internScope(bce, createScope))
+ return false;
+
+ MOZ_ASSERT(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
+BytecodeEmitter::EmitterScope::enterFunction(BytecodeEmitter* bce, FunctionBox* funbox)
+{
+ MOZ_ASSERT(this == bce->innermostEmitterScope);
+
+ // If there are parameter expressions, there is an extra var scope.
+ if (!funbox->hasExtraBodyVarScope())
+ bce->setVarEmitterScope(this);
+
+ if (!ensureCache(bce))
+ return false;
+
+ // Resolve body-level bindings, if there are any.
+ auto bindings = funbox->functionScopeBindings();
+ Maybe<uint32_t> lastLexicalSlot;
+ if (bindings) {
+ NameLocationMap& cache = *nameCache_;
+
+ BindingIter bi(*bindings, funbox->hasParameterExprs);
+ for (; bi; bi++) {
+ if (!checkSlotLimits(bce, bi))
+ return false;
+
+ NameLocation loc = NameLocation::fromBinding(bi.kind(), bi.location());
+ NameLocationMap::AddPtr p = cache.lookupForAdd(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->function()->hasRest());
+ p->value() = loc;
+ continue;
+ }
+
+ if (!cache.add(p, bi.name(), loc)) {
+ ReportOutOfMemory(bce->cx);
+ return false;
+ }
+ }
+
+ updateFrameFixedSlots(bce, bi);
+ } else {
+ nextFrameSlot_ = 0;
+ }
+
+ // If the function's scope may be extended at runtime due to sloppy direct
+ // eval and there is no extra var scope, 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->hasParameterExprs && funbox->hasExtensibleScope())
+ fallbackFreeNameLocation_ = Some(NameLocation::Dynamic());
+
+ // In case of parameter expressions, the parameters are lexical
+ // bindings and have TDZ.
+ if (funbox->hasParameterExprs && nextFrameSlot_) {
+ uint32_t paramFrameSlotEnd = 0;
+ for (BindingIter bi(*bindings, true); bi; bi++) {
+ if (!BindingKindIsLexical(bi.kind()))
+ break;
+
+ NameLocation loc = NameLocation::fromBinding(bi.kind(), bi.location());
+ if (loc.kind() == NameLocation::Kind::FrameSlot) {
+ MOZ_ASSERT(paramFrameSlotEnd <= loc.frameSlot());
+ paramFrameSlotEnd = loc.frameSlot() + 1;
+ }
+ }
+
+ if (!deadZoneFrameSlotRange(bce, 0, paramFrameSlotEnd))
+ return false;
+ }
+
+ // Create and intern the VM scope.
+ auto createScope = [funbox](ExclusiveContext* cx, HandleScope enclosing) {
+ RootedFunction fun(cx, funbox->function());
+ return FunctionScope::create(cx, funbox->functionScopeBindings(),
+ funbox->hasParameterExprs,
+ funbox->needsCallObjectRegardlessOfBindings(),
+ fun, enclosing);
+ };
+ if (!internBodyScope(bce, createScope))
+ return false;
+
+ return checkEnvironmentChainLength(bce);
+}
+
+bool
+BytecodeEmitter::EmitterScope::enterFunctionExtraBodyVar(BytecodeEmitter* bce, FunctionBox* funbox)
+{
+ MOZ_ASSERT(funbox->hasParameterExprs);
+ MOZ_ASSERT(funbox->extraVarScopeBindings() ||
+ funbox->needsExtraBodyVarEnvironmentRegardlessOfBindings());
+ MOZ_ASSERT(this == bce->innermostEmitterScope);
+
+ // 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()) {
+ BindingIter bi(*bindings, firstFrameSlot);
+ for (; bi; bi++) {
+ if (!checkSlotLimits(bce, bi))
+ return false;
+
+ NameLocation loc = NameLocation::fromBinding(bi.kind(), bi.location());
+ if (!putNameInCache(bce, bi.name(), loc))
+ return false;
+ }
+
+ updateFrameFixedSlots(bce, bi);
+ } 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->hasExtensibleScope())
+ fallbackFreeNameLocation_ = Some(NameLocation::Dynamic());
+
+ // Create and intern the VM scope.
+ auto createScope = [funbox, firstFrameSlot](ExclusiveContext* cx, HandleScope enclosing) {
+ return VarScope::create(cx, ScopeKind::FunctionBodyVar,
+ funbox->extraVarScopeBindings(), firstFrameSlot,
+ funbox->needsExtraBodyVarEnvironmentRegardlessOfBindings(),
+ enclosing);
+ };
+ if (!internScope(bce, createScope))
+ return false;
+
+ if (hasEnvironment()) {
+ if (!bce->emitInternedScopeOp(index(), JSOP_PUSHVARENV))
+ return false;
+ }
+
+ // The extra var scope needs a note to be mapped from a pc.
+ if (!appendScopeNote(bce))
+ return false;
+
+ return checkEnvironmentChainLength(bce);
+}
+
+class DynamicBindingIter : public BindingIter
+{
+ public:
+ explicit DynamicBindingIter(GlobalSharedContext* sc)
+ : BindingIter(*sc->bindings)
+ { }
+
+ explicit DynamicBindingIter(EvalSharedContext* sc)
+ : BindingIter(*sc->bindings, /* strict = */ false)
+ {
+ MOZ_ASSERT(!sc->strict());
+ }
+
+ JSOp bindingOp() const {
+ switch (kind()) {
+ case BindingKind::Var:
+ return JSOP_DEFVAR;
+ case BindingKind::Let:
+ return JSOP_DEFLET;
+ case BindingKind::Const:
+ return JSOP_DEFCONST;
+ default:
+ MOZ_CRASH("Bad BindingKind");
+ }
+ }
+};
+
+bool
+BytecodeEmitter::EmitterScope::enterGlobal(BytecodeEmitter* bce, GlobalSharedContext* globalsc)
+{
+ MOZ_ASSERT(this == bce->innermostEmitterScope);
+
+ 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());
+
+ auto createScope = [](ExclusiveContext* cx, HandleScope enclosing) {
+ MOZ_ASSERT(!enclosing);
+ return &cx->global()->emptyGlobalScope();
+ };
+ return internBodyScope(bce, createScope);
+ }
+
+ // Resolve binding names and emit DEF{VAR,LET,CONST} prologue ops.
+ if (globalsc->bindings) {
+ for (DynamicBindingIter bi(globalsc); bi; bi++) {
+ NameLocation loc = NameLocation::fromBinding(bi.kind(), bi.location());
+ JSAtom* name = bi.name();
+ if (!putNameInCache(bce, name, loc))
+ return false;
+
+ // Define the name in the prologue. Do not emit DEFVAR for
+ // functions that we'll emit DEFFUN for.
+ if (bi.isTopLevelFunction())
+ continue;
+
+ if (!bce->emitAtomOp(name, bi.bindingOp()))
+ 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());
+
+ auto createScope = [globalsc](ExclusiveContext* cx, HandleScope enclosing) {
+ MOZ_ASSERT(!enclosing);
+ return GlobalScope::create(cx, globalsc->scopeKind(), globalsc->bindings);
+ };
+ return internBodyScope(bce, createScope);
+}
+
+bool
+BytecodeEmitter::EmitterScope::enterEval(BytecodeEmitter* bce, EvalSharedContext* evalsc)
+{
+ MOZ_ASSERT(this == bce->innermostEmitterScope);
+
+ bce->setVarEmitterScope(this);
+
+ if (!ensureCache(bce))
+ return false;
+
+ // For simplicity, treat all free name lookups in eval scripts as dynamic.
+ fallbackFreeNameLocation_ = Some(NameLocation::Dynamic());
+
+ // Create the `var` scope. Note that there is also a lexical scope, created
+ // separately in emitScript().
+ auto createScope = [evalsc](ExclusiveContext* cx, HandleScope enclosing) {
+ ScopeKind scopeKind = evalsc->strict() ? ScopeKind::StrictEval : ScopeKind::Eval;
+ return EvalScope::create(cx, scopeKind, evalsc->bindings, enclosing);
+ };
+ if (!internBodyScope(bce, createScope))
+ return false;
+
+ if (hasEnvironment()) {
+ if (!bce->emitInternedScopeOp(index(), JSOP_PUSHVARENV))
+ return false;
+ } else {
+ // Resolve binding names and emit DEFVAR prologue ops if we don't have
+ // an environment (i.e., a sloppy eval not in a parameter expression).
+ // Eval scripts always have their own lexical scope, but non-strict
+ // scopes may introduce 'var' bindings to the nearest var scope.
+ //
+ // TODO: We may optimize strict eval bindings in the future to be on
+ // the frame. For now, handle everything dynamically.
+ if (!hasEnvironment() && evalsc->bindings) {
+ for (DynamicBindingIter bi(evalsc); bi; bi++) {
+ MOZ_ASSERT(bi.bindingOp() == JSOP_DEFVAR);
+
+ if (bi.isTopLevelFunction())
+ continue;
+
+ if (!bce->emitAtomOp(bi.name(), JSOP_DEFVAR))
+ return false;
+ }
+ }
+
+ // As an optimization, if the eval does not have its own var
+ // environment and is directly enclosed in a global scope, then all
+ // free name lookups are global.
+ if (scope(bce)->enclosing()->is<GlobalScope>())
+ fallbackFreeNameLocation_ = Some(NameLocation::Global(BindingKind::Var));
+ }
+
+ return true;
+}
+
+bool
+BytecodeEmitter::EmitterScope::enterModule(BytecodeEmitter* bce, ModuleSharedContext* modulesc)
+{
+ MOZ_ASSERT(this == bce->innermostEmitterScope);
+
+ 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::Data* bindings = modulesc->bindings) {
+ BindingIter bi(*bindings);
+ for (; bi; bi++) {
+ if (!checkSlotLimits(bce, bi))
+ return false;
+
+ NameLocation loc = NameLocation::fromBinding(bi.kind(), bi.location());
+ 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.
+ auto createScope = [modulesc](ExclusiveContext* cx, HandleScope enclosing) {
+ return ModuleScope::create(cx, modulesc->bindings, modulesc->module(), enclosing);
+ };
+ if (!internBodyScope(bce, createScope))
+ return false;
+
+ return checkEnvironmentChainLength(bce);
+}
+
+bool
+BytecodeEmitter::EmitterScope::enterWith(BytecodeEmitter* bce)
+{
+ MOZ_ASSERT(this == bce->innermostEmitterScope);
+
+ if (!ensureCache(bce))
+ return false;
+
+ // 'with' make all accesses dynamic and unanalyzable.
+ fallbackFreeNameLocation_ = Some(NameLocation::Dynamic());
+
+ auto createScope = [](ExclusiveContext* cx, HandleScope enclosing) {
+ return WithScope::create(cx, enclosing);
+ };
+ if (!internScope(bce, createScope))
+ return false;
+
+ if (!bce->emitInternedScopeOp(index(), JSOP_ENTERWITH))
+ return false;
+
+ if (!appendScopeNote(bce))
+ return false;
+
+ return checkEnvironmentChainLength(bce);
+}
+
+bool
+BytecodeEmitter::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->innermostEmitterScope);
+
+ ScopeKind kind = scope(bce)->kind();
+ switch (kind) {
+ case ScopeKind::Lexical:
+ case ScopeKind::SimpleCatch:
+ case ScopeKind::Catch:
+ if (!bce->emit1(hasEnvironment() ? JSOP_POPLEXICALENV : JSOP_DEBUGLEAVELEXICALENV))
+ return false;
+ break;
+
+ case ScopeKind::With:
+ if (!bce->emit1(JSOP_LEAVEWITH))
+ return false;
+ break;
+
+ case ScopeKind::ParameterExpressionVar:
+ MOZ_ASSERT(hasEnvironment());
+ if (!bce->emit1(JSOP_POPVARENV))
+ 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;
+ }
+
+ // 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)) {
+ // The extra function var scope is never popped once it's pushed,
+ // so its scope note extends until the end of any possible code.
+ uint32_t offset = kind == ScopeKind::FunctionBodyVar ? UINT32_MAX : bce->offset();
+ bce->scopeNoteList.recordEnd(noteIndex_, offset, bce->inPrologue());
+ }
+ }
+
+ return true;
+}
+
+Maybe<MaybeCheckTDZ>
+BytecodeEmitter::TDZCheckCache::needsTDZCheck(BytecodeEmitter* bce, JSAtom* name)
+{
+ if (!ensureCache(bce))
+ return Nothing();
+
+ CheckTDZMap::AddPtr p = cache_->lookupForAdd(name);
+ if (p)
+ return Some(p->value().wrapped);
+
+ MaybeCheckTDZ rv = CheckTDZ;
+ for (TDZCheckCache* it = enclosing(); it; it = it->enclosing()) {
+ if (it->cache_) {
+ if (CheckTDZMap::Ptr p2 = it->cache_->lookup(name)) {
+ rv = p2->value();
+ break;
+ }
+ }
+ }
+
+ if (!cache_->add(p, name, rv)) {
+ ReportOutOfMemory(bce->cx);
+ return Nothing();
+ }
+
+ return Some(rv);
+}
+
+bool
+BytecodeEmitter::TDZCheckCache::noteTDZCheck(BytecodeEmitter* bce, JSAtom* 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))
+ return false;
+ }
+
+ return true;
+}
+
+BytecodeEmitter::BytecodeEmitter(BytecodeEmitter* parent,
+ Parser<FullParseHandler>* parser, SharedContext* sc,
+ HandleScript script, Handle<LazyScript*> lazyScript,
+ uint32_t lineNum, EmitterMode emitterMode)
+ : sc(sc),
+ cx(sc->context),
+ parent(parent),
+ script(cx, script),
+ lazyScript(cx, lazyScript),
+ prologue(cx, lineNum),
+ main(cx, lineNum),
+ current(&main),
+ parser(parser),
+ atomIndices(cx->frontendCollectionPool()),
+ firstLine(lineNum),
+ maxFixedSlots(0),
+ maxStackDepth(0),
+ stackDepth(0),
+ arrayCompDepth(0),
+ emitLevel(0),
+ bodyScopeIndex(UINT32_MAX),
+ varEmitterScope(nullptr),
+ innermostNestableControl(nullptr),
+ innermostEmitterScope(nullptr),
+ innermostTDZCheckCache(nullptr),
+ constList(cx),
+ scopeList(cx),
+ tryNoteList(cx),
+ scopeNoteList(cx),
+ yieldOffsetList(cx),
+ typesetCount(0),
+ hasSingletons(false),
+ hasTryFinally(false),
+ emittingRunOnceLambda(false),
+ emitterMode(emitterMode),
+ functionBodyEndPosSet(false)
+{
+ MOZ_ASSERT_IF(emitterMode == LazyFunction, lazyScript);
+}
+
+BytecodeEmitter::BytecodeEmitter(BytecodeEmitter* parent,
+ Parser<FullParseHandler>* parser, SharedContext* sc,
+ HandleScript script, Handle<LazyScript*> lazyScript,
+ TokenPos bodyPosition, EmitterMode emitterMode)
+ : BytecodeEmitter(parent, parser, sc, script, lazyScript,
+ parser->tokenStream.srcCoords.lineNum(bodyPosition.begin),
+ emitterMode)
+{
+ setFunctionBodyEndPos(bodyPosition);
+}
+
+bool
+BytecodeEmitter::init()
+{
+ return atomIndices.acquire(cx);
+}
+
+template <typename Predicate /* (NestableControl*) -> bool */>
+BytecodeEmitter::NestableControl*
+BytecodeEmitter::findInnermostNestableControl(Predicate predicate) const
+{
+ return NestableControl::findNearest(innermostNestableControl, predicate);
+}
+
+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(JSAtom* name)
+{
+ return innermostEmitterScope->lookup(this, name);
+}
+
+Maybe<NameLocation>
+BytecodeEmitter::locationOfNameBoundInScope(JSAtom* name, EmitterScope* target)
+{
+ return innermostEmitterScope->locationBoundInScope(this, name, target);
+}
+
+Maybe<NameLocation>
+BytecodeEmitter::locationOfNameBoundInFunctionScope(JSAtom* name, EmitterScope* source)
+{
+ EmitterScope* funScope = source;
+ while (!funScope->scope(this)->is<FunctionScope>())
+ funScope = funScope->enclosingInFrame();
+ return source->locationBoundInScope(this, name, funScope);
+}
+
+bool
+BytecodeEmitter::emitCheck(ptrdiff_t delta, ptrdiff_t* offset)
+{
+ *offset = code().length();
+
+ // Start it off moderately large to avoid repeated resizings early on.
+ // ~98% of cases fit within 1024 bytes.
+ if (code().capacity() == 0 && !code().reserve(1024))
+ return false;
+
+ if (!code().growBy(delta)) {
+ ReportOutOfMemory(cx);
+ return false;
+ }
+ return true;
+}
+
+void
+BytecodeEmitter::updateDepth(ptrdiff_t target)
+{
+ jsbytecode* pc = code(target);
+
+ int nuses = StackUses(nullptr, pc);
+ int ndefs = StackDefs(nullptr, pc);
+
+ stackDepth -= nuses;
+ MOZ_ASSERT(stackDepth >= 0);
+ stackDepth += ndefs;
+
+ if ((uint32_t)stackDepth > maxStackDepth)
+ maxStackDepth = stackDepth;
+}
+
+#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));
+
+ ptrdiff_t offset;
+ if (!emitCheck(1, &offset))
+ return false;
+
+ jsbytecode* code = this->code(offset);
+ code[0] = jsbytecode(op);
+ updateDepth(offset);
+ return true;
+}
+
+bool
+BytecodeEmitter::emit2(JSOp op, uint8_t op1)
+{
+ MOZ_ASSERT(checkStrictOrSloppy(op));
+
+ ptrdiff_t offset;
+ if (!emitCheck(2, &offset))
+ return false;
+
+ jsbytecode* code = this->code(offset);
+ code[0] = jsbytecode(op);
+ code[1] = jsbytecode(op1);
+ updateDepth(offset);
+ return true;
+}
+
+bool
+BytecodeEmitter::emit3(JSOp op, jsbytecode op1, jsbytecode op2)
+{
+ MOZ_ASSERT(checkStrictOrSloppy(op));
+
+ /* These should filter through emitVarOp. */
+ MOZ_ASSERT(!IsArgOp(op));
+ MOZ_ASSERT(!IsLocalOp(op));
+
+ ptrdiff_t offset;
+ if (!emitCheck(3, &offset))
+ return false;
+
+ jsbytecode* code = this->code(offset);
+ code[0] = jsbytecode(op);
+ code[1] = op1;
+ code[2] = op2;
+ updateDepth(offset);
+ return true;
+}
+
+bool
+BytecodeEmitter::emitN(JSOp op, size_t extra, ptrdiff_t* offset)
+{
+ MOZ_ASSERT(checkStrictOrSloppy(op));
+ ptrdiff_t length = 1 + ptrdiff_t(extra);
+
+ ptrdiff_t off;
+ if (!emitCheck(length, &off))
+ return false;
+
+ jsbytecode* code = this->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)
+ updateDepth(off);
+
+ if (offset)
+ *offset = off;
+ return true;
+}
+
+bool
+BytecodeEmitter::emitJumpTarget(JumpTarget* target)
+{
+ ptrdiff_t off = offset();
+
+ // Alias consecutive jump targets.
+ if (off == current->lastTarget.offset + ptrdiff_t(JSOP_JUMPTARGET_LENGTH)) {
+ target->offset = current->lastTarget.offset;
+ return true;
+ }
+
+ target->offset = off;
+ current->lastTarget.offset = off;
+ if (!emit1(JSOP_JUMPTARGET))
+ return false;
+ return true;
+}
+
+void
+JumpList::push(jsbytecode* code, ptrdiff_t jumpOffset)
+{
+ SET_JUMP_OFFSET(&code[jumpOffset], offset - jumpOffset);
+ offset = jumpOffset;
+}
+
+void
+JumpList::patchAll(jsbytecode* code, JumpTarget target)
+{
+ ptrdiff_t delta;
+ for (ptrdiff_t jumpOffset = offset; jumpOffset != -1; jumpOffset += delta) {
+ jsbytecode* pc = &code[jumpOffset];
+ MOZ_ASSERT(IsJumpOpcode(JSOp(*pc)) || JSOp(*pc) == JSOP_LABEL);
+ delta = GET_JUMP_OFFSET(pc);
+ MOZ_ASSERT(delta < 0);
+ ptrdiff_t span = target.offset - jumpOffset;
+ SET_JUMP_OFFSET(pc, span);
+ }
+}
+
+bool
+BytecodeEmitter::emitJumpNoFallthrough(JSOp op, JumpList* jump)
+{
+ ptrdiff_t offset;
+ if (!emitCheck(5, &offset))
+ return false;
+
+ jsbytecode* code = this->code(offset);
+ code[0] = jsbytecode(op);
+ MOZ_ASSERT(-1 <= jump->offset && jump->offset < offset);
+ jump->push(this->code(0), offset);
+ updateDepth(offset);
+ return true;
+}
+
+bool
+BytecodeEmitter::emitJump(JSOp op, JumpList* jump)
+{
+ if (!emitJumpNoFallthrough(op, jump))
+ return false;
+ if (BytecodeFallsThrough(op)) {
+ JumpTarget fallthrough;
+ if (!emitJumpTarget(&fallthrough))
+ return false;
+ }
+ return true;
+}
+
+bool
+BytecodeEmitter::emitBackwardJump(JSOp op, JumpTarget target, JumpList* jump, JumpTarget* fallthrough)
+{
+ if (!emitJumpNoFallthrough(op, jump))
+ return false;
+ patchJumpsToTarget(*jump, target);
+
+ // Unconditionally create a fallthrough for closing iterators, and as a
+ // target for break statements.
+ if (!emitJumpTarget(fallthrough))
+ return false;
+ return true;
+}
+
+void
+BytecodeEmitter::patchJumpsToTarget(JumpList jump, JumpTarget target)
+{
+ MOZ_ASSERT(-1 <= jump.offset && jump.offset <= offset());
+ MOZ_ASSERT(0 <= target.offset && target.offset <= offset());
+ MOZ_ASSERT_IF(jump.offset != -1 && target.offset + 4 <= offset(),
+ BytecodeIsJumpTarget(JSOp(*code(target.offset))));
+ jump.patchAll(code(0), target);
+}
+
+bool
+BytecodeEmitter::emitJumpTargetAndPatch(JumpList jump)
+{
+ if (jump.offset == -1)
+ return true;
+ JumpTarget target;
+ if (!emitJumpTarget(&target))
+ return false;
+ patchJumpsToTarget(jump, target);
+ return true;
+}
+
+bool
+BytecodeEmitter::emitCall(JSOp op, uint16_t argc, ParseNode* pn)
+{
+ if (pn && !updateSourceCoordNotes(pn->pn_pos.begin))
+ return false;
+ return emit3(op, ARGC_HI(argc), ARGC_LO(argc));
+}
+
+bool
+BytecodeEmitter::emitDupAt(unsigned slotFromTop)
+{
+ MOZ_ASSERT(slotFromTop < unsigned(stackDepth));
+
+ if (slotFromTop >= JS_BIT(24)) {
+ reportError(nullptr, JSMSG_TOO_MANY_LOCALS);
+ return false;
+ }
+
+ ptrdiff_t off;
+ if (!emitN(JSOP_DUPAT, 3, &off))
+ return false;
+
+ jsbytecode* pc = code(off);
+ SET_UINT24(pc, slotFromTop);
+ return true;
+}
+
+bool
+BytecodeEmitter::emitCheckIsObj(CheckIsObjectKind kind)
+{
+ return emit2(JSOP_CHECKISOBJ, uint8_t(kind));
+}
+
+static inline unsigned
+LengthOfSetLine(unsigned line)
+{
+ return 1 /* SN_SETLINE */ + (line > SN_4BYTE_OFFSET_MASK ? 4 : 1);
+}
+
+/* Updates line number notes, not column notes. */
+bool
+BytecodeEmitter::updateLineNumberNotes(uint32_t offset)
+{
+ TokenStream* ts = &parser->tokenStream;
+ bool onThisLine;
+ if (!ts->srcCoords.isOnThisLine(offset, currentLine(), &onThisLine))
+ return ts->reportError(JSMSG_OUT_OF_MEMORY);
+ if (!onThisLine) {
+ unsigned line = ts->srcCoords.lineNum(offset);
+ unsigned delta = line - currentLine();
+
+ /*
+ * Encode any change in the current source line number by using
+ * either several SRC_NEWLINE notes or just one SRC_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
+ * SRC_SETLINE.
+ */
+ current->currentLine = line;
+ current->lastColumn = 0;
+ if (delta >= LengthOfSetLine(line)) {
+ if (!newSrcNote2(SRC_SETLINE, ptrdiff_t(line)))
+ return false;
+ } else {
+ do {
+ if (!newSrcNote(SRC_NEWLINE))
+ return false;
+ } while (--delta != 0);
+ }
+ }
+ 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;
+
+ uint32_t columnIndex = parser->tokenStream.srcCoords.columnIndex(offset);
+ ptrdiff_t colspan = ptrdiff_t(columnIndex) - ptrdiff_t(current->lastColumn);
+ if (colspan != 0) {
+ // If the column span is so large that we can't store it, then just
+ // discard this information. This can happen with minimized or otherwise
+ // machine-generated code. Even gigantic column numbers are still
+ // valuable if you have a source map to relate them to something real;
+ // but it's better to fail soft here.
+ if (!SN_REPRESENTABLE_COLSPAN(colspan))
+ return true;
+ if (!newSrcNote2(SRC_COLSPAN, SN_COLSPAN_TO_OFFSET(colspan)))
+ return false;
+ current->lastColumn = columnIndex;
+ }
+ return true;
+}
+
+bool
+BytecodeEmitter::emitLoopHead(ParseNode* nextpn, JumpTarget* top)
+{
+ if (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->isKind(PNK_LEXICALSCOPE))
+ nextpn = nextpn->scopeBody();
+ MOZ_ASSERT_IF(nextpn->isKind(PNK_STATEMENTLIST), nextpn->isArity(PN_LIST));
+ if (nextpn->isKind(PNK_STATEMENTLIST) && nextpn->pn_head)
+ nextpn = nextpn->pn_head;
+ if (!updateSourceCoordNotes(nextpn->pn_pos.begin))
+ return false;
+ }
+
+ *top = { offset() };
+ return emit1(JSOP_LOOPHEAD);
+}
+
+bool
+BytecodeEmitter::emitLoopEntry(ParseNode* nextpn, JumpList entryJump)
+{
+ if (nextpn) {
+ /* Update the line number, as for LOOPHEAD. */
+ if (nextpn->isKind(PNK_LEXICALSCOPE))
+ nextpn = nextpn->scopeBody();
+ MOZ_ASSERT_IF(nextpn->isKind(PNK_STATEMENTLIST), nextpn->isArity(PN_LIST));
+ if (nextpn->isKind(PNK_STATEMENTLIST) && nextpn->pn_head)
+ nextpn = nextpn->pn_head;
+ if (!updateSourceCoordNotes(nextpn->pn_pos.begin))
+ return false;
+ }
+
+ JumpTarget entry{ offset() };
+ patchJumpsToTarget(entryJump, entry);
+
+ LoopControl& loopInfo = innermostNestableControl->as<LoopControl>();
+ MOZ_ASSERT(loopInfo.loopDepth() > 0);
+
+ uint8_t loopDepthAndFlags = PackLoopEntryDepthHintAndFlags(loopInfo.loopDepth(),
+ loopInfo.canIonOsr());
+ return emit2(JSOP_LOOPENTRY, loopDepthAndFlags);
+}
+
+void
+BytecodeEmitter::checkTypeSet(JSOp op)
+{
+ if (CodeSpec[op].format & JOF_TYPESET) {
+ if (typesetCount < UINT16_MAX)
+ typesetCount++;
+ }
+}
+
+bool
+BytecodeEmitter::emitUint16Operand(JSOp op, uint32_t operand)
+{
+ MOZ_ASSERT(operand <= UINT16_MAX);
+ if (!emit3(op, UINT16_HI(operand), UINT16_LO(operand)))
+ return false;
+ checkTypeSet(op);
+ return true;
+}
+
+bool
+BytecodeEmitter::emitUint32Operand(JSOp op, uint32_t operand)
+{
+ ptrdiff_t off;
+ if (!emitN(op, 4, &off))
+ return false;
+ SET_UINT32(code(off), operand);
+ checkTypeSet(op);
+ return true;
+}
+
+bool
+BytecodeEmitter::flushPops(int* npops)
+{
+ MOZ_ASSERT(*npops != 0);
+ if (!emitUint16Operand(JSOP_POPN, *npops))
+ return false;
+
+ *npops = 0;
+ return true;
+}
+
+namespace {
+
+class NonLocalExitControl {
+ BytecodeEmitter* bce_;
+ const uint32_t savedScopeNoteIndex_;
+ const int savedDepth_;
+ uint32_t openScopeNoteIndex_;
+
+ NonLocalExitControl(const NonLocalExitControl&) = delete;
+
+ MOZ_MUST_USE bool leaveScope(BytecodeEmitter::EmitterScope* scope);
+
+ public:
+ explicit NonLocalExitControl(BytecodeEmitter* bce)
+ : bce_(bce),
+ savedScopeNoteIndex_(bce->scopeNoteList.length()),
+ savedDepth_(bce->stackDepth),
+ openScopeNoteIndex_(bce->innermostEmitterScope->noteIndex())
+ { }
+
+ ~NonLocalExitControl() {
+ for (uint32_t n = savedScopeNoteIndex_; n < bce_->scopeNoteList.length(); n++)
+ bce_->scopeNoteList.recordEnd(n, bce_->offset(), bce_->inPrologue());
+ bce_->stackDepth = savedDepth_;
+ }
+
+ MOZ_MUST_USE bool prepareForNonLocalJump(BytecodeEmitter::NestableControl* target);
+
+ MOZ_MUST_USE bool prepareForNonLocalJumpToOutermost() {
+ return prepareForNonLocalJump(nullptr);
+ }
+};
+
+bool
+NonLocalExitControl::leaveScope(BytecodeEmitter::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().
+ uint32_t enclosingScopeIndex = ScopeNote::NoScopeIndex;
+ if (es->enclosingInFrame())
+ enclosingScopeIndex = es->enclosingInFrame()->index();
+ if (!bce_->scopeNoteList.append(enclosingScopeIndex, bce_->offset(), bce_->inPrologue(),
+ openScopeNoteIndex_))
+ return false;
+ openScopeNoteIndex_ = bce_->scopeNoteList.length() - 1;
+
+ return true;
+}
+
+/*
+ * Emit additional bytecode(s) for non-local jumps.
+ */
+bool
+NonLocalExitControl::prepareForNonLocalJump(BytecodeEmitter::NestableControl* target)
+{
+ using NestableControl = BytecodeEmitter::NestableControl;
+ using EmitterScope = BytecodeEmitter::EmitterScope;
+
+ EmitterScope* es = bce_->innermostEmitterScope;
+ int npops = 0;
+
+ auto flushPops = [&npops](BytecodeEmitter* bce) {
+ if (npops && !bce->flushPops(&npops))
+ return false;
+ return true;
+ };
+
+ // Walk the nestable control stack and patch jumps.
+ for (NestableControl* control = bce_->innermostNestableControl;
+ control != target;
+ control = control->enclosing())
+ {
+ // Walk the scope stack and leave the scopes we entered. Leaving a scope
+ // may emit administrative ops like JSOP_POPLEXICALENV but never anything
+ // that manipulates the stack.
+ for (; es != control->emitterScope(); es = es->enclosingInFrame()) {
+ if (!leaveScope(es))
+ return false;
+ }
+
+ switch (control->kind()) {
+ case StatementKind::Finally: {
+ TryFinallyControl& finallyControl = control->as<TryFinallyControl>();
+ if (finallyControl.emittingSubroutine()) {
+ /*
+ * There's a [exception or hole, retsub pc-index] pair and the
+ * possible return value on the stack that we need to pop.
+ */
+ npops += 3;
+ } else {
+ if (!flushPops(bce_))
+ return false;
+ if (!bce_->emitJump(JSOP_GOSUB, &finallyControl.gosubs))
+ return false;
+ }
+ break;
+ }
+
+ case StatementKind::ForOfLoop:
+ npops += 2;
+ break;
+
+ case StatementKind::ForInLoop:
+ /* The iterator and the current value are on the stack. */
+ npops += 1;
+ if (!flushPops(bce_))
+ return false;
+ if (!bce_->emit1(JSOP_ENDITER))
+ return false;
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ EmitterScope* targetEmitterScope = target ? target->emitterScope() : bce_->varEmitterScope;
+ for (; es != targetEmitterScope; es = es->enclosingInFrame()) {
+ if (!leaveScope(es))
+ return false;
+ }
+
+ return flushPops(bce_);
+}
+
+} // anonymous namespace
+
+bool
+BytecodeEmitter::emitGoto(NestableControl* target, JumpList* jumplist, SrcNoteType noteType)
+{
+ NonLocalExitControl nle(this);
+
+ if (!nle.prepareForNonLocalJump(target))
+ return false;
+
+ if (noteType != SRC_NULL) {
+ if (!newSrcNote(noteType))
+ return false;
+ }
+
+ return emitJump(JSOP_GOTO, jumplist);
+}
+
+Scope*
+BytecodeEmitter::innermostScope() const
+{
+ return innermostEmitterScope->scope(this);
+}
+
+bool
+BytecodeEmitter::emitIndex32(JSOp op, uint32_t index)
+{
+ MOZ_ASSERT(checkStrictOrSloppy(op));
+
+ const size_t len = 1 + UINT32_INDEX_LEN;
+ MOZ_ASSERT(len == size_t(CodeSpec[op].length));
+
+ ptrdiff_t offset;
+ if (!emitCheck(len, &offset))
+ return false;
+
+ jsbytecode* code = this->code(offset);
+ code[0] = jsbytecode(op);
+ SET_UINT32_INDEX(code, index);
+ checkTypeSet(op);
+ updateDepth(offset);
+ return true;
+}
+
+bool
+BytecodeEmitter::emitIndexOp(JSOp op, uint32_t index)
+{
+ MOZ_ASSERT(checkStrictOrSloppy(op));
+
+ const size_t len = CodeSpec[op].length;
+ MOZ_ASSERT(len >= 1 + UINT32_INDEX_LEN);
+
+ ptrdiff_t offset;
+ if (!emitCheck(len, &offset))
+ return false;
+
+ jsbytecode* code = this->code(offset);
+ code[0] = jsbytecode(op);
+ SET_UINT32_INDEX(code, index);
+ checkTypeSet(op);
+ updateDepth(offset);
+ return true;
+}
+
+bool
+BytecodeEmitter::emitAtomOp(JSAtom* atom, JSOp op)
+{
+ MOZ_ASSERT(atom);
+ MOZ_ASSERT(JOF_OPTYPE(op) == JOF_ATOM);
+
+ // .generator lookups should be emitted as JSOP_GETALIASEDVAR instead of
+ // JSOP_GETNAME etc, to bypass |with| objects on the scope chain.
+ // It's safe to emit .this lookups though because |with| objects skip
+ // those.
+ MOZ_ASSERT_IF(op == JSOP_GETNAME || op == JSOP_GETGNAME,
+ atom != cx->names().dotGenerator);
+
+ if (op == JSOP_GETPROP && atom == cx->names().length) {
+ /* Specialize length accesses for the interpreter. */
+ op = JSOP_LENGTH;
+ }
+
+ uint32_t index;
+ if (!makeAtomIndex(atom, &index))
+ return false;
+
+ return emitIndexOp(op, index);
+}
+
+bool
+BytecodeEmitter::emitAtomOp(ParseNode* pn, JSOp op)
+{
+ MOZ_ASSERT(pn->pn_atom != nullptr);
+ return emitAtomOp(pn->pn_atom, op);
+}
+
+bool
+BytecodeEmitter::emitInternedScopeOp(uint32_t index, JSOp op)
+{
+ MOZ_ASSERT(JOF_OPTYPE(op) == JOF_SCOPE);
+ MOZ_ASSERT(index < scopeList.length());
+ return emitIndex32(op, index);
+}
+
+bool
+BytecodeEmitter::emitInternedObjectOp(uint32_t index, JSOp op)
+{
+ MOZ_ASSERT(JOF_OPTYPE(op) == JOF_OBJECT);
+ MOZ_ASSERT(index < objectList.length);
+ return emitIndex32(op, index);
+}
+
+bool
+BytecodeEmitter::emitObjectOp(ObjectBox* objbox, JSOp op)
+{
+ return emitInternedObjectOp(objectList.add(objbox), op);
+}
+
+bool
+BytecodeEmitter::emitObjectPairOp(ObjectBox* objbox1, ObjectBox* objbox2, JSOp op)
+{
+ uint32_t index = objectList.add(objbox1);
+ objectList.add(objbox2);
+ return emitInternedObjectOp(index, op);
+}
+
+bool
+BytecodeEmitter::emitRegExp(uint32_t index)
+{
+ return emitIndex32(JSOP_REGEXP, index);
+}
+
+bool
+BytecodeEmitter::emitLocalOp(JSOp op, uint32_t slot)
+{
+ MOZ_ASSERT(JOF_OPTYPE(op) != JOF_ENVCOORD);
+ MOZ_ASSERT(IsLocalOp(op));
+
+ ptrdiff_t off;
+ if (!emitN(op, LOCALNO_LEN, &off))
+ return false;
+
+ SET_LOCALNO(code(off), slot);
+ return true;
+}
+
+bool
+BytecodeEmitter::emitArgOp(JSOp op, uint16_t slot)
+{
+ MOZ_ASSERT(IsArgOp(op));
+ ptrdiff_t off;
+ if (!emitN(op, ARGNO_LEN, &off))
+ return false;
+
+ SET_ARGNO(code(off), slot);
+ return true;
+}
+
+bool
+BytecodeEmitter::emitEnvCoordOp(JSOp op, EnvironmentCoordinate ec)
+{
+ MOZ_ASSERT(JOF_OPTYPE(op) == JOF_ENVCOORD);
+
+ unsigned n = ENVCOORD_HOPS_LEN + ENVCOORD_SLOT_LEN;
+ MOZ_ASSERT(int(n) + 1 /* op */ == CodeSpec[op].length);
+
+ ptrdiff_t off;
+ if (!emitN(op, n, &off))
+ return false;
+
+ jsbytecode* pc = code(off);
+ SET_ENVCOORD_HOPS(pc, ec.hops());
+ pc += ENVCOORD_HOPS_LEN;
+ SET_ENVCOORD_SLOT(pc, ec.slot());
+ pc += ENVCOORD_SLOT_LEN;
+ checkTypeSet(op);
+ return true;
+}
+
+static JSOp
+GetIncDecInfo(ParseNodeKind kind, bool* post)
+{
+ MOZ_ASSERT(kind == PNK_POSTINCREMENT || kind == PNK_PREINCREMENT ||
+ kind == PNK_POSTDECREMENT || kind == PNK_PREDECREMENT);
+ *post = kind == PNK_POSTINCREMENT || kind == PNK_POSTDECREMENT;
+ return (kind == PNK_POSTINCREMENT || kind == PNK_PREINCREMENT) ? JSOP_ADD : JSOP_SUB;
+}
+
+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)
+{
+ JS_CHECK_RECURSION(cx, return false);
+
+ restart:
+
+ switch (pn->getKind()) {
+ // Trivial cases with no side effects.
+ case PNK_NOP:
+ case PNK_STRING:
+ case PNK_TEMPLATE_STRING:
+ case PNK_REGEXP:
+ case PNK_TRUE:
+ case PNK_FALSE:
+ case PNK_NULL:
+ case PNK_ELISION:
+ case PNK_GENERATOR:
+ case PNK_NUMBER:
+ case PNK_OBJECT_PROPERTY_NAME:
+ MOZ_ASSERT(pn->isArity(PN_NULLARY));
+ *answer = false;
+ return true;
+
+ // |this| can throw in derived class constructors, including nested arrow
+ // functions or eval.
+ case PNK_THIS:
+ MOZ_ASSERT(pn->isArity(PN_UNARY));
+ *answer = sc->needsThisTDZChecks();
+ return true;
+
+ // Trivial binary nodes with more token pos holders.
+ case PNK_NEWTARGET:
+ MOZ_ASSERT(pn->isArity(PN_BINARY));
+ MOZ_ASSERT(pn->pn_left->isKind(PNK_POSHOLDER));
+ MOZ_ASSERT(pn->pn_right->isKind(PNK_POSHOLDER));
+ *answer = false;
+ return true;
+
+ case PNK_BREAK:
+ case PNK_CONTINUE:
+ case PNK_DEBUGGER:
+ MOZ_ASSERT(pn->isArity(PN_NULLARY));
+ *answer = true;
+ return true;
+
+ // Watch out for getters!
+ case PNK_DOT:
+ MOZ_ASSERT(pn->isArity(PN_NAME));
+ *answer = true;
+ return true;
+
+ // Unary cases with side effects only if the child has them.
+ case PNK_TYPEOFEXPR:
+ case PNK_VOID:
+ case PNK_NOT:
+ MOZ_ASSERT(pn->isArity(PN_UNARY));
+ return checkSideEffects(pn->pn_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 PNK_COMPUTED_NAME:
+ MOZ_ASSERT(pn->isArity(PN_UNARY));
+ *answer = true;
+ return true;
+
+ // Looking up or evaluating the associated name could throw.
+ case PNK_TYPEOFNAME:
+ MOZ_ASSERT(pn->isArity(PN_UNARY));
+ *answer = true;
+ return true;
+
+ // These unary cases have side effects on the enclosing object/array,
+ // 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, for an object literal having a mutated prototype
+ // and an array comprehension containing no other effectful operations
+ // only produce a value, without affecting anything else.
+ case PNK_MUTATEPROTO:
+ case PNK_ARRAYPUSH:
+ MOZ_ASSERT(pn->isArity(PN_UNARY));
+ return checkSideEffects(pn->pn_kid, answer);
+
+ // Unary cases with obvious side effects.
+ case PNK_PREINCREMENT:
+ case PNK_POSTINCREMENT:
+ case PNK_PREDECREMENT:
+ case PNK_POSTDECREMENT:
+ case PNK_THROW:
+ MOZ_ASSERT(pn->isArity(PN_UNARY));
+ *answer = true;
+ return true;
+
+ // These might invoke valueOf/toString, even with a subexpression without
+ // side effects! Consider |+{ valueOf: null, toString: null }|.
+ case PNK_BITNOT:
+ case PNK_POS:
+ case PNK_NEG:
+ MOZ_ASSERT(pn->isArity(PN_UNARY));
+ *answer = true;
+ return true;
+
+ // This invokes the (user-controllable) iterator protocol.
+ case PNK_SPREAD:
+ MOZ_ASSERT(pn->isArity(PN_UNARY));
+ *answer = true;
+ return true;
+
+ case PNK_YIELD_STAR:
+ case PNK_YIELD:
+ case PNK_AWAIT:
+ MOZ_ASSERT(pn->isArity(PN_BINARY));
+ *answer = true;
+ return true;
+
+ // Deletion generally has side effects, even if isolated cases have none.
+ case PNK_DELETENAME:
+ case PNK_DELETEPROP:
+ case PNK_DELETEELEM:
+ MOZ_ASSERT(pn->isArity(PN_UNARY));
+ *answer = true;
+ return true;
+
+ // Deletion of a non-Reference expression has side effects only through
+ // evaluating the expression.
+ case PNK_DELETEEXPR: {
+ MOZ_ASSERT(pn->isArity(PN_UNARY));
+ ParseNode* expr = pn->pn_kid;
+ return checkSideEffects(expr, answer);
+ }
+
+ case PNK_SEMI:
+ MOZ_ASSERT(pn->isArity(PN_UNARY));
+ if (ParseNode* expr = pn->pn_kid)
+ return checkSideEffects(expr, answer);
+ *answer = false;
+ return true;
+
+ // Binary cases with obvious side effects.
+ case PNK_ASSIGN:
+ case PNK_ADDASSIGN:
+ case PNK_SUBASSIGN:
+ case PNK_BITORASSIGN:
+ case PNK_BITXORASSIGN:
+ case PNK_BITANDASSIGN:
+ case PNK_LSHASSIGN:
+ case PNK_RSHASSIGN:
+ case PNK_URSHASSIGN:
+ case PNK_MULASSIGN:
+ case PNK_DIVASSIGN:
+ case PNK_MODASSIGN:
+ case PNK_POWASSIGN:
+ case PNK_SETTHIS:
+ MOZ_ASSERT(pn->isArity(PN_BINARY));
+ *answer = true;
+ return true;
+
+ case PNK_STATEMENTLIST:
+ case PNK_CATCHLIST:
+ // Strict equality operations and logical operators are well-behaved and
+ // perform no conversions.
+ case PNK_OR:
+ case PNK_AND:
+ case PNK_STRICTEQ:
+ case PNK_STRICTNE:
+ // Any subexpression of a comma expression could be effectful.
+ case PNK_COMMA:
+ MOZ_ASSERT(pn->pn_count > 0);
+ MOZ_FALLTHROUGH;
+ // Subcomponents of a literal may be effectful.
+ case PNK_ARRAY:
+ case PNK_OBJECT:
+ MOZ_ASSERT(pn->isArity(PN_LIST));
+ for (ParseNode* item = pn->pn_head; item; item = item->pn_next) {
+ if (!checkSideEffects(item, answer))
+ return false;
+ if (*answer)
+ return true;
+ }
+ return true;
+
+ // Most other binary operations (parsed as lists in SpiderMonkey) may
+ // perform conversions triggering side effects. Math operations perform
+ // ToNumber and may fail invoking invalid user-defined toString/valueOf:
+ // |5 < { toString: null }|. |instanceof| throws if provided a
+ // non-object constructor: |null instanceof null|. |in| throws if given
+ // a non-object RHS: |5 in null|.
+ case PNK_BITOR:
+ case PNK_BITXOR:
+ case PNK_BITAND:
+ case PNK_EQ:
+ case PNK_NE:
+ case PNK_LT:
+ case PNK_LE:
+ case PNK_GT:
+ case PNK_GE:
+ case PNK_INSTANCEOF:
+ case PNK_IN:
+ case PNK_LSH:
+ case PNK_RSH:
+ case PNK_URSH:
+ case PNK_ADD:
+ case PNK_SUB:
+ case PNK_STAR:
+ case PNK_DIV:
+ case PNK_MOD:
+ case PNK_POW:
+ MOZ_ASSERT(pn->isArity(PN_LIST));
+ MOZ_ASSERT(pn->pn_count >= 2);
+ *answer = true;
+ return true;
+
+ case PNK_COLON:
+ case PNK_CASE:
+ MOZ_ASSERT(pn->isArity(PN_BINARY));
+ if (!checkSideEffects(pn->pn_left, answer))
+ return false;
+ if (*answer)
+ return true;
+ return checkSideEffects(pn->pn_right, answer);
+
+ // More getters.
+ case PNK_ELEM:
+ MOZ_ASSERT(pn->isArity(PN_BINARY));
+ *answer = true;
+ return true;
+
+ // These affect visible names in this code, or in other code.
+ case PNK_IMPORT:
+ case PNK_EXPORT_FROM:
+ case PNK_EXPORT_DEFAULT:
+ MOZ_ASSERT(pn->isArity(PN_BINARY));
+ *answer = true;
+ return true;
+
+ // Likewise.
+ case PNK_EXPORT:
+ MOZ_ASSERT(pn->isArity(PN_UNARY));
+ *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 PNK_DOWHILE:
+ case PNK_WHILE:
+ case PNK_FOR:
+ case PNK_COMPREHENSIONFOR:
+ MOZ_ASSERT(pn->isArity(PN_BINARY));
+ *answer = true;
+ return true;
+
+ // Declarations affect the name set of the relevant scope.
+ case PNK_VAR:
+ case PNK_CONST:
+ case PNK_LET:
+ MOZ_ASSERT(pn->isArity(PN_LIST));
+ *answer = true;
+ return true;
+
+ case PNK_IF:
+ case PNK_CONDITIONAL:
+ MOZ_ASSERT(pn->isArity(PN_TERNARY));
+ if (!checkSideEffects(pn->pn_kid1, answer))
+ return false;
+ if (*answer)
+ return true;
+ if (!checkSideEffects(pn->pn_kid2, answer))
+ return false;
+ if (*answer)
+ return true;
+ if ((pn = pn->pn_kid3))
+ goto restart;
+ return true;
+
+ // Function calls can invoke non-local code.
+ case PNK_NEW:
+ case PNK_CALL:
+ case PNK_TAGGED_TEMPLATE:
+ case PNK_SUPERCALL:
+ MOZ_ASSERT(pn->isArity(PN_LIST));
+ *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 PNK_CLASS:
+ MOZ_ASSERT(pn->isArity(PN_TERNARY));
+ *answer = true;
+ return true;
+
+ // |with| calls |ToObject| on its expression and so throws if that value
+ // is null/undefined.
+ case PNK_WITH:
+ MOZ_ASSERT(pn->isArity(PN_BINARY));
+ *answer = true;
+ return true;
+
+ case PNK_RETURN:
+ MOZ_ASSERT(pn->isArity(PN_BINARY));
+ *answer = true;
+ return true;
+
+ case PNK_NAME:
+ MOZ_ASSERT(pn->isArity(PN_NAME));
+ *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 PNK_SHORTHAND:
+ MOZ_ASSERT(pn->isArity(PN_BINARY));
+ *answer = true;
+ return true;
+
+ case PNK_FUNCTION:
+ MOZ_ASSERT(pn->isArity(PN_CODE));
+ /*
+ * 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 PNK_MODULE:
+ *answer = false;
+ return true;
+
+ // Generator expressions have no side effects on their own.
+ case PNK_GENEXP:
+ MOZ_ASSERT(pn->isArity(PN_LIST));
+ *answer = false;
+ return true;
+
+ case PNK_TRY:
+ MOZ_ASSERT(pn->isArity(PN_TERNARY));
+ if (!checkSideEffects(pn->pn_kid1, answer))
+ return false;
+ if (*answer)
+ return true;
+ if (ParseNode* catchList = pn->pn_kid2) {
+ MOZ_ASSERT(catchList->isKind(PNK_CATCHLIST));
+ if (!checkSideEffects(catchList, answer))
+ return false;
+ if (*answer)
+ return true;
+ }
+ if (ParseNode* finallyBlock = pn->pn_kid3) {
+ if (!checkSideEffects(finallyBlock, answer))
+ return false;
+ }
+ return true;
+
+ case PNK_CATCH:
+ MOZ_ASSERT(pn->isArity(PN_TERNARY));
+ if (!checkSideEffects(pn->pn_kid1, answer))
+ return false;
+ if (*answer)
+ return true;
+ if (ParseNode* cond = pn->pn_kid2) {
+ if (!checkSideEffects(cond, answer))
+ return false;
+ if (*answer)
+ return true;
+ }
+ return checkSideEffects(pn->pn_kid3, answer);
+
+ case PNK_SWITCH:
+ MOZ_ASSERT(pn->isArity(PN_BINARY));
+ if (!checkSideEffects(pn->pn_left, answer))
+ return false;
+ return *answer || checkSideEffects(pn->pn_right, answer);
+
+ case PNK_LABEL:
+ MOZ_ASSERT(pn->isArity(PN_NAME));
+ return checkSideEffects(pn->expr(), answer);
+
+ case PNK_LEXICALSCOPE:
+ MOZ_ASSERT(pn->isArity(PN_SCOPE));
+ return checkSideEffects(pn->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 PNK_TEMPLATE_STRING_LIST:
+ MOZ_ASSERT(pn->isArity(PN_LIST));
+ MOZ_ASSERT(pn->pn_count > 0);
+ MOZ_ASSERT((pn->pn_count % 2) == 1,
+ "template strings must alternate template and substitution "
+ "parts");
+ *answer = pn->pn_count > 1;
+ return true;
+
+ case PNK_ARRAYCOMP:
+ MOZ_ASSERT(pn->isArity(PN_LIST));
+ MOZ_ASSERT(pn->pn_count == 1);
+ return checkSideEffects(pn->pn_head, answer);
+
+ // This should be unreachable but is left as-is for now.
+ case PNK_PARAMSBODY:
+ *answer = true;
+ return true;
+
+ case PNK_FORIN: // by PNK_FOR/PNK_COMPREHENSIONFOR
+ case PNK_FOROF: // by PNK_FOR/PNK_COMPREHENSIONFOR
+ case PNK_FORHEAD: // by PNK_FOR/PNK_COMPREHENSIONFOR
+ case PNK_CLASSMETHOD: // by PNK_CLASS
+ case PNK_CLASSNAMES: // by PNK_CLASS
+ case PNK_CLASSMETHODLIST: // by PNK_CLASS
+ case PNK_IMPORT_SPEC_LIST: // by PNK_IMPORT
+ case PNK_IMPORT_SPEC: // by PNK_IMPORT
+ case PNK_EXPORT_BATCH_SPEC:// by PNK_EXPORT
+ case PNK_EXPORT_SPEC_LIST: // by PNK_EXPORT
+ case PNK_EXPORT_SPEC: // by PNK_EXPORT
+ case PNK_CALLSITEOBJ: // by PNK_TAGGED_TEMPLATE
+ case PNK_POSHOLDER: // by PNK_NEWTARGET
+ case PNK_SUPERBASE: // by PNK_ELEM and others
+ MOZ_CRASH("handled by parent nodes");
+
+ case PNK_LIMIT: // invalid sentinel value
+ MOZ_CRASH("invalid node kind");
+ }
+
+ MOZ_CRASH("invalid, unenumerated ParseNodeKind value encountered in "
+ "BytecodeEmitter::checkSideEffects");
+}
+
+bool
+BytecodeEmitter::isInLoop()
+{
+ return findInnermostNestableControl<LoopControl>();
+}
+
+bool
+BytecodeEmitter::checkSingletonContext()
+{
+ if (!script->treatAsRunOnce() || sc->isFunctionBox() || isInLoop())
+ return false;
+ hasSingletons = true;
+ return true;
+}
+
+bool
+BytecodeEmitter::checkRunOnceContext()
+{
+ return checkSingletonContext() || (!isInLoop() && isRunOnceLambda());
+}
+
+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;
+}
+
+bool
+BytecodeEmitter::maybeSetDisplayURL()
+{
+ if (tokenStream()->hasDisplayURL()) {
+ if (!parser->ss->setDisplayURL(cx, tokenStream()->displayURL()))
+ return false;
+ }
+ return true;
+}
+
+bool
+BytecodeEmitter::maybeSetSourceMap()
+{
+ if (tokenStream()->hasSourceMapURL()) {
+ MOZ_ASSERT(!parser->ss->hasSourceMapURL());
+ if (!parser->ss->setSourceMapURL(cx, tokenStream()->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 (parser->options().sourceMapURL()) {
+ // Warn about the replacement, but use the new one.
+ if (parser->ss->hasSourceMapURL()) {
+ if(!parser->report(ParseWarning, false, nullptr, JSMSG_ALREADY_HAS_PRAGMA,
+ parser->ss->filename(), "//# sourceMappingURL"))
+ return false;
+ }
+
+ if (!parser->ss->setSourceMapURL(cx, parser->options().sourceMapURL()))
+ return false;
+ }
+
+ return true;
+}
+
+void
+BytecodeEmitter::tellDebuggerAboutCompiledScript(ExclusiveContext* cx)
+{
+ // Note: when parsing off thread the resulting scripts need to be handed to
+ // the debugger after rejoining to the main thread.
+ if (!cx->isJSContext())
+ return;
+
+ // Lazy scripts are never top level (despite always being invoked with a
+ // nullptr parent), and so the hook should never be fired.
+ if (emitterMode != LazyFunction && !parent) {
+ Debugger::onNewScript(cx->asJSContext(), script);
+ }
+}
+
+inline TokenStream*
+BytecodeEmitter::tokenStream()
+{
+ return &parser->tokenStream;
+}
+
+bool
+BytecodeEmitter::reportError(ParseNode* pn, unsigned errorNumber, ...)
+{
+ TokenPos pos = pn ? pn->pn_pos : tokenStream()->currentToken().pos;
+
+ va_list args;
+ va_start(args, errorNumber);
+ bool result = tokenStream()->reportCompileErrorNumberVA(pos.begin, JSREPORT_ERROR,
+ errorNumber, args);
+ va_end(args);
+ return result;
+}
+
+bool
+BytecodeEmitter::reportStrictWarning(ParseNode* pn, unsigned errorNumber, ...)
+{
+ TokenPos pos = pn ? pn->pn_pos : tokenStream()->currentToken().pos;
+
+ va_list args;
+ va_start(args, errorNumber);
+ bool result = tokenStream()->reportStrictWarningErrorNumberVA(pos.begin, errorNumber, args);
+ va_end(args);
+ return result;
+}
+
+bool
+BytecodeEmitter::reportStrictModeError(ParseNode* pn, unsigned errorNumber, ...)
+{
+ TokenPos pos = pn ? pn->pn_pos : tokenStream()->currentToken().pos;
+
+ va_list args;
+ va_start(args, errorNumber);
+ bool result = tokenStream()->reportStrictModeErrorNumberVA(pos.begin, sc->strict(),
+ errorNumber, args);
+ va_end(args);
+ return result;
+}
+
+bool
+BytecodeEmitter::emitNewInit(JSProtoKey key)
+{
+ const size_t len = 1 + UINT32_INDEX_LEN;
+ ptrdiff_t offset;
+ if (!emitCheck(len, &offset))
+ return false;
+
+ jsbytecode* code = this->code(offset);
+ code[0] = JSOP_NEWINIT;
+ code[1] = jsbytecode(key);
+ code[2] = 0;
+ code[3] = 0;
+ code[4] = 0;
+ checkTypeSet(JSOP_NEWINIT);
+ updateDepth(offset);
+ return true;
+}
+
+bool
+BytecodeEmitter::iteratorResultShape(unsigned* shape)
+{
+ // No need to do any guessing for the object kind, since we know exactly how
+ // many properties we plan to have.
+ gc::AllocKind kind = gc::GetGCObjectKind(2);
+ RootedPlainObject obj(cx, NewBuiltinClassInstance<PlainObject>(cx, kind, TenuredObject));
+ if (!obj)
+ return false;
+
+ Rooted<jsid> value_id(cx, AtomToId(cx->names().value));
+ Rooted<jsid> done_id(cx, AtomToId(cx->names().done));
+ if (!NativeDefineProperty(cx, obj, value_id, UndefinedHandleValue, nullptr, nullptr,
+ JSPROP_ENUMERATE))
+ {
+ return false;
+ }
+ if (!NativeDefineProperty(cx, obj, done_id, UndefinedHandleValue, nullptr, nullptr,
+ JSPROP_ENUMERATE))
+ {
+ return false;
+ }
+
+ ObjectBox* objbox = parser->newObjectBox(obj);
+ if (!objbox)
+ return false;
+
+ *shape = objectList.add(objbox);
+
+ return true;
+}
+
+bool
+BytecodeEmitter::emitPrepareIteratorResult()
+{
+ unsigned shape;
+ if (!iteratorResultShape(&shape))
+ return false;
+ return emitIndex32(JSOP_NEWOBJECT, shape);
+}
+
+bool
+BytecodeEmitter::emitFinishIteratorResult(bool done)
+{
+ uint32_t value_id;
+ if (!makeAtomIndex(cx->names().value, &value_id))
+ return false;
+ uint32_t done_id;
+ if (!makeAtomIndex(cx->names().done, &done_id))
+ return false;
+
+ if (!emitIndex32(JSOP_INITPROP, value_id))
+ return false;
+ if (!emit1(done ? JSOP_TRUE : JSOP_FALSE))
+ return false;
+ if (!emitIndex32(JSOP_INITPROP, done_id))
+ return false;
+ return true;
+}
+
+bool
+BytecodeEmitter::emitGetNameAtLocation(JSAtom* name, const NameLocation& loc, bool callContext)
+{
+ switch (loc.kind()) {
+ case NameLocation::Kind::Dynamic:
+ if (!emitAtomOp(name, JSOP_GETNAME))
+ return false;
+ break;
+
+ case NameLocation::Kind::Global:
+ if (!emitAtomOp(name, JSOP_GETGNAME))
+ return false;
+ break;
+
+ case NameLocation::Kind::Intrinsic:
+ if (!emitAtomOp(name, JSOP_GETINTRINSIC))
+ return false;
+ break;
+
+ case NameLocation::Kind::NamedLambdaCallee:
+ if (!emit1(JSOP_CALLEE))
+ return false;
+ break;
+
+ case NameLocation::Kind::Import:
+ if (!emitAtomOp(name, JSOP_GETIMPORT))
+ return false;
+ break;
+
+ case NameLocation::Kind::ArgumentSlot:
+ if (!emitArgOp(JSOP_GETARG, loc.argumentSlot()))
+ return false;
+ break;
+
+ case NameLocation::Kind::FrameSlot:
+ if (loc.isLexical()) {
+ if (!emitTDZCheckIfNeeded(name, loc))
+ return false;
+ }
+ if (!emitLocalOp(JSOP_GETLOCAL, loc.frameSlot()))
+ return false;
+ break;
+
+ case NameLocation::Kind::EnvironmentCoordinate:
+ if (loc.isLexical()) {
+ if (!emitTDZCheckIfNeeded(name, loc))
+ return false;
+ }
+ if (!emitEnvCoordOp(JSOP_GETALIASEDVAR, loc.environmentCoordinate()))
+ return false;
+ break;
+
+ case NameLocation::Kind::DynamicAnnexBVar:
+ MOZ_CRASH("Synthesized vars for Annex B.3.3 should only be used in initialization");
+ }
+
+ // Need to provide |this| value for call.
+ if (callContext) {
+ switch (loc.kind()) {
+ case NameLocation::Kind::Dynamic: {
+ JSOp thisOp = needsImplicitThis() ? JSOP_IMPLICITTHIS : JSOP_GIMPLICITTHIS;
+ if (!emitAtomOp(name, thisOp))
+ return false;
+ break;
+ }
+
+ case NameLocation::Kind::Global:
+ if (!emitAtomOp(name, JSOP_GIMPLICITTHIS))
+ 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 (!emit1(JSOP_UNDEFINED))
+ return false;
+ break;
+
+ case NameLocation::Kind::DynamicAnnexBVar:
+ MOZ_CRASH("Synthesized vars for Annex B.3.3 should only be used in initialization");
+ }
+ }
+
+ return true;
+}
+
+bool
+BytecodeEmitter::emitGetName(ParseNode* pn, bool callContext)
+{
+ return emitGetName(pn->name(), callContext);
+}
+
+template <typename RHSEmitter>
+bool
+BytecodeEmitter::emitSetOrInitializeNameAtLocation(HandleAtom name, const NameLocation& loc,
+ RHSEmitter emitRhs, bool initialize)
+{
+ bool emittedBindOp = false;
+
+ switch (loc.kind()) {
+ case NameLocation::Kind::Dynamic:
+ case NameLocation::Kind::Import:
+ case NameLocation::Kind::DynamicAnnexBVar: {
+ uint32_t atomIndex;
+ if (!makeAtomIndex(name, &atomIndex))
+ return false;
+ if (loc.kind() == NameLocation::Kind::DynamicAnnexBVar) {
+ // Annex B vars always go on the nearest variable environment,
+ // even if lexical environments in between contain same-named
+ // bindings.
+ if (!emit1(JSOP_BINDVAR))
+ return false;
+ } else {
+ if (!emitIndexOp(JSOP_BINDNAME, atomIndex))
+ return false;
+ }
+ emittedBindOp = true;
+ if (!emitRhs(this, loc, emittedBindOp))
+ return false;
+ if (!emitIndexOp(strictifySetNameOp(JSOP_SETNAME), atomIndex))
+ return false;
+ break;
+ }
+
+ case NameLocation::Kind::Global: {
+ JSOp op;
+ uint32_t atomIndex;
+ if (!makeAtomIndex(name, &atomIndex))
+ return false;
+ if (loc.isLexical() && initialize) {
+ // INITGLEXICAL always gets the global lexical scope. It doesn't
+ // need a BINDGNAME.
+ MOZ_ASSERT(innermostScope()->is<GlobalScope>());
+ op = JSOP_INITGLEXICAL;
+ } else {
+ if (!emitIndexOp(JSOP_BINDGNAME, atomIndex))
+ return false;
+ emittedBindOp = true;
+ op = strictifySetNameOp(JSOP_SETGNAME);
+ }
+ if (!emitRhs(this, loc, emittedBindOp))
+ return false;
+ if (!emitIndexOp(op, atomIndex))
+ return false;
+ break;
+ }
+
+ case NameLocation::Kind::Intrinsic:
+ if (!emitRhs(this, loc, emittedBindOp))
+ return false;
+ if (!emitAtomOp(name, JSOP_SETINTRINSIC))
+ return false;
+ break;
+
+ case NameLocation::Kind::NamedLambdaCallee:
+ if (!emitRhs(this, loc, emittedBindOp))
+ return false;
+ // Assigning to the named lambda is a no-op in sloppy mode but
+ // throws in strict mode.
+ if (sc->strict() && !emit1(JSOP_THROWSETCALLEE))
+ return false;
+ break;
+
+ case NameLocation::Kind::ArgumentSlot: {
+ // If we assign to a positional formal parameter and the arguments
+ // object is unmapped (strict mode or function with
+ // default/rest/destructing args), parameters do not alias
+ // arguments[i], and to make the arguments object reflect initial
+ // parameter values prior to any mutation we create it eagerly
+ // whenever parameters are (or might, in the case of calls to eval)
+ // assigned.
+ FunctionBox* funbox = sc->asFunctionBox();
+ if (funbox->argumentsHasLocalBinding() && !funbox->hasMappedArgsObj())
+ funbox->setDefinitelyNeedsArgsObj();
+
+ if (!emitRhs(this, loc, emittedBindOp))
+ return false;
+ if (!emitArgOp(JSOP_SETARG, loc.argumentSlot()))
+ return false;
+ break;
+ }
+
+ case NameLocation::Kind::FrameSlot: {
+ JSOp op = JSOP_SETLOCAL;
+ if (!emitRhs(this, loc, emittedBindOp))
+ return false;
+ if (loc.isLexical()) {
+ if (initialize) {
+ op = JSOP_INITLEXICAL;
+ } else {
+ if (loc.isConst())
+ op = JSOP_THROWSETCONST;
+
+ if (!emitTDZCheckIfNeeded(name, loc))
+ return false;
+ }
+ }
+ if (!emitLocalOp(op, loc.frameSlot()))
+ return false;
+ if (op == JSOP_INITLEXICAL) {
+ if (!innermostTDZCheckCache->noteTDZCheck(this, name, DontCheckTDZ))
+ return false;
+ }
+ break;
+ }
+
+ case NameLocation::Kind::EnvironmentCoordinate: {
+ JSOp op = JSOP_SETALIASEDVAR;
+ if (!emitRhs(this, loc, emittedBindOp))
+ return false;
+ if (loc.isLexical()) {
+ if (initialize) {
+ op = JSOP_INITALIASEDLEXICAL;
+ } else {
+ if (loc.isConst())
+ op = JSOP_THROWSETALIASEDCONST;
+
+ if (!emitTDZCheckIfNeeded(name, loc))
+ 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_THROWSETALIASEDCONST;
+ if (sc->strict() && !emitEnvCoordOp(op, loc.environmentCoordinate()))
+ return false;
+ } else {
+ if (!emitEnvCoordOp(op, loc.environmentCoordinate()))
+ return false;
+ }
+ if (op == JSOP_INITALIASEDLEXICAL) {
+ if (!innermostTDZCheckCache->noteTDZCheck(this, name, DontCheckTDZ))
+ return false;
+ }
+ break;
+ }
+ }
+
+ return true;
+}
+
+bool
+BytecodeEmitter::emitTDZCheckIfNeeded(JSAtom* name, const NameLocation& loc)
+{
+ // Dynamic accesses have TDZ checks built into their VM code and should
+ // never emit explicit TDZ checks.
+ MOZ_ASSERT(loc.hasKnownSlot());
+ MOZ_ASSERT(loc.isLexical());
+
+ Maybe<MaybeCheckTDZ> check = innermostTDZCheckCache->needsTDZCheck(this, name);
+ if (!check)
+ return false;
+
+ // We've already emitted a check in this basic block.
+ if (*check == DontCheckTDZ)
+ return true;
+
+ if (loc.kind() == NameLocation::Kind::FrameSlot) {
+ if (!emitLocalOp(JSOP_CHECKLEXICAL, loc.frameSlot()))
+ return false;
+ } else {
+ if (!emitEnvCoordOp(JSOP_CHECKALIASEDLEXICAL, loc.environmentCoordinate()))
+ return false;
+ }
+
+ return innermostTDZCheckCache->noteTDZCheck(this, name, DontCheckTDZ);
+}
+
+bool
+BytecodeEmitter::emitPropLHS(ParseNode* pn)
+{
+ MOZ_ASSERT(pn->isKind(PNK_DOT));
+ MOZ_ASSERT(!pn->as<PropertyAccess>().isSuper());
+
+ ParseNode* pn2 = pn->pn_expr;
+
+ /*
+ * If the object operand is also a dotted property reference, reverse the
+ * list linked via pn_expr temporarily so we can iterate over it from the
+ * bottom up (reversing again as we go), to avoid excessive recursion.
+ */
+ if (pn2->isKind(PNK_DOT) && !pn2->as<PropertyAccess>().isSuper()) {
+ ParseNode* pndot = pn2;
+ ParseNode* pnup = nullptr;
+ ParseNode* pndown;
+ for (;;) {
+ /* Reverse pndot->pn_expr to point up, not down. */
+ pndown = pndot->pn_expr;
+ pndot->pn_expr = pnup;
+ if (!pndown->isKind(PNK_DOT) || pndown->as<PropertyAccess>().isSuper())
+ break;
+ pnup = pndot;
+ pndot = pndown;
+ }
+
+ /* pndown is a primary expression, not a dotted property reference. */
+ if (!emitTree(pndown))
+ return false;
+
+ do {
+ /* Walk back up the list, emitting annotated name ops. */
+ if (!emitAtomOp(pndot, JSOP_GETPROP))
+ return false;
+
+ /* Reverse the pn_expr link again. */
+ pnup = pndot->pn_expr;
+ pndot->pn_expr = pndown;
+ pndown = pndot;
+ } while ((pndot = pnup) != nullptr);
+ return true;
+ }
+
+ // The non-optimized case.
+ return emitTree(pn2);
+}
+
+bool
+BytecodeEmitter::emitSuperPropLHS(ParseNode* superBase, bool isCall)
+{
+ if (!emitGetThisForSuperBase(superBase))
+ return false;
+ if (isCall && !emit1(JSOP_DUP))
+ return false;
+ if (!emit1(JSOP_SUPERBASE))
+ return false;
+ return true;
+}
+
+bool
+BytecodeEmitter::emitPropOp(ParseNode* pn, JSOp op)
+{
+ MOZ_ASSERT(pn->isArity(PN_NAME));
+
+ if (!emitPropLHS(pn))
+ return false;
+
+ if (op == JSOP_CALLPROP && !emit1(JSOP_DUP))
+ return false;
+
+ if (!emitAtomOp(pn, op))
+ return false;
+
+ if (op == JSOP_CALLPROP && !emit1(JSOP_SWAP))
+ return false;
+
+ return true;
+}
+
+bool
+BytecodeEmitter::emitSuperPropOp(ParseNode* pn, JSOp op, bool isCall)
+{
+ ParseNode* base = &pn->as<PropertyAccess>().expression();
+ if (!emitSuperPropLHS(base, isCall))
+ return false;
+
+ if (!emitAtomOp(pn, op))
+ return false;
+
+ if (isCall && !emit1(JSOP_SWAP))
+ return false;
+
+ return true;
+}
+
+bool
+BytecodeEmitter::emitPropIncDec(ParseNode* pn)
+{
+ MOZ_ASSERT(pn->pn_kid->isKind(PNK_DOT));
+
+ bool post;
+ bool isSuper = pn->pn_kid->as<PropertyAccess>().isSuper();
+ JSOp binop = GetIncDecInfo(pn->getKind(), &post);
+
+ if (isSuper) {
+ ParseNode* base = &pn->pn_kid->as<PropertyAccess>().expression();
+ if (!emitSuperPropLHS(base)) // THIS OBJ
+ return false;
+ if (!emit1(JSOP_DUP2)) // THIS OBJ THIS OBJ
+ return false;
+ } else {
+ if (!emitPropLHS(pn->pn_kid)) // OBJ
+ return false;
+ if (!emit1(JSOP_DUP)) // OBJ OBJ
+ return false;
+ }
+ if (!emitAtomOp(pn->pn_kid, isSuper? JSOP_GETPROP_SUPER : JSOP_GETPROP)) // OBJ V
+ return false;
+ if (!emit1(JSOP_POS)) // OBJ N
+ return false;
+ if (post && !emit1(JSOP_DUP)) // OBJ N? N
+ return false;
+ if (!emit1(JSOP_ONE)) // OBJ N? N 1
+ return false;
+ if (!emit1(binop)) // OBJ N? N+1
+ return false;
+
+ if (post) {
+ if (!emit2(JSOP_PICK, 2 + isSuper)) // N? N+1 OBJ
+ return false;
+ if (!emit1(JSOP_SWAP)) // N? OBJ N+1
+ return false;
+ if (isSuper) {
+ if (!emit2(JSOP_PICK, 3)) // N THIS N+1 OBJ
+ return false;
+ if (!emit1(JSOP_SWAP)) // N THIS OBJ N+1
+ return false;
+ }
+ }
+
+ JSOp setOp = isSuper ? sc->strict() ? JSOP_STRICTSETPROP_SUPER : JSOP_SETPROP_SUPER
+ : sc->strict() ? JSOP_STRICTSETPROP : JSOP_SETPROP;
+ if (!emitAtomOp(pn->pn_kid, setOp)) // N? N+1
+ return false;
+ if (post && !emit1(JSOP_POP)) // RESULT
+ return false;
+
+ return true;
+}
+
+bool
+BytecodeEmitter::emitNameIncDec(ParseNode* pn)
+{
+ MOZ_ASSERT(pn->pn_kid->isKind(PNK_NAME));
+
+ bool post;
+ JSOp binop = GetIncDecInfo(pn->getKind(), &post);
+
+ auto emitRhs = [pn, post, binop](BytecodeEmitter* bce, const NameLocation& loc,
+ bool emittedBindOp)
+ {
+ JSAtom* name = pn->pn_kid->name();
+ if (!bce->emitGetNameAtLocation(name, loc, false)) // SCOPE? V
+ return false;
+ if (!bce->emit1(JSOP_POS)) // SCOPE? N
+ return false;
+ if (post && !bce->emit1(JSOP_DUP)) // SCOPE? N? N
+ return false;
+ if (!bce->emit1(JSOP_ONE)) // SCOPE? N? N 1
+ return false;
+ if (!bce->emit1(binop)) // SCOPE? N? N+1
+ return false;
+
+ if (post && emittedBindOp) {
+ if (!bce->emit2(JSOP_PICK, 2)) // N? N+1 SCOPE?
+ return false;
+ if (!bce->emit1(JSOP_SWAP)) // N? SCOPE? N+1
+ return false;
+ }
+
+ return true;
+ };
+
+ if (!emitSetName(pn->pn_kid, emitRhs))
+ return false;
+
+ if (post && !emit1(JSOP_POP))
+ return false;
+
+ return true;
+}
+
+bool
+BytecodeEmitter::emitElemOperands(ParseNode* pn, EmitElemOption opts)
+{
+ MOZ_ASSERT(pn->isArity(PN_BINARY));
+
+ if (!emitTree(pn->pn_left))
+ return false;
+
+ if (opts == EmitElemOption::IncDec) {
+ if (!emit1(JSOP_CHECKOBJCOERCIBLE))
+ return false;
+ } else if (opts == EmitElemOption::Call) {
+ if (!emit1(JSOP_DUP))
+ return false;
+ }
+
+ if (!emitTree(pn->pn_right))
+ return false;
+
+ if (opts == EmitElemOption::Set) {
+ if (!emit2(JSOP_PICK, 2))
+ return false;
+ } else if (opts == EmitElemOption::IncDec || opts == EmitElemOption::CompoundAssign) {
+ if (!emit1(JSOP_TOID))
+ return false;
+ }
+ return true;
+}
+
+bool
+BytecodeEmitter::emitSuperElemOperands(ParseNode* pn, EmitElemOption opts)
+{
+ MOZ_ASSERT(pn->isKind(PNK_ELEM) && pn->as<PropertyByValue>().isSuper());
+
+ // The ordering here is somewhat screwy. We need to evaluate the propval
+ // first, by spec. Do a little dance to not emit more than one JSOP_THIS.
+ // Since JSOP_THIS might throw in derived class constructors, we cannot
+ // just push it earlier as the receiver. We have to swap it down instead.
+
+ if (!emitTree(pn->pn_right))
+ return false;
+
+ // We need to convert the key to an object id first, so that we do not do
+ // it inside both the GETELEM and the SETELEM.
+ if (opts == EmitElemOption::IncDec || opts == EmitElemOption::CompoundAssign) {
+ if (!emit1(JSOP_TOID))
+ return false;
+ }
+
+ if (!emitGetThisForSuperBase(pn->pn_left))
+ return false;
+
+ if (opts == EmitElemOption::Call) {
+ if (!emit1(JSOP_SWAP))
+ return false;
+
+ // We need another |this| on top, also
+ if (!emitDupAt(1))
+ return false;
+ }
+
+ if (!emit1(JSOP_SUPERBASE))
+ return false;
+
+ if (opts == EmitElemOption::Set && !emit2(JSOP_PICK, 3))
+ return false;
+
+ return true;
+}
+
+bool
+BytecodeEmitter::emitElemOpBase(JSOp op)
+{
+ if (!emit1(op))
+ return false;
+
+ checkTypeSet(op);
+ return true;
+}
+
+bool
+BytecodeEmitter::emitElemOp(ParseNode* pn, JSOp op)
+{
+ EmitElemOption opts = EmitElemOption::Get;
+ if (op == JSOP_CALLELEM)
+ opts = EmitElemOption::Call;
+ else if (op == JSOP_SETELEM || op == JSOP_STRICTSETELEM)
+ opts = EmitElemOption::Set;
+
+ return emitElemOperands(pn, opts) && emitElemOpBase(op);
+}
+
+bool
+BytecodeEmitter::emitSuperElemOp(ParseNode* pn, JSOp op, bool isCall)
+{
+ EmitElemOption opts = EmitElemOption::Get;
+ if (isCall)
+ opts = EmitElemOption::Call;
+ else if (op == JSOP_SETELEM_SUPER || op == JSOP_STRICTSETELEM_SUPER)
+ opts = EmitElemOption::Set;
+
+ if (!emitSuperElemOperands(pn, opts))
+ return false;
+ if (!emitElemOpBase(op))
+ return false;
+
+ if (isCall && !emit1(JSOP_SWAP))
+ return false;
+
+ return true;
+}
+
+bool
+BytecodeEmitter::emitElemIncDec(ParseNode* pn)
+{
+ MOZ_ASSERT(pn->pn_kid->isKind(PNK_ELEM));
+
+ bool isSuper = pn->pn_kid->as<PropertyByValue>().isSuper();
+
+ // We need to convert the key to an object id first, so that we do not do
+ // it inside both the GETELEM and the SETELEM. This is done by
+ // emit(Super)ElemOperands.
+ if (isSuper) {
+ if (!emitSuperElemOperands(pn->pn_kid, EmitElemOption::IncDec))
+ return false;
+ } else {
+ if (!emitElemOperands(pn->pn_kid, EmitElemOption::IncDec))
+ return false;
+ }
+
+ bool post;
+ JSOp binop = GetIncDecInfo(pn->getKind(), &post);
+
+ JSOp getOp;
+ if (isSuper) {
+ // There's no such thing as JSOP_DUP3, so we have to be creative.
+ // Note that pushing things again is no fewer JSOps.
+ if (!emitDupAt(2)) // KEY THIS OBJ KEY
+ return false;
+ if (!emitDupAt(2)) // KEY THIS OBJ KEY THIS
+ return false;
+ if (!emitDupAt(2)) // KEY THIS OBJ KEY THIS OBJ
+ return false;
+ getOp = JSOP_GETELEM_SUPER;
+ } else {
+ // OBJ KEY
+ if (!emit1(JSOP_DUP2)) // OBJ KEY OBJ KEY
+ return false;
+ getOp = JSOP_GETELEM;
+ }
+ if (!emitElemOpBase(getOp)) // OBJ KEY V
+ return false;
+ if (!emit1(JSOP_POS)) // OBJ KEY N
+ return false;
+ if (post && !emit1(JSOP_DUP)) // OBJ KEY N? N
+ return false;
+ if (!emit1(JSOP_ONE)) // OBJ KEY N? N 1
+ return false;
+ if (!emit1(binop)) // OBJ KEY N? N+1
+ return false;
+
+ if (post) {
+ if (isSuper) {
+ // We have one more value to rotate around, because of |this|
+ // on the stack
+ if (!emit2(JSOP_PICK, 4))
+ return false;
+ }
+ if (!emit2(JSOP_PICK, 3 + isSuper)) // KEY N N+1 OBJ
+ return false;
+ if (!emit2(JSOP_PICK, 3 + isSuper)) // N N+1 OBJ KEY
+ return false;
+ if (!emit2(JSOP_PICK, 2 + isSuper)) // N OBJ KEY N+1
+ return false;
+ }
+
+ JSOp setOp = isSuper ? (sc->strict() ? JSOP_STRICTSETELEM_SUPER : JSOP_SETELEM_SUPER)
+ : (sc->strict() ? JSOP_STRICTSETELEM : JSOP_SETELEM);
+ if (!emitElemOpBase(setOp)) // N? N+1
+ return false;
+ if (post && !emit1(JSOP_POP)) // RESULT
+ return false;
+
+ return true;
+}
+
+bool
+BytecodeEmitter::emitCallIncDec(ParseNode* incDec)
+{
+ MOZ_ASSERT(incDec->isKind(PNK_PREINCREMENT) ||
+ incDec->isKind(PNK_POSTINCREMENT) ||
+ incDec->isKind(PNK_PREDECREMENT) ||
+ incDec->isKind(PNK_POSTDECREMENT));
+
+ MOZ_ASSERT(incDec->pn_kid->isKind(PNK_CALL));
+
+ ParseNode* call = incDec->pn_kid;
+ if (!emitTree(call)) // CALLRESULT
+ return false;
+ if (!emit1(JSOP_POS)) // N
+ return false;
+
+ // The increment/decrement has no side effects, so proceed to throw for
+ // invalid assignment target.
+ return emitUint16Operand(JSOP_THROWMSG, JSMSG_BAD_LEFTSIDE_OF_ASS);
+}
+
+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 < JS_BIT(16)) {
+ if (!emitUint16Operand(JSOP_UINT16, u))
+ return false;
+ } else if (u < JS_BIT(24)) {
+ ptrdiff_t off;
+ if (!emitN(JSOP_UINT24, 3, &off))
+ return false;
+ SET_UINT24(code(off), u);
+ } else {
+ ptrdiff_t off;
+ if (!emitN(JSOP_INT32, 4, &off))
+ return false;
+ SET_INT32(code(off), ival);
+ }
+ return true;
+ }
+
+ if (!constList.append(DoubleValue(dval)))
+ return false;
+
+ return emitIndex32(JSOP_DOUBLE, constList.length() - 1);
+}
+
+/*
+ * 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(ParseNode* pn)
+{
+ ParseNode* cases = pn->pn_right;
+ MOZ_ASSERT(cases->isKind(PNK_LEXICALSCOPE) || cases->isKind(PNK_STATEMENTLIST));
+
+ // Emit code for the discriminant.
+ if (!emitTree(pn->pn_left))
+ return false;
+
+ // Enter the scope before pushing the switch BreakableControl since all
+ // breaks are under this scope.
+ Maybe<TDZCheckCache> tdzCache;
+ Maybe<EmitterScope> emitterScope;
+ if (cases->isKind(PNK_LEXICALSCOPE)) {
+ if (!cases->isEmptyScope()) {
+ tdzCache.emplace(this);
+ emitterScope.emplace(this);
+ if (!emitterScope->enterLexical(this, ScopeKind::Lexical, cases->scopeBindings()))
+ return false;
+ }
+
+ // Advance |cases| to refer to the switch case list.
+ cases = cases->scopeBody();
+
+ // 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->pn_xflags & PNX_FUNCDEFS) {
+ MOZ_ASSERT(emitterScope);
+ for (ParseNode* caseNode = cases->pn_head; caseNode; caseNode = caseNode->pn_next) {
+ if (caseNode->pn_right->pn_xflags & PNX_FUNCDEFS) {
+ if (!emitHoistedFunctionsInList(caseNode->pn_right))
+ return false;
+ }
+ }
+ }
+ }
+
+ // After entering the scope, push the switch control.
+ BreakableControl controlInfo(this, StatementKind::Switch);
+
+ ptrdiff_t top = offset();
+
+ // Switch bytecodes run from here till end of final case.
+ uint32_t caseCount = cases->pn_count;
+ if (caseCount > JS_BIT(16)) {
+ parser->tokenStream.reportError(JSMSG_TOO_MANY_CASES);
+ return false;
+ }
+
+ // Try for most optimal, fall back if not dense ints.
+ JSOp switchOp = JSOP_TABLESWITCH;
+ uint32_t tableLength = 0;
+ int32_t low, high;
+ bool hasDefault = false;
+ CaseClause* firstCase = cases->pn_head ? &cases->pn_head->as<CaseClause>() : nullptr;
+ if (caseCount == 0 ||
+ (caseCount == 1 && (hasDefault = firstCase->isDefault())))
+ {
+ caseCount = 0;
+ low = 0;
+ high = -1;
+ } else {
+ Vector<jsbitmap, 128, SystemAllocPolicy> intmap;
+ int32_t intmapBitLength = 0;
+
+ low = JSVAL_INT_MAX;
+ high = JSVAL_INT_MIN;
+
+ for (CaseClause* caseNode = firstCase; caseNode; caseNode = caseNode->next()) {
+ if (caseNode->isDefault()) {
+ hasDefault = true;
+ caseCount--; // one of the "cases" was the default
+ continue;
+ }
+
+ if (switchOp == JSOP_CONDSWITCH)
+ continue;
+
+ MOZ_ASSERT(switchOp == JSOP_TABLESWITCH);
+
+ ParseNode* caseValue = caseNode->caseExpression();
+
+ if (caseValue->getKind() != PNK_NUMBER) {
+ switchOp = JSOP_CONDSWITCH;
+ continue;
+ }
+
+ int32_t i;
+ if (!NumberIsInt32(caseValue->pn_dval, &i)) {
+ switchOp = JSOP_CONDSWITCH;
+ continue;
+ }
+
+ if (unsigned(i + int(JS_BIT(15))) >= unsigned(JS_BIT(16))) {
+ switchOp = JSOP_CONDSWITCH;
+ continue;
+ }
+ if (i < low)
+ low = i;
+ if (i > high)
+ high = i;
+
+ // Check for duplicates, which require a JSOP_CONDSWITCH.
+ // We bias i by 65536 if it's negative, and hope that's a rare
+ // case (because it requires a malloc'd bitmap).
+ if (i < 0)
+ i += JS_BIT(16);
+ if (i >= intmapBitLength) {
+ size_t newLength = (i / JS_BITMAP_NBITS) + 1;
+ if (!intmap.resize(newLength))
+ return false;
+ intmapBitLength = newLength * JS_BITMAP_NBITS;
+ }
+ if (JS_TEST_BIT(intmap, i)) {
+ switchOp = JSOP_CONDSWITCH;
+ continue;
+ }
+ JS_SET_BIT(intmap, i);
+ }
+
+ // Compute table length and select condswitch instead if overlarge or
+ // more than half-sparse.
+ if (switchOp == JSOP_TABLESWITCH) {
+ tableLength = uint32_t(high - low + 1);
+ if (tableLength >= JS_BIT(16) || tableLength > 2 * caseCount)
+ switchOp = JSOP_CONDSWITCH;
+ }
+ }
+
+ // The note has one or two offsets: first tells total switch code length;
+ // second (if condswitch) tells offset to first JSOP_CASE.
+ unsigned noteIndex;
+ size_t switchSize;
+ if (switchOp == JSOP_CONDSWITCH) {
+ // 0 bytes of immediate for unoptimized switch.
+ switchSize = 0;
+ if (!newSrcNote3(SRC_CONDSWITCH, 0, 0, &noteIndex))
+ return false;
+ } else {
+ MOZ_ASSERT(switchOp == JSOP_TABLESWITCH);
+
+ // 3 offsets (len, low, high) before the table, 1 per entry.
+ switchSize = size_t(JUMP_OFFSET_LEN * (3 + tableLength));
+ if (!newSrcNote2(SRC_TABLESWITCH, 0, &noteIndex))
+ return false;
+ }
+
+ // Emit switchOp followed by switchSize bytes of jump or lookup table.
+ MOZ_ASSERT(top == offset());
+ if (!emitN(switchOp, switchSize))
+ return false;
+
+ Vector<CaseClause*, 32, SystemAllocPolicy> table;
+
+ JumpList condSwitchDefaultOff;
+ if (switchOp == JSOP_CONDSWITCH) {
+ unsigned caseNoteIndex;
+ bool beforeCases = true;
+ ptrdiff_t lastCaseOffset = -1;
+
+ // The case conditions need their own TDZ cache since they might not
+ // all execute.
+ TDZCheckCache tdzCache(this);
+
+ // Emit code for evaluating cases and jumping to case statements.
+ for (CaseClause* caseNode = firstCase; caseNode; caseNode = caseNode->next()) {
+ ParseNode* caseValue = caseNode->caseExpression();
+
+ // If the expression is a literal, suppress line number emission so
+ // that debugging works more naturally.
+ if (caseValue) {
+ if (!emitTree(caseValue,
+ caseValue->isLiteral() ? SUPPRESS_LINENOTE : EMIT_LINENOTE))
+ {
+ return false;
+ }
+ }
+
+ if (!beforeCases) {
+ // prevCase is the previous JSOP_CASE's bytecode offset.
+ if (!setSrcNoteOffset(caseNoteIndex, 0, offset() - lastCaseOffset))
+ return false;
+ }
+ if (!caseValue) {
+ // This is the default clause.
+ continue;
+ }
+
+ if (!newSrcNote2(SRC_NEXTCASE, 0, &caseNoteIndex))
+ return false;
+
+ // The case clauses are produced before any of the case body. The
+ // JumpList is saved on the parsed tree, then later restored and
+ // patched when generating the cases body.
+ JumpList caseJump;
+ if (!emitJump(JSOP_CASE, &caseJump))
+ return false;
+ caseNode->setOffset(caseJump.offset);
+ lastCaseOffset = caseJump.offset;
+
+ if (beforeCases) {
+ // Switch note's second offset is to first JSOP_CASE.
+ unsigned noteCount = notes().length();
+ if (!setSrcNoteOffset(noteIndex, 1, lastCaseOffset - top))
+ return false;
+ unsigned noteCountDelta = notes().length() - noteCount;
+ if (noteCountDelta != 0)
+ caseNoteIndex += noteCountDelta;
+ beforeCases = false;
+ }
+ }
+
+ // If we didn't have an explicit default (which could fall in between
+ // cases, preventing us from fusing this setSrcNoteOffset with the call
+ // in the loop above), link the last case to the implicit default for
+ // the benefit of IonBuilder.
+ if (!hasDefault &&
+ !beforeCases &&
+ !setSrcNoteOffset(caseNoteIndex, 0, offset() - lastCaseOffset))
+ {
+ return false;
+ }
+
+ // Emit default even if no explicit default statement.
+ if (!emitJump(JSOP_DEFAULT, &condSwitchDefaultOff))
+ return false;
+ } else {
+ MOZ_ASSERT(switchOp == JSOP_TABLESWITCH);
+
+ // skip default offset.
+ jsbytecode* pc = code(top + JUMP_OFFSET_LEN);
+
+ // Fill in switch bounds, which we know fit in 16-bit offsets.
+ SET_JUMP_OFFSET(pc, low);
+ pc += JUMP_OFFSET_LEN;
+ SET_JUMP_OFFSET(pc, high);
+ pc += JUMP_OFFSET_LEN;
+
+ if (tableLength != 0) {
+ if (!table.growBy(tableLength))
+ return false;
+
+ for (CaseClause* caseNode = firstCase; caseNode; caseNode = caseNode->next()) {
+ if (ParseNode* caseValue = caseNode->caseExpression()) {
+ MOZ_ASSERT(caseValue->isKind(PNK_NUMBER));
+
+ int32_t i = int32_t(caseValue->pn_dval);
+ MOZ_ASSERT(double(i) == caseValue->pn_dval);
+
+ i -= low;
+ MOZ_ASSERT(uint32_t(i) < tableLength);
+ MOZ_ASSERT(!table[i]);
+ table[i] = caseNode;
+ }
+ }
+ }
+ }
+
+ JumpTarget defaultOffset{ -1 };
+
+ // Emit code for each case's statements.
+ for (CaseClause* caseNode = firstCase; caseNode; caseNode = caseNode->next()) {
+ if (switchOp == JSOP_CONDSWITCH && !caseNode->isDefault()) {
+ // The case offset got saved in the caseNode structure after
+ // emitting the JSOP_CASE jump instruction above.
+ JumpList caseCond;
+ caseCond.offset = caseNode->offset();
+ if (!emitJumpTargetAndPatch(caseCond))
+ return false;
+ }
+
+ JumpTarget here;
+ if (!emitJumpTarget(&here))
+ return false;
+ if (caseNode->isDefault())
+ defaultOffset = here;
+
+ // If this is emitted as a TABLESWITCH, we'll need to know this case's
+ // offset later when emitting the table. Store it in the node's
+ // pn_offset (giving the field a different meaning vs. how we used it
+ // on the immediately preceding line of code).
+ caseNode->setOffset(here.offset);
+
+ TDZCheckCache tdzCache(this);
+
+ if (!emitTree(caseNode->statementList()))
+ return false;
+ }
+
+ if (!hasDefault) {
+ // If no default case, offset for default is to end of switch.
+ if (!emitJumpTarget(&defaultOffset))
+ return false;
+ }
+ MOZ_ASSERT(defaultOffset.offset != -1);
+
+ // Set the default offset (to end of switch if no default).
+ jsbytecode* pc;
+ if (switchOp == JSOP_CONDSWITCH) {
+ pc = nullptr;
+ patchJumpsToTarget(condSwitchDefaultOff, defaultOffset);
+ } else {
+ MOZ_ASSERT(switchOp == JSOP_TABLESWITCH);
+ pc = code(top);
+ SET_JUMP_OFFSET(pc, defaultOffset.offset - top);
+ pc += JUMP_OFFSET_LEN;
+ }
+
+ // Set the SRC_SWITCH note's offset operand to tell end of switch.
+ if (!setSrcNoteOffset(noteIndex, 0, lastNonJumpTargetOffset() - top))
+ return false;
+
+ if (switchOp == JSOP_TABLESWITCH) {
+ // Skip over the already-initialized switch bounds.
+ pc += 2 * JUMP_OFFSET_LEN;
+
+ // Fill in the jump table, if there is one.
+ for (uint32_t i = 0; i < tableLength; i++) {
+ CaseClause* caseNode = table[i];
+ ptrdiff_t off = caseNode ? caseNode->offset() - top : 0;
+ SET_JUMP_OFFSET(pc, off);
+ pc += JUMP_OFFSET_LEN;
+ }
+ }
+
+ // Patch breaks before leaving the scope, as all breaks are under the
+ // lexical scope if it exists.
+ if (!controlInfo.patchBreaks(this))
+ return false;
+
+ if (emitterScope && !emitterScope->leave(this))
+ return false;
+
+ return true;
+}
+
+bool
+BytecodeEmitter::isRunOnceLambda()
+{
+ // The run once lambda flags set by the parser are approximate, and we look
+ // at properties of the function itself before deciding to emit a function
+ // as a run once lambda.
+
+ if (!(parent && parent->emittingRunOnceLambda) &&
+ (emitterMode != LazyFunction || !lazyScript->treatAsRunOnce()))
+ {
+ return false;
+ }
+
+ FunctionBox* funbox = sc->asFunctionBox();
+ return !funbox->argumentsHasLocalBinding() &&
+ !funbox->isGenerator() &&
+ !funbox->function()->name();
+}
+
+bool
+BytecodeEmitter::emitYieldOp(JSOp op)
+{
+ if (op == JSOP_FINALYIELDRVAL)
+ return emit1(JSOP_FINALYIELDRVAL);
+
+ MOZ_ASSERT(op == JSOP_INITIALYIELD || op == JSOP_YIELD);
+
+ ptrdiff_t off;
+ if (!emitN(op, 3, &off))
+ return false;
+
+ uint32_t yieldIndex = yieldOffsetList.length();
+ if (yieldIndex >= JS_BIT(24)) {
+ reportError(nullptr, JSMSG_TOO_MANY_YIELDS);
+ return false;
+ }
+
+ SET_UINT24(code(off), yieldIndex);
+
+ if (!yieldOffsetList.append(offset()))
+ return false;
+
+ return emit1(JSOP_DEBUGAFTERYIELD);
+}
+
+bool
+BytecodeEmitter::emitSetThis(ParseNode* pn)
+{
+ // PNK_SETTHIS is used to update |this| after a super() call in a derived
+ // class constructor.
+
+ MOZ_ASSERT(pn->isKind(PNK_SETTHIS));
+ MOZ_ASSERT(pn->pn_left->isKind(PNK_NAME));
+
+ RootedAtom name(cx, pn->pn_left->name());
+ auto emitRhs = [&name, pn](BytecodeEmitter* bce, const NameLocation&, bool) {
+ // Emit the new |this| value.
+ if (!bce->emitTree(pn->pn_right))
+ 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 (!bce->emitGetName(name))
+ return false;
+ if (!bce->emit1(JSOP_CHECKTHISREINIT))
+ return false;
+ if (!bce->emit1(JSOP_POP))
+ return false;
+ return true;
+ };
+
+ // 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;
+ }
+
+ return emitSetOrInitializeNameAtLocation(name, lexicalLoc, emitRhs, true);
+}
+
+bool
+BytecodeEmitter::emitScript(ParseNode* body)
+{
+ TDZCheckCache tdzCache(this);
+ EmitterScope emitterScope(this);
+ if (sc->isGlobalContext()) {
+ switchToPrologue();
+ if (!emitterScope.enterGlobal(this, sc->asGlobalContext()))
+ return false;
+ switchToMain();
+ } else if (sc->isEvalContext()) {
+ switchToPrologue();
+ if (!emitterScope.enterEval(this, sc->asEvalContext()))
+ return false;
+ switchToMain();
+ } else {
+ MOZ_ASSERT(sc->isModuleContext());
+ if (!emitterScope.enterModule(this, sc->asModuleContext()))
+ return false;
+ }
+
+ setFunctionBodyEndPos(body->pn_pos);
+
+ if (sc->isEvalContext() && !sc->strict() &&
+ body->isKind(PNK_LEXICALSCOPE) && !body->isEmptyScope())
+ {
+ // Sloppy eval scripts may need to emit DEFFUNs in the prologue. If there is
+ // an immediately enclosed lexical scope, we need to enter the lexical
+ // scope in the prologue for the DEFFUNs to pick up the right
+ // environment chain.
+ EmitterScope lexicalEmitterScope(this);
+
+ switchToPrologue();
+ if (!lexicalEmitterScope.enterLexical(this, ScopeKind::Lexical, body->scopeBindings()))
+ return false;
+ switchToMain();
+
+ if (!emitLexicalScopeBody(body->scopeBody()))
+ return false;
+
+ if (!lexicalEmitterScope.leave(this))
+ return false;
+ } else {
+ if (!emitTree(body))
+ return false;
+ }
+
+ if (!emit1(JSOP_RETRVAL))
+ return false;
+
+ if (!emitterScope.leave(this))
+ return false;
+
+ if (!JSScript::fullyInitFromEmitter(cx, script, this))
+ return false;
+
+ // URL and source map information must be set before firing
+ // Debugger::onNewScript.
+ if (!maybeSetDisplayURL() || !maybeSetSourceMap())
+ return false;
+
+ tellDebuggerAboutCompiledScript(cx);
+
+ return true;
+}
+
+bool
+BytecodeEmitter::emitFunctionScript(ParseNode* body)
+{
+ FunctionBox* funbox = sc->asFunctionBox();
+
+ // The ordering of these EmitterScopes is important. The named lambda
+ // scope needs to enclose the function scope needs to enclose the extra
+ // var scope.
+
+ Maybe<EmitterScope> namedLambdaEmitterScope;
+ if (funbox->namedLambdaBindings()) {
+ namedLambdaEmitterScope.emplace(this);
+ if (!namedLambdaEmitterScope->enterNamedLambda(this, funbox))
+ return false;
+ }
+
+ /*
+ * Emit a prologue for run-once scripts which will deoptimize JIT code
+ * if the script ends up running multiple times via foo.caller related
+ * shenanigans.
+ *
+ * Also mark the script so that initializers created within it may be
+ * given more precise types.
+ */
+ if (isRunOnceLambda()) {
+ script->setTreatAsRunOnce();
+ MOZ_ASSERT(!script->hasRunOnce());
+
+ switchToPrologue();
+ if (!emit1(JSOP_RUNONCE))
+ return false;
+ switchToMain();
+ }
+
+ setFunctionBodyEndPos(body->pn_pos);
+ if (!emitTree(body))
+ return false;
+
+ if (!updateSourceCoordNotes(body->pn_pos.end))
+ return false;
+
+ // Always end the script with a JSOP_RETRVAL. Some other parts of the
+ // codebase depend on this opcode,
+ // e.g. InterpreterRegs::setToEndOfScript.
+ if (!emit1(JSOP_RETRVAL))
+ return false;
+
+ if (namedLambdaEmitterScope) {
+ if (!namedLambdaEmitterScope->leave(this))
+ return false;
+ namedLambdaEmitterScope.reset();
+ }
+
+ if (!JSScript::fullyInitFromEmitter(cx, script, this))
+ return false;
+
+ // URL and source map information must be set before firing
+ // Debugger::onNewScript. Only top-level functions need this, as compiling
+ // the outer scripts of nested functions already processed the source.
+ if (emitterMode != LazyFunction && !parent) {
+ if (!maybeSetDisplayURL() || !maybeSetSourceMap())
+ return false;
+
+ tellDebuggerAboutCompiledScript(cx);
+ }
+
+ return true;
+}
+
+template <typename NameEmitter>
+bool
+BytecodeEmitter::emitDestructuringDeclsWithEmitter(ParseNode* pattern, NameEmitter emitName)
+{
+ if (pattern->isKind(PNK_ARRAY)) {
+ for (ParseNode* element = pattern->pn_head; element; element = element->pn_next) {
+ if (element->isKind(PNK_ELISION))
+ continue;
+ ParseNode* target = element;
+ if (element->isKind(PNK_SPREAD)) {
+ target = element->pn_kid;
+ }
+ if (target->isKind(PNK_ASSIGN))
+ target = target->pn_left;
+ if (target->isKind(PNK_NAME)) {
+ if (!emitName(this, target))
+ return false;
+ } else {
+ if (!emitDestructuringDeclsWithEmitter(target, emitName))
+ return false;
+ }
+ }
+ return true;
+ }
+
+ MOZ_ASSERT(pattern->isKind(PNK_OBJECT));
+ for (ParseNode* member = pattern->pn_head; member; member = member->pn_next) {
+ MOZ_ASSERT(member->isKind(PNK_MUTATEPROTO) ||
+ member->isKind(PNK_COLON) ||
+ member->isKind(PNK_SHORTHAND));
+
+ ParseNode* target = member->isKind(PNK_MUTATEPROTO) ? member->pn_kid : member->pn_right;
+
+ if (target->isKind(PNK_ASSIGN))
+ target = target->pn_left;
+ if (target->isKind(PNK_NAME)) {
+ if (!emitName(this, target))
+ return false;
+ } else {
+ if (!emitDestructuringDeclsWithEmitter(target, emitName))
+ return false;
+ }
+ }
+ return true;
+}
+
+bool
+BytecodeEmitter::emitDestructuringLHS(ParseNode* target, DestructuringFlavor flav)
+{
+ // Now emit the lvalue opcode sequence. If the lvalue is a nested
+ // destructuring initialiser-form, call ourselves to handle it, then pop
+ // the matched value. Otherwise emit an lvalue bytecode sequence followed
+ // by an assignment op.
+ if (target->isKind(PNK_SPREAD))
+ target = target->pn_kid;
+ else if (target->isKind(PNK_ASSIGN))
+ target = target->pn_left;
+ if (target->isKind(PNK_ARRAY) || target->isKind(PNK_OBJECT)) {
+ if (!emitDestructuringOps(target, flav))
+ return false;
+ // Per its post-condition, emitDestructuringOps has left the
+ // to-be-destructured value on top of the stack.
+ if (!emit1(JSOP_POP))
+ return false;
+ } else {
+ switch (target->getKind()) {
+ case PNK_NAME: {
+ auto emitSwapScopeAndRhs = [](BytecodeEmitter* bce, const NameLocation&,
+ bool emittedBindOp)
+ {
+ if (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.
+ return bce->emit1(JSOP_SWAP);
+ }
+
+ // In cases of emitting a frame slot or environment slot,
+ // nothing needs be done.
+ return true;
+ };
+
+ RootedAtom name(cx, target->name());
+ switch (flav) {
+ case DestructuringDeclaration:
+ if (!emitInitializeName(name, emitSwapScopeAndRhs))
+ return false;
+ break;
+
+ case DestructuringFormalParameterInVarScope: {
+ // If there's an parameter expression var scope, the
+ // destructuring declaration needs to initialize the name in
+ // the function scope. The innermost scope is the var scope,
+ // and its enclosing scope is the function scope.
+ EmitterScope* funScope = innermostEmitterScope->enclosingInFrame();
+ NameLocation paramLoc = *locationOfNameBoundInScope(name, funScope);
+ if (!emitSetOrInitializeNameAtLocation(name, paramLoc, emitSwapScopeAndRhs, true))
+ return false;
+ break;
+ }
+
+ case DestructuringAssignment:
+ if (!emitSetName(name, emitSwapScopeAndRhs))
+ return false;
+ break;
+ }
+
+ break;
+ }
+
+ case PNK_DOT: {
+ // See the (PNK_NAME, JSOP_SETNAME) case above.
+ //
+ // In `a.x = b`, `a` is evaluated first, then `b`, then a
+ // JSOP_SETPROP instruction.
+ //
+ // In `[a.x] = [b]`, per spec, `b` is evaluated before `a`. Then we
+ // need a property set -- but the operands are on the stack in the
+ // wrong order for JSOP_SETPROP, so we have to add a JSOP_SWAP.
+ JSOp setOp;
+ if (target->as<PropertyAccess>().isSuper()) {
+ if (!emitSuperPropLHS(&target->as<PropertyAccess>().expression()))
+ return false;
+ if (!emit2(JSOP_PICK, 2))
+ return false;
+ setOp = sc->strict() ? JSOP_STRICTSETPROP_SUPER : JSOP_SETPROP_SUPER;
+ } else {
+ if (!emitTree(target->pn_expr))
+ return false;
+ if (!emit1(JSOP_SWAP))
+ return false;
+ setOp = sc->strict() ? JSOP_STRICTSETPROP : JSOP_SETPROP;
+ }
+ if (!emitAtomOp(target, setOp))
+ return false;
+ break;
+ }
+
+ case PNK_ELEM: {
+ // See the comment at `case PNK_DOT:` above. This case,
+ // `[a[x]] = [b]`, is handled much the same way. The JSOP_SWAP
+ // is emitted by emitElemOperands.
+ if (target->as<PropertyByValue>().isSuper()) {
+ JSOp setOp = sc->strict() ? JSOP_STRICTSETELEM_SUPER : JSOP_SETELEM_SUPER;
+ if (!emitSuperElemOp(target, setOp))
+ return false;
+ } else {
+ JSOp setOp = sc->strict() ? JSOP_STRICTSETELEM : JSOP_SETELEM;
+ if (!emitElemOp(target, setOp))
+ return false;
+ }
+ break;
+ }
+
+ case PNK_CALL:
+ MOZ_ASSERT_UNREACHABLE("Parser::reportIfNotValidSimpleAssignmentTarget "
+ "rejects function calls as assignment "
+ "targets in destructuring assignments");
+ break;
+
+ default:
+ MOZ_CRASH("emitDestructuringLHS: bad lhs kind");
+ }
+
+ // Pop the assigned value.
+ if (!emit1(JSOP_POP))
+ return false;
+ }
+
+ return true;
+}
+
+bool
+BytecodeEmitter::emitConditionallyExecutedDestructuringLHS(ParseNode* target, DestructuringFlavor flav)
+{
+ TDZCheckCache tdzCache(this);
+ return emitDestructuringLHS(target, flav);
+}
+
+bool
+BytecodeEmitter::emitIteratorNext(ParseNode* pn, bool allowSelfHosted)
+{
+ MOZ_ASSERT(allowSelfHosted || emitterMode != BytecodeEmitter::SelfHosting,
+ ".next() iteration is prohibited in self-hosted code because it "
+ "can run user-modifiable iteration code");
+
+ if (!emit1(JSOP_DUP)) // ... ITER ITER
+ return false;
+ if (!emitAtomOp(cx->names().next, JSOP_CALLPROP)) // ... ITER NEXT
+ return false;
+ if (!emit1(JSOP_SWAP)) // ... NEXT ITER
+ return false;
+ if (!emitCall(JSOP_CALL, 0, pn)) // ... RESULT
+ return false;
+ if (!emitCheckIsObj(CheckIsObjectKind::IteratorNext)) // ... RESULT
+ return false;
+ checkTypeSet(JSOP_CALL);
+ return true;
+}
+
+bool
+BytecodeEmitter::emitDefault(ParseNode* defaultExpr)
+{
+ if (!emit1(JSOP_DUP)) // VALUE VALUE
+ return false;
+ if (!emit1(JSOP_UNDEFINED)) // VALUE VALUE UNDEFINED
+ return false;
+ if (!emit1(JSOP_STRICTEQ)) // VALUE EQL?
+ return false;
+ // Emit source note to enable ion compilation.
+ if (!newSrcNote(SRC_IF))
+ return false;
+ JumpList jump;
+ if (!emitJump(JSOP_IFEQ, &jump)) // VALUE
+ return false;
+ if (!emit1(JSOP_POP)) // .
+ return false;
+ if (!emitConditionallyExecutedTree(defaultExpr)) // DEFAULTVALUE
+ return false;
+ if (!emitJumpTargetAndPatch(jump))
+ return false;
+ return true;
+}
+
+class MOZ_STACK_CLASS IfThenElseEmitter
+{
+ BytecodeEmitter* bce_;
+ JumpList jumpAroundThen_;
+ JumpList jumpsAroundElse_;
+ unsigned noteIndex_;
+ int32_t thenDepth_;
+#ifdef DEBUG
+ int32_t pushed_;
+ bool calculatedPushed_;
+#endif
+ enum State {
+ Start,
+ If,
+ Cond,
+ IfElse,
+ Else,
+ End
+ };
+ State state_;
+
+ public:
+ explicit IfThenElseEmitter(BytecodeEmitter* bce)
+ : bce_(bce),
+ noteIndex_(-1),
+ thenDepth_(0),
+#ifdef DEBUG
+ pushed_(0),
+ calculatedPushed_(false),
+#endif
+ state_(Start)
+ {}
+
+ ~IfThenElseEmitter()
+ {}
+
+ private:
+ bool emitIf(State nextState) {
+ MOZ_ASSERT(state_ == Start || state_ == Else);
+ MOZ_ASSERT(nextState == If || nextState == IfElse || nextState == Cond);
+
+ // Clear jumpAroundThen_ offset that points previous JSOP_IFEQ.
+ if (state_ == Else)
+ jumpAroundThen_ = JumpList();
+
+ // Emit an annotated branch-if-false around the then part.
+ SrcNoteType type = nextState == If ? SRC_IF : nextState == IfElse ? SRC_IF_ELSE : SRC_COND;
+ if (!bce_->newSrcNote(type, &noteIndex_))
+ return false;
+ if (!bce_->emitJump(JSOP_IFEQ, &jumpAroundThen_))
+ return false;
+
+ // To restore stack depth in else part, save depth of the then part.
+#ifdef DEBUG
+ // If DEBUG, this is also necessary to calculate |pushed_|.
+ thenDepth_ = bce_->stackDepth;
+#else
+ if (nextState == IfElse || nextState == Cond)
+ thenDepth_ = bce_->stackDepth;
+#endif
+ state_ = nextState;
+ return true;
+ }
+
+ public:
+ bool emitIf() {
+ return emitIf(If);
+ }
+
+ bool emitCond() {
+ return emitIf(Cond);
+ }
+
+ bool emitIfElse() {
+ return emitIf(IfElse);
+ }
+
+ bool emitElse() {
+ MOZ_ASSERT(state_ == IfElse || state_ == Cond);
+
+ calculateOrCheckPushed();
+
+ // 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;
+
+ // Annotate SRC_IF_ELSE or SRC_COND with the offset from branch to
+ // jump, for IonMonkey's benefit. We can't just "back up" from the pc
+ // of the else clause, because we don't know whether an extended
+ // jump was required to leap from the end of the then clause over
+ // the else clause.
+ if (!bce_->setSrcNoteOffset(noteIndex_, 0,
+ jumpsAroundElse_.offset - jumpAroundThen_.offset))
+ {
+ return false;
+ }
+
+ // Restore stack depth of the then part.
+ bce_->stackDepth = thenDepth_;
+ state_ = Else;
+ return true;
+ }
+
+ bool emitEnd() {
+ MOZ_ASSERT(state_ == If || state_ == Else);
+
+ calculateOrCheckPushed();
+
+ if (state_ == If) {
+ // No else part, 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;
+
+ state_ = End;
+ return true;
+ }
+
+ void calculateOrCheckPushed() {
+#ifdef DEBUG
+ if (!calculatedPushed_) {
+ pushed_ = bce_->stackDepth - thenDepth_;
+ calculatedPushed_ = true;
+ } else {
+ MOZ_ASSERT(pushed_ == bce_->stackDepth - thenDepth_);
+ }
+#endif
+ }
+
+#ifdef DEBUG
+ int32_t pushed() const {
+ return pushed_;
+ }
+
+ int32_t popped() const {
+ return -pushed_;
+ }
+#endif
+};
+
+bool
+BytecodeEmitter::emitDestructuringOpsArray(ParseNode* pattern, DestructuringFlavor flav)
+{
+ MOZ_ASSERT(pattern->isKind(PNK_ARRAY));
+ MOZ_ASSERT(pattern->isArity(PN_LIST));
+ MOZ_ASSERT(this->stackDepth != 0);
+
+ // Here's pseudo code for |let [a, b, , c=y, ...d] = x;|
+ //
+ // let x, y;
+ // let a, b, c, d;
+ // let tmp, done, iter, result; // stack values
+ //
+ // iter = x[Symbol.iterator]();
+ //
+ // // ==== emitted by loop for a ====
+ // result = iter.next();
+ // done = result.done;
+ //
+ // if (done) {
+ // a = undefined;
+ //
+ // result = undefined;
+ // done = true;
+ // } else {
+ // a = result.value;
+ //
+ // // Do next element's .next() and .done access here
+ // result = iter.next();
+ // done = result.done;
+ // }
+ //
+ // // ==== emitted by loop for b ====
+ // if (done) {
+ // b = undefined;
+ //
+ // result = undefined;
+ // done = true;
+ // } else {
+ // b = result.value;
+ //
+ // result = iter.next();
+ // done = result.done;
+ // }
+ //
+ // // ==== emitted by loop for elision ====
+ // if (done) {
+ // result = undefined
+ // done = true
+ // } else {
+ // result.value;
+ //
+ // result = iter.next();
+ // done = result.done;
+ // }
+ //
+ // // ==== emitted by loop for c ====
+ // if (done) {
+ // c = y;
+ // } else {
+ // tmp = result.value;
+ // if (tmp === undefined)
+ // tmp = y;
+ // c = tmp;
+ //
+ // // Don't do next element's .next() and .done access if
+ // // this is the last non-spread element.
+ // }
+ //
+ // // ==== emitted by loop for d ====
+ // if (done) {
+ // // Assing empty array when completed
+ // d = [];
+ // } else {
+ // d = [...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)) // ... OBJ OBJ
+ return false;
+ if (!emitIterator()) // ... OBJ? ITER
+ return false;
+ bool needToPopIterator = true;
+
+ for (ParseNode* member = pattern->pn_head; member; member = member->pn_next) {
+ bool isHead = member == pattern->pn_head;
+ if (member->isKind(PNK_SPREAD)) {
+ IfThenElseEmitter ifThenElse(this);
+ if (!isHead) {
+ // If spread is not the first element of the pattern,
+ // iterator can already be completed.
+ if (!ifThenElse.emitIfElse()) // ... OBJ? ITER
+ return false;
+
+ if (!emit1(JSOP_POP)) // ... OBJ?
+ return false;
+ if (!emitUint32Operand(JSOP_NEWARRAY, 0)) // ... OBJ? ARRAY
+ return false;
+ if (!emitConditionallyExecutedDestructuringLHS(member, flav)) // ... OBJ?
+ return false;
+
+ if (!ifThenElse.emitElse()) // ... OBJ? ITER
+ return false;
+ }
+
+ // If iterator is not completed, create a new array with the rest
+ // of the iterator.
+ if (!emitUint32Operand(JSOP_NEWARRAY, 0)) // ... OBJ? ITER ARRAY
+ return false;
+ if (!emitNumberOp(0)) // ... OBJ? ITER ARRAY INDEX
+ return false;
+ if (!emitSpread()) // ... OBJ? ARRAY INDEX
+ return false;
+ if (!emit1(JSOP_POP)) // ... OBJ? ARRAY
+ return false;
+ if (!emitConditionallyExecutedDestructuringLHS(member, flav)) // ... OBJ?
+ return false;
+
+ if (!isHead) {
+ if (!ifThenElse.emitEnd())
+ return false;
+ MOZ_ASSERT(ifThenElse.popped() == 1);
+ }
+ needToPopIterator = false;
+ MOZ_ASSERT(!member->pn_next);
+ break;
+ }
+
+ ParseNode* pndefault = nullptr;
+ ParseNode* subpattern = member;
+ if (subpattern->isKind(PNK_ASSIGN)) {
+ pndefault = subpattern->pn_right;
+ subpattern = subpattern->pn_left;
+ }
+
+ bool isElision = subpattern->isKind(PNK_ELISION);
+ bool hasNextNonSpread = member->pn_next && !member->pn_next->isKind(PNK_SPREAD);
+ bool hasNextSpread = member->pn_next && member->pn_next->isKind(PNK_SPREAD);
+
+ MOZ_ASSERT(!subpattern->isKind(PNK_SPREAD));
+
+ auto emitNext = [pattern](ExclusiveContext* cx, BytecodeEmitter* bce) {
+ if (!bce->emit1(JSOP_DUP)) // ... OBJ? ITER ITER
+ return false;
+ if (!bce->emitIteratorNext(pattern)) // ... OBJ? ITER RESULT
+ return false;
+ if (!bce->emit1(JSOP_DUP)) // ... OBJ? ITER RESULT RESULT
+ return false;
+ if (!bce->emitAtomOp(cx->names().done, JSOP_GETPROP)) // ... OBJ? ITER RESULT DONE?
+ return false;
+ return true;
+ };
+
+ if (isHead) {
+ if (!emitNext(cx, this)) // ... OBJ? ITER RESULT DONE?
+ return false;
+ }
+
+ IfThenElseEmitter ifThenElse(this);
+ if (!ifThenElse.emitIfElse()) // ... OBJ? ITER RESULT
+ return false;
+
+ if (!emit1(JSOP_POP)) // ... OBJ? ITER
+ return false;
+ if (pndefault) {
+ // Emit only pndefault tree here, as undefined check in emitDefault
+ // should always be true.
+ if (!emitConditionallyExecutedTree(pndefault)) // ... OBJ? ITER VALUE
+ return false;
+ } else {
+ if (!isElision) {
+ if (!emit1(JSOP_UNDEFINED)) // ... OBJ? ITER UNDEFINED
+ return false;
+ if (!emit1(JSOP_NOP_DESTRUCTURING))
+ return false;
+ }
+ }
+ if (!isElision) {
+ if (!emitConditionallyExecutedDestructuringLHS(subpattern, flav)) // ... OBJ? ITER
+ return false;
+ } else if (pndefault) {
+ if (!emit1(JSOP_POP)) // ... OBJ? ITER
+ return false;
+ }
+
+ // Setup next element's result when the iterator is done.
+ if (hasNextNonSpread) {
+ if (!emit1(JSOP_UNDEFINED)) // ... OBJ? ITER RESULT
+ return false;
+ if (!emit1(JSOP_NOP_DESTRUCTURING))
+ return false;
+ if (!emit1(JSOP_TRUE)) // ... OBJ? ITER RESULT DONE?
+ return false;
+ } else if (hasNextSpread) {
+ if (!emit1(JSOP_TRUE)) // ... OBJ? ITER DONE?
+ return false;
+ }
+
+ if (!ifThenElse.emitElse()) // ... OBJ? ITER RESULT
+ return false;
+
+ if (!emitAtomOp(cx->names().value, JSOP_GETPROP)) // ... OBJ? ITER VALUE
+ return false;
+
+ if (pndefault) {
+ if (!emitDefault(pndefault)) // ... OBJ? ITER VALUE
+ return false;
+ }
+
+ if (!isElision) {
+ if (!emitConditionallyExecutedDestructuringLHS(subpattern, flav)) // ... OBJ? ITER
+ return false;
+ } else {
+ if (!emit1(JSOP_POP)) // ... OBJ? ITER
+ return false;
+ }
+
+ // Setup next element's result when the iterator is not done.
+ if (hasNextNonSpread) {
+ if (!emitNext(cx, this)) // ... OBJ? ITER RESULT DONE?
+ return false;
+ } else if (hasNextSpread) {
+ if (!emit1(JSOP_FALSE)) // ... OBJ? ITER DONE?
+ return false;
+ }
+
+ if (!ifThenElse.emitEnd())
+ return false;
+ if (hasNextNonSpread)
+ MOZ_ASSERT(ifThenElse.pushed() == 1);
+ else if (hasNextSpread)
+ MOZ_ASSERT(ifThenElse.pushed() == 0);
+ else
+ MOZ_ASSERT(ifThenElse.popped() == 1);
+ }
+
+ if (needToPopIterator) {
+ if (!emit1(JSOP_POP)) // ... OBJ?
+ return false;
+ }
+
+ return true;
+}
+
+bool
+BytecodeEmitter::emitComputedPropertyName(ParseNode* computedPropName)
+{
+ MOZ_ASSERT(computedPropName->isKind(PNK_COMPUTED_NAME));
+ return emitTree(computedPropName->pn_kid) && emit1(JSOP_TOID);
+}
+
+bool
+BytecodeEmitter::emitDestructuringOpsObject(ParseNode* pattern, DestructuringFlavor flav)
+{
+ MOZ_ASSERT(pattern->isKind(PNK_OBJECT));
+ MOZ_ASSERT(pattern->isArity(PN_LIST));
+
+ MOZ_ASSERT(this->stackDepth > 0); // ... RHS
+
+ if (!emitRequireObjectCoercible()) // ... RHS
+ return false;
+
+ for (ParseNode* member = pattern->pn_head; member; member = member->pn_next) {
+ // Duplicate the value being destructured to use as a reference base.
+ if (!emit1(JSOP_DUP)) // ... RHS RHS
+ return false;
+
+ // Now push the property name currently being matched, which is the
+ // current property name "label" on the left of a colon in the object
+ // initialiser.
+ bool needsGetElem = true;
+
+ ParseNode* subpattern;
+ if (member->isKind(PNK_MUTATEPROTO)) {
+ if (!emitAtomOp(cx->names().proto, JSOP_GETPROP)) // ... RHS PROP
+ return false;
+ needsGetElem = false;
+ subpattern = member->pn_kid;
+ } else {
+ MOZ_ASSERT(member->isKind(PNK_COLON) || member->isKind(PNK_SHORTHAND));
+
+ ParseNode* key = member->pn_left;
+ if (key->isKind(PNK_NUMBER)) {
+ if (!emitNumberOp(key->pn_dval)) // ... RHS RHS KEY
+ return false;
+ } else if (key->isKind(PNK_OBJECT_PROPERTY_NAME) || key->isKind(PNK_STRING)) {
+ PropertyName* name = key->pn_atom->asPropertyName();
+
+ // The parser already checked for atoms representing indexes and
+ // used PNK_NUMBER instead, but also watch for ids which TI treats
+ // as indexes for simplification of downstream analysis.
+ jsid id = NameToId(name);
+ if (id != IdToTypeId(id)) {
+ if (!emitTree(key)) // ... RHS RHS KEY
+ return false;
+ } else {
+ if (!emitAtomOp(name, JSOP_GETPROP)) // ...RHS PROP
+ return false;
+ needsGetElem = false;
+ }
+ } else {
+ if (!emitComputedPropertyName(key)) // ... RHS RHS KEY
+ return false;
+ }
+
+ subpattern = member->pn_right;
+ }
+
+ // Get the property value if not done already.
+ if (needsGetElem && !emitElemOpBase(JSOP_GETELEM)) // ... RHS PROP
+ return false;
+
+ if (subpattern->isKind(PNK_ASSIGN)) {
+ if (!emitDefault(subpattern->pn_right))
+ return false;
+ subpattern = subpattern->pn_left;
+ }
+
+ // Destructure PROP per this member's subpattern.
+ if (!emitDestructuringLHS(subpattern, flav))
+ return false;
+ }
+
+ return true;
+}
+
+bool
+BytecodeEmitter::emitDestructuringOps(ParseNode* pattern, DestructuringFlavor flav)
+{
+ if (pattern->isKind(PNK_ARRAY))
+ return emitDestructuringOpsArray(pattern, flav);
+ return emitDestructuringOpsObject(pattern, flav);
+}
+
+bool
+BytecodeEmitter::emitTemplateString(ParseNode* pn)
+{
+ MOZ_ASSERT(pn->isArity(PN_LIST));
+
+ bool pushedString = false;
+
+ for (ParseNode* pn2 = pn->pn_head; pn2 != NULL; pn2 = pn2->pn_next) {
+ bool isString = (pn2->getKind() == PNK_STRING || pn2->getKind() == PNK_TEMPLATE_STRING);
+
+ // 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 && pn2->pn_atom->empty())
+ continue;
+
+ if (!isString) {
+ // We update source notes before emitting the expression
+ if (!updateSourceCoordNotes(pn2->pn_pos.begin))
+ return false;
+ }
+
+ if (!emitTree(pn2))
+ return false;
+
+ if (!isString) {
+ // We need to convert the expression to a string
+ if (!emit1(JSOP_TOSTRING))
+ return false;
+ }
+
+ if (pushedString) {
+ // We've pushed two strings onto the stack. Add them together, leaving just one.
+ if (!emit1(JSOP_ADD))
+ return false;
+ } else {
+ pushedString = true;
+ }
+ }
+
+ if (!pushedString) {
+ // All strings were empty, this can happen for something like `${""}`.
+ // Just push an empty string.
+ if (!emitAtomOp(cx->names().empty, JSOP_STRING))
+ return false;
+ }
+
+ return true;
+}
+
+bool
+BytecodeEmitter::emitDeclarationList(ParseNode* declList)
+{
+ MOZ_ASSERT(declList->isArity(PN_LIST));
+
+ ParseNode* next;
+ for (ParseNode* decl = declList->pn_head; decl; decl = next) {
+ if (!updateSourceCoordNotes(decl->pn_pos.begin))
+ return false;
+ next = decl->pn_next;
+
+ if (decl->isKind(PNK_ASSIGN)) {
+ MOZ_ASSERT(decl->isOp(JSOP_NOP));
+
+ ParseNode* pattern = decl->pn_left;
+ MOZ_ASSERT(pattern->isKind(PNK_ARRAY) || pattern->isKind(PNK_OBJECT));
+
+ if (!emitTree(decl->pn_right))
+ return false;
+
+ if (!emitDestructuringOps(pattern, DestructuringDeclaration))
+ return false;
+
+ if (!emit1(JSOP_POP))
+ return false;
+ } else {
+ if (!emitSingleDeclaration(declList, decl, decl->expr()))
+ return false;
+ }
+ }
+ return true;
+}
+
+bool
+BytecodeEmitter::emitSingleDeclaration(ParseNode* declList, ParseNode* decl,
+ ParseNode* initializer)
+{
+ MOZ_ASSERT(decl->isKind(PNK_NAME));
+
+ // Nothing to do for initializer-less 'var' declarations, as there's no TDZ.
+ if (!initializer && declList->isKind(PNK_VAR))
+ return true;
+
+ auto emitRhs = [initializer, declList](BytecodeEmitter* bce, const NameLocation&, bool) {
+ if (!initializer) {
+ // Lexical declarations are initialized to undefined without an
+ // initializer.
+ MOZ_ASSERT(declList->isKind(PNK_LET),
+ "var declarations without initializers handled above, "
+ "and const declarations must have initializers");
+ return bce->emit1(JSOP_UNDEFINED);
+ }
+
+ MOZ_ASSERT(initializer);
+ return bce->emitTree(initializer);
+ };
+
+ if (!emitInitializeName(decl, emitRhs))
+ return false;
+
+ // Pop the RHS.
+ return emit1(JSOP_POP);
+}
+
+static bool
+EmitAssignmentRhs(BytecodeEmitter* bce, ParseNode* rhs, uint8_t offset)
+{
+ // If there is a RHS tree, emit the tree.
+ if (rhs)
+ return bce->emitTree(rhs);
+
+ // Otherwise the RHS value to assign is already on the stack, i.e., the
+ // next enumeration value in a for-in or for-of loop. Depending on how
+ // many other values have been pushed on the stack, we need to get the
+ // already-pushed RHS value.
+ if (offset != 1 && !bce->emit2(JSOP_PICK, offset - 1))
+ return false;
+
+ return true;
+}
+
+bool
+BytecodeEmitter::emitAssignment(ParseNode* lhs, JSOp op, ParseNode* rhs)
+{
+ // Name assignments are handled separately because choosing ops and when
+ // to emit BINDNAME is involved and should avoid duplication.
+ if (lhs->isKind(PNK_NAME)) {
+ auto emitRhs = [op, lhs, rhs](BytecodeEmitter* bce, const NameLocation& lhsLoc,
+ bool emittedBindOp)
+ {
+ // For compound assignments, first get the LHS value, then emit
+ // the RHS and the op.
+ if (op != JSOP_NOP) {
+ if (lhsLoc.kind() == NameLocation::Kind::Dynamic) {
+ // For dynamic accesses we can do better than a GETNAME
+ // since the assignment already emitted a BINDNAME on the
+ // top of the stack. As an optimization, use that to get
+ // the name.
+ if (!bce->emit1(JSOP_DUP))
+ return false;
+ if (!bce->emitAtomOp(lhs, JSOP_GETXPROP))
+ return false;
+ } else {
+ if (!bce->emitGetNameAtLocation(lhs->name(), lhsLoc))
+ return false;
+ }
+ }
+
+ // Emit the RHS. If we emitted a BIND[G]NAME, then the scope is on
+ // the top of the stack and we need to pick the right RHS value.
+ if (!EmitAssignmentRhs(bce, rhs, emittedBindOp ? 2 : 1))
+ return false;
+
+ // Emit the compound assignment op if there is one.
+ if (op != JSOP_NOP && !bce->emit1(op))
+ return false;
+
+ return true;
+ };
+
+ return emitSetName(lhs, emitRhs);
+ }
+
+ // Deal with non-name assignments.
+ uint32_t atomIndex = (uint32_t) -1;
+ uint8_t offset = 1;
+
+ switch (lhs->getKind()) {
+ case PNK_DOT:
+ if (lhs->as<PropertyAccess>().isSuper()) {
+ if (!emitSuperPropLHS(&lhs->as<PropertyAccess>().expression()))
+ return false;
+ offset += 2;
+ } else {
+ if (!emitTree(lhs->expr()))
+ return false;
+ offset += 1;
+ }
+ if (!makeAtomIndex(lhs->pn_atom, &atomIndex))
+ return false;
+ break;
+ case PNK_ELEM: {
+ MOZ_ASSERT(lhs->isArity(PN_BINARY));
+ EmitElemOption opt = op == JSOP_NOP ? EmitElemOption::Get : EmitElemOption::CompoundAssign;
+ if (lhs->as<PropertyByValue>().isSuper()) {
+ if (!emitSuperElemOperands(lhs, opt))
+ return false;
+ offset += 3;
+ } else {
+ if (!emitElemOperands(lhs, opt))
+ return false;
+ offset += 2;
+ }
+ break;
+ }
+ case PNK_ARRAY:
+ case PNK_OBJECT:
+ break;
+ case PNK_CALL:
+ if (!emitTree(lhs))
+ return false;
+
+ // Assignment to function calls is forbidden, but we have to make the
+ // call first. Now we can throw.
+ if (!emitUint16Operand(JSOP_THROWMSG, JSMSG_BAD_LEFTSIDE_OF_ASS))
+ return false;
+
+ // Rebalance the stack to placate stack-depth assertions.
+ if (!emit1(JSOP_POP))
+ return false;
+ break;
+ default:
+ MOZ_ASSERT(0);
+ }
+
+ if (op != JSOP_NOP) {
+ MOZ_ASSERT(rhs);
+ switch (lhs->getKind()) {
+ case PNK_DOT: {
+ JSOp getOp;
+ if (lhs->as<PropertyAccess>().isSuper()) {
+ if (!emit1(JSOP_DUP2))
+ return false;
+ getOp = JSOP_GETPROP_SUPER;
+ } else {
+ if (!emit1(JSOP_DUP))
+ return false;
+ bool isLength = (lhs->pn_atom == cx->names().length);
+ getOp = isLength ? JSOP_LENGTH : JSOP_GETPROP;
+ }
+ if (!emitIndex32(getOp, atomIndex))
+ return false;
+ break;
+ }
+ case PNK_ELEM: {
+ JSOp elemOp;
+ if (lhs->as<PropertyByValue>().isSuper()) {
+ if (!emitDupAt(2))
+ return false;
+ if (!emitDupAt(2))
+ return false;
+ if (!emitDupAt(2))
+ return false;
+ elemOp = JSOP_GETELEM_SUPER;
+ } else {
+ if (!emit1(JSOP_DUP2))
+ return false;
+ elemOp = JSOP_GETELEM;
+ }
+ if (!emitElemOpBase(elemOp))
+ return false;
+ break;
+ }
+ case PNK_CALL:
+ // 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))
+ return false;
+ break;
+ default:;
+ }
+ }
+
+ if (!EmitAssignmentRhs(this, rhs, offset))
+ return false;
+
+ /* If += etc., emit the binary operator with a source note. */
+ if (op != JSOP_NOP) {
+ if (!newSrcNote(SRC_ASSIGNOP))
+ return false;
+ if (!emit1(op))
+ return false;
+ }
+
+ /* Finally, emit the specialized assignment bytecode. */
+ switch (lhs->getKind()) {
+ case PNK_DOT: {
+ JSOp setOp = lhs->as<PropertyAccess>().isSuper() ?
+ (sc->strict() ? JSOP_STRICTSETPROP_SUPER : JSOP_SETPROP_SUPER) :
+ (sc->strict() ? JSOP_STRICTSETPROP : JSOP_SETPROP);
+ if (!emitIndexOp(setOp, atomIndex))
+ return false;
+ break;
+ }
+ case PNK_CALL:
+ // We threw above, so nothing to do here.
+ break;
+ case PNK_ELEM: {
+ JSOp setOp = lhs->as<PropertyByValue>().isSuper() ?
+ sc->strict() ? JSOP_STRICTSETELEM_SUPER : JSOP_SETELEM_SUPER :
+ sc->strict() ? JSOP_STRICTSETELEM : JSOP_SETELEM;
+ if (!emit1(setOp))
+ return false;
+ break;
+ }
+ case PNK_ARRAY:
+ case PNK_OBJECT:
+ if (!emitDestructuringOps(lhs, DestructuringAssignment))
+ return false;
+ break;
+ default:
+ MOZ_ASSERT(0);
+ }
+ return true;
+}
+
+bool
+ParseNode::getConstantValue(ExclusiveContext* cx, AllowConstantObjects allowObjects,
+ MutableHandleValue vp, Value* compare, size_t ncompare,
+ NewObjectKind newKind)
+{
+ MOZ_ASSERT(newKind == TenuredObject || newKind == SingletonObject);
+
+ switch (getKind()) {
+ case PNK_NUMBER:
+ vp.setNumber(pn_dval);
+ return true;
+ case PNK_TEMPLATE_STRING:
+ case PNK_STRING:
+ vp.setString(pn_atom);
+ return true;
+ case PNK_TRUE:
+ vp.setBoolean(true);
+ return true;
+ case PNK_FALSE:
+ vp.setBoolean(false);
+ return true;
+ case PNK_NULL:
+ vp.setNull();
+ return true;
+ case PNK_CALLSITEOBJ:
+ case PNK_ARRAY: {
+ unsigned count;
+ ParseNode* pn;
+
+ if (allowObjects == DontAllowObjects) {
+ vp.setMagic(JS_GENERIC_MAGIC);
+ return true;
+ }
+
+ ObjectGroup::NewArrayKind arrayKind = ObjectGroup::NewArrayKind::Normal;
+ if (allowObjects == ForCopyOnWriteArray) {
+ arrayKind = ObjectGroup::NewArrayKind::CopyOnWrite;
+ allowObjects = DontAllowObjects;
+ }
+
+ if (getKind() == PNK_CALLSITEOBJ) {
+ count = pn_count - 1;
+ pn = pn_head->pn_next;
+ } else {
+ MOZ_ASSERT(isOp(JSOP_NEWINIT) && !(pn_xflags & PNX_NONCONST));
+ count = pn_count;
+ pn = pn_head;
+ }
+
+ AutoValueVector values(cx);
+ if (!values.appendN(MagicValue(JS_ELEMENTS_HOLE), count))
+ return false;
+ size_t idx;
+ for (idx = 0; pn; idx++, pn = pn->pn_next) {
+ if (!pn->getConstantValue(cx, allowObjects, values[idx], values.begin(), idx))
+ return false;
+ if (values[idx].isMagic(JS_GENERIC_MAGIC)) {
+ vp.setMagic(JS_GENERIC_MAGIC);
+ return true;
+ }
+ }
+ MOZ_ASSERT(idx == count);
+
+ JSObject* obj = ObjectGroup::newArrayObject(cx, values.begin(), values.length(),
+ newKind, arrayKind);
+ if (!obj)
+ return false;
+
+ if (!CombineArrayElementTypes(cx, obj, compare, ncompare))
+ return false;
+
+ vp.setObject(*obj);
+ return true;
+ }
+ case PNK_OBJECT: {
+ MOZ_ASSERT(isOp(JSOP_NEWINIT));
+ MOZ_ASSERT(!(pn_xflags & PNX_NONCONST));
+
+ if (allowObjects == DontAllowObjects) {
+ vp.setMagic(JS_GENERIC_MAGIC);
+ return true;
+ }
+ MOZ_ASSERT(allowObjects == AllowObjects);
+
+ Rooted<IdValueVector> properties(cx, IdValueVector(cx));
+
+ RootedValue value(cx), idvalue(cx);
+ for (ParseNode* pn = pn_head; pn; pn = pn->pn_next) {
+ if (!pn->pn_right->getConstantValue(cx, allowObjects, &value))
+ return false;
+ if (value.isMagic(JS_GENERIC_MAGIC)) {
+ vp.setMagic(JS_GENERIC_MAGIC);
+ return true;
+ }
+
+ ParseNode* pnid = pn->pn_left;
+ if (pnid->isKind(PNK_NUMBER)) {
+ idvalue = NumberValue(pnid->pn_dval);
+ } else {
+ MOZ_ASSERT(pnid->isKind(PNK_OBJECT_PROPERTY_NAME) || pnid->isKind(PNK_STRING));
+ MOZ_ASSERT(pnid->pn_atom != cx->names().proto);
+ idvalue = StringValue(pnid->pn_atom);
+ }
+
+ RootedId id(cx);
+ if (!ValueToId<CanGC>(cx, idvalue, &id))
+ return false;
+
+ if (!properties.append(IdValuePair(id, value)))
+ return false;
+ }
+
+ JSObject* obj = ObjectGroup::newPlainObject(cx, properties.begin(), properties.length(),
+ newKind);
+ if (!obj)
+ return false;
+
+ if (!CombinePlainObjectPropertyTypes(cx, obj, compare, ncompare))
+ return false;
+
+ vp.setObject(*obj);
+ return true;
+ }
+ default:
+ MOZ_CRASH("Unexpected node");
+ }
+ return false;
+}
+
+bool
+BytecodeEmitter::emitSingletonInitialiser(ParseNode* pn)
+{
+ NewObjectKind newKind = (pn->getKind() == PNK_OBJECT) ? SingletonObject : TenuredObject;
+
+ RootedValue value(cx);
+ if (!pn->getConstantValue(cx, ParseNode::AllowObjects, &value, nullptr, 0, newKind))
+ return false;
+
+ MOZ_ASSERT_IF(newKind == SingletonObject, value.toObject().isSingleton());
+
+ ObjectBox* objbox = parser->newObjectBox(&value.toObject());
+ if (!objbox)
+ return false;
+
+ return emitObjectOp(objbox, JSOP_OBJECT);
+}
+
+bool
+BytecodeEmitter::emitCallSiteObject(ParseNode* pn)
+{
+ RootedValue value(cx);
+ if (!pn->getConstantValue(cx, ParseNode::AllowObjects, &value))
+ return false;
+
+ MOZ_ASSERT(value.isObject());
+
+ ObjectBox* objbox1 = parser->newObjectBox(&value.toObject());
+ if (!objbox1)
+ return false;
+
+ if (!pn->as<CallSiteNode>().getRawArrayValue(cx, &value))
+ return false;
+
+ MOZ_ASSERT(value.isObject());
+
+ ObjectBox* objbox2 = parser->newObjectBox(&value.toObject());
+ if (!objbox2)
+ return false;
+
+ return emitObjectPairOp(objbox1, objbox2, JSOP_CALLSITEOBJ);
+}
+
+/* See the SRC_FOR source note offsetBias comments later in this file. */
+JS_STATIC_ASSERT(JSOP_NOP_LENGTH == 1);
+JS_STATIC_ASSERT(JSOP_POP_LENGTH == 1);
+
+namespace {
+
+class EmitLevelManager
+{
+ BytecodeEmitter* bce;
+ public:
+ explicit EmitLevelManager(BytecodeEmitter* bce) : bce(bce) { bce->emitLevel++; }
+ ~EmitLevelManager() { bce->emitLevel--; }
+};
+
+} /* anonymous namespace */
+
+bool
+BytecodeEmitter::emitCatch(ParseNode* pn)
+{
+ // We must be nested under a try-finally statement.
+ TryFinallyControl& controlInfo = innermostNestableControl->as<TryFinallyControl>();
+
+ /* Pick up the pending exception and bind it to the catch variable. */
+ if (!emit1(JSOP_EXCEPTION))
+ return false;
+
+ /*
+ * Dup the exception object if there is a guard for rethrowing to use
+ * it later when rethrowing or in other catches.
+ */
+ if (pn->pn_kid2 && !emit1(JSOP_DUP))
+ return false;
+
+ ParseNode* pn2 = pn->pn_kid1;
+ switch (pn2->getKind()) {
+ case PNK_ARRAY:
+ case PNK_OBJECT:
+ if (!emitDestructuringOps(pn2, DestructuringDeclaration))
+ return false;
+ if (!emit1(JSOP_POP))
+ return false;
+ break;
+
+ case PNK_NAME:
+ if (!emitLexicalInitialization(pn2))
+ return false;
+ if (!emit1(JSOP_POP))
+ return false;
+ break;
+
+ default:
+ MOZ_ASSERT(0);
+ }
+
+ // If there is a guard expression, emit it and arrange to jump to the next
+ // catch block if the guard expression is false.
+ if (pn->pn_kid2) {
+ if (!emitTree(pn->pn_kid2))
+ return false;
+
+ // If the guard expression is false, fall through, pop the block scope,
+ // and jump to the next catch block. Otherwise jump over that code and
+ // pop the dupped exception.
+ JumpList guardCheck;
+ if (!emitJump(JSOP_IFNE, &guardCheck))
+ return false;
+
+ {
+ NonLocalExitControl nle(this);
+
+ // Move exception back to cx->exception to prepare for
+ // the next catch.
+ if (!emit1(JSOP_THROWING))
+ return false;
+
+ // Leave the scope for this catch block.
+ if (!nle.prepareForNonLocalJump(&controlInfo))
+ return false;
+
+ // Jump to the next handler added by emitTry.
+ if (!emitJump(JSOP_GOTO, &controlInfo.guardJump))
+ return false;
+ }
+
+ // Back to normal control flow.
+ if (!emitJumpTargetAndPatch(guardCheck))
+ return false;
+
+ // Pop duplicated exception object as we no longer need it.
+ if (!emit1(JSOP_POP))
+ return false;
+ }
+
+ /* Emit the catch body. */
+ return emitTree(pn->pn_kid3);
+}
+
+// Using MOZ_NEVER_INLINE in here is a workaround for llvm.org/pr14047. See the
+// comment on EmitSwitch.
+MOZ_NEVER_INLINE bool
+BytecodeEmitter::emitTry(ParseNode* pn)
+{
+ // Track jumps-over-catches and gosubs-to-finally for later fixup.
+ //
+ // When a finally block is active, non-local jumps (including
+ // jumps-over-catches) result in a GOSUB being written into the bytecode
+ // stream and fixed-up later.
+ //
+ TryFinallyControl controlInfo(this, pn->pn_kid3 ? StatementKind::Finally : StatementKind::Try);
+
+ // 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.
+ //
+ int depth = stackDepth;
+
+ // Record the try location, then emit the try block.
+ unsigned noteIndex;
+ if (!newSrcNote(SRC_TRY, &noteIndex))
+ return false;
+ if (!emit1(JSOP_TRY))
+ return false;
+
+ ptrdiff_t tryStart = offset();
+ if (!emitTree(pn->pn_kid1))
+ return false;
+ MOZ_ASSERT(depth == stackDepth);
+
+ // GOSUB to finally, if present.
+ if (pn->pn_kid3) {
+ if (!emitJump(JSOP_GOSUB, &controlInfo.gosubs))
+ return false;
+ }
+
+ // Source note points to the jump at the end of the try block.
+ if (!setSrcNoteOffset(noteIndex, 0, offset() - tryStart + JSOP_TRY_LENGTH))
+ return false;
+
+ // Emit jump over catch and/or finally.
+ JumpList catchJump;
+ if (!emitJump(JSOP_GOTO, &catchJump))
+ return false;
+
+ JumpTarget tryEnd;
+ if (!emitJumpTarget(&tryEnd))
+ return false;
+
+ // If this try has a catch block, emit it.
+ ParseNode* catchList = pn->pn_kid2;
+ if (catchList) {
+ MOZ_ASSERT(catchList->isKind(PNK_CATCHLIST));
+
+ // The emitted code for a catch block looks like:
+ //
+ // [pushlexicalenv] only if any local aliased
+ // exception
+ // if there is a catchguard:
+ // dup
+ // setlocal 0; pop assign or possibly destructure exception
+ // if there is a catchguard:
+ // < catchguard code >
+ // ifne POST
+ // debugleaveblock
+ // [poplexicalenv] only if any local aliased
+ // throwing pop exception to cx->exception
+ // goto <next catch block>
+ // POST: pop
+ // < catch block contents >
+ // debugleaveblock
+ // [poplexicalenv] only if any local aliased
+ // goto <end of catch blocks> non-local; finally applies
+ //
+ // If there's no catch block without a catchguard, the last <next catch
+ // block> points to rethrow code. This code will [gosub] to the finally
+ // code if appropriate, and is also used for the catch-all trynote for
+ // capturing exceptions thrown from catch{} blocks.
+ //
+ for (ParseNode* pn3 = catchList->pn_head; pn3; pn3 = pn3->pn_next) {
+ MOZ_ASSERT(this->stackDepth == depth);
+
+ // 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 (!emit1(JSOP_UNDEFINED))
+ return false;
+ if (!emit1(JSOP_SETRVAL))
+ return false;
+
+ // Emit the lexical scope and catch body.
+ MOZ_ASSERT(pn3->isKind(PNK_LEXICALSCOPE));
+ if (!emitTree(pn3))
+ return false;
+
+ // gosub <finally>, if required.
+ if (pn->pn_kid3) {
+ if (!emitJump(JSOP_GOSUB, &controlInfo.gosubs))
+ return false;
+ MOZ_ASSERT(this->stackDepth == depth);
+ }
+
+ // Jump over the remaining catch blocks. This will get fixed
+ // up to jump to after catch/finally.
+ if (!emitJump(JSOP_GOTO, &catchJump))
+ return false;
+
+ // If this catch block had a guard clause, patch the guard jump to
+ // come here.
+ if (controlInfo.guardJump.offset != -1) {
+ if (!emitJumpTargetAndPatch(controlInfo.guardJump))
+ return false;
+ controlInfo.guardJump.offset = -1;
+
+ // If this catch block is the last one, rethrow, delegating
+ // execution of any finally block to the exception handler.
+ if (!pn3->pn_next) {
+ if (!emit1(JSOP_EXCEPTION))
+ return false;
+ if (!emit1(JSOP_THROW))
+ return false;
+ }
+ }
+ }
+ }
+
+ MOZ_ASSERT(this->stackDepth == depth);
+
+ // Emit the finally handler, if there is one.
+ JumpTarget finallyStart{ 0 };
+ if (pn->pn_kid3) {
+ if (!emitJumpTarget(&finallyStart))
+ return false;
+
+ // Fix up the gosubs that might have been emitted before non-local
+ // jumps to the finally code.
+ patchJumpsToTarget(controlInfo.gosubs, finallyStart);
+
+ // Indicate that we're emitting a subroutine body.
+ controlInfo.setEmittingSubroutine();
+ if (!updateSourceCoordNotes(pn->pn_kid3->pn_pos.begin))
+ return false;
+ if (!emit1(JSOP_FINALLY))
+ return false;
+ if (!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 (!emit1(JSOP_UNDEFINED))
+ return false;
+ if (!emit1(JSOP_SETRVAL))
+ return false;
+
+ if (!emitTree(pn->pn_kid3))
+ return false;
+ if (!emit1(JSOP_SETRVAL))
+ return false;
+ if (!emit1(JSOP_RETSUB))
+ return false;
+ hasTryFinally = true;
+ MOZ_ASSERT(this->stackDepth == depth);
+ }
+
+ // ReconstructPCStack needs a NOP here to mark the end of the last catch block.
+ if (!emit1(JSOP_NOP))
+ return false;
+
+ // Fix up the end-of-try/catch jumps to come here.
+ if (!emitJumpTargetAndPatch(catchJump))
+ 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 (catchList && !tryNoteList.append(JSTRY_CATCH, depth, tryStart, 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 (pn->pn_kid3 && !tryNoteList.append(JSTRY_FINALLY, depth, tryStart, finallyStart.offset))
+ return false;
+
+ return true;
+}
+
+bool
+BytecodeEmitter::emitIf(ParseNode* pn)
+{
+ IfThenElseEmitter ifThenElse(this);
+
+ if_again:
+ /* Emit code for the condition before pushing stmtInfo. */
+ if (!emitConditionallyExecutedTree(pn->pn_kid1))
+ return false;
+
+ ParseNode* elseNode = pn->pn_kid3;
+ if (elseNode) {
+ if (!ifThenElse.emitIfElse())
+ return false;
+ } else {
+ if (!ifThenElse.emitIf())
+ return false;
+ }
+
+ /* Emit code for the then part. */
+ if (!emitConditionallyExecutedTree(pn->pn_kid2))
+ return false;
+
+ if (elseNode) {
+ if (!ifThenElse.emitElse())
+ return false;
+
+ if (elseNode->isKind(PNK_IF)) {
+ pn = elseNode;
+ goto if_again;
+ }
+
+ /* Emit code for the else part. */
+ if (!emitConditionallyExecutedTree(elseNode))
+ return false;
+ }
+
+ if (!ifThenElse.emitEnd())
+ return false;
+
+ return true;
+}
+
+bool
+BytecodeEmitter::emitHoistedFunctionsInList(ParseNode* list)
+{
+ MOZ_ASSERT(list->pn_xflags & PNX_FUNCDEFS);
+
+ for (ParseNode* pn = list->pn_head; pn; pn = pn->pn_next) {
+ ParseNode* maybeFun = pn;
+
+ if (!sc->strict()) {
+ while (maybeFun->isKind(PNK_LABEL))
+ maybeFun = maybeFun->as<LabeledStatement>().statement();
+ }
+
+ if (maybeFun->isKind(PNK_FUNCTION) && maybeFun->functionIsHoisted()) {
+ if (!emitTree(maybeFun))
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool
+BytecodeEmitter::emitLexicalScopeBody(ParseNode* body, EmitLineNumberNote emitLineNote)
+{
+ if (body->isKind(PNK_STATEMENTLIST) && body->pn_xflags & PNX_FUNCDEFS) {
+ // 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))
+ return false;
+ }
+
+ // Line notes were updated by emitLexicalScope.
+ return emitTree(body, 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(ParseNode* pn)
+{
+ MOZ_ASSERT(pn->isKind(PNK_LEXICALSCOPE));
+
+ TDZCheckCache tdzCache(this);
+
+ ParseNode* body = pn->scopeBody();
+ if (pn->isEmptyScope())
+ return emitLexicalScopeBody(body);
+
+ // Update line number notes before emitting TDZ poison in
+ // EmitterScope::enterLexical to avoid spurious pausing on seemingly
+ // non-effectful lines in Debugger.
+ //
+ // For example, consider the following code.
+ //
+ // L1: {
+ // L2: let x = 42;
+ // L3: }
+ //
+ // If line number notes were not updated before the TDZ poison, the TDZ
+ // poison bytecode sequence of 'uninitialized; initlexical' will have line
+ // number L1, and the Debugger will pause there.
+ if (!ParseNodeRequiresSpecialLineNumberNotes(body)) {
+ ParseNode* pnForPos = body;
+ if (body->isKind(PNK_STATEMENTLIST) && body->pn_head)
+ pnForPos = body->pn_head;
+ if (!updateLineNumberNotes(pnForPos->pn_pos.begin))
+ return false;
+ }
+
+ EmitterScope emitterScope(this);
+ ScopeKind kind;
+ if (body->isKind(PNK_CATCH))
+ kind = body->pn_kid1->isKind(PNK_NAME) ? ScopeKind::SimpleCatch : ScopeKind::Catch;
+ else
+ kind = ScopeKind::Lexical;
+
+ if (!emitterScope.enterLexical(this, kind, pn->scopeBindings()))
+ return false;
+
+ if (body->isKind(PNK_FOR)) {
+ // 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, &emitterScope))
+ return false;
+ } else {
+ if (!emitLexicalScopeBody(body, SUPPRESS_LINENOTE))
+ return false;
+ }
+
+ return emitterScope.leave(this);
+}
+
+bool
+BytecodeEmitter::emitWith(ParseNode* pn)
+{
+ if (!emitTree(pn->pn_left))
+ return false;
+
+ EmitterScope emitterScope(this);
+ if (!emitterScope.enterWith(this))
+ return false;
+
+ if (!emitTree(pn->pn_right))
+ return false;
+
+ return emitterScope.leave(this);
+}
+
+bool
+BytecodeEmitter::emitRequireObjectCoercible()
+{
+ // For simplicity, handle this in self-hosted code, at cost of 13 bytes of
+ // bytecode versus 1 byte for a dedicated opcode. As more places need this
+ // behavior, we may want to reconsider this tradeoff.
+
+#ifdef DEBUG
+ auto depth = this->stackDepth;
+#endif
+ MOZ_ASSERT(depth > 0); // VAL
+ if (!emit1(JSOP_DUP)) // VAL VAL
+ return false;
+
+ // Note that "intrinsic" is a misnomer: we're calling a *self-hosted*
+ // function that's not an intrinsic! But it nonetheless works as desired.
+ if (!emitAtomOp(cx->names().RequireObjectCoercible,
+ JSOP_GETINTRINSIC)) // VAL VAL REQUIREOBJECTCOERCIBLE
+ {
+ return false;
+ }
+ if (!emit1(JSOP_UNDEFINED)) // VAL VAL REQUIREOBJECTCOERCIBLE UNDEFINED
+ return false;
+ if (!emit2(JSOP_PICK, 2)) // VAL REQUIREOBJECTCOERCIBLE UNDEFINED VAL
+ return false;
+ if (!emitCall(JSOP_CALL, 1)) // VAL IGNORED
+ return false;
+ checkTypeSet(JSOP_CALL);
+
+ if (!emit1(JSOP_POP)) // VAL
+ return false;
+
+ MOZ_ASSERT(depth == this->stackDepth);
+ return true;
+}
+
+bool
+BytecodeEmitter::emitIterator()
+{
+ // Convert iterable to iterator.
+ if (!emit1(JSOP_DUP)) // OBJ OBJ
+ return false;
+ if (!emit2(JSOP_SYMBOL, uint8_t(JS::SymbolCode::iterator))) // OBJ OBJ @@ITERATOR
+ return false;
+ if (!emitElemOpBase(JSOP_CALLELEM)) // OBJ ITERFN
+ return false;
+ if (!emit1(JSOP_SWAP)) // ITERFN OBJ
+ return false;
+ if (!emitCall(JSOP_CALLITER, 0)) // ITER
+ return false;
+ checkTypeSet(JSOP_CALLITER);
+ if (!emitCheckIsObj(CheckIsObjectKind::GetIterator)) // ITER
+ return false;
+ return true;
+}
+
+bool
+BytecodeEmitter::emitSpread(bool allowSelfHosted)
+{
+ LoopControl loopInfo(this, StatementKind::Spread);
+
+ // Jump down to the loop condition to minimize overhead assuming at least
+ // one iteration, as the other loop forms do. Annotate so IonMonkey can
+ // find the loop-closing jump.
+ unsigned noteIndex;
+ if (!newSrcNote(SRC_FOR_OF, &noteIndex))
+ return false;
+
+ // Jump down to the loop condition to minimize overhead, assuming at least
+ // one iteration. (This is also what we do for loops; whether this
+ // assumption holds for spreads is an unanswered question.)
+ JumpList initialJump;
+ if (!emitJump(JSOP_GOTO, &initialJump)) // ITER ARR I (during the goto)
+ return false;
+
+ JumpTarget top{ -1 };
+ if (!emitLoopHead(nullptr, &top)) // ITER ARR I
+ return false;
+
+ // When we enter the goto above, we have ITER ARR I on the stack. But when
+ // we reach this point on the loop backedge (if spreading produces at least
+ // one value), we've additionally pushed a RESULT iteration value.
+ // Increment manually to reflect this.
+ this->stackDepth++;
+
+ JumpList beq;
+ JumpTarget breakTarget{ -1 };
+ {
+#ifdef DEBUG
+ auto loopDepth = this->stackDepth;
+#endif
+
+ // Emit code to assign result.value to the iteration variable.
+ if (!emitAtomOp(cx->names().value, JSOP_GETPROP)) // ITER ARR I VALUE
+ return false;
+ if (!emit1(JSOP_INITELEM_INC)) // ITER ARR (I+1)
+ return false;
+
+ MOZ_ASSERT(this->stackDepth == loopDepth - 1);
+
+ // Spread operations can't contain |continue|, so don't bother setting loop
+ // and enclosing "update" offsets, as we do with for-loops.
+
+ // COME FROM the beginning of the loop to here.
+ if (!emitLoopEntry(nullptr, initialJump)) // ITER ARR I
+ return false;
+
+ if (!emitDupAt(2)) // ITER ARR I ITER
+ return false;
+ if (!emitIteratorNext(nullptr, allowSelfHosted)) // ITER ARR I RESULT
+ return false;
+ if (!emit1(JSOP_DUP)) // ITER ARR I RESULT RESULT
+ return false;
+ if (!emitAtomOp(cx->names().done, JSOP_GETPROP)) // ITER ARR I RESULT DONE?
+ return false;
+
+ if (!emitBackwardJump(JSOP_IFEQ, top, &beq, &breakTarget)) // ITER ARR I RESULT
+ return false;
+
+ MOZ_ASSERT(this->stackDepth == loopDepth);
+ }
+
+ // Let Ion know where the closing jump of this loop is.
+ if (!setSrcNoteOffset(noteIndex, 0, beq.offset - initialJump.offset))
+ return false;
+
+ // No breaks or continues should occur in spreads.
+ MOZ_ASSERT(loopInfo.breaks.offset == -1);
+ MOZ_ASSERT(loopInfo.continues.offset == -1);
+
+ if (!tryNoteList.append(JSTRY_FOR_OF, stackDepth, top.offset, breakTarget.offset))
+ return false;
+
+ if (!emit2(JSOP_PICK, 3)) // ARR FINAL_INDEX RESULT ITER
+ return false;
+
+ return emitUint16Operand(JSOP_POPN, 2); // ARR FINAL_INDEX
+}
+
+bool
+BytecodeEmitter::emitInitializeForInOrOfTarget(ParseNode* forHead)
+{
+ MOZ_ASSERT(forHead->isKind(PNK_FORIN) || forHead->isKind(PNK_FOROF));
+ MOZ_ASSERT(forHead->isArity(PN_TERNARY));
+
+ MOZ_ASSERT(this->stackDepth >= 1,
+ "must have a per-iteration value for initializing");
+
+ ParseNode* target = forHead->pn_kid1;
+ MOZ_ASSERT(!forHead->pn_kid2);
+
+ // If the for-in/of loop didn't have a variable declaration, per-loop
+ // initialization is just assigning the iteration value to a target
+ // expression.
+ if (!parser->handler.isDeclarationList(target))
+ return emitAssignment(target, JSOP_NOP, nullptr); // ... ITERVAL
+
+ // Otherwise, per-loop initialization is (possibly) declaration
+ // initialization. If the declaration is a lexical declaration, it must be
+ // initialized. If the declaration is a variable declaration, an
+ // assignment to that name (which does *not* necessarily assign to the
+ // variable!) must be generated.
+
+ if (!updateSourceCoordNotes(target->pn_pos.begin))
+ return false;
+
+ MOZ_ASSERT(target->isForLoopDeclaration());
+ target = parser->handler.singleBindingFromDeclaration(target);
+
+ if (target->isKind(PNK_NAME)) {
+ auto emitSwapScopeAndRhs = [](BytecodeEmitter* bce, const NameLocation&,
+ bool emittedBindOp)
+ {
+ if (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(bce->stackDepth >= 2);
+ return bce->emit1(JSOP_SWAP);
+ }
+
+ // In cases of emitting a frame slot or environment slot,
+ // nothing needs be done.
+ MOZ_ASSERT(bce->stackDepth >= 1);
+ return true;
+ };
+
+ // The caller handles removing the iteration value from the stack.
+ return emitInitializeName(target, emitSwapScopeAndRhs);
+ }
+
+ MOZ_ASSERT(!target->isKind(PNK_ASSIGN),
+ "for-in/of loop destructuring declarations can't have initializers");
+
+ MOZ_ASSERT(target->isKind(PNK_ARRAY) || target->isKind(PNK_OBJECT));
+ return emitDestructuringOps(target, DestructuringDeclaration);
+}
+
+bool
+BytecodeEmitter::emitForOf(ParseNode* forOfLoop, EmitterScope* headLexicalEmitterScope)
+{
+ MOZ_ASSERT(forOfLoop->isKind(PNK_FOR));
+ MOZ_ASSERT(forOfLoop->isArity(PN_BINARY));
+
+ ParseNode* forOfHead = forOfLoop->pn_left;
+ MOZ_ASSERT(forOfHead->isKind(PNK_FOROF));
+ MOZ_ASSERT(forOfHead->isArity(PN_TERNARY));
+
+ // Evaluate the expression being iterated.
+ ParseNode* forHeadExpr = forOfHead->pn_kid3;
+ if (!emitTree(forHeadExpr)) // ITERABLE
+ return false;
+ if (!emitIterator()) // ITER
+ return false;
+
+ // For-of loops have both the iterator and the value on the stack. Push
+ // undefined to balance the stack.
+ if (!emit1(JSOP_UNDEFINED)) // ITER RESULT
+ return false;
+
+ LoopControl loopInfo(this, StatementKind::ForOfLoop);
+
+ // Annotate so IonMonkey can find the loop-closing jump.
+ unsigned noteIndex;
+ if (!newSrcNote(SRC_FOR_OF, &noteIndex))
+ return false;
+
+ JumpList initialJump;
+ if (!emitJump(JSOP_GOTO, &initialJump)) // ITER RESULT
+ return false;
+
+ JumpTarget top{ -1 };
+ if (!emitLoopHead(nullptr, &top)) // ITER RESULT
+ 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.
+ DebugOnly<ParseNode*> forOfTarget = forOfHead->pn_kid1;
+ MOZ_ASSERT(forOfTarget->isKind(PNK_LET) || forOfTarget->isKind(PNK_CONST));
+ MOZ_ASSERT(headLexicalEmitterScope == innermostEmitterScope);
+ MOZ_ASSERT(headLexicalEmitterScope->scope(this)->kind() == ScopeKind::Lexical);
+
+ if (headLexicalEmitterScope->hasEnvironment()) {
+ if (!emit1(JSOP_RECREATELEXICALENV)) // ITER RESULT
+ return false;
+ }
+
+ // For uncaptured bindings, put them back in TDZ.
+ if (!headLexicalEmitterScope->deadZoneFrameSlots(this))
+ return false;
+ }
+
+ JumpList beq;
+ JumpTarget breakTarget{ -1 };
+ {
+#ifdef DEBUG
+ auto loopDepth = this->stackDepth;
+#endif
+
+ // Emit code to assign result.value to the iteration variable.
+ if (!emit1(JSOP_DUP)) // ITER RESULT RESULT
+ return false;
+ if (!emitAtomOp(cx->names().value, JSOP_GETPROP)) // ITER RESULT VALUE
+ return false;
+
+ if (!emitInitializeForInOrOfTarget(forOfHead)) // ITER RESULT VALUE
+ return false;
+
+ if (!emit1(JSOP_POP)) // ITER RESULT
+ return false;
+
+ MOZ_ASSERT(this->stackDepth == loopDepth,
+ "the stack must be balanced around the initializing "
+ "operation");
+
+ // Perform the loop body.
+ ParseNode* forBody = forOfLoop->pn_right;
+ if (!emitTree(forBody)) // ITER RESULT
+ return false;
+
+ // Set offset for continues.
+ loopInfo.continueTarget = { offset() };
+
+ if (!emitLoopEntry(forHeadExpr, initialJump)) // ITER RESULT
+ return false;
+
+ if (!emit1(JSOP_POP)) // ITER
+ return false;
+ if (!emit1(JSOP_DUP)) // ITER ITER
+ return false;
+
+ if (!emitIteratorNext(forOfHead)) // ITER RESULT
+ return false;
+ if (!emit1(JSOP_DUP)) // ITER RESULT RESULT
+ return false;
+ if (!emitAtomOp(cx->names().done, JSOP_GETPROP)) // ITER RESULT DONE?
+ return false;
+
+ if (!emitBackwardJump(JSOP_IFEQ, top, &beq, &breakTarget))
+ return false; // ITER RESULT
+
+ MOZ_ASSERT(this->stackDepth == loopDepth);
+ }
+
+ // Let Ion know where the closing jump of this loop is.
+ if (!setSrcNoteOffset(noteIndex, 0, beq.offset - initialJump.offset))
+ return false;
+
+ if (!loopInfo.patchBreaksAndContinues(this))
+ return false;
+
+ if (!tryNoteList.append(JSTRY_FOR_OF, stackDepth, top.offset, breakTarget.offset))
+ return false;
+
+ return emitUint16Operand(JSOP_POPN, 2); //
+}
+
+bool
+BytecodeEmitter::emitForIn(ParseNode* forInLoop, EmitterScope* headLexicalEmitterScope)
+{
+ MOZ_ASSERT(forInLoop->isKind(PNK_FOR));
+ MOZ_ASSERT(forInLoop->isArity(PN_BINARY));
+ MOZ_ASSERT(forInLoop->isOp(JSOP_ITER));
+
+ ParseNode* forInHead = forInLoop->pn_left;
+ MOZ_ASSERT(forInHead->isKind(PNK_FORIN));
+ MOZ_ASSERT(forInHead->isArity(PN_TERNARY));
+
+ // Annex B: Evaluate the var-initializer expression if present.
+ // |for (var i = initializer in expr) { ... }|
+ ParseNode* forInTarget = forInHead->pn_kid1;
+ if (parser->handler.isDeclarationList(forInTarget)) {
+ ParseNode* decl = parser->handler.singleBindingFromDeclaration(forInTarget);
+ if (decl->isKind(PNK_NAME)) {
+ if (ParseNode* initializer = decl->expr()) {
+ MOZ_ASSERT(forInTarget->isKind(PNK_VAR),
+ "for-in initializers are only permitted for |var| declarations");
+
+ if (!updateSourceCoordNotes(decl->pn_pos.begin))
+ return false;
+
+ auto emitRhs = [initializer](BytecodeEmitter* bce, const NameLocation&, bool) {
+ return bce->emitTree(initializer);
+ };
+
+ if (!emitInitializeName(decl, emitRhs))
+ return false;
+
+ // Pop the initializer.
+ if (!emit1(JSOP_POP))
+ return false;
+ }
+ }
+ }
+
+ // Evaluate the expression being iterated.
+ ParseNode* expr = forInHead->pn_kid3;
+ if (!emitTree(expr)) // EXPR
+ return false;
+
+ // Convert the value to the appropriate sort of iterator object for the
+ // loop variant (for-in, for-each-in, or destructuring for-in).
+ unsigned iflags = forInLoop->pn_iflags;
+ MOZ_ASSERT(0 == (iflags & ~(JSITER_FOREACH | JSITER_ENUMERATE)));
+ if (!emit2(JSOP_ITER, AssertedCast<uint8_t>(iflags))) // ITER
+ return false;
+
+ // For-in loops have both the iterator and the value on the stack. Push
+ // undefined to balance the stack.
+ if (!emit1(JSOP_UNDEFINED)) // ITER ITERVAL
+ return false;
+
+ LoopControl loopInfo(this, StatementKind::ForInLoop);
+
+ /* Annotate so IonMonkey can find the loop-closing jump. */
+ unsigned noteIndex;
+ if (!newSrcNote(SRC_FOR_IN, &noteIndex))
+ return false;
+
+ // Jump down to the loop condition to minimize overhead (assuming at least
+ // one iteration, just like the other loop forms).
+ JumpList initialJump;
+ if (!emitJump(JSOP_GOTO, &initialJump)) // ITER ITERVAL
+ return false;
+
+ JumpTarget top{ -1 };
+ if (!emitLoopHead(nullptr, &top)) // ITER ITERVAL
+ 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-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(forInTarget->isKind(PNK_LET) || forInTarget->isKind(PNK_CONST));
+ MOZ_ASSERT(headLexicalEmitterScope == innermostEmitterScope);
+ MOZ_ASSERT(headLexicalEmitterScope->scope(this)->kind() == ScopeKind::Lexical);
+
+ if (headLexicalEmitterScope->hasEnvironment()) {
+ if (!emit1(JSOP_RECREATELEXICALENV)) // ITER ITERVAL
+ return false;
+ }
+
+ // For uncaptured bindings, put them back in TDZ.
+ if (!headLexicalEmitterScope->deadZoneFrameSlots(this))
+ return false;
+ }
+
+ {
+#ifdef DEBUG
+ auto loopDepth = this->stackDepth;
+#endif
+ MOZ_ASSERT(loopDepth >= 2);
+
+ if (!emitInitializeForInOrOfTarget(forInHead)) // ITER ITERVAL
+ return false;
+
+ MOZ_ASSERT(this->stackDepth == loopDepth,
+ "iterator and iterval must be left on the stack");
+ }
+
+ // Perform the loop body.
+ ParseNode* forBody = forInLoop->pn_right;
+ if (!emitTree(forBody)) // ITER ITERVAL
+ return false;
+
+ // Set offset for continues.
+ loopInfo.continueTarget = { offset() };
+
+ if (!emitLoopEntry(nullptr, initialJump)) // ITER ITERVAL
+ return false;
+ if (!emit1(JSOP_POP)) // ITER
+ return false;
+ if (!emit1(JSOP_MOREITER)) // ITER NEXTITERVAL?
+ return false;
+ if (!emit1(JSOP_ISNOITER)) // ITER NEXTITERVAL? ISNOITER
+ return false;
+
+ JumpList beq;
+ JumpTarget breakTarget{ -1 };
+ if (!emitBackwardJump(JSOP_IFEQ, top, &beq, &breakTarget))
+ return false; // ITER NEXTITERVAL
+
+ // Set the srcnote offset so we can find the closing jump.
+ if (!setSrcNoteOffset(noteIndex, 0, beq.offset - initialJump.offset))
+ return false;
+
+ if (!loopInfo.patchBreaksAndContinues(this))
+ return false;
+
+ // Pop the enumeration value.
+ if (!emit1(JSOP_POP)) // ITER
+ return false;
+
+ if (!tryNoteList.append(JSTRY_FOR_IN, this->stackDepth, top.offset, offset()))
+ return false;
+
+ return emit1(JSOP_ENDITER); //
+}
+
+/* C-style `for (init; cond; update) ...` loop. */
+bool
+BytecodeEmitter::emitCStyleFor(ParseNode* pn, EmitterScope* headLexicalEmitterScope)
+{
+ LoopControl loopInfo(this, StatementKind::ForLoop);
+
+ ParseNode* forHead = pn->pn_left;
+ ParseNode* forBody = pn->pn_right;
+
+ // If the head of this for-loop declared any lexical variables, the parser
+ // wrapped this PNK_FOR node in a PNK_LEXICALSCOPE representing the
+ // implicit scope of those variables. By the time we get here, we have
+ // already entered that scope. So far, so good.
+ //
+ // ### 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.
+ //
+ // No freshening occurs in `for (const ...;;)` as there's no point: you
+ // can't reassign consts. This is observable through the Debugger API. (The
+ // ES6 spec also skips cloning the environment in this case.)
+ bool forLoopRequiresFreshening = false;
+ if (ParseNode* init = forHead->pn_kid1) {
+ // 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 (!updateSourceCoordNotes(init->pn_pos.begin))
+ return false;
+ if (!emitTree(init))
+ return false;
+
+ if (!init->isForLoopDeclaration()) {
+ // 'init' is an expression, not a declaration. emitTree left its
+ // value on the stack.
+ if (!emit1(JSOP_POP))
+ return false;
+ }
+
+ // 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.
+ forLoopRequiresFreshening = init->isKind(PNK_LET) && headLexicalEmitterScope;
+ if (forLoopRequiresFreshening) {
+ // 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(headLexicalEmitterScope == innermostEmitterScope);
+ MOZ_ASSERT(headLexicalEmitterScope->scope(this)->kind() == ScopeKind::Lexical);
+
+ if (headLexicalEmitterScope->hasEnvironment()) {
+ if (!emit1(JSOP_FRESHENLEXICALENV))
+ return false;
+ }
+ }
+ }
+
+ /*
+ * NB: the SRC_FOR note has offsetBias 1 (JSOP_NOP_LENGTH).
+ * Use tmp to hold the biased srcnote "top" offset, which differs
+ * from the top local variable by the length of the JSOP_GOTO
+ * emitted in between tmp and top if this loop has a condition.
+ */
+ unsigned noteIndex;
+ if (!newSrcNote(SRC_FOR, &noteIndex))
+ return false;
+ if (!emit1(JSOP_NOP))
+ return false;
+ ptrdiff_t tmp = offset();
+
+ JumpList jmp;
+ if (forHead->pn_kid2) {
+ /* Goto the loop condition, which branches back to iterate. */
+ if (!emitJump(JSOP_GOTO, &jmp))
+ return false;
+ }
+
+ /* Emit code for the loop body. */
+ JumpTarget top{ -1 };
+ if (!emitLoopHead(forBody, &top))
+ return false;
+ if (jmp.offset == -1 && !emitLoopEntry(forBody, jmp))
+ return false;
+
+ if (!emitConditionallyExecutedTree(forBody))
+ return false;
+
+ // Set loop and enclosing "update" offsets, for continue. Note that we
+ // continue to immediately *before* the block-freshening: continuing must
+ // refresh the block.
+ if (!emitJumpTarget(&loopInfo.continueTarget))
+ return false;
+
+ // ES 13.7.4.8 step 3.e. The per-iteration freshening.
+ if (forLoopRequiresFreshening) {
+ MOZ_ASSERT(headLexicalEmitterScope == innermostEmitterScope);
+ MOZ_ASSERT(headLexicalEmitterScope->scope(this)->kind() == ScopeKind::Lexical);
+
+ if (headLexicalEmitterScope->hasEnvironment()) {
+ if (!emit1(JSOP_FRESHENLEXICALENV))
+ return false;
+ }
+ }
+
+ // Check for update code to do before the condition (if any).
+ // The update code may not be executed at all; it needs its own TDZ cache.
+ if (ParseNode* update = forHead->pn_kid3) {
+ TDZCheckCache tdzCache(this);
+
+ if (!updateSourceCoordNotes(update->pn_pos.begin))
+ return false;
+ if (!emitTree(update))
+ return false;
+ if (!emit1(JSOP_POP))
+ return false;
+
+ /* Restore the absolute line number for source note readers. */
+ uint32_t lineNum = parser->tokenStream.srcCoords.lineNum(pn->pn_pos.end);
+ if (currentLine() != lineNum) {
+ if (!newSrcNote2(SRC_SETLINE, ptrdiff_t(lineNum)))
+ return false;
+ current->currentLine = lineNum;
+ current->lastColumn = 0;
+ }
+ }
+
+ ptrdiff_t tmp3 = offset();
+
+ if (forHead->pn_kid2) {
+ /* Fix up the goto from top to target the loop condition. */
+ MOZ_ASSERT(jmp.offset >= 0);
+ if (!emitLoopEntry(forHead->pn_kid2, jmp))
+ return false;
+
+ if (!emitTree(forHead->pn_kid2))
+ return false;
+ } else if (!forHead->pn_kid3) {
+ // 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 (!updateSourceCoordNotes(pn->pn_pos.begin))
+ return false;
+ }
+
+ /* Set the first note offset so we can find the loop condition. */
+ if (!setSrcNoteOffset(noteIndex, 0, tmp3 - tmp))
+ return false;
+ if (!setSrcNoteOffset(noteIndex, 1, loopInfo.continueTarget.offset - tmp))
+ return false;
+
+ /* If no loop condition, just emit a loop-closing jump. */
+ JumpList beq;
+ JumpTarget breakTarget{ -1 };
+ if (!emitBackwardJump(forHead->pn_kid2 ? JSOP_IFNE : JSOP_GOTO, top, &beq, &breakTarget))
+ return false;
+
+ /* The third note offset helps us find the loop-closing jump. */
+ if (!setSrcNoteOffset(noteIndex, 2, beq.offset - tmp))
+ return false;
+
+ if (!tryNoteList.append(JSTRY_LOOP, stackDepth, top.offset, breakTarget.offset))
+ return false;
+
+ if (!loopInfo.patchBreaksAndContinues(this))
+ return false;
+
+ return true;
+}
+
+bool
+BytecodeEmitter::emitFor(ParseNode* pn, EmitterScope* headLexicalEmitterScope)
+{
+ MOZ_ASSERT(pn->isKind(PNK_FOR));
+
+ if (pn->pn_left->isKind(PNK_FORHEAD))
+ return emitCStyleFor(pn, headLexicalEmitterScope);
+
+ if (!updateLineNumberNotes(pn->pn_pos.begin))
+ return false;
+
+ if (pn->pn_left->isKind(PNK_FORIN))
+ return emitForIn(pn, headLexicalEmitterScope);
+
+ MOZ_ASSERT(pn->pn_left->isKind(PNK_FOROF));
+ return emitForOf(pn, headLexicalEmitterScope);
+}
+
+bool
+BytecodeEmitter::emitComprehensionForInOrOfVariables(ParseNode* pn, bool* lexicalScope)
+{
+ // ES6 specifies that lexical for-loop variables get a fresh binding each
+ // iteration, and that evaluation of the expression looped over occurs with
+ // these variables dead zoned. But these rules only apply to *standard*
+ // for-in/of loops, and we haven't extended these requirements to
+ // comprehension syntax.
+
+ *lexicalScope = pn->isKind(PNK_LEXICALSCOPE);
+ if (*lexicalScope) {
+ // This is initially-ES7-tracked syntax, now with considerably murkier
+ // outlook. The scope work is done by the caller by instantiating an
+ // EmitterScope. There's nothing to do here.
+ } else {
+ // This is legacy comprehension syntax. We'll have PNK_LET here, using
+ // a lexical scope provided by/for the entire comprehension. Name
+ // analysis assumes declarations initialize lets, but as we're handling
+ // this declaration manually, we must also initialize manually to avoid
+ // triggering dead zone checks.
+ MOZ_ASSERT(pn->isKind(PNK_LET));
+ MOZ_ASSERT(pn->pn_count == 1);
+
+ if (!emitDeclarationList(pn))
+ return false;
+ }
+
+ return true;
+}
+
+bool
+BytecodeEmitter::emitComprehensionForOf(ParseNode* pn)
+{
+ MOZ_ASSERT(pn->isKind(PNK_COMPREHENSIONFOR));
+
+ ParseNode* forHead = pn->pn_left;
+ MOZ_ASSERT(forHead->isKind(PNK_FOROF));
+
+ ParseNode* forHeadExpr = forHead->pn_kid3;
+ ParseNode* forBody = pn->pn_right;
+
+ ParseNode* loopDecl = forHead->pn_kid1;
+ bool lexicalScope = false;
+ if (!emitComprehensionForInOrOfVariables(loopDecl, &lexicalScope))
+ return false;
+
+ // For-of loops run with two values on the stack: the iterator and the
+ // current result object.
+
+ // Evaluate the expression to the right of 'of'.
+ if (!emitTree(forHeadExpr)) // EXPR
+ return false;
+ if (!emitIterator()) // ITER
+ return false;
+
+ // Push a dummy result so that we properly enter iteration midstream.
+ if (!emit1(JSOP_UNDEFINED)) // ITER RESULT
+ return false;
+
+ // Enter the block before the loop body, after evaluating the obj.
+ // Initialize let bindings with undefined when entering, as the name
+ // assigned to is a plain assignment.
+ TDZCheckCache tdzCache(this);
+ Maybe<EmitterScope> emitterScope;
+ ParseNode* loopVariableName;
+ if (lexicalScope) {
+ loopVariableName = parser->handler.singleBindingFromDeclaration(loopDecl->pn_expr);
+ emitterScope.emplace(this);
+ if (!emitterScope->enterComprehensionFor(this, loopDecl->scopeBindings()))
+ return false;
+ } else {
+ loopVariableName = parser->handler.singleBindingFromDeclaration(loopDecl);
+ }
+
+ LoopControl loopInfo(this, StatementKind::ForOfLoop);
+
+ // Jump down to the loop condition to minimize overhead assuming at least
+ // one iteration, as the other loop forms do. Annotate so IonMonkey can
+ // find the loop-closing jump.
+ unsigned noteIndex;
+ if (!newSrcNote(SRC_FOR_OF, &noteIndex))
+ return false;
+ JumpList jmp;
+ if (!emitJump(JSOP_GOTO, &jmp))
+ return false;
+
+ JumpTarget top{ -1 };
+ if (!emitLoopHead(nullptr, &top))
+ return false;
+
+#ifdef DEBUG
+ int loopDepth = this->stackDepth;
+#endif
+
+ // Emit code to assign result.value to the iteration variable.
+ if (!emit1(JSOP_DUP)) // ITER RESULT RESULT
+ return false;
+ if (!emitAtomOp(cx->names().value, JSOP_GETPROP)) // ITER RESULT VALUE
+ return false;
+ if (!emitAssignment(loopVariableName, JSOP_NOP, nullptr)) // ITER RESULT VALUE
+ return false;
+ if (!emit1(JSOP_POP)) // ITER RESULT
+ return false;
+
+ // The stack should be balanced around the assignment opcode sequence.
+ MOZ_ASSERT(this->stackDepth == loopDepth);
+
+ // Emit code for the loop body.
+ if (!emitTree(forBody))
+ return false;
+
+ // Set offset for continues.
+ loopInfo.continueTarget = { offset() };
+
+ if (!emitLoopEntry(forHeadExpr, jmp))
+ return false;
+
+ if (!emit1(JSOP_POP)) // ITER
+ return false;
+ if (!emit1(JSOP_DUP)) // ITER ITER
+ return false;
+ if (!emitIteratorNext(forHead)) // ITER RESULT
+ return false;
+ if (!emit1(JSOP_DUP)) // ITER RESULT RESULT
+ return false;
+ if (!emitAtomOp(cx->names().done, JSOP_GETPROP)) // ITER RESULT DONE?
+ return false;
+
+ JumpList beq;
+ JumpTarget breakTarget{ -1 };
+ if (!emitBackwardJump(JSOP_IFEQ, top, &beq, &breakTarget)) // ITER RESULT
+ return false;
+
+ MOZ_ASSERT(this->stackDepth == loopDepth);
+
+ // Let Ion know where the closing jump of this loop is.
+ if (!setSrcNoteOffset(noteIndex, 0, beq.offset - jmp.offset))
+ return false;
+
+ if (!loopInfo.patchBreaksAndContinues(this))
+ return false;
+
+ if (!tryNoteList.append(JSTRY_FOR_OF, stackDepth, top.offset, breakTarget.offset))
+ return false;
+
+ if (emitterScope) {
+ if (!emitterScope->leave(this))
+ return false;
+ emitterScope.reset();
+ }
+
+ // Pop the result and the iter.
+ return emitUint16Operand(JSOP_POPN, 2); //
+}
+
+bool
+BytecodeEmitter::emitComprehensionForIn(ParseNode* pn)
+{
+ MOZ_ASSERT(pn->isKind(PNK_COMPREHENSIONFOR));
+
+ ParseNode* forHead = pn->pn_left;
+ MOZ_ASSERT(forHead->isKind(PNK_FORIN));
+
+ ParseNode* forBody = pn->pn_right;
+
+ ParseNode* loopDecl = forHead->pn_kid1;
+ bool lexicalScope = false;
+ if (loopDecl && !emitComprehensionForInOrOfVariables(loopDecl, &lexicalScope))
+ return false;
+
+ // Evaluate the expression to the right of 'in'.
+ if (!emitTree(forHead->pn_kid3))
+ return false;
+
+ /*
+ * Emit a bytecode to convert top of stack value to the iterator
+ * object depending on the loop variant (for-in, for-each-in, or
+ * destructuring for-in).
+ */
+ MOZ_ASSERT(pn->isOp(JSOP_ITER));
+ if (!emit2(JSOP_ITER, (uint8_t) pn->pn_iflags))
+ return false;
+
+ // For-in loops have both the iterator and the value on the stack. Push
+ // undefined to balance the stack.
+ if (!emit1(JSOP_UNDEFINED))
+ return false;
+
+ // Enter the block before the loop body, after evaluating the obj.
+ // Initialize let bindings with undefined when entering, as the name
+ // assigned to is a plain assignment.
+ TDZCheckCache tdzCache(this);
+ Maybe<EmitterScope> emitterScope;
+ if (lexicalScope) {
+ emitterScope.emplace(this);
+ if (!emitterScope->enterComprehensionFor(this, loopDecl->scopeBindings()))
+ return false;
+ }
+
+ LoopControl loopInfo(this, StatementKind::ForInLoop);
+
+ /* Annotate so IonMonkey can find the loop-closing jump. */
+ unsigned noteIndex;
+ if (!newSrcNote(SRC_FOR_IN, &noteIndex))
+ return false;
+
+ /*
+ * Jump down to the loop condition to minimize overhead assuming at
+ * least one iteration, as the other loop forms do.
+ */
+ JumpList jmp;
+ if (!emitJump(JSOP_GOTO, &jmp))
+ return false;
+
+ JumpTarget top{ -1 };
+ if (!emitLoopHead(nullptr, &top))
+ return false;
+
+#ifdef DEBUG
+ int loopDepth = this->stackDepth;
+#endif
+
+ // Emit code to assign the enumeration value to the left hand side, but
+ // also leave it on the stack.
+ if (!emitAssignment(forHead->pn_kid2, JSOP_NOP, nullptr))
+ return false;
+
+ /* The stack should be balanced around the assignment opcode sequence. */
+ MOZ_ASSERT(this->stackDepth == loopDepth);
+
+ /* Emit code for the loop body. */
+ if (!emitTree(forBody))
+ return false;
+
+ // Set offset for continues.
+ loopInfo.continueTarget = { offset() };
+
+ if (!emitLoopEntry(nullptr, jmp))
+ return false;
+ if (!emit1(JSOP_POP))
+ return false;
+ if (!emit1(JSOP_MOREITER))
+ return false;
+ if (!emit1(JSOP_ISNOITER))
+ return false;
+ JumpList beq;
+ JumpTarget breakTarget{ -1 };
+ if (!emitBackwardJump(JSOP_IFEQ, top, &beq, &breakTarget))
+ return false;
+
+ /* Set the srcnote offset so we can find the closing jump. */
+ if (!setSrcNoteOffset(noteIndex, 0, beq.offset - jmp.offset))
+ return false;
+
+ if (!loopInfo.patchBreaksAndContinues(this))
+ return false;
+
+ // Pop the enumeration value.
+ if (!emit1(JSOP_POP))
+ return false;
+
+ JumpTarget endIter{ offset() };
+ if (!tryNoteList.append(JSTRY_FOR_IN, this->stackDepth, top.offset, endIter.offset))
+ return false;
+ if (!emit1(JSOP_ENDITER))
+ return false;
+
+ if (emitterScope) {
+ if (!emitterScope->leave(this))
+ return false;
+ emitterScope.reset();
+ }
+
+ return true;
+}
+
+bool
+BytecodeEmitter::emitComprehensionFor(ParseNode* compFor)
+{
+ MOZ_ASSERT(compFor->pn_left->isKind(PNK_FORIN) ||
+ compFor->pn_left->isKind(PNK_FOROF));
+
+ if (!updateLineNumberNotes(compFor->pn_pos.begin))
+ return false;
+
+ return compFor->pn_left->isKind(PNK_FORIN)
+ ? emitComprehensionForIn(compFor)
+ : emitComprehensionForOf(compFor);
+}
+
+MOZ_NEVER_INLINE bool
+BytecodeEmitter::emitFunction(ParseNode* pn, bool needsProto)
+{
+ FunctionBox* funbox = pn->pn_funbox;
+ RootedFunction fun(cx, funbox->function());
+ RootedAtom name(cx, fun->name());
+ MOZ_ASSERT_IF(fun->isInterpretedLazy(), fun->lazyScript());
+ MOZ_ASSERT_IF(pn->isOp(JSOP_FUNWITHPROTO), needsProto);
+
+ /*
+ * Set the |wasEmitted| flag in the funbox once the function has been
+ * emitted. Function definitions that need hoisting to the top of the
+ * function will be seen by emitFunction in two places.
+ */
+ if (funbox->wasEmitted && pn->functionIsHoisted()) {
+ // Annex B block-scoped functions are hoisted like any other
+ // block-scoped function to the top of their scope. When their
+ // definitions are seen for the second time, we need to emit the
+ // assignment that assigns the function to the outer 'var' binding.
+ if (funbox->isAnnexB) {
+ auto emitRhs = [&name](BytecodeEmitter* bce, const NameLocation&, bool) {
+ // The RHS is the value of the lexically bound name in the
+ // innermost scope.
+ return bce->emitGetName(name);
+ };
+
+ // 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 = locationOfNameBoundInScope(name, varEmitterScope);
+
+ // If there are parameter expressions, the var name could be a
+ // parameter.
+ if (!lhsLoc && sc->isFunctionBox() && sc->asFunctionBox()->hasExtraBodyVarScope())
+ lhsLoc = locationOfNameBoundInScope(name, varEmitterScope->enclosingInFrame());
+
+ if (!lhsLoc) {
+ lhsLoc = Some(NameLocation::DynamicAnnexBVar());
+ } else {
+ MOZ_ASSERT(lhsLoc->bindingKind() == BindingKind::Var ||
+ lhsLoc->bindingKind() == BindingKind::FormalParameter ||
+ (lhsLoc->bindingKind() == BindingKind::Let &&
+ sc->asFunctionBox()->hasParameterExprs));
+ }
+
+ if (!emitSetOrInitializeNameAtLocation(name, *lhsLoc, emitRhs, false))
+ return false;
+ if (!emit1(JSOP_POP))
+ return false;
+ }
+
+ MOZ_ASSERT_IF(fun->hasScript(), fun->nonLazyScript());
+ MOZ_ASSERT(pn->functionIsHoisted());
+ return true;
+ }
+
+ funbox->wasEmitted = true;
+
+ /*
+ * Mark as singletons any function which will only be executed once, or
+ * which is inner to a lambda we only expect to run once. In the latter
+ * case, if the lambda runs multiple times then CloneFunctionObject will
+ * make a deep clone of its contents.
+ */
+ if (fun->isInterpreted()) {
+ bool singleton = checkRunOnceContext();
+ if (!JSFunction::setTypeForScriptedFunction(cx, fun, singleton))
+ return false;
+
+ SharedContext* outersc = sc;
+ if (fun->isInterpretedLazy()) {
+ // We need to update the static scope chain regardless of whether
+ // the LazyScript has already been initialized, due to the case
+ // where we previously successfully compiled an inner function's
+ // lazy script but failed to compile the outer script after the
+ // fact. If we attempt to compile the outer script again, the
+ // static scope chain will be newly allocated and will mismatch
+ // the previously compiled LazyScript's.
+ ScriptSourceObject* source = &script->sourceObject()->as<ScriptSourceObject>();
+ fun->lazyScript()->setEnclosingScopeAndSource(innermostScope(), source);
+ if (emittingRunOnceLambda)
+ fun->lazyScript()->setTreatAsRunOnce();
+ } else {
+ MOZ_ASSERT_IF(outersc->strict(), funbox->strictScript);
+
+ // Inherit most things (principals, version, etc) from the
+ // parent. Use default values for the rest.
+ Rooted<JSScript*> parent(cx, script);
+ MOZ_ASSERT(parent->getVersion() == parser->options().version);
+ MOZ_ASSERT(parent->mutedErrors() == parser->options().mutedErrors());
+ const TransitiveCompileOptions& transitiveOptions = parser->options();
+ CompileOptions options(cx, transitiveOptions);
+
+ Rooted<JSObject*> sourceObject(cx, script->sourceObject());
+ Rooted<JSScript*> script(cx, JSScript::Create(cx, options, sourceObject,
+ funbox->bufStart, funbox->bufEnd));
+ if (!script)
+ return false;
+
+ BytecodeEmitter bce2(this, parser, funbox, script, /* lazyScript = */ nullptr,
+ pn->pn_pos, emitterMode);
+ if (!bce2.init())
+ return false;
+
+ /* We measured the max scope depth when we parsed the function. */
+ if (!bce2.emitFunctionScript(pn->pn_body))
+ return false;
+
+ if (funbox->isLikelyConstructorWrapper())
+ script->setLikelyConstructorWrapper();
+ }
+
+ if (outersc->isFunctionBox())
+ outersc->asFunctionBox()->setHasInnerFunctions();
+ } else {
+ MOZ_ASSERT(IsAsmJSModule(fun));
+ }
+
+ /* Make the function object a literal in the outer script's pool. */
+ unsigned index = objectList.add(pn->pn_funbox);
+
+ /* Non-hoisted functions simply emit their respective op. */
+ if (!pn->functionIsHoisted()) {
+ /* JSOP_LAMBDA_ARROW is always preceded by a new.target */
+ MOZ_ASSERT(fun->isArrow() == (pn->getOp() == JSOP_LAMBDA_ARROW));
+ if (funbox->isAsync()) {
+ MOZ_ASSERT(!needsProto);
+ return emitAsyncWrapper(index, funbox->needsHomeObject(), fun->isArrow());
+ }
+
+ if (fun->isArrow()) {
+ if (sc->allowNewTarget()) {
+ if (!emit1(JSOP_NEWTARGET))
+ return false;
+ } else {
+ if (!emit1(JSOP_NULL))
+ return false;
+ }
+ }
+
+ if (needsProto) {
+ MOZ_ASSERT(pn->getOp() == JSOP_FUNWITHPROTO || pn->getOp() == JSOP_LAMBDA);
+ pn->setOp(JSOP_FUNWITHPROTO);
+ }
+
+ if (pn->getOp() == JSOP_DEFFUN) {
+ if (!emitIndex32(JSOP_LAMBDA, index))
+ return false;
+ return emit1(JSOP_DEFFUN);
+ }
+
+ return emitIndex32(pn->getOp(), index);
+ }
+
+ MOZ_ASSERT(!needsProto);
+
+ bool topLevelFunction;
+ if (sc->isFunctionBox() || (sc->isEvalContext() && sc->strict())) {
+ // No nested functions inside other functions are top-level.
+ topLevelFunction = false;
+ } else {
+ // In sloppy eval scripts, top-level functions in are accessed
+ // dynamically. In global and module scripts, top-level functions are
+ // those bound in the var scope.
+ NameLocation loc = lookupName(name);
+ topLevelFunction = loc.kind() == NameLocation::Kind::Dynamic ||
+ loc.bindingKind() == BindingKind::Var;
+ }
+
+ if (topLevelFunction) {
+ if (sc->isModuleContext()) {
+ // For modules, we record the function and instantiate the binding
+ // during ModuleDeclarationInstantiation(), before the script is run.
+
+ RootedModuleObject module(cx, sc->asModuleContext()->module());
+ if (!module->noteFunctionDeclaration(cx, name, fun))
+ return false;
+ } else {
+ MOZ_ASSERT(sc->isGlobalContext() || sc->isEvalContext());
+ MOZ_ASSERT(pn->getOp() == JSOP_NOP);
+ switchToPrologue();
+ if (funbox->isAsync()) {
+ if (!emitAsyncWrapper(index, fun->isMethod(), fun->isArrow()))
+ return false;
+ } else {
+ if (!emitIndex32(JSOP_LAMBDA, index))
+ return false;
+ }
+ if (!emit1(JSOP_DEFFUN))
+ return false;
+ if (!updateSourceCoordNotes(pn->pn_pos.begin))
+ return false;
+ switchToMain();
+ }
+ } else {
+ // For functions nested within functions and blocks, make a lambda and
+ // initialize the binding name of the function in the current scope.
+
+ bool isAsync = funbox->isAsync();
+ auto emitLambda = [index, isAsync](BytecodeEmitter* bce, const NameLocation&, bool) {
+ if (isAsync) {
+ return bce->emitAsyncWrapper(index, /* needsHomeObject = */ false,
+ /* isArrow = */ false);
+ }
+ return bce->emitIndexOp(JSOP_LAMBDA, index);
+ };
+
+ if (!emitInitializeName(name, emitLambda))
+ return false;
+ if (!emit1(JSOP_POP))
+ return false;
+ }
+
+ return true;
+}
+
+bool
+BytecodeEmitter::emitAsyncWrapperLambda(unsigned index, bool isArrow) {
+ if (isArrow) {
+ if (sc->allowNewTarget()) {
+ if (!emit1(JSOP_NEWTARGET))
+ return false;
+ } else {
+ if (!emit1(JSOP_NULL))
+ return false;
+ }
+ if (!emitIndex32(JSOP_LAMBDA_ARROW, index))
+ return false;
+ } else {
+ if (!emitIndex32(JSOP_LAMBDA, index))
+ return false;
+ }
+
+ return true;
+}
+
+bool
+BytecodeEmitter::emitAsyncWrapper(unsigned index, bool needsHomeObject, bool isArrow)
+{
+ // needsHomeObject can be true for propertyList for extended class.
+ // In that case push both unwrapped and wrapped function, in order to
+ // initialize home object of unwrapped function, and set wrapped function
+ // as a property.
+ //
+ // lambda // unwrapped
+ // dup // unwrapped unwrapped
+ // toasync // unwrapped wrapped
+ //
+ // Emitted code is surrounded by the following code.
+ //
+ // // classObj classCtor classProto
+ // (emitted code) // classObj classCtor classProto unwrapped wrapped
+ // swap // classObj classCtor classProto wrapped unwrapped
+ // inithomeobject 1 // classObj classCtor classProto wrapped unwrapped
+ // // initialize the home object of unwrapped
+ // // with classProto here
+ // pop // classObj classCtor classProto wrapped
+ // inithiddenprop // classObj classCtor classProto wrapped
+ // // initialize the property of the classProto
+ // // with wrapped function here
+ // pop // classObj classCtor classProto
+ //
+ // needsHomeObject is false for other cases, push wrapped function only.
+ if (!emitAsyncWrapperLambda(index, isArrow))
+ return false;
+ if (needsHomeObject) {
+ if (!emit1(JSOP_DUP))
+ return false;
+ }
+ if (!emit1(JSOP_TOASYNC))
+ return false;
+ return true;
+}
+
+bool
+BytecodeEmitter::emitDo(ParseNode* pn)
+{
+ /* Emit an annotated nop so IonBuilder can recognize the 'do' loop. */
+ unsigned noteIndex;
+ if (!newSrcNote(SRC_WHILE, &noteIndex))
+ return false;
+ if (!emit1(JSOP_NOP))
+ return false;
+
+ unsigned noteIndex2;
+ if (!newSrcNote(SRC_WHILE, &noteIndex2))
+ return false;
+
+ /* Compile the loop body. */
+ JumpTarget top;
+ if (!emitLoopHead(pn->pn_left, &top))
+ return false;
+
+ LoopControl loopInfo(this, StatementKind::DoLoop);
+
+ JumpList empty;
+ if (!emitLoopEntry(nullptr, empty))
+ return false;
+
+ if (!emitTree(pn->pn_left))
+ return false;
+
+ // Set the offset for continues.
+ if (!emitJumpTarget(&loopInfo.continueTarget))
+ return false;
+
+ /* Compile the loop condition, now that continues know where to go. */
+ if (!emitTree(pn->pn_right))
+ return false;
+
+ JumpList beq;
+ JumpTarget breakTarget{ -1 };
+ if (!emitBackwardJump(JSOP_IFNE, top, &beq, &breakTarget))
+ return false;
+
+ if (!tryNoteList.append(JSTRY_LOOP, stackDepth, top.offset, breakTarget.offset))
+ return false;
+
+ /*
+ * Update the annotations with the update and back edge positions, for
+ * IonBuilder.
+ *
+ * Be careful: We must set noteIndex2 before noteIndex in case the noteIndex
+ * note gets bigger.
+ */
+ if (!setSrcNoteOffset(noteIndex2, 0, beq.offset - top.offset))
+ return false;
+ if (!setSrcNoteOffset(noteIndex, 0, 1 + (loopInfo.continueTarget.offset - top.offset)))
+ return false;
+
+ if (!loopInfo.patchBreaksAndContinues(this))
+ return false;
+
+ return true;
+}
+
+bool
+BytecodeEmitter::emitWhile(ParseNode* pn)
+{
+ /*
+ * Minimize bytecodes issued for one or more iterations by jumping to
+ * the condition below the body and closing the loop if the condition
+ * is true with a backward branch. For iteration count i:
+ *
+ * i test at the top test at the bottom
+ * = =============== ==================
+ * 0 ifeq-pass goto; ifne-fail
+ * 1 ifeq-fail; goto; ifne-pass goto; ifne-pass; ifne-fail
+ * 2 2*(ifeq-fail; goto); ifeq-pass goto; 2*ifne-pass; ifne-fail
+ * . . .
+ * N N*(ifeq-fail; goto); ifeq-pass goto; N*ifne-pass; ifne-fail
+ */
+
+ // If we have a single-line while, like "while (x) ;", we want to
+ // emit the line note before the initial goto, 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 after the initial goto, so that
+ // "cont" stops on each iteration -- but without a stop before the
+ // first iteration.
+ if (parser->tokenStream.srcCoords.lineNum(pn->pn_pos.begin) ==
+ parser->tokenStream.srcCoords.lineNum(pn->pn_pos.end) &&
+ !updateSourceCoordNotes(pn->pn_pos.begin))
+ return false;
+
+ JumpTarget top{ -1 };
+ if (!emitJumpTarget(&top))
+ return false;
+
+ LoopControl loopInfo(this, StatementKind::WhileLoop);
+ loopInfo.continueTarget = top;
+
+ unsigned noteIndex;
+ if (!newSrcNote(SRC_WHILE, &noteIndex))
+ return false;
+
+ JumpList jmp;
+ if (!emitJump(JSOP_GOTO, &jmp))
+ return false;
+
+ if (!emitLoopHead(pn->pn_right, &top))
+ return false;
+
+ if (!emitConditionallyExecutedTree(pn->pn_right))
+ return false;
+
+ if (!emitLoopEntry(pn->pn_left, jmp))
+ return false;
+ if (!emitTree(pn->pn_left))
+ return false;
+
+ JumpList beq;
+ JumpTarget breakTarget{ -1 };
+ if (!emitBackwardJump(JSOP_IFNE, top, &beq, &breakTarget))
+ return false;
+
+ if (!tryNoteList.append(JSTRY_LOOP, stackDepth, top.offset, breakTarget.offset))
+ return false;
+
+ if (!setSrcNoteOffset(noteIndex, 0, beq.offset - jmp.offset))
+ return false;
+
+ if (!loopInfo.patchBreaksAndContinues(this))
+ return false;
+
+ return true;
+}
+
+bool
+BytecodeEmitter::emitBreak(PropertyName* label)
+{
+ BreakableControl* target;
+ SrcNoteType noteType;
+ 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);
+ noteType = SRC_BREAK2LABEL;
+ } else {
+ auto isNotLabel = [](BreakableControl* control) {
+ return !control->is<LabelControl>();
+ };
+ target = findInnermostNestableControl<BreakableControl>(isNotLabel);
+ noteType = (target->kind() == StatementKind::Switch) ? SRC_SWITCHBREAK : SRC_BREAK;
+ }
+
+ return emitGoto(target, &target->breaks, noteType);
+}
+
+bool
+BytecodeEmitter::emitContinue(PropertyName* label)
+{
+ LoopControl* target = nullptr;
+ if (label) {
+ // Find the loop statement enclosed by the matching label.
+ NestableControl* control = innermostNestableControl;
+ while (!control->is<LabelControl>() || control->as<LabelControl>().label() != label) {
+ if (control->is<LoopControl>())
+ target = &control->as<LoopControl>();
+ control = control->enclosing();
+ }
+ } else {
+ target = findInnermostNestableControl<LoopControl>();
+ }
+ return emitGoto(target, &target->continues, SRC_CONTINUE);
+}
+
+bool
+BytecodeEmitter::emitGetFunctionThis(ParseNode* pn)
+{
+ MOZ_ASSERT(sc->thisBinding() == ThisBinding::Function);
+ MOZ_ASSERT(pn->isKind(PNK_NAME));
+ MOZ_ASSERT(pn->name() == cx->names().dotThis);
+
+ if (!emitTree(pn))
+ return false;
+ if (sc->needsThisTDZChecks() && !emit1(JSOP_CHECKTHIS))
+ return false;
+
+ return true;
+}
+
+bool
+BytecodeEmitter::emitGetThisForSuperBase(ParseNode* pn)
+{
+ MOZ_ASSERT(pn->isKind(PNK_SUPERBASE));
+ return emitGetFunctionThis(pn->pn_kid);
+}
+
+bool
+BytecodeEmitter::emitThisLiteral(ParseNode* pn)
+{
+ MOZ_ASSERT(pn->isKind(PNK_THIS));
+
+ if (ParseNode* thisName = pn->pn_kid)
+ return emitGetFunctionThis(thisName);
+
+ if (sc->thisBinding() == ThisBinding::Module)
+ return emit1(JSOP_UNDEFINED);
+
+ MOZ_ASSERT(sc->thisBinding() == ThisBinding::Global);
+ return emit1(JSOP_GLOBALTHIS);
+}
+
+bool
+BytecodeEmitter::emitCheckDerivedClassConstructorReturn()
+{
+ MOZ_ASSERT(lookupName(cx->names().dotThis).hasKnownSlot());
+ if (!emitGetName(cx->names().dotThis))
+ return false;
+ if (!emit1(JSOP_CHECKRETURN))
+ return false;
+ return true;
+}
+
+bool
+BytecodeEmitter::emitReturn(ParseNode* pn)
+{
+ if (!updateSourceCoordNotes(pn->pn_pos.begin))
+ return false;
+
+ if (sc->isFunctionBox() && sc->asFunctionBox()->isStarGenerator()) {
+ if (!emitPrepareIteratorResult())
+ return false;
+ }
+
+ /* Push a return value */
+ if (ParseNode* pn2 = pn->pn_kid) {
+ if (!emitTree(pn2))
+ return false;
+ } else {
+ /* No explicit return value provided */
+ if (!emit1(JSOP_UNDEFINED))
+ return false;
+ }
+
+ if (sc->isFunctionBox() && sc->asFunctionBox()->isStarGenerator()) {
+ if (!emitFinishIteratorResult(true))
+ return false;
+ }
+
+ // We know functionBodyEndPos is set because "return" is only
+ // valid in a function, and so we've passed through
+ // emitFunctionScript.
+ MOZ_ASSERT(functionBodyEndPosSet);
+ if (!updateSourceCoordNotes(functionBodyEndPos))
+ return false;
+
+ /*
+ * EmitNonLocalJumpFixup may add fixup bytecode to close open try
+ * blocks having finally clauses and to exit intermingled let blocks.
+ * We can't simply transfer control flow to our caller in that case,
+ * because we must gosub to those finally clauses from inner to outer,
+ * with the correct stack pointer (i.e., after popping any with,
+ * for/in, etc., slots nested inside the finally's try).
+ *
+ * In this case we mutate JSOP_RETURN into JSOP_SETRVAL and add an
+ * extra JSOP_RETRVAL after the fixups.
+ */
+ ptrdiff_t top = offset();
+
+ bool isGenerator = sc->isFunctionBox() && sc->asFunctionBox()->isGenerator();
+ bool isDerivedClassConstructor =
+ sc->isFunctionBox() && sc->asFunctionBox()->isDerivedClassConstructor();
+
+ if (!emit1((isGenerator || isDerivedClassConstructor) ? JSOP_SETRVAL : JSOP_RETURN))
+ return false;
+
+ // Make sure that we emit this before popping the blocks in prepareForNonLocalJump,
+ // to ensure that the error is thrown while the scope-chain is still intact.
+ if (isDerivedClassConstructor) {
+ if (!emitCheckDerivedClassConstructorReturn())
+ return false;
+ }
+
+ NonLocalExitControl nle(this);
+
+ if (!nle.prepareForNonLocalJumpToOutermost())
+ return false;
+
+ if (isGenerator) {
+ // We know that .generator is on the function scope, as we just exited
+ // all nested scopes.
+ NameLocation loc =
+ *locationOfNameBoundInFunctionScope(cx->names().dotGenerator, varEmitterScope);
+ if (!emitGetNameAtLocation(cx->names().dotGenerator, loc))
+ return false;
+ if (!emitYieldOp(JSOP_FINALYIELDRVAL))
+ return false;
+ } else if (isDerivedClassConstructor) {
+ MOZ_ASSERT(code()[top] == JSOP_SETRVAL);
+ if (!emit1(JSOP_RETRVAL))
+ return false;
+ } else if (top + static_cast<ptrdiff_t>(JSOP_RETURN_LENGTH) != offset()) {
+ code()[top] = JSOP_SETRVAL;
+ if (!emit1(JSOP_RETRVAL))
+ return false;
+ }
+
+ return true;
+}
+
+bool
+BytecodeEmitter::emitYield(ParseNode* pn)
+{
+ MOZ_ASSERT(sc->isFunctionBox());
+
+ if (pn->getOp() == JSOP_YIELD) {
+ if (sc->asFunctionBox()->isStarGenerator()) {
+ if (!emitPrepareIteratorResult())
+ return false;
+ }
+ if (pn->pn_left) {
+ if (!emitTree(pn->pn_left))
+ return false;
+ } else {
+ if (!emit1(JSOP_UNDEFINED))
+ return false;
+ }
+ if (sc->asFunctionBox()->isStarGenerator()) {
+ if (!emitFinishIteratorResult(false))
+ return false;
+ }
+ } else {
+ MOZ_ASSERT(pn->getOp() == JSOP_INITIALYIELD);
+ }
+
+ if (!emitTree(pn->pn_right))
+ return false;
+
+ if (!emitYieldOp(pn->getOp()))
+ return false;
+
+ if (pn->getOp() == JSOP_INITIALYIELD && !emit1(JSOP_POP))
+ return false;
+
+ return true;
+}
+
+bool
+BytecodeEmitter::emitYieldStar(ParseNode* iter, ParseNode* gen)
+{
+ MOZ_ASSERT(sc->isFunctionBox());
+ MOZ_ASSERT(sc->asFunctionBox()->isStarGenerator());
+
+ if (!emitTree(iter)) // ITERABLE
+ return false;
+ if (!emitIterator()) // ITER
+ return false;
+
+ // Initial send value is undefined.
+ if (!emit1(JSOP_UNDEFINED)) // ITER RECEIVED
+ return false;
+
+ int depth = stackDepth;
+ MOZ_ASSERT(depth >= 2);
+
+ JumpList send;
+ if (!emitJump(JSOP_GOTO, &send)) // goto send
+ return false;
+
+ // Try prologue. // ITER RESULT
+ unsigned noteIndex;
+ if (!newSrcNote(SRC_TRY, &noteIndex))
+ return false;
+ JumpTarget tryStart{ offset() };
+ if (!emit1(JSOP_TRY)) // tryStart:
+ return false;
+ MOZ_ASSERT(this->stackDepth == depth);
+
+ // Load the generator object.
+ if (!emitTree(gen)) // ITER RESULT GENOBJ
+ return false;
+
+ // Yield RESULT as-is, without re-boxing.
+ if (!emitYieldOp(JSOP_YIELD)) // ITER RECEIVED
+ return false;
+
+ // Try epilogue.
+ if (!setSrcNoteOffset(noteIndex, 0, offset() - tryStart.offset))
+ return false;
+ if (!emitJump(JSOP_GOTO, &send)) // goto send
+ return false;
+
+ JumpTarget tryEnd;
+ if (!emitJumpTarget(&tryEnd)) // tryEnd:
+ return false;
+
+ // Catch location.
+ stackDepth = uint32_t(depth); // ITER RESULT
+ if (!emit1(JSOP_POP)) // ITER
+ return false;
+ // THROW? = 'throw' in ITER
+ if (!emit1(JSOP_EXCEPTION)) // ITER EXCEPTION
+ return false;
+ if (!emit1(JSOP_SWAP)) // EXCEPTION ITER
+ return false;
+ if (!emit1(JSOP_DUP)) // EXCEPTION ITER ITER
+ return false;
+ if (!emitAtomOp(cx->names().throw_, JSOP_STRING)) // EXCEPTION ITER ITER "throw"
+ return false;
+ if (!emit1(JSOP_SWAP)) // EXCEPTION ITER "throw" ITER
+ return false;
+ if (!emit1(JSOP_IN)) // EXCEPTION ITER THROW?
+ return false;
+ // if (THROW?) goto delegate
+ JumpList checkThrow;
+ if (!emitJump(JSOP_IFNE, &checkThrow)) // EXCEPTION ITER
+ return false;
+ if (!emit1(JSOP_POP)) // EXCEPTION
+ return false;
+ if (!emit1(JSOP_THROW)) // throw EXCEPTION
+ return false;
+
+ if (!emitJumpTargetAndPatch(checkThrow)) // delegate:
+ return false;
+ // RESULT = ITER.throw(EXCEPTION) // EXCEPTION ITER
+ stackDepth = uint32_t(depth);
+ if (!emit1(JSOP_DUP)) // EXCEPTION ITER ITER
+ return false;
+ if (!emit1(JSOP_DUP)) // EXCEPTION ITER ITER ITER
+ return false;
+ if (!emitAtomOp(cx->names().throw_, JSOP_CALLPROP)) // EXCEPTION ITER ITER THROW
+ return false;
+ if (!emit1(JSOP_SWAP)) // EXCEPTION ITER THROW ITER
+ return false;
+ if (!emit2(JSOP_PICK, 3)) // ITER THROW ITER EXCEPTION
+ return false;
+ if (!emitCall(JSOP_CALL, 1, iter)) // ITER RESULT
+ return false;
+ checkTypeSet(JSOP_CALL);
+ MOZ_ASSERT(this->stackDepth == depth);
+ JumpList checkResult;
+ if (!emitJump(JSOP_GOTO, &checkResult)) // goto checkResult
+ return false;
+
+ // Catch epilogue.
+
+ // This is a peace offering to ReconstructPCStack. See the note in EmitTry.
+ if (!emit1(JSOP_NOP))
+ return false;
+ if (!tryNoteList.append(JSTRY_CATCH, depth, tryStart.offset + JSOP_TRY_LENGTH, tryEnd.offset))
+ return false;
+
+ // After the try/catch block: send the received value to the iterator.
+ if (!emitJumpTargetAndPatch(send)) // send:
+ return false;
+
+ // Send location.
+ // result = iter.next(received) // ITER RECEIVED
+ if (!emit1(JSOP_SWAP)) // RECEIVED ITER
+ return false;
+ if (!emit1(JSOP_DUP)) // RECEIVED ITER ITER
+ return false;
+ if (!emit1(JSOP_DUP)) // RECEIVED ITER ITER ITER
+ return false;
+ if (!emitAtomOp(cx->names().next, JSOP_CALLPROP)) // RECEIVED ITER ITER NEXT
+ return false;
+ if (!emit1(JSOP_SWAP)) // RECEIVED ITER NEXT ITER
+ return false;
+ if (!emit2(JSOP_PICK, 3)) // ITER NEXT ITER RECEIVED
+ return false;
+ if (!emitCall(JSOP_CALL, 1, iter)) // ITER RESULT
+ return false;
+ if (!emitCheckIsObj(CheckIsObjectKind::IteratorNext)) // ITER RESULT
+ return false;
+ checkTypeSet(JSOP_CALL);
+ MOZ_ASSERT(this->stackDepth == depth);
+
+ if (!emitJumpTargetAndPatch(checkResult)) // checkResult:
+ return false;
+
+ // if (!result.done) goto tryStart; // ITER RESULT
+ if (!emit1(JSOP_DUP)) // ITER RESULT RESULT
+ return false;
+ if (!emitAtomOp(cx->names().done, JSOP_GETPROP)) // ITER RESULT DONE
+ return false;
+ // if (!DONE) goto tryStart;
+ JumpList beq;
+ JumpTarget breakTarget{ -1 };
+ if (!emitBackwardJump(JSOP_IFEQ, tryStart, &beq, &breakTarget)) // ITER RESULT
+ return false;
+
+ // result.value
+ if (!emit1(JSOP_SWAP)) // RESULT ITER
+ return false;
+ if (!emit1(JSOP_POP)) // RESULT
+ return false;
+ if (!emitAtomOp(cx->names().value, JSOP_GETPROP)) // VALUE
+ return false;
+
+ MOZ_ASSERT(this->stackDepth == depth - 1);
+
+ return true;
+}
+
+bool
+BytecodeEmitter::emitStatementList(ParseNode* pn)
+{
+ MOZ_ASSERT(pn->isArity(PN_LIST));
+ for (ParseNode* pn2 = pn->pn_head; pn2; pn2 = pn2->pn_next) {
+ if (!emitTree(pn2))
+ return false;
+ }
+ return true;
+}
+
+bool
+BytecodeEmitter::emitStatement(ParseNode* pn)
+{
+ MOZ_ASSERT(pn->isKind(PNK_SEMI));
+
+ ParseNode* pn2 = pn->pn_kid;
+ if (!pn2)
+ return true;
+
+ if (!updateSourceCoordNotes(pn->pn_pos.begin))
+ return false;
+
+ /*
+ * 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->isFunctionBox())
+ MOZ_ASSERT(!script->noScriptRval());
+ else
+ useful = wantval = !script->noScriptRval();
+
+ /* Don't eliminate expressions with side effects. */
+ if (!useful) {
+ if (!checkSideEffects(pn2, &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() >= offset())
+ {
+ useful = true;
+ }
+ }
+
+ if (useful) {
+ JSOp op = wantval ? JSOP_SETRVAL : JSOP_POP;
+ MOZ_ASSERT_IF(pn2->isKind(PNK_ASSIGN), pn2->isOp(JSOP_NOP));
+ if (!emitTree(pn2))
+ return false;
+ if (!emit1(op))
+ return false;
+ } else if (pn->isDirectivePrologueMember()) {
+ // Don't complain about directive prologue members; just don't emit
+ // their code.
+ } else {
+ if (JSAtom* atom = pn->isStringExprStatement()) {
+ // Warn if encountering a non-directive prologue member string
+ // expression statement, that is inconsistent with the current
+ // directive prologue. That is, a script *not* starting with
+ // "use strict" should warn for any "use strict" statements seen
+ // later in the script, because such statements are misleading.
+ const char* directive = nullptr;
+ if (atom == cx->names().useStrict) {
+ if (!sc->strictScript)
+ directive = js_useStrict_str;
+ } else if (atom == cx->names().useAsm) {
+ if (sc->isFunctionBox()) {
+ if (IsAsmJSModule(sc->asFunctionBox()->function()))
+ directive = js_useAsm_str;
+ }
+ }
+
+ if (directive) {
+ if (!reportStrictWarning(pn2, JSMSG_CONTRARY_NONDIRECTIVE, directive))
+ return false;
+ }
+ } else {
+ current->currentLine = parser->tokenStream.srcCoords.lineNum(pn2->pn_pos.begin);
+ current->lastColumn = 0;
+ if (!reportStrictWarning(pn2, JSMSG_USELESS_EXPR))
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool
+BytecodeEmitter::emitDeleteName(ParseNode* node)
+{
+ MOZ_ASSERT(node->isKind(PNK_DELETENAME));
+ MOZ_ASSERT(node->isArity(PN_UNARY));
+
+ ParseNode* nameExpr = node->pn_kid;
+ MOZ_ASSERT(nameExpr->isKind(PNK_NAME));
+
+ return emitAtomOp(nameExpr, JSOP_DELNAME);
+}
+
+bool
+BytecodeEmitter::emitDeleteProperty(ParseNode* node)
+{
+ MOZ_ASSERT(node->isKind(PNK_DELETEPROP));
+ MOZ_ASSERT(node->isArity(PN_UNARY));
+
+ ParseNode* propExpr = node->pn_kid;
+ MOZ_ASSERT(propExpr->isKind(PNK_DOT));
+
+ if (propExpr->as<PropertyAccess>().isSuper()) {
+ // Still have to calculate the base, even though we are are going
+ // to throw unconditionally, as calculating the base could also
+ // throw.
+ if (!emit1(JSOP_SUPERBASE))
+ return false;
+
+ return emitUint16Operand(JSOP_THROWMSG, JSMSG_CANT_DELETE_SUPER);
+ }
+
+ JSOp delOp = sc->strict() ? JSOP_STRICTDELPROP : JSOP_DELPROP;
+ return emitPropOp(propExpr, delOp);
+}
+
+bool
+BytecodeEmitter::emitDeleteElement(ParseNode* node)
+{
+ MOZ_ASSERT(node->isKind(PNK_DELETEELEM));
+ MOZ_ASSERT(node->isArity(PN_UNARY));
+
+ ParseNode* elemExpr = node->pn_kid;
+ MOZ_ASSERT(elemExpr->isKind(PNK_ELEM));
+
+ if (elemExpr->as<PropertyByValue>().isSuper()) {
+ // Still have to calculate everything, even though we're gonna throw
+ // since it may have side effects
+ if (!emitTree(elemExpr->pn_right))
+ return false;
+
+ if (!emit1(JSOP_SUPERBASE))
+ return false;
+ if (!emitUint16Operand(JSOP_THROWMSG, JSMSG_CANT_DELETE_SUPER))
+ return false;
+
+ // Another wrinkle: Balance the stack from the emitter's point of view.
+ // Execution will not reach here, as the last bytecode threw.
+ return emit1(JSOP_POP);
+ }
+
+ JSOp delOp = sc->strict() ? JSOP_STRICTDELELEM : JSOP_DELELEM;
+ return emitElemOp(elemExpr, delOp);
+}
+
+bool
+BytecodeEmitter::emitDeleteExpression(ParseNode* node)
+{
+ MOZ_ASSERT(node->isKind(PNK_DELETEEXPR));
+ MOZ_ASSERT(node->isArity(PN_UNARY));
+
+ ParseNode* expression = node->pn_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);
+}
+
+static const char *
+SelfHostedCallFunctionName(JSAtom* name, ExclusiveContext* cx)
+{
+ if (name == cx->names().callFunction)
+ return "callFunction";
+ if (name == cx->names().callContentFunction)
+ return "callContentFunction";
+ if (name == cx->names().constructContentFunction)
+ return "constructContentFunction";
+
+ MOZ_CRASH("Unknown self-hosted call function name");
+}
+
+bool
+BytecodeEmitter::emitSelfHostedCallFunction(ParseNode* pn)
+{
+ // 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.
+ ParseNode* pn2 = pn->pn_head;
+ const char* errorName = SelfHostedCallFunctionName(pn2->name(), cx);
+
+ if (pn->pn_count < 3) {
+ reportError(pn, JSMSG_MORE_ARGS_NEEDED, errorName, "2", "s");
+ return false;
+ }
+
+ JSOp callOp = pn->getOp();
+ if (callOp != JSOP_CALL) {
+ reportError(pn, JSMSG_NOT_CONSTRUCTOR, errorName);
+ return false;
+ }
+
+ bool constructing = pn2->name() == cx->names().constructContentFunction;
+ ParseNode* funNode = pn2->pn_next;
+ if (constructing)
+ callOp = JSOP_NEW;
+ else if (funNode->getKind() == PNK_NAME && funNode->name() == cx->names().std_Function_apply)
+ callOp = JSOP_FUNAPPLY;
+
+ if (!emitTree(funNode))
+ return false;
+
+#ifdef DEBUG
+ if (emitterMode == BytecodeEmitter::SelfHosting &&
+ pn2->name() == cx->names().callFunction)
+ {
+ if (!emit1(JSOP_DEBUGCHECKSELFHOSTED))
+ return false;
+ }
+#endif
+
+ ParseNode* thisOrNewTarget = funNode->pn_next;
+ if (constructing) {
+ // Save off the new.target value, but here emit a proper |this| for a
+ // constructing call.
+ if (!emit1(JSOP_IS_CONSTRUCTING))
+ return false;
+ } else {
+ // It's |this|, emit it.
+ if (!emitTree(thisOrNewTarget))
+ return false;
+ }
+
+ for (ParseNode* argpn = thisOrNewTarget->pn_next; argpn; argpn = argpn->pn_next) {
+ if (!emitTree(argpn))
+ return false;
+ }
+
+ if (constructing) {
+ if (!emitTree(thisOrNewTarget))
+ return false;
+ }
+
+ uint32_t argc = pn->pn_count - 3;
+ if (!emitCall(callOp, argc))
+ return false;
+
+ checkTypeSet(callOp);
+ return true;
+}
+
+bool
+BytecodeEmitter::emitSelfHostedResumeGenerator(ParseNode* pn)
+{
+ // Syntax: resumeGenerator(gen, value, 'next'|'throw'|'close')
+ if (pn->pn_count != 4) {
+ reportError(pn, JSMSG_MORE_ARGS_NEEDED, "resumeGenerator", "1", "s");
+ return false;
+ }
+
+ ParseNode* funNode = pn->pn_head; // The resumeGenerator node.
+
+ ParseNode* genNode = funNode->pn_next;
+ if (!emitTree(genNode))
+ return false;
+
+ ParseNode* valNode = genNode->pn_next;
+ if (!emitTree(valNode))
+ return false;
+
+ ParseNode* kindNode = valNode->pn_next;
+ MOZ_ASSERT(kindNode->isKind(PNK_STRING));
+ uint16_t operand = GeneratorObject::getResumeKind(cx, kindNode->pn_atom);
+ MOZ_ASSERT(!kindNode->pn_next);
+
+ if (!emitCall(JSOP_RESUME, operand))
+ return false;
+
+ return true;
+}
+
+bool
+BytecodeEmitter::emitSelfHostedForceInterpreter(ParseNode* pn)
+{
+ if (!emit1(JSOP_FORCEINTERPRETER))
+ return false;
+ if (!emit1(JSOP_UNDEFINED))
+ return false;
+ return true;
+}
+
+bool
+BytecodeEmitter::emitSelfHostedAllowContentSpread(ParseNode* pn)
+{
+ if (pn->pn_count != 2) {
+ reportError(pn, JSMSG_MORE_ARGS_NEEDED, "allowContentSpread", "1", "");
+ return false;
+ }
+
+ // We're just here as a sentinel. Pass the value through directly.
+ return emitTree(pn->pn_head->pn_next);
+}
+
+bool
+BytecodeEmitter::isRestParameter(ParseNode* pn, bool* result)
+{
+ if (!sc->isFunctionBox()) {
+ *result = false;
+ return true;
+ }
+
+ FunctionBox* funbox = sc->asFunctionBox();
+ RootedFunction fun(cx, funbox->function());
+ if (!fun->hasRest()) {
+ *result = false;
+ return true;
+ }
+
+ if (!pn->isKind(PNK_NAME)) {
+ if (emitterMode == BytecodeEmitter::SelfHosting && pn->isKind(PNK_CALL)) {
+ ParseNode* pn2 = pn->pn_head;
+ if (pn2->getKind() == PNK_NAME && pn2->name() == cx->names().allowContentSpread)
+ return isRestParameter(pn2->pn_next, result);
+ }
+ *result = false;
+ return true;
+ }
+
+ JSAtom* name = pn->name();
+ Maybe<NameLocation> paramLoc = locationOfNameBoundInFunctionScope(name);
+ if (paramLoc && lookupName(name) == *paramLoc) {
+ FunctionScope::Data* bindings = funbox->functionScopeBindings();
+ if (bindings->nonPositionalFormalStart > 0) {
+ // |paramName| can be nullptr when the rest destructuring syntax is
+ // used: `function f(...[]) {}`.
+ JSAtom* paramName = bindings->names[bindings->nonPositionalFormalStart - 1].name();
+ *result = paramName && name == paramName;
+ return true;
+ }
+ }
+
+ return true;
+}
+
+bool
+BytecodeEmitter::emitOptimizeSpread(ParseNode* arg0, JumpList* jmp, bool* emitted)
+{
+ // Emit a pereparation code to optimize the spread call with a rest
+ // parameter:
+ //
+ // function f(...args) {
+ // g(...args);
+ // }
+ //
+ // If the spread operand is a rest parameter and it's optimizable array,
+ // skip spread operation and pass it directly to spread call operation.
+ // See the comment in OptimizeSpreadCall in Interpreter.cpp for the
+ // optimizable conditons.
+ bool result = false;
+ if (!isRestParameter(arg0, &result))
+ return false;
+
+ if (!result) {
+ *emitted = false;
+ return true;
+ }
+
+ if (!emitTree(arg0))
+ return false;
+
+ if (!emit1(JSOP_OPTIMIZE_SPREADCALL))
+ return false;
+
+ if (!emitJump(JSOP_IFNE, jmp))
+ return false;
+
+ if (!emit1(JSOP_POP))
+ return false;
+
+ *emitted = true;
+ return true;
+}
+
+bool
+BytecodeEmitter::emitCallOrNew(ParseNode* pn)
+{
+ bool callop = pn->isKind(PNK_CALL) || pn->isKind(PNK_TAGGED_TEMPLATE);
+ /*
+ * Emit callable invocation or operator new (constructor call) code.
+ * First, emit code for the left operand to evaluate the callable or
+ * constructable object expression.
+ *
+ * For operator new, we emit JSOP_GETPROP instead of JSOP_CALLPROP, etc.
+ * This is necessary to interpose the lambda-initialized method read
+ * barrier -- see the code in jsinterp.cpp for JSOP_LAMBDA followed by
+ * JSOP_{SET,INIT}PROP.
+ *
+ * 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).
+ */
+ uint32_t argc = pn->pn_count - 1;
+
+ if (argc >= ARGC_LIMIT) {
+ parser->tokenStream.reportError(callop
+ ? JSMSG_TOO_MANY_FUN_ARGS
+ : JSMSG_TOO_MANY_CON_ARGS);
+ return false;
+ }
+
+ ParseNode* pn2 = pn->pn_head;
+ bool spread = JOF_OPTYPE(pn->getOp()) == JOF_BYTE;
+ switch (pn2->getKind()) {
+ case PNK_NAME:
+ if (emitterMode == BytecodeEmitter::SelfHosting && !spread) {
+ // Calls to "forceInterpreter", "callFunction",
+ // "callContentFunction", or "resumeGenerator" in self-hosted
+ // code generate inline bytecode.
+ if (pn2->name() == cx->names().callFunction ||
+ pn2->name() == cx->names().callContentFunction ||
+ pn2->name() == cx->names().constructContentFunction)
+ {
+ return emitSelfHostedCallFunction(pn);
+ }
+ if (pn2->name() == cx->names().resumeGenerator)
+ return emitSelfHostedResumeGenerator(pn);
+ if (pn2->name() == cx->names().forceInterpreter)
+ return emitSelfHostedForceInterpreter(pn);
+ if (pn2->name() == cx->names().allowContentSpread)
+ return emitSelfHostedAllowContentSpread(pn);
+ // Fall through.
+ }
+ if (!emitGetName(pn2, callop))
+ return false;
+ break;
+ case PNK_DOT:
+ MOZ_ASSERT(emitterMode != BytecodeEmitter::SelfHosting);
+ if (pn2->as<PropertyAccess>().isSuper()) {
+ if (!emitSuperPropOp(pn2, JSOP_GETPROP_SUPER, /* isCall = */ callop))
+ return false;
+ } else {
+ if (!emitPropOp(pn2, callop ? JSOP_CALLPROP : JSOP_GETPROP))
+ return false;
+ }
+
+ break;
+ case PNK_ELEM:
+ MOZ_ASSERT(emitterMode != BytecodeEmitter::SelfHosting);
+ if (pn2->as<PropertyByValue>().isSuper()) {
+ if (!emitSuperElemOp(pn2, JSOP_GETELEM_SUPER, /* isCall = */ callop))
+ return false;
+ } else {
+ if (!emitElemOp(pn2, callop ? JSOP_CALLELEM : JSOP_GETELEM))
+ return false;
+ if (callop) {
+ if (!emit1(JSOP_SWAP))
+ return false;
+ }
+ }
+
+ break;
+ case PNK_FUNCTION:
+ /*
+ * Top level lambdas which are immediately invoked should be
+ * treated as only running once. Every time they execute we will
+ * create new types and scripts for their contents, to increase
+ * the quality of type information within them and enable more
+ * backend optimizations. Note that this does not depend on the
+ * lambda being invoked at most once (it may be named or be
+ * accessed via foo.caller indirection), as multiple executions
+ * will just cause the inner scripts to be repeatedly cloned.
+ */
+ MOZ_ASSERT(!emittingRunOnceLambda);
+ if (checkRunOnceContext()) {
+ emittingRunOnceLambda = true;
+ if (!emitTree(pn2))
+ return false;
+ emittingRunOnceLambda = false;
+ } else {
+ if (!emitTree(pn2))
+ return false;
+ }
+ callop = false;
+ break;
+ case PNK_SUPERBASE:
+ MOZ_ASSERT(pn->isKind(PNK_SUPERCALL));
+ MOZ_ASSERT(parser->handler.isSuperBase(pn2));
+ if (!emit1(JSOP_SUPERFUN))
+ return false;
+ break;
+ default:
+ if (!emitTree(pn2))
+ return false;
+ callop = false; /* trigger JSOP_UNDEFINED after */
+ break;
+ }
+
+ bool isNewOp = pn->getOp() == JSOP_NEW || pn->getOp() == JSOP_SPREADNEW ||
+ pn->getOp() == JSOP_SUPERCALL || pn->getOp() == JSOP_SPREADSUPERCALL;
+
+
+ // Emit room for |this|.
+ if (!callop) {
+ if (isNewOp) {
+ if (!emit1(JSOP_IS_CONSTRUCTING))
+ return false;
+ } else {
+ if (!emit1(JSOP_UNDEFINED))
+ return false;
+ }
+ }
+
+ /*
+ * Emit code for each argument in order, then emit the JSOP_*CALL or
+ * JSOP_NEW bytecode with a two-byte immediate telling how many args
+ * were pushed on the operand stack.
+ */
+ if (!spread) {
+ for (ParseNode* pn3 = pn2->pn_next; pn3; pn3 = pn3->pn_next) {
+ if (!emitTree(pn3))
+ return false;
+ }
+
+ if (isNewOp) {
+ if (pn->isKind(PNK_SUPERCALL)) {
+ if (!emit1(JSOP_NEWTARGET))
+ return false;
+ } else {
+ // Repush the callee as new.target
+ if (!emitDupAt(argc + 1))
+ return false;
+ }
+ }
+ } else {
+ ParseNode* args = pn2->pn_next;
+ JumpList jmp;
+ bool optCodeEmitted = false;
+ if (argc == 1) {
+ if (!emitOptimizeSpread(args->pn_kid, &jmp, &optCodeEmitted))
+ return false;
+ }
+
+ if (!emitArray(args, argc, JSOP_SPREADCALLARRAY))
+ return false;
+
+ if (optCodeEmitted) {
+ if (!emitJumpTargetAndPatch(jmp))
+ return false;
+ }
+
+ if (isNewOp) {
+ if (pn->isKind(PNK_SUPERCALL)) {
+ if (!emit1(JSOP_NEWTARGET))
+ return false;
+ } else {
+ if (!emitDupAt(2))
+ return false;
+ }
+ }
+ }
+
+ if (!spread) {
+ if (!emitCall(pn->getOp(), argc, pn))
+ return false;
+ } else {
+ if (!emit1(pn->getOp()))
+ return false;
+ }
+ checkTypeSet(pn->getOp());
+ if (pn->isOp(JSOP_EVAL) ||
+ pn->isOp(JSOP_STRICTEVAL) ||
+ pn->isOp(JSOP_SPREADEVAL) ||
+ pn->isOp(JSOP_STRICTSPREADEVAL))
+ {
+ uint32_t lineNum = parser->tokenStream.srcCoords.lineNum(pn->pn_pos.begin);
+ if (!emitUint32Operand(JSOP_LINENO, lineNum))
+ return false;
+ }
+
+ return true;
+}
+
+bool
+BytecodeEmitter::emitRightAssociative(ParseNode* pn)
+{
+ // ** is the only right-associative operator.
+ MOZ_ASSERT(pn->isKind(PNK_POW));
+ MOZ_ASSERT(pn->isArity(PN_LIST));
+
+ // Right-associative operator chain.
+ for (ParseNode* subexpr = pn->pn_head; subexpr; subexpr = subexpr->pn_next) {
+ if (!emitTree(subexpr))
+ return false;
+ }
+ for (uint32_t i = 0; i < pn->pn_count - 1; i++) {
+ if (!emit1(JSOP_POW))
+ return false;
+ }
+ return true;
+}
+
+bool
+BytecodeEmitter::emitLeftAssociative(ParseNode* pn)
+{
+ MOZ_ASSERT(pn->isArity(PN_LIST));
+
+ // Left-associative operator chain.
+ if (!emitTree(pn->pn_head))
+ return false;
+ JSOp op = pn->getOp();
+ ParseNode* nextExpr = pn->pn_head->pn_next;
+ do {
+ if (!emitTree(nextExpr))
+ return false;
+ if (!emit1(op))
+ return false;
+ } while ((nextExpr = nextExpr->pn_next));
+ return true;
+}
+
+bool
+BytecodeEmitter::emitLogical(ParseNode* pn)
+{
+ MOZ_ASSERT(pn->isArity(PN_LIST));
+
+ /*
+ * JSOP_OR converts the operand on the stack to boolean, leaves the original
+ * value on the stack and jumps if true; otherwise it falls into the next
+ * bytecode, which pops the left operand and then evaluates the right operand.
+ * The jump goes around the right operand evaluation.
+ *
+ * JSOP_AND converts the operand on the stack to boolean and jumps if false;
+ * otherwise it falls into the right operand's bytecode.
+ */
+
+ TDZCheckCache tdzCache(this);
+
+ /* Left-associative operator chain: avoid too much recursion. */
+ ParseNode* pn2 = pn->pn_head;
+ if (!emitTree(pn2))
+ return false;
+ JSOp op = pn->getOp();
+ JumpList jump;
+ if (!emitJump(op, &jump))
+ return false;
+ if (!emit1(JSOP_POP))
+ return false;
+
+ /* Emit nodes between the head and the tail. */
+ while ((pn2 = pn2->pn_next)->pn_next) {
+ if (!emitTree(pn2))
+ return false;
+ if (!emitJump(op, &jump))
+ return false;
+ if (!emit1(JSOP_POP))
+ return false;
+ }
+ if (!emitTree(pn2))
+ return false;
+
+ if (!emitJumpTargetAndPatch(jump))
+ return false;
+ return true;
+}
+
+bool
+BytecodeEmitter::emitSequenceExpr(ParseNode* pn)
+{
+ for (ParseNode* child = pn->pn_head; ; child = child->pn_next) {
+ if (!updateSourceCoordNotes(child->pn_pos.begin))
+ return false;
+ if (!emitTree(child))
+ return false;
+ if (!child->pn_next)
+ break;
+ if (!emit1(JSOP_POP))
+ return false;
+ }
+ return true;
+}
+
+// Using MOZ_NEVER_INLINE in here is a workaround for llvm.org/pr14047. See
+// the comment on emitSwitch.
+MOZ_NEVER_INLINE bool
+BytecodeEmitter::emitIncOrDec(ParseNode* pn)
+{
+ switch (pn->pn_kid->getKind()) {
+ case PNK_DOT:
+ return emitPropIncDec(pn);
+ case PNK_ELEM:
+ return emitElemIncDec(pn);
+ case PNK_CALL:
+ return emitCallIncDec(pn);
+ default:
+ return emitNameIncDec(pn);
+ }
+
+ 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::emitLabeledStatement(const LabeledStatement* pn)
+{
+ /*
+ * Emit a JSOP_LABEL instruction. The argument is the offset to the statement
+ * following the labeled statement.
+ */
+ uint32_t index;
+ if (!makeAtomIndex(pn->label(), &index))
+ return false;
+
+ JumpList top;
+ if (!emitJump(JSOP_LABEL, &top))
+ return false;
+
+ /* Emit code for the labeled statement. */
+ LabelControl controlInfo(this, pn->label(), offset());
+
+ if (!emitTree(pn->statement()))
+ return false;
+
+ /* Patch the JSOP_LABEL offset. */
+ JumpTarget brk{ lastNonJumpTargetOffset() };
+ patchJumpsToTarget(top, brk);
+
+ if (!controlInfo.patchBreaks(this))
+ return false;
+
+ return true;
+}
+
+bool
+BytecodeEmitter::emitConditionalExpression(ConditionalExpression& conditional)
+{
+ /* Emit the condition, then branch if false to the else part. */
+ if (!emitTree(&conditional.condition()))
+ return false;
+
+ IfThenElseEmitter ifThenElse(this);
+ if (!ifThenElse.emitCond())
+ return false;
+
+ if (!emitConditionallyExecutedTree(&conditional.thenExpression()))
+ return false;
+
+ if (!ifThenElse.emitElse())
+ return false;
+
+ if (!emitConditionallyExecutedTree(&conditional.elseExpression()))
+ return false;
+
+ if (!ifThenElse.emitEnd())
+ return false;
+ MOZ_ASSERT(ifThenElse.pushed() == 1);
+
+ return true;
+}
+
+bool
+BytecodeEmitter::emitPropertyList(ParseNode* pn, MutableHandlePlainObject objp, PropListType type)
+{
+ for (ParseNode* propdef = pn->pn_head; propdef; propdef = propdef->pn_next) {
+ if (!updateSourceCoordNotes(propdef->pn_pos.begin))
+ return false;
+
+ // Handle __proto__: v specially because *only* this form, and no other
+ // involving "__proto__", performs [[Prototype]] mutation.
+ if (propdef->isKind(PNK_MUTATEPROTO)) {
+ MOZ_ASSERT(type == ObjectLiteral);
+ if (!emitTree(propdef->pn_kid))
+ return false;
+ objp.set(nullptr);
+ if (!emit1(JSOP_MUTATEPROTO))
+ return false;
+ continue;
+ }
+
+ bool extraPop = false;
+ if (type == ClassBody && propdef->as<ClassMethod>().isStatic()) {
+ extraPop = true;
+ if (!emit1(JSOP_DUP2))
+ return false;
+ if (!emit1(JSOP_POP))
+ return false;
+ }
+
+ /* Emit an index for t[2] for later consumption by JSOP_INITELEM. */
+ ParseNode* key = propdef->pn_left;
+ bool isIndex = false;
+ if (key->isKind(PNK_NUMBER)) {
+ if (!emitNumberOp(key->pn_dval))
+ return false;
+ isIndex = true;
+ } else if (key->isKind(PNK_OBJECT_PROPERTY_NAME) || key->isKind(PNK_STRING)) {
+ // EmitClass took care of constructor already.
+ if (type == ClassBody && key->pn_atom == cx->names().constructor &&
+ !propdef->as<ClassMethod>().isStatic())
+ {
+ continue;
+ }
+
+ // The parser already checked for atoms representing indexes and
+ // used PNK_NUMBER instead, but also watch for ids which TI treats
+ // as indexes for simpliciation of downstream analysis.
+ jsid id = NameToId(key->pn_atom->asPropertyName());
+ if (id != IdToTypeId(id)) {
+ if (!emitTree(key))
+ return false;
+ isIndex = true;
+ }
+ } else {
+ if (!emitComputedPropertyName(key))
+ return false;
+ isIndex = true;
+ }
+
+ /* Emit code for the property initializer. */
+ if (!emitTree(propdef->pn_right))
+ return false;
+
+ JSOp op = propdef->getOp();
+ MOZ_ASSERT(op == JSOP_INITPROP ||
+ op == JSOP_INITPROP_GETTER ||
+ op == JSOP_INITPROP_SETTER);
+
+ if (op == JSOP_INITPROP_GETTER || op == JSOP_INITPROP_SETTER)
+ objp.set(nullptr);
+
+ if (propdef->pn_right->isKind(PNK_FUNCTION) &&
+ propdef->pn_right->pn_funbox->needsHomeObject())
+ {
+ MOZ_ASSERT(propdef->pn_right->pn_funbox->function()->allowSuperProperty());
+ bool isAsync = propdef->pn_right->pn_funbox->isAsync();
+ if (isAsync) {
+ if (!emit1(JSOP_SWAP))
+ return false;
+ }
+ if (!emit2(JSOP_INITHOMEOBJECT, isIndex + isAsync))
+ return false;
+ if (isAsync) {
+ if (!emit1(JSOP_POP))
+ return false;
+ }
+ }
+
+ // Class methods are not enumerable.
+ if (type == ClassBody) {
+ switch (op) {
+ case JSOP_INITPROP: op = JSOP_INITHIDDENPROP; break;
+ case JSOP_INITPROP_GETTER: op = JSOP_INITHIDDENPROP_GETTER; break;
+ case JSOP_INITPROP_SETTER: op = JSOP_INITHIDDENPROP_SETTER; break;
+ default: MOZ_CRASH("Invalid op");
+ }
+ }
+
+ if (isIndex) {
+ objp.set(nullptr);
+ switch (op) {
+ case JSOP_INITPROP: op = JSOP_INITELEM; break;
+ case JSOP_INITHIDDENPROP: op = JSOP_INITHIDDENELEM; break;
+ case JSOP_INITPROP_GETTER: op = JSOP_INITELEM_GETTER; break;
+ case JSOP_INITHIDDENPROP_GETTER: op = JSOP_INITHIDDENELEM_GETTER; break;
+ case JSOP_INITPROP_SETTER: op = JSOP_INITELEM_SETTER; break;
+ case JSOP_INITHIDDENPROP_SETTER: op = JSOP_INITHIDDENELEM_SETTER; break;
+ default: MOZ_CRASH("Invalid op");
+ }
+ if (!emit1(op))
+ return false;
+ } else {
+ MOZ_ASSERT(key->isKind(PNK_OBJECT_PROPERTY_NAME) || key->isKind(PNK_STRING));
+
+ uint32_t index;
+ if (!makeAtomIndex(key->pn_atom, &index))
+ return false;
+
+ if (objp) {
+ MOZ_ASSERT(type == ObjectLiteral);
+ MOZ_ASSERT(!IsHiddenInitOp(op));
+ MOZ_ASSERT(!objp->inDictionaryMode());
+ Rooted<jsid> id(cx, AtomToId(key->pn_atom));
+ RootedValue undefinedValue(cx, UndefinedValue());
+ if (!NativeDefineProperty(cx, objp, id, undefinedValue, nullptr, nullptr,
+ JSPROP_ENUMERATE))
+ {
+ return false;
+ }
+ if (objp->inDictionaryMode())
+ objp.set(nullptr);
+ }
+
+ if (!emitIndex32(op, index))
+ return false;
+ }
+
+ if (extraPop) {
+ if (!emit1(JSOP_POP))
+ return false;
+ }
+ }
+ return true;
+}
+
+// Using MOZ_NEVER_INLINE in here is a workaround for llvm.org/pr14047. See
+// the comment on emitSwitch.
+MOZ_NEVER_INLINE bool
+BytecodeEmitter::emitObject(ParseNode* pn)
+{
+ if (!(pn->pn_xflags & PNX_NONCONST) && pn->pn_head && checkSingletonContext())
+ return emitSingletonInitialiser(pn);
+
+ /*
+ * 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__).
+ */
+ ptrdiff_t offset = this->offset();
+ if (!emitNewInit(JSProto_Object))
+ return false;
+
+ /*
+ * Try to construct the shape of the object as we go, so we can emit a
+ * JSOP_NEWOBJECT with the final shape instead.
+ */
+ RootedPlainObject obj(cx);
+ // No need to do any guessing for the object kind, since we know exactly
+ // how many properties we plan to have.
+ gc::AllocKind kind = gc::GetGCObjectKind(pn->pn_count);
+ obj = NewBuiltinClassInstance<PlainObject>(cx, kind, TenuredObject);
+ if (!obj)
+ return false;
+
+ if (!emitPropertyList(pn, &obj, ObjectLiteral))
+ return false;
+
+ if (obj) {
+ /*
+ * The object survived and has a predictable shape: update the original
+ * bytecode.
+ */
+ ObjectBox* objbox = parser->newObjectBox(obj);
+ if (!objbox)
+ return false;
+
+ static_assert(JSOP_NEWINIT_LENGTH == JSOP_NEWOBJECT_LENGTH,
+ "newinit and newobject must have equal length to edit in-place");
+
+ uint32_t index = objectList.add(objbox);
+ jsbytecode* code = this->code(offset);
+ code[0] = JSOP_NEWOBJECT;
+ code[1] = jsbytecode(index >> 24);
+ code[2] = jsbytecode(index >> 16);
+ code[3] = jsbytecode(index >> 8);
+ code[4] = jsbytecode(index);
+ }
+
+ return true;
+}
+
+bool
+BytecodeEmitter::emitArrayComp(ParseNode* pn)
+{
+ if (!emitNewInit(JSProto_Array))
+ return false;
+
+ /*
+ * Pass the new array's stack index to the PNK_ARRAYPUSH case via
+ * arrayCompDepth, then simply traverse the PNK_FOR node and
+ * its kids under pn2 to generate this comprehension.
+ */
+ MOZ_ASSERT(stackDepth > 0);
+ uint32_t saveDepth = arrayCompDepth;
+ arrayCompDepth = (uint32_t) (stackDepth - 1);
+ if (!emitTree(pn->pn_head))
+ return false;
+ arrayCompDepth = saveDepth;
+
+ return true;
+}
+
+bool
+BytecodeEmitter::emitArrayLiteral(ParseNode* pn)
+{
+ if (!(pn->pn_xflags & PNX_NONCONST) && pn->pn_head) {
+ if (checkSingletonContext()) {
+ // Bake in the object entirely if it will only be created once.
+ return emitSingletonInitialiser(pn);
+ }
+
+ // If the array consists entirely of primitive values, make a
+ // template object with copy on write elements that can be reused
+ // every time the initializer executes.
+ if (emitterMode != BytecodeEmitter::SelfHosting && pn->pn_count != 0) {
+ RootedValue value(cx);
+ if (!pn->getConstantValue(cx, ParseNode::ForCopyOnWriteArray, &value))
+ return false;
+ if (!value.isMagic(JS_GENERIC_MAGIC)) {
+ // Note: the group of the template object might not yet reflect
+ // that the object has copy on write elements. When the
+ // interpreter or JIT compiler fetches the template, it should
+ // use ObjectGroup::getOrFixupCopyOnWriteObject to make sure the
+ // group for the template is accurate. We don't do this here as we
+ // want to use ObjectGroup::allocationSiteGroup, which requires a
+ // finished script.
+ JSObject* obj = &value.toObject();
+ MOZ_ASSERT(obj->is<ArrayObject>() &&
+ obj->as<ArrayObject>().denseElementsAreCopyOnWrite());
+
+ ObjectBox* objbox = parser->newObjectBox(obj);
+ if (!objbox)
+ return false;
+
+ return emitObjectOp(objbox, JSOP_NEWARRAY_COPYONWRITE);
+ }
+ }
+ }
+
+ return emitArray(pn->pn_head, pn->pn_count, JSOP_NEWARRAY);
+}
+
+bool
+BytecodeEmitter::emitArray(ParseNode* pn, uint32_t count, JSOp op)
+{
+
+ /*
+ * 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_INITELEM_ARRAY 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.
+ */
+ MOZ_ASSERT(op == JSOP_NEWARRAY || op == JSOP_SPREADCALLARRAY);
+
+ uint32_t nspread = 0;
+ for (ParseNode* elt = pn; elt; elt = elt->pn_next) {
+ if (elt->isKind(PNK_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_JSOP_NEWARRAY, "
+ "BaselineCompiler::emit_JSOP_INITELEM_ARRAY, "
+ "and DoSetElemFallback's handling of JSOP_INITELEM_ARRAY");
+ 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(op, count - nspread)) // ARRAY
+ return false;
+
+ ParseNode* pn2 = pn;
+ uint32_t index;
+ bool afterSpread = false;
+ for (index = 0; pn2; index++, pn2 = pn2->pn_next) {
+ if (!afterSpread && pn2->isKind(PNK_SPREAD)) {
+ afterSpread = true;
+ if (!emitNumberOp(index)) // ARRAY INDEX
+ return false;
+ }
+ if (!updateSourceCoordNotes(pn2->pn_pos.begin))
+ return false;
+
+ bool allowSelfHostedSpread = false;
+ if (pn2->isKind(PNK_ELISION)) {
+ if (!emit1(JSOP_HOLE))
+ return false;
+ } else {
+ ParseNode* expr;
+ if (pn2->isKind(PNK_SPREAD)) {
+ expr = pn2->pn_kid;
+
+ if (emitterMode == BytecodeEmitter::SelfHosting &&
+ expr->isKind(PNK_CALL) &&
+ expr->pn_head->name() == cx->names().allowContentSpread)
+ {
+ allowSelfHostedSpread = true;
+ }
+ } else {
+ expr = pn2;
+ }
+ if (!emitTree(expr)) // ARRAY INDEX? VALUE
+ return false;
+ }
+ if (pn2->isKind(PNK_SPREAD)) {
+ if (!emitIterator()) // ARRAY INDEX ITER
+ return false;
+ if (!emit2(JSOP_PICK, 2)) // INDEX ITER ARRAY
+ return false;
+ if (!emit2(JSOP_PICK, 2)) // ITER ARRAY INDEX
+ return false;
+ if (!emitSpread(allowSelfHostedSpread)) // ARRAY INDEX
+ return false;
+ } else if (afterSpread) {
+ if (!emit1(JSOP_INITELEM_INC))
+ return false;
+ } else {
+ if (!emitUint32Operand(JSOP_INITELEM_ARRAY, index))
+ return false;
+ }
+ }
+ MOZ_ASSERT(index == count);
+ if (afterSpread) {
+ if (!emit1(JSOP_POP)) // ARRAY
+ return false;
+ }
+ return true;
+}
+
+bool
+BytecodeEmitter::emitUnary(ParseNode* pn)
+{
+ if (!updateSourceCoordNotes(pn->pn_pos.begin))
+ return false;
+
+ /* Unary op, including unary +/-. */
+ JSOp op = pn->getOp();
+ ParseNode* pn2 = pn->pn_kid;
+
+ if (!emitTree(pn2))
+ return false;
+
+ return emit1(op);
+}
+
+bool
+BytecodeEmitter::emitTypeof(ParseNode* node, JSOp op)
+{
+ MOZ_ASSERT(op == JSOP_TYPEOF || op == JSOP_TYPEOFEXPR);
+
+ if (!updateSourceCoordNotes(node->pn_pos.begin))
+ return false;
+
+ if (!emitTree(node->pn_kid))
+ return false;
+
+ return emit1(op);
+}
+
+bool
+BytecodeEmitter::emitFunctionFormalParametersAndBody(ParseNode *pn)
+{
+ MOZ_ASSERT(pn->isKind(PNK_PARAMSBODY));
+
+ ParseNode* funBody = pn->last();
+ FunctionBox* funbox = sc->asFunctionBox();
+
+ TDZCheckCache tdzCache(this);
+
+ if (funbox->hasParameterExprs) {
+ EmitterScope funEmitterScope(this);
+ if (!funEmitterScope.enterFunction(this, funbox))
+ return false;
+
+ if (!emitInitializeFunctionSpecialNames())
+ return false;
+
+ if (!emitFunctionFormalParameters(pn))
+ return false;
+
+ {
+ Maybe<EmitterScope> extraVarEmitterScope;
+
+ if (funbox->hasExtraBodyVarScope()) {
+ extraVarEmitterScope.emplace(this);
+ if (!extraVarEmitterScope->enterFunctionExtraBodyVar(this, funbox))
+ return false;
+
+ // 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; }
+ //
+ RootedAtom name(cx);
+ if (funbox->extraVarScopeBindings() && funbox->functionScopeBindings()) {
+ for (BindingIter bi(*funbox->functionScopeBindings(), true); bi; bi++) {
+ name = bi.name();
+
+ // There may not be a var binding of the same name.
+ if (!locationOfNameBoundInScope(name, extraVarEmitterScope.ptr()))
+ continue;
+
+ // The '.this' and '.generator' function special
+ // bindings should never appear in the extra var
+ // scope. 'arguments', however, may.
+ MOZ_ASSERT(name != cx->names().dotThis &&
+ name != cx->names().dotGenerator);
+
+ NameLocation paramLoc = *locationOfNameBoundInScope(name, &funEmitterScope);
+ auto emitRhs = [&name, &paramLoc](BytecodeEmitter* bce,
+ const NameLocation&, bool)
+ {
+ return bce->emitGetNameAtLocation(name, paramLoc);
+ };
+
+ if (!emitInitializeName(name, emitRhs))
+ return false;
+ if (!emit1(JSOP_POP))
+ return false;
+ }
+ }
+ }
+
+ if (!emitFunctionBody(funBody))
+ return false;
+
+ if (extraVarEmitterScope && !extraVarEmitterScope->leave(this))
+ return false;
+ }
+
+ return funEmitterScope.leave(this);
+ }
+
+ // No parameter expressions. Enter the function body scope and emit
+ // everything.
+ //
+ // 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.
+ EmitterScope emitterScope(this);
+
+ switchToPrologue();
+ if (!emitterScope.enterFunction(this, funbox))
+ return false;
+
+ if (!emitInitializeFunctionSpecialNames())
+ return false;
+ switchToMain();
+
+ if (!emitFunctionFormalParameters(pn))
+ return false;
+
+ if (!emitFunctionBody(funBody))
+ return false;
+
+ return emitterScope.leave(this);
+}
+
+bool
+BytecodeEmitter::emitFunctionFormalParameters(ParseNode* pn)
+{
+ ParseNode* funBody = pn->last();
+ FunctionBox* funbox = sc->asFunctionBox();
+ EmitterScope* funScope = innermostEmitterScope;
+
+ bool hasParameterExprs = funbox->hasParameterExprs;
+ bool hasRest = funbox->function()->hasRest();
+
+ uint16_t argSlot = 0;
+ for (ParseNode* arg = pn->pn_head; arg != funBody; arg = arg->pn_next, argSlot++) {
+ ParseNode* bindingElement = arg;
+ ParseNode* initializer = nullptr;
+ if (arg->isKind(PNK_ASSIGN)) {
+ bindingElement = arg->pn_left;
+ initializer = arg->pn_right;
+ }
+
+ // Left-hand sides are either simple names or destructuring patterns.
+ MOZ_ASSERT(bindingElement->isKind(PNK_NAME) ||
+ bindingElement->isKind(PNK_ARRAY) ||
+ bindingElement->isKind(PNK_ARRAYCOMP) ||
+ bindingElement->isKind(PNK_OBJECT));
+
+ // The rest parameter doesn't have an initializer.
+ bool isRest = hasRest && arg->pn_next == funBody;
+ MOZ_ASSERT_IF(isRest, !initializer);
+
+ bool isDestructuring = !bindingElement->isKind(PNK_NAME);
+
+ // ES 14.1.19 says if BindingElement contains an expression in the
+ // production FormalParameter : BindingElement, it is evaluated in a
+ // new var environment. This is needed to prevent vars from escaping
+ // direct eval in parameter expressions.
+ Maybe<EmitterScope> paramExprVarScope;
+ if (funbox->hasDirectEvalInParameterExpr && (isDestructuring || initializer)) {
+ paramExprVarScope.emplace(this);
+ if (!paramExprVarScope->enterParameterExpressionVar(this))
+ return false;
+ }
+
+ // First push the RHS if there is a default expression or if it is
+ // rest.
+
+ if (initializer) {
+ // If we have an initializer, emit the initializer and assign it
+ // to the argument slot. TDZ is taken care of afterwards.
+ MOZ_ASSERT(hasParameterExprs);
+ if (!emitArgOp(JSOP_GETARG, argSlot))
+ return false;
+ if (!emit1(JSOP_DUP))
+ return false;
+ if (!emit1(JSOP_UNDEFINED))
+ return false;
+ if (!emit1(JSOP_STRICTEQ))
+ return false;
+ // Emit source note to enable Ion compilation.
+ if (!newSrcNote(SRC_IF))
+ return false;
+ JumpList jump;
+ if (!emitJump(JSOP_IFEQ, &jump))
+ return false;
+ if (!emit1(JSOP_POP))
+ return false;
+ if (!emitConditionallyExecutedTree(initializer))
+ return false;
+ if (!emitJumpTargetAndPatch(jump))
+ return false;
+ } else if (isRest) {
+ if (!emit1(JSOP_REST))
+ return false;
+ checkTypeSet(JSOP_REST);
+ }
+
+ // Initialize the parameter name.
+
+ if (isDestructuring) {
+ // If we had an initializer or the rest parameter, the value is
+ // already on the stack.
+ if (!initializer && !isRest && !emitArgOp(JSOP_GETARG, argSlot))
+ return false;
+
+ // If there's an parameter expression var scope, the destructuring
+ // declaration needs to initialize the name in the function scope,
+ // which is not the innermost scope.
+ if (!emitDestructuringOps(bindingElement,
+ paramExprVarScope
+ ? DestructuringFormalParameterInVarScope
+ : DestructuringDeclaration))
+ {
+ return false;
+ }
+
+ if (!emit1(JSOP_POP))
+ return false;
+ } else {
+ RootedAtom paramName(cx, bindingElement->name());
+ NameLocation paramLoc = *locationOfNameBoundInScope(paramName, funScope);
+
+ if (hasParameterExprs) {
+ auto emitRhs = [argSlot, initializer, isRest](BytecodeEmitter* bce,
+ const NameLocation&, bool)
+ {
+ // If we had an initializer or a rest parameter, the value is
+ // already on the stack.
+ if (!initializer && !isRest)
+ return bce->emitArgOp(JSOP_GETARG, argSlot);
+ return true;
+ };
+
+ if (!emitSetOrInitializeNameAtLocation(paramName, paramLoc, emitRhs, true))
+ return false;
+ if (!emit1(JSOP_POP))
+ return false;
+ } else if (isRest) {
+ // The rest value is already on top of the stack.
+ auto nop = [](BytecodeEmitter*, const NameLocation&, bool) {
+ return true;
+ };
+
+ if (!emitSetOrInitializeNameAtLocation(paramName, paramLoc, nop, true))
+ return false;
+ if (!emit1(JSOP_POP))
+ return false;
+ }
+ }
+
+ if (paramExprVarScope) {
+ if (!paramExprVarScope->leave(this))
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool
+BytecodeEmitter::emitInitializeFunctionSpecialNames()
+{
+ FunctionBox* funbox = sc->asFunctionBox();
+
+ auto emitInitializeFunctionSpecialName = [](BytecodeEmitter* bce, HandlePropertyName name,
+ JSOp op)
+ {
+ // A special name must be slotful, either on the frame or on the
+ // call environment.
+ MOZ_ASSERT(bce->lookupName(name).hasKnownSlot());
+
+ auto emitInitial = [op](BytecodeEmitter* bce, const NameLocation&, bool) {
+ return bce->emit1(op);
+ };
+
+ if (!bce->emitInitializeName(name, emitInitial))
+ return false;
+ if (!bce->emit1(JSOP_POP))
+ return false;
+
+ return true;
+ };
+
+ // Do nothing if the function doesn't have an arguments binding.
+ if (funbox->argumentsHasLocalBinding()) {
+ if (!emitInitializeFunctionSpecialName(this, cx->names().arguments, JSOP_ARGUMENTS))
+ 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->hasThisBinding()) {
+ if (!emitInitializeFunctionSpecialName(this, cx->names().dotThis, JSOP_FUNCTIONTHIS))
+ return false;
+ }
+
+ return true;
+}
+
+bool
+BytecodeEmitter::emitFunctionBody(ParseNode* funBody)
+{
+ FunctionBox* funbox = sc->asFunctionBox();
+
+ if (!emitTree(funBody))
+ return false;
+
+ if (funbox->isGenerator()) {
+ // If we fall off the end of a generator, do a final yield.
+ if (funbox->isStarGenerator() && !emitPrepareIteratorResult())
+ return false;
+
+ if (!emit1(JSOP_UNDEFINED))
+ return false;
+
+ if (sc->asFunctionBox()->isStarGenerator() && !emitFinishIteratorResult(true))
+ return false;
+
+ if (!emit1(JSOP_SETRVAL))
+ return false;
+
+ NameLocation loc = *locationOfNameBoundInFunctionScope(cx->names().dotGenerator);
+ if (!emitGetNameAtLocation(cx->names().dotGenerator, loc))
+ return false;
+
+ // No need to check for finally blocks, etc as in EmitReturn.
+ if (!emitYieldOp(JSOP_FINALYIELDRVAL))
+ 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 (hasTryFinally) {
+ if (!emit1(JSOP_UNDEFINED))
+ return false;
+ if (!emit1(JSOP_SETRVAL))
+ return false;
+ }
+ }
+
+ if (funbox->isDerivedClassConstructor()) {
+ if (!emitCheckDerivedClassConstructorReturn())
+ return false;
+ }
+
+ return true;
+}
+
+bool
+BytecodeEmitter::emitLexicalInitialization(ParseNode* pn)
+{
+ // The caller has pushed the RHS to the top of the stack. Assert that the
+ // name is lexical and no BIND[G]NAME ops were emitted.
+ auto assertLexical = [](BytecodeEmitter*, const NameLocation& loc, bool emittedBindOp) {
+ MOZ_ASSERT(loc.isLexical());
+ MOZ_ASSERT(!emittedBindOp);
+ return true;
+ };
+ return emitInitializeName(pn, assertLexical);
+}
+
+// This follows ES6 14.5.14 (ClassDefinitionEvaluation) and ES6 14.5.15
+// (BindingClassDeclarationEvaluation).
+bool
+BytecodeEmitter::emitClass(ParseNode* pn)
+{
+ ClassNode& classNode = pn->as<ClassNode>();
+
+ ClassNames* names = classNode.names();
+
+ ParseNode* heritageExpression = classNode.heritage();
+
+ ParseNode* classMethods = classNode.methodList();
+ ParseNode* constructor = nullptr;
+ for (ParseNode* mn = classMethods->pn_head; mn; mn = mn->pn_next) {
+ ClassMethod& method = mn->as<ClassMethod>();
+ ParseNode& methodName = method.name();
+ if (!method.isStatic() &&
+ (methodName.isKind(PNK_OBJECT_PROPERTY_NAME) || methodName.isKind(PNK_STRING)) &&
+ methodName.pn_atom == cx->names().constructor)
+ {
+ constructor = &method.method();
+ break;
+ }
+ }
+
+ bool savedStrictness = sc->setLocalStrictMode(true);
+
+ Maybe<TDZCheckCache> tdzCache;
+ Maybe<EmitterScope> emitterScope;
+ if (names) {
+ tdzCache.emplace(this);
+ emitterScope.emplace(this);
+ if (!emitterScope->enterLexical(this, ScopeKind::Lexical, classNode.scopeBindings()))
+ return false;
+ }
+
+ // This is kind of silly. In order to the get the home object defined on
+ // the constructor, we have to make it second, but we want the prototype
+ // on top for EmitPropertyList, because we expect static properties to be
+ // rarer. The result is a few more swaps than we would like. Such is life.
+ if (heritageExpression) {
+ if (!emitTree(heritageExpression))
+ return false;
+ if (!emit1(JSOP_CLASSHERITAGE))
+ return false;
+ if (!emit1(JSOP_OBJWITHPROTO))
+ return false;
+
+ // JSOP_CLASSHERITAGE leaves both protos on the stack. After
+ // creating the prototype, swap it to the bottom to make the
+ // constructor.
+ if (!emit1(JSOP_SWAP))
+ return false;
+ } else {
+ if (!emitNewInit(JSProto_Object))
+ return false;
+ }
+
+ if (constructor) {
+ if (!emitFunction(constructor, !!heritageExpression))
+ return false;
+ if (constructor->pn_funbox->needsHomeObject()) {
+ if (!emit2(JSOP_INITHOMEOBJECT, 0))
+ return false;
+ }
+ } else {
+ JSAtom *name = names ? names->innerBinding()->pn_atom : cx->names().empty;
+ if (heritageExpression) {
+ if (!emitAtomOp(name, JSOP_DERIVEDCONSTRUCTOR))
+ return false;
+ } else {
+ if (!emitAtomOp(name, JSOP_CLASSCONSTRUCTOR))
+ return false;
+ }
+ }
+
+ if (!emit1(JSOP_SWAP))
+ return false;
+
+ if (!emit1(JSOP_DUP2))
+ return false;
+ if (!emitAtomOp(cx->names().prototype, JSOP_INITLOCKEDPROP))
+ return false;
+ if (!emitAtomOp(cx->names().constructor, JSOP_INITHIDDENPROP))
+ return false;
+
+ RootedPlainObject obj(cx);
+ if (!emitPropertyList(classMethods, &obj, ClassBody))
+ return false;
+
+ if (!emit1(JSOP_POP))
+ return false;
+
+ if (names) {
+ ParseNode* innerName = names->innerBinding();
+ if (!emitLexicalInitialization(innerName))
+ return false;
+
+ // Pop the inner scope.
+ if (!emitterScope->leave(this))
+ return false;
+ emitterScope.reset();
+
+ ParseNode* outerName = names->outerBinding();
+ if (outerName) {
+ if (!emitLexicalInitialization(outerName))
+ return false;
+ // Only class statements make outer bindings, and they do not leave
+ // themselves on the stack.
+ if (!emit1(JSOP_POP))
+ return false;
+ }
+ }
+
+ MOZ_ALWAYS_TRUE(sc->setLocalStrictMode(savedStrictness));
+
+ return true;
+}
+
+bool
+BytecodeEmitter::emitTree(ParseNode* pn, EmitLineNumberNote emitLineNote)
+{
+ JS_CHECK_RECURSION(cx, return false);
+
+ EmitLevelManager elm(this);
+
+ /* 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 PNK_FUNCTION:
+ if (!emitFunction(pn))
+ return false;
+ break;
+
+ case PNK_PARAMSBODY:
+ if (!emitFunctionFormalParametersAndBody(pn))
+ return false;
+ break;
+
+ case PNK_IF:
+ if (!emitIf(pn))
+ return false;
+ break;
+
+ case PNK_SWITCH:
+ if (!emitSwitch(pn))
+ return false;
+ break;
+
+ case PNK_WHILE:
+ if (!emitWhile(pn))
+ return false;
+ break;
+
+ case PNK_DOWHILE:
+ if (!emitDo(pn))
+ return false;
+ break;
+
+ case PNK_FOR:
+ if (!emitFor(pn))
+ return false;
+ break;
+
+ case PNK_COMPREHENSIONFOR:
+ if (!emitComprehensionFor(pn))
+ return false;
+ break;
+
+ case PNK_BREAK:
+ if (!emitBreak(pn->as<BreakStatement>().label()))
+ return false;
+ break;
+
+ case PNK_CONTINUE:
+ if (!emitContinue(pn->as<ContinueStatement>().label()))
+ return false;
+ break;
+
+ case PNK_WITH:
+ if (!emitWith(pn))
+ return false;
+ break;
+
+ case PNK_TRY:
+ if (!emitTry(pn))
+ return false;
+ break;
+
+ case PNK_CATCH:
+ if (!emitCatch(pn))
+ return false;
+ break;
+
+ case PNK_VAR:
+ if (!emitDeclarationList(pn))
+ return false;
+ break;
+
+ case PNK_RETURN:
+ if (!emitReturn(pn))
+ return false;
+ break;
+
+ case PNK_YIELD_STAR:
+ if (!emitYieldStar(pn->pn_left, pn->pn_right))
+ return false;
+ break;
+
+ case PNK_GENERATOR:
+ if (!emit1(JSOP_GENERATOR))
+ return false;
+ break;
+
+ case PNK_YIELD:
+ case PNK_AWAIT:
+ if (!emitYield(pn))
+ return false;
+ break;
+
+ case PNK_STATEMENTLIST:
+ if (!emitStatementList(pn))
+ return false;
+ break;
+
+ case PNK_SEMI:
+ if (!emitStatement(pn))
+ return false;
+ break;
+
+ case PNK_LABEL:
+ if (!emitLabeledStatement(&pn->as<LabeledStatement>()))
+ return false;
+ break;
+
+ case PNK_COMMA:
+ if (!emitSequenceExpr(pn))
+ return false;
+ break;
+
+ case PNK_ASSIGN:
+ case PNK_ADDASSIGN:
+ case PNK_SUBASSIGN:
+ case PNK_BITORASSIGN:
+ case PNK_BITXORASSIGN:
+ case PNK_BITANDASSIGN:
+ case PNK_LSHASSIGN:
+ case PNK_RSHASSIGN:
+ case PNK_URSHASSIGN:
+ case PNK_MULASSIGN:
+ case PNK_DIVASSIGN:
+ case PNK_MODASSIGN:
+ case PNK_POWASSIGN:
+ if (!emitAssignment(pn->pn_left, pn->getOp(), pn->pn_right))
+ return false;
+ break;
+
+ case PNK_CONDITIONAL:
+ if (!emitConditionalExpression(pn->as<ConditionalExpression>()))
+ return false;
+ break;
+
+ case PNK_OR:
+ case PNK_AND:
+ if (!emitLogical(pn))
+ return false;
+ break;
+
+ case PNK_ADD:
+ case PNK_SUB:
+ case PNK_BITOR:
+ case PNK_BITXOR:
+ case PNK_BITAND:
+ case PNK_STRICTEQ:
+ case PNK_EQ:
+ case PNK_STRICTNE:
+ case PNK_NE:
+ case PNK_LT:
+ case PNK_LE:
+ case PNK_GT:
+ case PNK_GE:
+ case PNK_IN:
+ case PNK_INSTANCEOF:
+ case PNK_LSH:
+ case PNK_RSH:
+ case PNK_URSH:
+ case PNK_STAR:
+ case PNK_DIV:
+ case PNK_MOD:
+ if (!emitLeftAssociative(pn))
+ return false;
+ break;
+
+ case PNK_POW:
+ if (!emitRightAssociative(pn))
+ return false;
+ break;
+
+ case PNK_TYPEOFNAME:
+ if (!emitTypeof(pn, JSOP_TYPEOF))
+ return false;
+ break;
+
+ case PNK_TYPEOFEXPR:
+ if (!emitTypeof(pn, JSOP_TYPEOFEXPR))
+ return false;
+ break;
+
+ case PNK_THROW:
+ case PNK_VOID:
+ case PNK_NOT:
+ case PNK_BITNOT:
+ case PNK_POS:
+ case PNK_NEG:
+ if (!emitUnary(pn))
+ return false;
+ break;
+
+ case PNK_PREINCREMENT:
+ case PNK_PREDECREMENT:
+ case PNK_POSTINCREMENT:
+ case PNK_POSTDECREMENT:
+ if (!emitIncOrDec(pn))
+ return false;
+ break;
+
+ case PNK_DELETENAME:
+ if (!emitDeleteName(pn))
+ return false;
+ break;
+
+ case PNK_DELETEPROP:
+ if (!emitDeleteProperty(pn))
+ return false;
+ break;
+
+ case PNK_DELETEELEM:
+ if (!emitDeleteElement(pn))
+ return false;
+ break;
+
+ case PNK_DELETEEXPR:
+ if (!emitDeleteExpression(pn))
+ return false;
+ break;
+
+ case PNK_DOT:
+ if (pn->as<PropertyAccess>().isSuper()) {
+ if (!emitSuperPropOp(pn, JSOP_GETPROP_SUPER))
+ return false;
+ } else {
+ if (!emitPropOp(pn, JSOP_GETPROP))
+ return false;
+ }
+ break;
+
+ case PNK_ELEM:
+ if (pn->as<PropertyByValue>().isSuper()) {
+ if (!emitSuperElemOp(pn, JSOP_GETELEM_SUPER))
+ return false;
+ } else {
+ if (!emitElemOp(pn, JSOP_GETELEM))
+ return false;
+ }
+ break;
+
+ case PNK_NEW:
+ case PNK_TAGGED_TEMPLATE:
+ case PNK_CALL:
+ case PNK_GENEXP:
+ case PNK_SUPERCALL:
+ if (!emitCallOrNew(pn))
+ return false;
+ break;
+
+ case PNK_LEXICALSCOPE:
+ if (!emitLexicalScope(pn))
+ return false;
+ break;
+
+ case PNK_CONST:
+ case PNK_LET:
+ if (!emitDeclarationList(pn))
+ return false;
+ break;
+
+ case PNK_IMPORT:
+ MOZ_ASSERT(sc->isModuleContext());
+ break;
+
+ case PNK_EXPORT:
+ MOZ_ASSERT(sc->isModuleContext());
+ if (pn->pn_kid->getKind() != PNK_EXPORT_SPEC_LIST) {
+ if (!emitTree(pn->pn_kid))
+ return false;
+ }
+ break;
+
+ case PNK_EXPORT_DEFAULT:
+ MOZ_ASSERT(sc->isModuleContext());
+ if (!emitTree(pn->pn_kid))
+ return false;
+ if (pn->pn_right) {
+ if (!emitLexicalInitialization(pn->pn_right))
+ return false;
+ if (!emit1(JSOP_POP))
+ return false;
+ }
+ break;
+
+ case PNK_EXPORT_FROM:
+ MOZ_ASSERT(sc->isModuleContext());
+ break;
+
+ case PNK_ARRAYPUSH:
+ /*
+ * The array object's stack index is in arrayCompDepth. See below
+ * under the array initialiser code generator for array comprehension
+ * special casing.
+ */
+ if (!emitTree(pn->pn_kid))
+ return false;
+ if (!emitDupAt(this->stackDepth - 1 - arrayCompDepth))
+ return false;
+ if (!emit1(JSOP_ARRAYPUSH))
+ return false;
+ break;
+
+ case PNK_CALLSITEOBJ:
+ if (!emitCallSiteObject(pn))
+ return false;
+ break;
+
+ case PNK_ARRAY:
+ if (!emitArrayLiteral(pn))
+ return false;
+ break;
+
+ case PNK_ARRAYCOMP:
+ if (!emitArrayComp(pn))
+ return false;
+ break;
+
+ case PNK_OBJECT:
+ if (!emitObject(pn))
+ return false;
+ break;
+
+ case PNK_NAME:
+ if (!emitGetName(pn))
+ return false;
+ break;
+
+ case PNK_TEMPLATE_STRING_LIST:
+ if (!emitTemplateString(pn))
+ return false;
+ break;
+
+ case PNK_TEMPLATE_STRING:
+ case PNK_STRING:
+ if (!emitAtomOp(pn, JSOP_STRING))
+ return false;
+ break;
+
+ case PNK_NUMBER:
+ if (!emitNumberOp(pn->pn_dval))
+ return false;
+ break;
+
+ case PNK_REGEXP:
+ if (!emitRegExp(objectList.add(pn->as<RegExpLiteral>().objbox())))
+ return false;
+ break;
+
+ case PNK_TRUE:
+ case PNK_FALSE:
+ case PNK_NULL:
+ if (!emit1(pn->getOp()))
+ return false;
+ break;
+
+ case PNK_THIS:
+ if (!emitThisLiteral(pn))
+ return false;
+ break;
+
+ case PNK_DEBUGGER:
+ if (!updateSourceCoordNotes(pn->pn_pos.begin))
+ return false;
+ if (!emit1(JSOP_DEBUGGER))
+ return false;
+ break;
+
+ case PNK_NOP:
+ MOZ_ASSERT(pn->getArity() == PN_NULLARY);
+ break;
+
+ case PNK_CLASS:
+ if (!emitClass(pn))
+ return false;
+ break;
+
+ case PNK_NEWTARGET:
+ if (!emit1(JSOP_NEWTARGET))
+ return false;
+ break;
+
+ case PNK_SETTHIS:
+ if (!emitSetThis(pn))
+ return false;
+ break;
+
+ case PNK_POSHOLDER:
+ MOZ_FALLTHROUGH_ASSERT("Should never try to emit PNK_POSHOLDER");
+
+ default:
+ MOZ_ASSERT(0);
+ }
+
+ /* bce->emitLevel == 1 means we're last on the stack, so finish up. */
+ if (emitLevel == 1) {
+ if (!updateSourceCoordNotes(pn->pn_pos.end))
+ return false;
+ }
+ return true;
+}
+
+bool
+BytecodeEmitter::emitConditionallyExecutedTree(ParseNode* pn)
+{
+ // Code that may be conditionally executed always need their own TDZ
+ // cache.
+ TDZCheckCache tdzCache(this);
+ return emitTree(pn);
+}
+
+static bool
+AllocSrcNote(ExclusiveContext* cx, SrcNotesVector& notes, unsigned* index)
+{
+ // Start it off moderately large to avoid repeated resizings early on.
+ // ~99% of cases fit within 256 bytes.
+ if (notes.capacity() == 0 && !notes.reserve(256))
+ return false;
+
+ if (!notes.growBy(1)) {
+ ReportOutOfMemory(cx);
+ return false;
+ }
+
+ *index = notes.length() - 1;
+ return true;
+}
+
+bool
+BytecodeEmitter::newSrcNote(SrcNoteType type, unsigned* indexp)
+{
+ SrcNotesVector& notes = this->notes();
+ unsigned index;
+ if (!AllocSrcNote(cx, notes, &index))
+ return false;
+
+ /*
+ * 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.
+ */
+ ptrdiff_t offset = this->offset();
+ ptrdiff_t delta = offset - lastNoteOffset();
+ current->lastNoteOffset = offset;
+ if (delta >= SN_DELTA_LIMIT) {
+ do {
+ ptrdiff_t xdelta = Min(delta, SN_XDELTA_MASK);
+ SN_MAKE_XDELTA(&notes[index], xdelta);
+ delta -= xdelta;
+ if (!AllocSrcNote(cx, notes, &index))
+ return false;
+ } while (delta >= SN_DELTA_LIMIT);
+ }
+
+ /*
+ * Initialize type and delta, then allocate the minimum number of notes
+ * needed for type's arity. Usually, we won't need more, but if an offset
+ * does take two bytes, setSrcNoteOffset will grow notes.
+ */
+ SN_MAKE_NOTE(&notes[index], type, delta);
+ for (int n = (int)js_SrcNoteSpec[type].arity; n > 0; n--) {
+ if (!newSrcNote(SRC_NULL))
+ 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 (!setSrcNoteOffset(index, 0, offset))
+ return false;
+ if (indexp)
+ *indexp = index;
+ return true;
+}
+
+bool
+BytecodeEmitter::newSrcNote3(SrcNoteType type, ptrdiff_t offset1, ptrdiff_t offset2,
+ unsigned* indexp)
+{
+ unsigned index;
+ if (!newSrcNote(type, &index))
+ return false;
+ if (!setSrcNoteOffset(index, 0, offset1))
+ return false;
+ if (!setSrcNoteOffset(index, 1, offset2))
+ return false;
+ if (indexp)
+ *indexp = index;
+ return true;
+}
+
+bool
+BytecodeEmitter::addToSrcNoteDelta(jssrcnote* sn, ptrdiff_t delta)
+{
+ /*
+ * Called only from finishTakingSrcNotes to add to main script note
+ * deltas, and only by a small positive amount.
+ */
+ MOZ_ASSERT(current == &main);
+ MOZ_ASSERT((unsigned) delta < (unsigned) SN_XDELTA_LIMIT);
+
+ ptrdiff_t base = SN_DELTA(sn);
+ ptrdiff_t limit = SN_IS_XDELTA(sn) ? SN_XDELTA_LIMIT : SN_DELTA_LIMIT;
+ ptrdiff_t newdelta = base + delta;
+ if (newdelta < limit) {
+ SN_SET_DELTA(sn, newdelta);
+ } else {
+ jssrcnote xdelta;
+ SN_MAKE_XDELTA(&xdelta, delta);
+ if (!main.notes.insert(sn, xdelta))
+ return false;
+ }
+ return true;
+}
+
+bool
+BytecodeEmitter::setSrcNoteOffset(unsigned index, unsigned which, ptrdiff_t offset)
+{
+ if (!SN_REPRESENTABLE_OFFSET(offset)) {
+ parser->tokenStream.reportError(JSMSG_NEED_DIET, js_script_str);
+ return false;
+ }
+
+ SrcNotesVector& notes = this->notes();
+
+ /* Find the offset numbered which (i.e., skip exactly which offsets). */
+ jssrcnote* sn = &notes[index];
+ MOZ_ASSERT(SN_TYPE(sn) != SRC_XDELTA);
+ MOZ_ASSERT((int) which < js_SrcNoteSpec[SN_TYPE(sn)].arity);
+ for (sn++; which; sn++, which--) {
+ if (*sn & SN_4BYTE_OFFSET_FLAG)
+ sn += 3;
+ }
+
+ /*
+ * See if the new offset requires four bytes either by being too big or if
+ * the offset has already been inflated (in which case, we need to stay big
+ * to not break the srcnote encoding if this isn't the last srcnote).
+ */
+ if (offset > (ptrdiff_t)SN_4BYTE_OFFSET_MASK || (*sn & SN_4BYTE_OFFSET_FLAG)) {
+ /* Maybe this offset was already set to a four-byte value. */
+ if (!(*sn & SN_4BYTE_OFFSET_FLAG)) {
+ /* Insert three dummy bytes that will be overwritten shortly. */
+ jssrcnote dummy = 0;
+ if (!(sn = notes.insert(sn, dummy)) ||
+ !(sn = notes.insert(sn, dummy)) ||
+ !(sn = notes.insert(sn, dummy)))
+ {
+ ReportOutOfMemory(cx);
+ return false;
+ }
+ }
+ *sn++ = (jssrcnote)(SN_4BYTE_OFFSET_FLAG | (offset >> 24));
+ *sn++ = (jssrcnote)(offset >> 16);
+ *sn++ = (jssrcnote)(offset >> 8);
+ }
+ *sn = (jssrcnote)offset;
+ return true;
+}
+
+bool
+BytecodeEmitter::finishTakingSrcNotes(uint32_t* out)
+{
+ MOZ_ASSERT(current == &main);
+
+ unsigned prologueCount = prologue.notes.length();
+ if (prologueCount && prologue.currentLine != firstLine) {
+ switchToPrologue();
+ if (!newSrcNote2(SRC_SETLINE, ptrdiff_t(firstLine)))
+ return false;
+ switchToMain();
+ } else {
+ /*
+ * Either no prologue srcnotes, or no line number change over prologue.
+ * We don't need a SRC_SETLINE, but we may need to adjust the offset
+ * of the first main note, by adding to its delta and possibly even
+ * prepending SRC_XDELTA notes to it to account for prologue bytecodes
+ * that came at and after the last annotated bytecode.
+ */
+ ptrdiff_t offset = prologueOffset() - prologue.lastNoteOffset;
+ MOZ_ASSERT(offset >= 0);
+ if (offset > 0 && main.notes.length() != 0) {
+ /* NB: Use as much of the first main note's delta as we can. */
+ jssrcnote* sn = main.notes.begin();
+ ptrdiff_t delta = SN_IS_XDELTA(sn)
+ ? SN_XDELTA_MASK - (*sn & SN_XDELTA_MASK)
+ : SN_DELTA_MASK - (*sn & SN_DELTA_MASK);
+ if (offset < delta)
+ delta = offset;
+ for (;;) {
+ if (!addToSrcNoteDelta(sn, delta))
+ return false;
+ offset -= delta;
+ if (offset == 0)
+ break;
+ delta = Min(offset, SN_XDELTA_MASK);
+ sn = main.notes.begin();
+ }
+ }
+ }
+
+ // The prologue count might have changed, so we can't reuse prologueCount.
+ // The + 1 is to account for the final SN_MAKE_TERMINATOR that is appended
+ // when the notes are copied to their final destination by CopySrcNotes.
+ *out = prologue.notes.length() + main.notes.length() + 1;
+ return true;
+}
+
+void
+BytecodeEmitter::copySrcNotes(jssrcnote* destination, uint32_t nsrcnotes)
+{
+ unsigned prologueCount = prologue.notes.length();
+ unsigned mainCount = main.notes.length();
+ unsigned totalCount = prologueCount + mainCount;
+ MOZ_ASSERT(totalCount == nsrcnotes - 1);
+ if (prologueCount)
+ PodCopy(destination, prologue.notes.begin(), prologueCount);
+ PodCopy(destination + prologueCount, main.notes.begin(), mainCount);
+ SN_MAKE_TERMINATOR(&destination[totalCount]);
+}
+
+void
+CGConstList::finish(ConstArray* array)
+{
+ MOZ_ASSERT(length() == array->length);
+
+ for (unsigned i = 0; i < length(); i++)
+ array->vector[i] = list[i];
+}
+
+bool
+CGObjectList::isAdded(ObjectBox* objbox)
+{
+ // An objbox added to CGObjectList as non-first element has non-null
+ // emitLink member. The first element has null emitLink.
+ // Check for firstbox to cover the first element.
+ return objbox->emitLink || objbox == firstbox;
+}
+
+/*
+ * Find the index of the given object for code generator.
+ *
+ * Since the emitter refers to each parsed object only once, for the index we
+ * use the number of already indexed objects. We also add the object to a list
+ * to convert the list to a fixed-size array when we complete code generation,
+ * see js::CGObjectList::finish below.
+ */
+unsigned
+CGObjectList::add(ObjectBox* objbox)
+{
+ if (isAdded(objbox))
+ return indexOf(objbox->object);
+
+ objbox->emitLink = lastbox;
+ lastbox = objbox;
+
+ // See the comment in CGObjectList::isAdded.
+ if (!firstbox)
+ firstbox = objbox;
+ return length++;
+}
+
+unsigned
+CGObjectList::indexOf(JSObject* obj)
+{
+ MOZ_ASSERT(length > 0);
+ unsigned index = length - 1;
+ for (ObjectBox* box = lastbox; box->object != obj; box = box->emitLink)
+ index--;
+ return index;
+}
+
+void
+CGObjectList::finish(ObjectArray* array)
+{
+ MOZ_ASSERT(length <= INDEX_LIMIT);
+ MOZ_ASSERT(length == array->length);
+
+ js::GCPtrObject* cursor = array->vector + array->length;
+ ObjectBox* objbox = lastbox;
+ do {
+ --cursor;
+ MOZ_ASSERT(!*cursor);
+ MOZ_ASSERT(objbox->object->isTenured());
+ *cursor = objbox->object;
+
+ ObjectBox* tmp = objbox->emitLink;
+ // Clear emitLink for CGObjectList::isAdded.
+ objbox->emitLink = nullptr;
+ objbox = tmp;
+ } while (objbox != nullptr);
+ MOZ_ASSERT(cursor == array->vector);
+}
+
+ObjectBox*
+CGObjectList::find(uint32_t index)
+{
+ MOZ_ASSERT(index < length);
+ ObjectBox* box = lastbox;
+ for (unsigned n = length - 1; n > index; n--)
+ box = box->emitLink;
+ return box;
+}
+
+void
+CGScopeList::finish(ScopeArray* array)
+{
+ MOZ_ASSERT(length() <= INDEX_LIMIT);
+ MOZ_ASSERT(length() == array->length);
+ for (uint32_t i = 0; i < length(); i++)
+ array->vector[i].init(vector[i]);
+}
+
+bool
+CGTryNoteList::append(JSTryNoteKind kind, uint32_t stackDepth, size_t start, size_t end)
+{
+ MOZ_ASSERT(start <= end);
+ MOZ_ASSERT(size_t(uint32_t(start)) == start);
+ MOZ_ASSERT(size_t(uint32_t(end)) == end);
+
+ JSTryNote note;
+ note.kind = kind;
+ note.stackDepth = stackDepth;
+ note.start = uint32_t(start);
+ note.length = uint32_t(end - start);
+
+ return list.append(note);
+}
+
+void
+CGTryNoteList::finish(TryNoteArray* array)
+{
+ MOZ_ASSERT(length() == array->length);
+
+ for (unsigned i = 0; i < length(); i++)
+ array->vector[i] = list[i];
+}
+
+bool
+CGScopeNoteList::append(uint32_t scopeIndex, uint32_t offset, bool inPrologue,
+ uint32_t parent)
+{
+ CGScopeNote note;
+ mozilla::PodZero(&note);
+
+ note.index = scopeIndex;
+ note.start = offset;
+ note.parent = parent;
+ note.startInPrologue = inPrologue;
+
+ return list.append(note);
+}
+
+void
+CGScopeNoteList::recordEnd(uint32_t index, uint32_t offset, bool inPrologue)
+{
+ MOZ_ASSERT(index < length());
+ MOZ_ASSERT(list[index].length == 0);
+ list[index].end = offset;
+ list[index].endInPrologue = inPrologue;
+}
+
+void
+CGScopeNoteList::finish(ScopeNoteArray* array, uint32_t prologueLength)
+{
+ MOZ_ASSERT(length() == array->length);
+
+ for (unsigned i = 0; i < length(); i++) {
+ if (!list[i].startInPrologue)
+ list[i].start += prologueLength;
+ if (!list[i].endInPrologue && list[i].end != UINT32_MAX)
+ list[i].end += prologueLength;
+ MOZ_ASSERT(list[i].end >= list[i].start);
+ list[i].length = list[i].end - list[i].start;
+ array->vector[i] = list[i];
+ }
+}
+
+void
+CGYieldOffsetList::finish(YieldOffsetArray& array, uint32_t prologueLength)
+{
+ MOZ_ASSERT(length() == array.length());
+
+ for (unsigned i = 0; i < length(); i++)
+ array[i] = prologueLength + list[i];
+}
+
+/*
+ * We should try to get rid of offsetBias (always 0 or 1, where 1 is
+ * JSOP_{NOP,POP}_LENGTH), which is used only by SRC_FOR.
+ */
+const JSSrcNoteSpec js_SrcNoteSpec[] = {
+#define DEFINE_SRC_NOTE_SPEC(sym, name, arity) { name, arity },
+ FOR_EACH_SRC_NOTE_TYPE(DEFINE_SRC_NOTE_SPEC)
+#undef DEFINE_SRC_NOTE_SPEC
+};
+
+static int
+SrcNoteArity(jssrcnote* sn)
+{
+ MOZ_ASSERT(SN_TYPE(sn) < SRC_LAST);
+ return js_SrcNoteSpec[SN_TYPE(sn)].arity;
+}
+
+JS_FRIEND_API(unsigned)
+js::SrcNoteLength(jssrcnote* sn)
+{
+ unsigned arity;
+ jssrcnote* base;
+
+ arity = SrcNoteArity(sn);
+ for (base = sn++; arity; sn++, arity--) {
+ if (*sn & SN_4BYTE_OFFSET_FLAG)
+ sn += 3;
+ }
+ return sn - base;
+}
+
+JS_FRIEND_API(ptrdiff_t)
+js::GetSrcNoteOffset(jssrcnote* sn, unsigned which)
+{
+ /* Find the offset numbered which (i.e., skip exactly which offsets). */
+ MOZ_ASSERT(SN_TYPE(sn) != SRC_XDELTA);
+ MOZ_ASSERT((int) which < SrcNoteArity(sn));
+ for (sn++; which; sn++, which--) {
+ if (*sn & SN_4BYTE_OFFSET_FLAG)
+ sn += 3;
+ }
+ if (*sn & SN_4BYTE_OFFSET_FLAG) {
+ return (ptrdiff_t)(((uint32_t)(sn[0] & SN_4BYTE_OFFSET_MASK) << 24)
+ | (sn[1] << 16)
+ | (sn[2] << 8)
+ | sn[3]);
+ }
+ return (ptrdiff_t)*sn;
+}
diff --git a/js/src/frontend/BytecodeEmitter.h b/js/src/frontend/BytecodeEmitter.h
new file mode 100644
index 0000000000..1bb4191ee1
--- /dev/null
+++ b/js/src/frontend/BytecodeEmitter.h
@@ -0,0 +1,763 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sts=4 et sw=4 tw=99:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "jscntxt.h"
+#include "jsopcode.h"
+#include "jsscript.h"
+
+#include "ds/InlineTable.h"
+#include "frontend/Parser.h"
+#include "frontend/SharedContext.h"
+#include "frontend/SourceNotes.h"
+#include "vm/Interpreter.h"
+
+namespace js {
+namespace frontend {
+
+class FullParseHandler;
+class ObjectBox;
+class ParseNode;
+template <typename ParseHandler> class Parser;
+class SharedContext;
+class TokenStream;
+
+class CGConstList {
+ Vector<Value> list;
+ public:
+ explicit CGConstList(ExclusiveContext* cx) : list(cx) {}
+ MOZ_MUST_USE bool append(const Value& v) {
+ MOZ_ASSERT_IF(v.isString(), v.toString()->isAtom());
+ return list.append(v);
+ }
+ size_t length() const { return list.length(); }
+ void finish(ConstArray* array);
+};
+
+struct CGObjectList {
+ uint32_t length; /* number of emitted so far objects */
+ ObjectBox* firstbox; /* first emitted object */
+ ObjectBox* lastbox; /* last emitted object */
+
+ CGObjectList() : length(0), firstbox(nullptr), lastbox(nullptr) {}
+
+ bool isAdded(ObjectBox* objbox);
+ unsigned add(ObjectBox* objbox);
+ unsigned indexOf(JSObject* obj);
+ void finish(ObjectArray* array);
+ ObjectBox* find(uint32_t index);
+};
+
+struct MOZ_STACK_CLASS CGScopeList {
+ Rooted<GCVector<Scope*>> vector;
+
+ explicit CGScopeList(ExclusiveContext* cx)
+ : vector(cx, GCVector<Scope*>(cx))
+ { }
+
+ bool append(Scope* scope) { return vector.append(scope); }
+ uint32_t length() const { return vector.length(); }
+ void finish(ScopeArray* array);
+};
+
+struct CGTryNoteList {
+ Vector<JSTryNote> list;
+ explicit CGTryNoteList(ExclusiveContext* cx) : list(cx) {}
+
+ MOZ_MUST_USE bool append(JSTryNoteKind kind, uint32_t stackDepth, size_t start, size_t end);
+ size_t length() const { return list.length(); }
+ void finish(TryNoteArray* array);
+};
+
+struct CGScopeNote : public ScopeNote
+{
+ // The end offset. Used to compute the length; may need adjusting first if
+ // in the prologue.
+ uint32_t end;
+
+ // Is the start offset in the prologue?
+ bool startInPrologue;
+
+ // Is the end offset in the prologue?
+ bool endInPrologue;
+};
+
+struct CGScopeNoteList {
+ Vector<CGScopeNote> list;
+ explicit CGScopeNoteList(ExclusiveContext* cx) : list(cx) {}
+
+ MOZ_MUST_USE bool append(uint32_t scopeIndex, uint32_t offset, bool inPrologue,
+ uint32_t parent);
+ void recordEnd(uint32_t index, uint32_t offset, bool inPrologue);
+ size_t length() const { return list.length(); }
+ void finish(ScopeNoteArray* array, uint32_t prologueLength);
+};
+
+struct CGYieldOffsetList {
+ Vector<uint32_t> list;
+ explicit CGYieldOffsetList(ExclusiveContext* cx) : list(cx) {}
+
+ MOZ_MUST_USE bool append(uint32_t offset) { return list.append(offset); }
+ size_t length() const { return list.length(); }
+ void finish(YieldOffsetArray& array, uint32_t prologueLength);
+};
+
+// Use zero inline elements because these go on the stack and affect how many
+// nested functions are possible.
+typedef Vector<jsbytecode, 0> BytecodeVector;
+typedef Vector<jssrcnote, 0> SrcNotesVector;
+
+// Linked list of jump instructions that need to be patched. The linked list is
+// stored in the bytes of the incomplete bytecode that will be patched, so no
+// extra memory is needed, and patching the instructions destroys the list.
+//
+// Example:
+//
+// JumpList brList;
+// if (!emitJump(JSOP_IFEQ, &brList))
+// return false;
+// ...
+// JumpTarget label;
+// if (!emitJumpTarget(&label))
+// return false;
+// ...
+// if (!emitJump(JSOP_GOTO, &brList))
+// return false;
+// ...
+// patchJumpsToTarget(brList, label);
+//
+// +-> -1
+// |
+// |
+// ifeq .. <+ + +-+ ifeq ..
+// .. | | ..
+// label: | +-> label:
+// jumptarget | | jumptarget
+// .. | | ..
+// goto .. <+ + +-+ goto .. <+
+// | |
+// | |
+// + +
+// brList brList
+//
+// | ^
+// +------- patchJumpsToTarget -------+
+//
+
+// Offset of a jump target instruction, used for patching jump instructions.
+struct JumpTarget {
+ ptrdiff_t offset;
+};
+
+struct JumpList {
+ // -1 is used to mark the end of jump lists.
+ JumpList() : offset(-1) {}
+ ptrdiff_t offset;
+
+ // Add a jump instruction to the list.
+ void push(jsbytecode* code, ptrdiff_t jumpOffset);
+
+ // Patch all jump instructions in this list to jump to `target`. This
+ // clobbers the list.
+ void patchAll(jsbytecode* code, JumpTarget target);
+};
+
+struct MOZ_STACK_CLASS BytecodeEmitter
+{
+ class TDZCheckCache;
+ class NestableControl;
+ class EmitterScope;
+
+ SharedContext* const sc; /* context shared between parsing and bytecode generation */
+
+ ExclusiveContext* const cx;
+
+ BytecodeEmitter* const parent; /* enclosing function or global context */
+
+ Rooted<JSScript*> script; /* the JSScript we're ultimately producing */
+
+ Rooted<LazyScript*> lazyScript; /* the lazy script if mode is LazyFunction,
+ nullptr otherwise. */
+
+ struct EmitSection {
+ BytecodeVector code; /* bytecode */
+ SrcNotesVector notes; /* source notes, see below */
+ ptrdiff_t lastNoteOffset; /* code offset for last source note */
+ uint32_t currentLine; /* line number for tree-based srcnote gen */
+ uint32_t lastColumn; /* zero-based column index on currentLine of
+ last SRC_COLSPAN-annotated opcode */
+ JumpTarget lastTarget; // Last jump target emitted.
+
+ EmitSection(ExclusiveContext* cx, uint32_t lineNum)
+ : code(cx), notes(cx), lastNoteOffset(0), currentLine(lineNum), lastColumn(0),
+ lastTarget{ -1 - ptrdiff_t(JSOP_JUMPTARGET_LENGTH) }
+ {}
+ };
+ EmitSection prologue, main, *current;
+
+ Parser<FullParseHandler>* const parser;
+
+ PooledMapPtr<AtomIndexMap> atomIndices; /* literals indexed for mapping */
+ unsigned firstLine; /* first line, for JSScript::initFromEmitter */
+
+ uint32_t maxFixedSlots; /* maximum number of fixed frame slots so far */
+ uint32_t maxStackDepth; /* maximum number of expression stack slots so far */
+
+ int32_t stackDepth; /* current stack depth in script frame */
+
+ uint32_t arrayCompDepth; /* stack depth of array in comprehension */
+
+ unsigned emitLevel; /* emitTree recursion level */
+
+ uint32_t bodyScopeIndex; /* index into scopeList of the body scope */
+
+ EmitterScope* varEmitterScope;
+ NestableControl* innermostNestableControl;
+ EmitterScope* innermostEmitterScope;
+ TDZCheckCache* innermostTDZCheckCache;
+
+ CGConstList constList; /* constants to be included with the script */
+ CGObjectList objectList; /* list of emitted objects */
+ CGScopeList scopeList; /* list of emitted scopes */
+ CGTryNoteList tryNoteList; /* list of emitted try notes */
+ CGScopeNoteList scopeNoteList; /* list of emitted block scope notes */
+
+ /*
+ * For each yield op, map the yield index (stored as bytecode operand) to
+ * the offset of the next op.
+ */
+ CGYieldOffsetList yieldOffsetList;
+
+ uint16_t typesetCount; /* Number of JOF_TYPESET opcodes generated */
+
+ bool hasSingletons:1; /* script contains singleton initializer JSOP_OBJECT */
+
+ bool hasTryFinally:1; /* script contains finally block */
+
+ bool emittingRunOnceLambda:1; /* true while emitting a lambda which is only
+ expected to run once. */
+
+ bool isRunOnceLambda();
+
+ 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;
+
+ // The end location of a function body that is being emitted.
+ uint32_t functionBodyEndPos;
+ // Whether functionBodyEndPos was set.
+ bool functionBodyEndPosSet;
+
+ /*
+ * 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.
+ */
+ BytecodeEmitter(BytecodeEmitter* parent, Parser<FullParseHandler>* parser, SharedContext* sc,
+ HandleScript script, Handle<LazyScript*> lazyScript, uint32_t lineNum,
+ EmitterMode emitterMode = Normal);
+
+ // An alternate constructor that uses a TokenPos for the starting
+ // line and that sets functionBodyEndPos as well.
+ BytecodeEmitter(BytecodeEmitter* parent, Parser<FullParseHandler>* parser, SharedContext* sc,
+ HandleScript script, Handle<LazyScript*> lazyScript,
+ TokenPos bodyPosition, EmitterMode emitterMode = Normal);
+
+ MOZ_MUST_USE bool init();
+
+ template <typename Predicate /* (NestableControl*) -> bool */>
+ NestableControl* findInnermostNestableControl(Predicate predicate) const;
+
+ template <typename T>
+ T* findInnermostNestableControl() const;
+
+ template <typename T, typename Predicate /* (T*) -> bool */>
+ T* findInnermostNestableControl(Predicate predicate) const;
+
+ NameLocation lookupName(JSAtom* name);
+
+ // To implement Annex B and the formal parameter defaults scope semantics
+ // requires accessing names that would otherwise be shadowed. This method
+ // returns the access location of a name that is known to be bound in a
+ // target scope.
+ mozilla::Maybe<NameLocation> locationOfNameBoundInScope(JSAtom* name, EmitterScope* target);
+
+ // Get the location of a name known to be bound in the function scope,
+ // starting at the source scope.
+ mozilla::Maybe<NameLocation> locationOfNameBoundInFunctionScope(JSAtom* name,
+ EmitterScope* source);
+
+ mozilla::Maybe<NameLocation> locationOfNameBoundInFunctionScope(JSAtom* name) {
+ return locationOfNameBoundInFunctionScope(name, innermostEmitterScope);
+ }
+
+ void setVarEmitterScope(EmitterScope* emitterScope) {
+ MOZ_ASSERT(emitterScope);
+ MOZ_ASSERT(!varEmitterScope);
+ varEmitterScope = emitterScope;
+ }
+
+ Scope* bodyScope() const { return scopeList.vector[bodyScopeIndex]; }
+ Scope* outermostScope() const { return scopeList.vector[0]; }
+ Scope* innermostScope() const;
+
+ MOZ_ALWAYS_INLINE
+ MOZ_MUST_USE bool makeAtomIndex(JSAtom* atom, uint32_t* indexp) {
+ MOZ_ASSERT(atomIndices);
+ AtomIndexMap::AddPtr p = atomIndices->lookupForAdd(atom);
+ if (p) {
+ *indexp = p->value();
+ return true;
+ }
+
+ uint32_t index = atomIndices->count();
+ if (!atomIndices->add(p, atom, index))
+ return false;
+
+ *indexp = index;
+ return true;
+ }
+
+ bool isInLoop();
+ MOZ_MUST_USE bool checkSingletonContext();
+
+ // Check whether our function is in a run-once context (a toplevel
+ // run-one script or a run-once lambda).
+ MOZ_MUST_USE bool checkRunOnceContext();
+
+ bool needsImplicitThis();
+
+ MOZ_MUST_USE bool maybeSetDisplayURL();
+ MOZ_MUST_USE bool maybeSetSourceMap();
+ void tellDebuggerAboutCompiledScript(ExclusiveContext* cx);
+
+ inline TokenStream* tokenStream();
+
+ BytecodeVector& code() const { return current->code; }
+ jsbytecode* code(ptrdiff_t offset) const { return current->code.begin() + offset; }
+ ptrdiff_t offset() const { return current->code.end() - current->code.begin(); }
+ ptrdiff_t prologueOffset() const { return prologue.code.end() - prologue.code.begin(); }
+ void switchToMain() { current = &main; }
+ void switchToPrologue() { current = &prologue; }
+ bool inPrologue() const { return current == &prologue; }
+
+ SrcNotesVector& notes() const { return current->notes; }
+ ptrdiff_t lastNoteOffset() const { return current->lastNoteOffset; }
+ unsigned currentLine() const { return current->currentLine; }
+ unsigned lastColumn() const { return current->lastColumn; }
+
+ // Check if the last emitted opcode is a jump target.
+ bool lastOpcodeIsJumpTarget() const {
+ return offset() - current->lastTarget.offset == ptrdiff_t(JSOP_JUMPTARGET_LENGTH);
+ }
+
+ // JumpTarget should not be part of the emitted statement, as they can be
+ // aliased by multiple statements. If we included the jump target as part of
+ // the statement we might have issues where the enclosing statement might
+ // not contain all the opcodes of the enclosed statements.
+ ptrdiff_t lastNonJumpTargetOffset() const {
+ return lastOpcodeIsJumpTarget() ? current->lastTarget.offset : offset();
+ }
+
+ void setFunctionBodyEndPos(TokenPos pos) {
+ functionBodyEndPos = pos.end;
+ functionBodyEndPosSet = true;
+ }
+
+ bool reportError(ParseNode* pn, unsigned errorNumber, ...);
+ bool reportStrictWarning(ParseNode* pn, unsigned errorNumber, ...);
+ bool reportStrictModeError(ParseNode* pn, unsigned errorNumber, ...);
+
+ // If pn contains a useful expression, return true with *answer set to true.
+ // If pn contains a useless expression, return true with *answer set to
+ // false. Return false on error.
+ //
+ // The caller should initialize *answer to false and invoke this function on
+ // an expression statement or similar subtree to decide whether the tree
+ // could produce code that has any side effects. For an expression
+ // statement, we define useless code as code with no side effects, because
+ // the main effect, the value left on the stack after the code executes,
+ // will be discarded by a pop bytecode.
+ MOZ_MUST_USE bool checkSideEffects(ParseNode* pn, bool* answer);
+
+#ifdef DEBUG
+ MOZ_MUST_USE bool checkStrictOrSloppy(JSOp op);
+#endif
+
+ // Append a new source note of the given type (and therefore size) to the
+ // notes dynamic array, updating noteCount. Return the new note's index
+ // within the array pointed at by current->notes as outparam.
+ MOZ_MUST_USE bool newSrcNote(SrcNoteType type, unsigned* indexp = nullptr);
+ MOZ_MUST_USE bool newSrcNote2(SrcNoteType type, ptrdiff_t offset, unsigned* indexp = nullptr);
+ MOZ_MUST_USE bool newSrcNote3(SrcNoteType type, ptrdiff_t offset1, ptrdiff_t offset2,
+ unsigned* indexp = nullptr);
+
+ void copySrcNotes(jssrcnote* destination, uint32_t nsrcnotes);
+ MOZ_MUST_USE bool setSrcNoteOffset(unsigned index, unsigned which, ptrdiff_t offset);
+
+ // NB: this function can add at most one extra extended delta note.
+ MOZ_MUST_USE bool addToSrcNoteDelta(jssrcnote* sn, ptrdiff_t delta);
+
+ // Finish taking source notes in cx's notePool. If successful, the final
+ // source note count is stored in the out outparam.
+ MOZ_MUST_USE bool finishTakingSrcNotes(uint32_t* out);
+
+ // Control whether emitTree emits a line number note.
+ enum EmitLineNumberNote {
+ EMIT_LINENOTE,
+ SUPPRESS_LINENOTE
+ };
+
+ // Emit code for the tree rooted at pn.
+ MOZ_MUST_USE bool emitTree(ParseNode* pn, EmitLineNumberNote emitLineNote = EMIT_LINENOTE);
+
+ // Emit code for the tree rooted at pn with its own TDZ cache.
+ MOZ_MUST_USE bool emitConditionallyExecutedTree(ParseNode* pn);
+
+ // Emit global, eval, or module code for tree rooted at body. Always
+ // encompasses the entire source.
+ MOZ_MUST_USE bool emitScript(ParseNode* body);
+
+ // Emit function code for the tree rooted at body.
+ MOZ_MUST_USE bool emitFunctionScript(ParseNode* body);
+
+ // If op is JOF_TYPESET (see the type barriers comment in TypeInference.h),
+ // reserve a type set to store its result.
+ void checkTypeSet(JSOp op);
+
+ void updateDepth(ptrdiff_t target);
+ MOZ_MUST_USE bool updateLineNumberNotes(uint32_t offset);
+ MOZ_MUST_USE bool updateSourceCoordNotes(uint32_t offset);
+
+ JSOp strictifySetNameOp(JSOp op);
+
+ MOZ_MUST_USE bool flushPops(int* npops);
+
+ MOZ_MUST_USE bool emitCheck(ptrdiff_t delta, ptrdiff_t* offset);
+
+ // Emit one bytecode.
+ MOZ_MUST_USE bool emit1(JSOp op);
+
+ // Emit two bytecodes, an opcode (op) with a byte of immediate operand
+ // (op1).
+ MOZ_MUST_USE bool emit2(JSOp op, uint8_t op1);
+
+ // Emit three bytecodes, an opcode with two bytes of immediate operands.
+ MOZ_MUST_USE bool emit3(JSOp op, jsbytecode op1, jsbytecode op2);
+
+ // Helper to emit JSOP_DUPAT. The argument is the value's depth on the
+ // JS stack, as measured from the top.
+ MOZ_MUST_USE bool emitDupAt(unsigned slotFromTop);
+
+ // Helper to emit JSOP_CHECKISOBJ.
+ MOZ_MUST_USE bool emitCheckIsObj(CheckIsObjectKind kind);
+
+ // Emit a bytecode followed by an uint16 immediate operand stored in
+ // big-endian order.
+ MOZ_MUST_USE bool emitUint16Operand(JSOp op, uint32_t operand);
+
+ // Emit a bytecode followed by an uint32 immediate operand.
+ MOZ_MUST_USE bool emitUint32Operand(JSOp op, uint32_t operand);
+
+ // Emit (1 + extra) bytecodes, for N bytes of op and its immediate operand.
+ MOZ_MUST_USE bool emitN(JSOp op, size_t extra, ptrdiff_t* offset = nullptr);
+
+ MOZ_MUST_USE bool emitNumberOp(double dval);
+
+ MOZ_MUST_USE bool emitThisLiteral(ParseNode* pn);
+ MOZ_MUST_USE bool emitGetFunctionThis(ParseNode* pn);
+ MOZ_MUST_USE bool emitGetThisForSuperBase(ParseNode* pn);
+ MOZ_MUST_USE bool emitSetThis(ParseNode* pn);
+ MOZ_MUST_USE bool emitCheckDerivedClassConstructorReturn();
+
+ // Handle jump opcodes and jump targets.
+ MOZ_MUST_USE bool emitJumpTarget(JumpTarget* target);
+ MOZ_MUST_USE bool emitJumpNoFallthrough(JSOp op, JumpList* jump);
+ MOZ_MUST_USE bool emitJump(JSOp op, JumpList* jump);
+ MOZ_MUST_USE bool emitBackwardJump(JSOp op, JumpTarget target, JumpList* jump,
+ JumpTarget* fallthrough);
+ void patchJumpsToTarget(JumpList jump, JumpTarget target);
+ MOZ_MUST_USE bool emitJumpTargetAndPatch(JumpList jump);
+
+ MOZ_MUST_USE bool emitCall(JSOp op, uint16_t argc, ParseNode* pn = nullptr);
+ MOZ_MUST_USE bool emitCallIncDec(ParseNode* incDec);
+
+ MOZ_MUST_USE bool emitLoopHead(ParseNode* nextpn, JumpTarget* top);
+ MOZ_MUST_USE bool emitLoopEntry(ParseNode* nextpn, JumpList entryJump);
+
+ MOZ_MUST_USE bool emitGoto(NestableControl* target, JumpList* jumplist,
+ SrcNoteType noteType = SRC_NULL);
+
+ MOZ_MUST_USE bool emitIndex32(JSOp op, uint32_t index);
+ MOZ_MUST_USE bool emitIndexOp(JSOp op, uint32_t index);
+
+ MOZ_MUST_USE bool emitAtomOp(JSAtom* atom, JSOp op);
+ MOZ_MUST_USE bool emitAtomOp(ParseNode* pn, JSOp op);
+
+ MOZ_MUST_USE bool emitArrayLiteral(ParseNode* pn);
+ MOZ_MUST_USE bool emitArray(ParseNode* pn, uint32_t count, JSOp op);
+ MOZ_MUST_USE bool emitArrayComp(ParseNode* pn);
+
+ MOZ_MUST_USE bool emitInternedScopeOp(uint32_t index, JSOp op);
+ MOZ_MUST_USE bool emitInternedObjectOp(uint32_t index, JSOp op);
+ MOZ_MUST_USE bool emitObjectOp(ObjectBox* objbox, JSOp op);
+ MOZ_MUST_USE bool emitObjectPairOp(ObjectBox* objbox1, ObjectBox* objbox2, JSOp op);
+ MOZ_MUST_USE bool emitRegExp(uint32_t index);
+
+ MOZ_NEVER_INLINE MOZ_MUST_USE bool emitFunction(ParseNode* pn, bool needsProto = false);
+ MOZ_NEVER_INLINE MOZ_MUST_USE bool emitObject(ParseNode* pn);
+
+ MOZ_MUST_USE bool emitHoistedFunctionsInList(ParseNode* pn);
+
+ MOZ_MUST_USE bool emitPropertyList(ParseNode* pn, MutableHandlePlainObject objp,
+ PropListType type);
+
+ // To catch accidental misuse, emitUint16Operand/emit3 assert that they are
+ // not used to unconditionally emit JSOP_GETLOCAL. Variable access should
+ // instead be emitted using EmitVarOp. In special cases, when the caller
+ // definitely knows that a given local slot is unaliased, this function may be
+ // used as a non-asserting version of emitUint16Operand.
+ MOZ_MUST_USE bool emitLocalOp(JSOp op, uint32_t slot);
+
+ MOZ_MUST_USE bool emitArgOp(JSOp op, uint16_t slot);
+ MOZ_MUST_USE bool emitEnvCoordOp(JSOp op, EnvironmentCoordinate ec);
+
+ MOZ_MUST_USE bool emitGetNameAtLocation(JSAtom* name, const NameLocation& loc,
+ bool callContext = false);
+ MOZ_MUST_USE bool emitGetName(JSAtom* name, bool callContext = false) {
+ return emitGetNameAtLocation(name, lookupName(name), callContext);
+ }
+ MOZ_MUST_USE bool emitGetName(ParseNode* pn, bool callContext = false);
+
+ template <typename RHSEmitter>
+ MOZ_MUST_USE bool emitSetOrInitializeNameAtLocation(HandleAtom name, const NameLocation& loc,
+ RHSEmitter emitRhs, bool initialize);
+ template <typename RHSEmitter>
+ MOZ_MUST_USE bool emitSetOrInitializeName(HandleAtom name, RHSEmitter emitRhs,
+ bool initialize)
+ {
+ return emitSetOrInitializeNameAtLocation(name, lookupName(name), emitRhs, initialize);
+ }
+ template <typename RHSEmitter>
+ MOZ_MUST_USE bool emitSetName(ParseNode* pn, RHSEmitter emitRhs) {
+ RootedAtom name(cx, pn->name());
+ return emitSetName(name, emitRhs);
+ }
+ template <typename RHSEmitter>
+ MOZ_MUST_USE bool emitSetName(HandleAtom name, RHSEmitter emitRhs) {
+ return emitSetOrInitializeName(name, emitRhs, false);
+ }
+ template <typename RHSEmitter>
+ MOZ_MUST_USE bool emitInitializeName(ParseNode* pn, RHSEmitter emitRhs) {
+ RootedAtom name(cx, pn->name());
+ return emitInitializeName(name, emitRhs);
+ }
+ template <typename RHSEmitter>
+ MOZ_MUST_USE bool emitInitializeName(HandleAtom name, RHSEmitter emitRhs) {
+ return emitSetOrInitializeName(name, emitRhs, true);
+ }
+
+ MOZ_MUST_USE bool emitTDZCheckIfNeeded(JSAtom* name, const NameLocation& loc);
+
+ MOZ_MUST_USE bool emitNameIncDec(ParseNode* pn);
+
+ MOZ_MUST_USE bool emitDeclarationList(ParseNode* decls);
+ MOZ_MUST_USE bool emitSingleDeclaration(ParseNode* decls, ParseNode* decl,
+ ParseNode* initializer);
+
+ MOZ_MUST_USE bool emitNewInit(JSProtoKey key);
+ MOZ_MUST_USE bool emitSingletonInitialiser(ParseNode* pn);
+
+ MOZ_MUST_USE bool emitPrepareIteratorResult();
+ MOZ_MUST_USE bool emitFinishIteratorResult(bool done);
+ MOZ_MUST_USE bool iteratorResultShape(unsigned* shape);
+
+ MOZ_MUST_USE bool emitYield(ParseNode* pn);
+ MOZ_MUST_USE bool emitYieldOp(JSOp op);
+ MOZ_MUST_USE bool emitYieldStar(ParseNode* iter, ParseNode* gen);
+
+ MOZ_MUST_USE bool emitPropLHS(ParseNode* pn);
+ MOZ_MUST_USE bool emitPropOp(ParseNode* pn, JSOp op);
+ MOZ_MUST_USE bool emitPropIncDec(ParseNode* pn);
+
+ MOZ_MUST_USE bool emitAsyncWrapperLambda(unsigned index, bool isArrow);
+ MOZ_MUST_USE bool emitAsyncWrapper(unsigned index, bool needsHomeObject, bool isArrow);
+
+ MOZ_MUST_USE bool emitComputedPropertyName(ParseNode* computedPropName);
+
+ // Emit bytecode to put operands for a JSOP_GETELEM/CALLELEM/SETELEM/DELELEM
+ // opcode onto the stack in the right order. In the case of SETELEM, the
+ // value to be assigned must already be pushed.
+ enum class EmitElemOption { Get, Set, Call, IncDec, CompoundAssign };
+ MOZ_MUST_USE bool emitElemOperands(ParseNode* pn, EmitElemOption opts);
+
+ MOZ_MUST_USE bool emitElemOpBase(JSOp op);
+ MOZ_MUST_USE bool emitElemOp(ParseNode* pn, JSOp op);
+ MOZ_MUST_USE bool emitElemIncDec(ParseNode* pn);
+
+ MOZ_MUST_USE bool emitCatch(ParseNode* pn);
+ MOZ_MUST_USE bool emitIf(ParseNode* pn);
+ MOZ_MUST_USE bool emitWith(ParseNode* pn);
+
+ MOZ_NEVER_INLINE MOZ_MUST_USE bool emitLabeledStatement(const LabeledStatement* pn);
+ MOZ_NEVER_INLINE MOZ_MUST_USE bool emitLexicalScope(ParseNode* pn);
+ MOZ_MUST_USE bool emitLexicalScopeBody(ParseNode* body,
+ EmitLineNumberNote emitLineNote = EMIT_LINENOTE);
+ MOZ_NEVER_INLINE MOZ_MUST_USE bool emitSwitch(ParseNode* pn);
+ MOZ_NEVER_INLINE MOZ_MUST_USE bool emitTry(ParseNode* pn);
+
+ enum DestructuringFlavor {
+ // Destructuring into a declaration.
+ DestructuringDeclaration,
+
+ // Destructuring into a formal parameter, when the formal parameters
+ // contain an expression that might be evaluated, and thus require
+ // this destructuring to assign not into the innermost scope that
+ // contains the function body's vars, but into its enclosing scope for
+ // parameter expressions.
+ DestructuringFormalParameterInVarScope,
+
+ // Destructuring as part of an AssignmentExpression.
+ DestructuringAssignment
+ };
+
+ // emitDestructuringLHS assumes the to-be-destructured value has been pushed on
+ // the stack and emits code to destructure a single lhs expression (either a
+ // name or a compound []/{} expression).
+ MOZ_MUST_USE bool emitDestructuringLHS(ParseNode* target, DestructuringFlavor flav);
+ MOZ_MUST_USE bool emitConditionallyExecutedDestructuringLHS(ParseNode* target,
+ DestructuringFlavor flav);
+
+ // emitDestructuringOps assumes the to-be-destructured value has been
+ // pushed on the stack and emits code to destructure each part of a [] or
+ // {} lhs expression.
+ MOZ_MUST_USE bool emitDestructuringOps(ParseNode* pattern, DestructuringFlavor flav);
+ MOZ_MUST_USE bool emitDestructuringOpsArray(ParseNode* pattern, DestructuringFlavor flav);
+ MOZ_MUST_USE bool emitDestructuringOpsObject(ParseNode* pattern, DestructuringFlavor flav);
+
+ typedef bool
+ (*DestructuringDeclEmitter)(BytecodeEmitter* bce, ParseNode* pn);
+
+ template <typename NameEmitter>
+ MOZ_MUST_USE bool emitDestructuringDeclsWithEmitter(ParseNode* pattern, NameEmitter emitName);
+
+ // Throw a TypeError if the value atop the stack isn't convertible to an
+ // object, with no overall effect on the stack.
+ MOZ_MUST_USE bool emitRequireObjectCoercible();
+
+ // emitIterator expects the iterable to already be on the stack.
+ // It will replace that stack value with the corresponding iterator
+ MOZ_MUST_USE bool emitIterator();
+
+ // Pops iterator from the top of the stack. Pushes the result of |.next()|
+ // onto the stack.
+ MOZ_MUST_USE bool emitIteratorNext(ParseNode* pn, bool allowSelfHosted = false);
+
+ // 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|.
+ MOZ_MUST_USE bool emitDefault(ParseNode* defaultExpr);
+
+ MOZ_MUST_USE bool emitCallSiteObject(ParseNode* pn);
+ MOZ_MUST_USE bool emitTemplateString(ParseNode* pn);
+ MOZ_MUST_USE bool emitAssignment(ParseNode* lhs, JSOp op, ParseNode* rhs);
+
+ MOZ_MUST_USE bool emitReturn(ParseNode* pn);
+ MOZ_MUST_USE bool emitStatement(ParseNode* pn);
+ MOZ_MUST_USE bool emitStatementList(ParseNode* pn);
+
+ MOZ_MUST_USE bool emitDeleteName(ParseNode* pn);
+ MOZ_MUST_USE bool emitDeleteProperty(ParseNode* pn);
+ MOZ_MUST_USE bool emitDeleteElement(ParseNode* pn);
+ MOZ_MUST_USE bool emitDeleteExpression(ParseNode* pn);
+
+ // |op| must be JSOP_TYPEOF or JSOP_TYPEOFEXPR.
+ MOZ_MUST_USE bool emitTypeof(ParseNode* node, JSOp op);
+
+ MOZ_MUST_USE bool emitUnary(ParseNode* pn);
+ MOZ_MUST_USE bool emitRightAssociative(ParseNode* pn);
+ MOZ_MUST_USE bool emitLeftAssociative(ParseNode* pn);
+ MOZ_MUST_USE bool emitLogical(ParseNode* pn);
+ MOZ_MUST_USE bool emitSequenceExpr(ParseNode* pn);
+
+ MOZ_NEVER_INLINE MOZ_MUST_USE bool emitIncOrDec(ParseNode* pn);
+
+ MOZ_MUST_USE bool emitConditionalExpression(ConditionalExpression& conditional);
+
+ MOZ_MUST_USE bool isRestParameter(ParseNode* pn, bool* result);
+ MOZ_MUST_USE bool emitOptimizeSpread(ParseNode* arg0, JumpList* jmp, bool* emitted);
+
+ MOZ_MUST_USE bool emitCallOrNew(ParseNode* pn);
+ MOZ_MUST_USE bool emitSelfHostedCallFunction(ParseNode* pn);
+ MOZ_MUST_USE bool emitSelfHostedResumeGenerator(ParseNode* pn);
+ MOZ_MUST_USE bool emitSelfHostedForceInterpreter(ParseNode* pn);
+ MOZ_MUST_USE bool emitSelfHostedAllowContentSpread(ParseNode* pn);
+
+ MOZ_MUST_USE bool emitComprehensionFor(ParseNode* compFor);
+ MOZ_MUST_USE bool emitComprehensionForIn(ParseNode* pn);
+ MOZ_MUST_USE bool emitComprehensionForInOrOfVariables(ParseNode* pn, bool* lexicalScope);
+ MOZ_MUST_USE bool emitComprehensionForOf(ParseNode* pn);
+
+ MOZ_MUST_USE bool emitDo(ParseNode* pn);
+ MOZ_MUST_USE bool emitWhile(ParseNode* pn);
+
+ MOZ_MUST_USE bool emitFor(ParseNode* pn, EmitterScope* headLexicalEmitterScope = nullptr);
+ MOZ_MUST_USE bool emitCStyleFor(ParseNode* pn, EmitterScope* headLexicalEmitterScope);
+ MOZ_MUST_USE bool emitForIn(ParseNode* pn, EmitterScope* headLexicalEmitterScope);
+ MOZ_MUST_USE bool emitForOf(ParseNode* pn, EmitterScope* headLexicalEmitterScope);
+
+ MOZ_MUST_USE bool emitInitializeForInOrOfTarget(ParseNode* forHead);
+
+ MOZ_MUST_USE bool emitBreak(PropertyName* label);
+ MOZ_MUST_USE bool emitContinue(PropertyName* label);
+
+ MOZ_MUST_USE bool emitFunctionFormalParametersAndBody(ParseNode* pn);
+ MOZ_MUST_USE bool emitFunctionFormalParameters(ParseNode* pn);
+ MOZ_MUST_USE bool emitInitializeFunctionSpecialNames();
+ MOZ_MUST_USE bool emitFunctionBody(ParseNode* pn);
+ MOZ_MUST_USE bool emitLexicalInitialization(ParseNode* pn);
+
+ // Emit bytecode for the spread operator.
+ //
+ // emitSpread expects the current index (I) of the array, the array itself
+ // and the iterator to be on the stack in that order (iterator on the bottom).
+ // It will pop the iterator and I, then iterate over the iterator by calling
+ // |.next()| and put the results into the I-th element of array with
+ // incrementing I, then push the result I (it will be original I +
+ // iteration count). The stack after iteration will look like |ARRAY INDEX|.
+ MOZ_MUST_USE bool emitSpread(bool allowSelfHosted = false);
+
+ MOZ_MUST_USE bool emitClass(ParseNode* pn);
+ MOZ_MUST_USE bool emitSuperPropLHS(ParseNode* superBase, bool isCall = false);
+ MOZ_MUST_USE bool emitSuperPropOp(ParseNode* pn, JSOp op, bool isCall = false);
+ MOZ_MUST_USE bool emitSuperElemOperands(ParseNode* pn,
+ EmitElemOption opts = EmitElemOption::Get);
+ MOZ_MUST_USE bool emitSuperElemOp(ParseNode* pn, JSOp op, bool isCall = false);
+};
+
+} /* namespace frontend */
+} /* namespace js */
+
+#endif /* frontend_BytecodeEmitter_h */
diff --git a/js/src/frontend/FoldConstants.cpp b/js/src/frontend/FoldConstants.cpp
new file mode 100644
index 0000000000..6f62ffac68
--- /dev/null
+++ b/js/src/frontend/FoldConstants.cpp
@@ -0,0 +1,1928 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sts=4 et sw=4 tw=99:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "jslibmath.h"
+
+#include "frontend/ParseNode.h"
+#include "frontend/Parser.h"
+#include "js/Conversions.h"
+
+#include "jscntxtinlines.h"
+#include "jsobjinlines.h"
+
+using namespace js;
+using namespace js::frontend;
+
+using mozilla::IsNaN;
+using mozilla::IsNegative;
+using mozilla::NegativeInfinity;
+using mozilla::PositiveInfinity;
+using JS::GenericNaN;
+using JS::ToInt32;
+using JS::ToUint32;
+
+static bool
+ContainsHoistedDeclaration(ExclusiveContext* cx, ParseNode* node, bool* result);
+
+static bool
+ListContainsHoistedDeclaration(ExclusiveContext* cx, ListNode* list, bool* result)
+{
+ for (ParseNode* node = list->pn_head; node; node = node->pn_next) {
+ if (!ContainsHoistedDeclaration(cx, node, result))
+ return false;
+ if (*result)
+ return true;
+ }
+
+ *result = false;
+ return true;
+}
+
+// Determines whether the given ParseNode contains any declarations whose
+// visibility will extend outside the node itself -- that is, whether the
+// ParseNode contains any var statements.
+//
+// THIS IS NOT A GENERAL-PURPOSE FUNCTION. It is only written to work in the
+// specific context of deciding that |node|, as one arm of a PNK_IF controlled
+// by a constant condition, contains a declaration that forbids |node| being
+// completely eliminated as dead.
+static bool
+ContainsHoistedDeclaration(ExclusiveContext* cx, ParseNode* node, bool* result)
+{
+ JS_CHECK_RECURSION(cx, return false);
+
+ restart:
+
+ // With a better-typed AST, we would have distinct parse node classes for
+ // expressions and for statements and would characterize expressions with
+ // ExpressionKind and statements with StatementKind. Perhaps someday. In
+ // the meantime we must characterize every ParseNodeKind, even the
+ // expression/sub-expression ones that, if we handle all statement kinds
+ // correctly, we'll never see.
+ switch (node->getKind()) {
+ // Base case.
+ case PNK_VAR:
+ *result = true;
+ return true;
+
+ // Non-global lexical declarations are block-scoped (ergo not hoistable).
+ case PNK_LET:
+ case PNK_CONST:
+ MOZ_ASSERT(node->isArity(PN_LIST));
+ *result = false;
+ return true;
+
+ // Similarly to the lexical declarations above, classes cannot add hoisted
+ // declarations
+ case PNK_CLASS:
+ MOZ_ASSERT(node->isArity(PN_TERNARY));
+ *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 PNK_FUNCTION:
+ MOZ_ASSERT(node->isArity(PN_CODE));
+ *result = false;
+ return true;
+
+ case PNK_MODULE:
+ *result = false;
+ return true;
+
+ // Statements with no sub-components at all.
+ case PNK_NOP: // induced by function f() {} function f() {}
+ case PNK_DEBUGGER:
+ MOZ_ASSERT(node->isArity(PN_NULLARY));
+ *result = false;
+ return true;
+
+ // Statements containing only an expression have no declarations.
+ case PNK_SEMI:
+ case PNK_THROW:
+ case PNK_RETURN:
+ MOZ_ASSERT(node->isArity(PN_UNARY));
+ *result = false;
+ return true;
+
+ // These two aren't statements in the spec, but we sometimes insert them
+ // in statement lists anyway.
+ case PNK_YIELD_STAR:
+ case PNK_YIELD:
+ MOZ_ASSERT(node->isArity(PN_BINARY));
+ *result = false;
+ return true;
+
+ // Other statements with no sub-statement components.
+ case PNK_BREAK:
+ case PNK_CONTINUE:
+ case PNK_IMPORT:
+ case PNK_IMPORT_SPEC_LIST:
+ case PNK_IMPORT_SPEC:
+ case PNK_EXPORT_FROM:
+ case PNK_EXPORT_DEFAULT:
+ case PNK_EXPORT_SPEC_LIST:
+ case PNK_EXPORT_SPEC:
+ case PNK_EXPORT:
+ case PNK_EXPORT_BATCH_SPEC:
+ *result = false;
+ return true;
+
+ // Statements possibly containing hoistable declarations only in the left
+ // half, in ParseNode terms -- the loop body in AST terms.
+ case PNK_DOWHILE:
+ return ContainsHoistedDeclaration(cx, node->pn_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 PNK_WHILE:
+ case PNK_WITH:
+ return ContainsHoistedDeclaration(cx, node->pn_right, result);
+
+ case PNK_LABEL:
+ return ContainsHoistedDeclaration(cx, node->pn_expr, result);
+
+ // Statements with more complicated structures.
+
+ // if-statement nodes may have hoisted declarations in their consequent
+ // and alternative components.
+ case PNK_IF: {
+ MOZ_ASSERT(node->isArity(PN_TERNARY));
+
+ ParseNode* consequent = node->pn_kid2;
+ if (!ContainsHoistedDeclaration(cx, consequent, result))
+ return false;
+ if (*result)
+ return true;
+
+ if ((node = node->pn_kid3))
+ goto restart;
+
+ *result = false;
+ return true;
+ }
+
+ // Legacy array and generator comprehensions use PNK_IF to represent
+ // conditions specified in the comprehension tail: for example,
+ // [x for (x in obj) if (x)]. The consequent of such PNK_IF nodes is
+ // either PNK_YIELD in a PNK_SEMI statement (generator comprehensions) or
+ // PNK_ARRAYPUSH (array comprehensions) . The first case is consistent
+ // with normal if-statement structure with consequent/alternative as
+ // statements. The second case is abnormal and requires that we not
+ // banish PNK_ARRAYPUSH to the unreachable list, handling it explicitly.
+ //
+ // We could require that this one weird PNK_ARRAYPUSH case be packaged in
+ // a PNK_SEMI, for consistency. That requires careful bytecode emitter
+ // adjustment that seems unwarranted for a deprecated feature.
+ case PNK_ARRAYPUSH:
+ *result = false;
+ return true;
+
+ // try-statements have statements to execute, and one or both of a
+ // catch-list and a finally-block.
+ case PNK_TRY: {
+ MOZ_ASSERT(node->isArity(PN_TERNARY));
+ MOZ_ASSERT(node->pn_kid2 || node->pn_kid3,
+ "must have either catch(es) or finally");
+
+ ParseNode* tryBlock = node->pn_kid1;
+ if (!ContainsHoistedDeclaration(cx, tryBlock, result))
+ return false;
+ if (*result)
+ return true;
+
+ if (ParseNode* catchList = node->pn_kid2) {
+ for (ParseNode* lexicalScope = catchList->pn_head;
+ lexicalScope;
+ lexicalScope = lexicalScope->pn_next)
+ {
+ MOZ_ASSERT(lexicalScope->isKind(PNK_LEXICALSCOPE));
+
+ ParseNode* catchNode = lexicalScope->pn_expr;
+ MOZ_ASSERT(catchNode->isKind(PNK_CATCH));
+
+ ParseNode* catchStatements = catchNode->pn_kid3;
+ if (!ContainsHoistedDeclaration(cx, catchStatements, result))
+ return false;
+ if (*result)
+ return true;
+ }
+ }
+
+ if (ParseNode* finallyBlock = node->pn_kid3)
+ return ContainsHoistedDeclaration(cx, finallyBlock, result);
+
+ *result = false;
+ return true;
+ }
+
+ // A switch node's left half is an expression; only its right half (a
+ // list of cases/defaults, or a block node) could contain hoisted
+ // declarations.
+ case PNK_SWITCH:
+ MOZ_ASSERT(node->isArity(PN_BINARY));
+ return ContainsHoistedDeclaration(cx, node->pn_right, result);
+
+ case PNK_CASE:
+ return ContainsHoistedDeclaration(cx, node->as<CaseClause>().statementList(), result);
+
+ case PNK_FOR:
+ case PNK_COMPREHENSIONFOR: {
+ MOZ_ASSERT(node->isArity(PN_BINARY));
+
+ ParseNode* loopHead = node->pn_left;
+ MOZ_ASSERT(loopHead->isKind(PNK_FORHEAD) ||
+ loopHead->isKind(PNK_FORIN) ||
+ loopHead->isKind(PNK_FOROF));
+
+ if (loopHead->isKind(PNK_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.)
+ MOZ_ASSERT(loopHead->isArity(PN_TERNARY));
+
+ ParseNode* init = loopHead->pn_kid1;
+ if (init && init->isKind(PNK_VAR)) {
+ *result = true;
+ return true;
+ }
+ } else {
+ MOZ_ASSERT(loopHead->isKind(PNK_FORIN) || loopHead->isKind(PNK_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.
+ MOZ_ASSERT(loopHead->isArity(PN_TERNARY));
+
+ ParseNode* decl = loopHead->pn_kid1;
+ if (decl && decl->isKind(PNK_VAR)) {
+ *result = true;
+ return true;
+ }
+ }
+
+ ParseNode* loopBody = node->pn_right;
+ return ContainsHoistedDeclaration(cx, loopBody, result);
+ }
+
+ case PNK_LEXICALSCOPE: {
+ MOZ_ASSERT(node->isArity(PN_SCOPE));
+ ParseNode* expr = node->pn_expr;
+
+ if (expr->isKind(PNK_FOR) || expr->isKind(PNK_FUNCTION))
+ return ContainsHoistedDeclaration(cx, expr, result);
+
+ MOZ_ASSERT(expr->isKind(PNK_STATEMENTLIST));
+ return ListContainsHoistedDeclaration(cx, &node->pn_expr->as<ListNode>(), result);
+ }
+
+ // List nodes with all non-null children.
+ case PNK_STATEMENTLIST:
+ return ListContainsHoistedDeclaration(cx, &node->as<ListNode>(), result);
+
+ // Grammar sub-components that should never be reached directly by this
+ // method, because some parent component should have asserted itself.
+ case PNK_OBJECT_PROPERTY_NAME:
+ case PNK_COMPUTED_NAME:
+ case PNK_SPREAD:
+ case PNK_MUTATEPROTO:
+ case PNK_COLON:
+ case PNK_SHORTHAND:
+ case PNK_CONDITIONAL:
+ case PNK_TYPEOFNAME:
+ case PNK_TYPEOFEXPR:
+ case PNK_AWAIT:
+ case PNK_VOID:
+ case PNK_NOT:
+ case PNK_BITNOT:
+ case PNK_DELETENAME:
+ case PNK_DELETEPROP:
+ case PNK_DELETEELEM:
+ case PNK_DELETEEXPR:
+ case PNK_POS:
+ case PNK_NEG:
+ case PNK_PREINCREMENT:
+ case PNK_POSTINCREMENT:
+ case PNK_PREDECREMENT:
+ case PNK_POSTDECREMENT:
+ case PNK_OR:
+ case PNK_AND:
+ case PNK_BITOR:
+ case PNK_BITXOR:
+ case PNK_BITAND:
+ case PNK_STRICTEQ:
+ case PNK_EQ:
+ case PNK_STRICTNE:
+ case PNK_NE:
+ case PNK_LT:
+ case PNK_LE:
+ case PNK_GT:
+ case PNK_GE:
+ case PNK_INSTANCEOF:
+ case PNK_IN:
+ case PNK_LSH:
+ case PNK_RSH:
+ case PNK_URSH:
+ case PNK_ADD:
+ case PNK_SUB:
+ case PNK_STAR:
+ case PNK_DIV:
+ case PNK_MOD:
+ case PNK_POW:
+ case PNK_ASSIGN:
+ case PNK_ADDASSIGN:
+ case PNK_SUBASSIGN:
+ case PNK_BITORASSIGN:
+ case PNK_BITXORASSIGN:
+ case PNK_BITANDASSIGN:
+ case PNK_LSHASSIGN:
+ case PNK_RSHASSIGN:
+ case PNK_URSHASSIGN:
+ case PNK_MULASSIGN:
+ case PNK_DIVASSIGN:
+ case PNK_MODASSIGN:
+ case PNK_POWASSIGN:
+ case PNK_COMMA:
+ case PNK_ARRAY:
+ case PNK_OBJECT:
+ case PNK_DOT:
+ case PNK_ELEM:
+ case PNK_CALL:
+ case PNK_NAME:
+ case PNK_TEMPLATE_STRING:
+ case PNK_TEMPLATE_STRING_LIST:
+ case PNK_TAGGED_TEMPLATE:
+ case PNK_CALLSITEOBJ:
+ case PNK_STRING:
+ case PNK_REGEXP:
+ case PNK_TRUE:
+ case PNK_FALSE:
+ case PNK_NULL:
+ case PNK_THIS:
+ case PNK_ELISION:
+ case PNK_NUMBER:
+ case PNK_NEW:
+ case PNK_GENERATOR:
+ case PNK_GENEXP:
+ case PNK_ARRAYCOMP:
+ case PNK_PARAMSBODY:
+ case PNK_CATCHLIST:
+ case PNK_CATCH:
+ case PNK_FORIN:
+ case PNK_FOROF:
+ case PNK_FORHEAD:
+ case PNK_CLASSMETHOD:
+ case PNK_CLASSMETHODLIST:
+ case PNK_CLASSNAMES:
+ case PNK_NEWTARGET:
+ case PNK_POSHOLDER:
+ case PNK_SUPERCALL:
+ case PNK_SUPERBASE:
+ case PNK_SETTHIS:
+ MOZ_CRASH("ContainsHoistedDeclaration should have indicated false on "
+ "some parent node without recurring to test this node");
+
+ case PNK_LIMIT: // invalid sentinel value
+ MOZ_CRASH("unexpected PNK_LIMIT in node");
+ }
+
+ MOZ_CRASH("invalid node kind");
+}
+
+/*
+ * Fold from one constant type to another.
+ * XXX handles only strings and numbers for now
+ */
+static bool
+FoldType(ExclusiveContext* cx, ParseNode* pn, ParseNodeKind kind)
+{
+ if (!pn->isKind(kind)) {
+ switch (kind) {
+ case PNK_NUMBER:
+ if (pn->isKind(PNK_STRING)) {
+ double d;
+ if (!StringToNumber(cx, pn->pn_atom, &d))
+ return false;
+ pn->pn_dval = d;
+ pn->setKind(PNK_NUMBER);
+ pn->setOp(JSOP_DOUBLE);
+ }
+ break;
+
+ case PNK_STRING:
+ if (pn->isKind(PNK_NUMBER)) {
+ pn->pn_atom = NumberToAtom(cx, pn->pn_dval);
+ if (!pn->pn_atom)
+ return false;
+ pn->setKind(PNK_STRING);
+ pn->setOp(JSOP_STRING);
+ }
+ break;
+
+ default:;
+ }
+ }
+ return true;
+}
+
+// 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.
+static void
+ReplaceNode(ParseNode** pnp, ParseNode* pn)
+{
+ pn->pn_next = (*pnp)->pn_next;
+ *pnp = pn;
+}
+
+static bool
+IsEffectless(ParseNode* node)
+{
+ return node->isKind(PNK_TRUE) ||
+ node->isKind(PNK_FALSE) ||
+ node->isKind(PNK_STRING) ||
+ node->isKind(PNK_TEMPLATE_STRING) ||
+ node->isKind(PNK_NUMBER) ||
+ node->isKind(PNK_NULL) ||
+ node->isKind(PNK_FUNCTION) ||
+ node->isKind(PNK_GENEXP);
+}
+
+enum Truthiness { Truthy, Falsy, Unknown };
+
+static Truthiness
+Boolish(ParseNode* pn)
+{
+ switch (pn->getKind()) {
+ case PNK_NUMBER:
+ return (pn->pn_dval != 0 && !IsNaN(pn->pn_dval)) ? Truthy : Falsy;
+
+ case PNK_STRING:
+ case PNK_TEMPLATE_STRING:
+ return (pn->pn_atom->length() > 0) ? Truthy : Falsy;
+
+ case PNK_TRUE:
+ case PNK_FUNCTION:
+ case PNK_GENEXP:
+ return Truthy;
+
+ case PNK_FALSE:
+ case PNK_NULL:
+ return Falsy;
+
+ case PNK_VOID: {
+ // |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->pn_kid;
+ } while (pn->isKind(PNK_VOID));
+
+ return IsEffectless(pn) ? Falsy : Unknown;
+ }
+
+ default:
+ return Unknown;
+ }
+}
+
+static bool
+Fold(ExclusiveContext* cx, ParseNode** pnp, Parser<FullParseHandler>& parser, bool inGenexpLambda);
+
+static bool
+FoldCondition(ExclusiveContext* cx, ParseNode** nodePtr, Parser<FullParseHandler>& parser,
+ bool inGenexpLambda)
+{
+ // Conditions fold like any other expression...
+ if (!Fold(cx, nodePtr, parser, inGenexpLambda))
+ return false;
+
+ // ...but then they sometimes can be further folded to constants.
+ ParseNode* node = *nodePtr;
+ Truthiness t = Boolish(node);
+ if (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.
+ parser.prepareNodeForMutation(node);
+ if (t == Truthy) {
+ node->setKind(PNK_TRUE);
+ node->setOp(JSOP_TRUE);
+ } else {
+ node->setKind(PNK_FALSE);
+ node->setOp(JSOP_FALSE);
+ }
+ node->setArity(PN_NULLARY);
+ }
+
+ return true;
+}
+
+static bool
+FoldTypeOfExpr(ExclusiveContext* cx, ParseNode* node, Parser<FullParseHandler>& parser,
+ bool inGenexpLambda)
+{
+ MOZ_ASSERT(node->isKind(PNK_TYPEOFEXPR));
+ MOZ_ASSERT(node->isArity(PN_UNARY));
+
+ ParseNode*& expr = node->pn_kid;
+ if (!Fold(cx, &expr, parser, inGenexpLambda))
+ return false;
+
+ // Constant-fold the entire |typeof| if given a constant with known type.
+ RootedPropertyName result(cx);
+ if (expr->isKind(PNK_STRING) || expr->isKind(PNK_TEMPLATE_STRING))
+ result = cx->names().string;
+ else if (expr->isKind(PNK_NUMBER))
+ result = cx->names().number;
+ else if (expr->isKind(PNK_NULL))
+ result = cx->names().object;
+ else if (expr->isKind(PNK_TRUE) || expr->isKind(PNK_FALSE))
+ result = cx->names().boolean;
+ else if (expr->isKind(PNK_FUNCTION))
+ result = cx->names().function;
+
+ if (result) {
+ parser.prepareNodeForMutation(node);
+
+ node->setKind(PNK_STRING);
+ node->setArity(PN_NULLARY);
+ node->setOp(JSOP_NOP);
+ node->pn_atom = result;
+ }
+
+ return true;
+}
+
+static bool
+FoldDeleteExpr(ExclusiveContext* cx, ParseNode* node, Parser<FullParseHandler>& parser,
+ bool inGenexpLambda)
+{
+ MOZ_ASSERT(node->isKind(PNK_DELETEEXPR));
+ MOZ_ASSERT(node->isArity(PN_UNARY));
+
+ ParseNode*& expr = node->pn_kid;
+ if (!Fold(cx, &expr, parser, inGenexpLambda))
+ return false;
+
+ // Expression deletion evaluates the expression, then evaluates to true.
+ // For effectless expressions, eliminate the expression evaluation.
+ if (IsEffectless(expr)) {
+ parser.prepareNodeForMutation(node);
+ node->setKind(PNK_TRUE);
+ node->setArity(PN_NULLARY);
+ node->setOp(JSOP_TRUE);
+ }
+
+ return true;
+}
+
+static bool
+FoldDeleteElement(ExclusiveContext* cx, ParseNode* node, Parser<FullParseHandler>& parser,
+ bool inGenexpLambda)
+{
+ MOZ_ASSERT(node->isKind(PNK_DELETEELEM));
+ MOZ_ASSERT(node->isArity(PN_UNARY));
+ MOZ_ASSERT(node->pn_kid->isKind(PNK_ELEM));
+
+ ParseNode*& expr = node->pn_kid;
+ if (!Fold(cx, &expr, parser, inGenexpLambda))
+ return false;
+
+ // 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(PNK_ELEM) || expr->isKind(PNK_DOT));
+ if (expr->isKind(PNK_DOT))
+ node->setKind(PNK_DELETEPROP);
+
+ return true;
+}
+
+static bool
+FoldDeleteProperty(ExclusiveContext* cx, ParseNode* node, Parser<FullParseHandler>& parser,
+ bool inGenexpLambda)
+{
+ MOZ_ASSERT(node->isKind(PNK_DELETEPROP));
+ MOZ_ASSERT(node->isArity(PN_UNARY));
+ MOZ_ASSERT(node->pn_kid->isKind(PNK_DOT));
+
+ ParseNode*& expr = node->pn_kid;
+#ifdef DEBUG
+ ParseNodeKind oldKind = expr->getKind();
+#endif
+
+ if (!Fold(cx, &expr, parser, inGenexpLambda))
+ return false;
+
+ MOZ_ASSERT(expr->isKind(oldKind),
+ "kind should have remained invariant under folding");
+
+ return true;
+}
+
+static bool
+FoldNot(ExclusiveContext* cx, ParseNode* node, Parser<FullParseHandler>& parser,
+ bool inGenexpLambda)
+{
+ MOZ_ASSERT(node->isKind(PNK_NOT));
+ MOZ_ASSERT(node->isArity(PN_UNARY));
+
+ ParseNode*& expr = node->pn_kid;
+ if (!FoldCondition(cx, &expr, parser, inGenexpLambda))
+ return false;
+
+ if (expr->isKind(PNK_NUMBER)) {
+ double d = expr->pn_dval;
+
+ parser.prepareNodeForMutation(node);
+ if (d == 0 || IsNaN(d)) {
+ node->setKind(PNK_TRUE);
+ node->setOp(JSOP_TRUE);
+ } else {
+ node->setKind(PNK_FALSE);
+ node->setOp(JSOP_FALSE);
+ }
+ node->setArity(PN_NULLARY);
+ } else if (expr->isKind(PNK_TRUE) || expr->isKind(PNK_FALSE)) {
+ bool newval = !expr->isKind(PNK_TRUE);
+
+ parser.prepareNodeForMutation(node);
+ node->setKind(newval ? PNK_TRUE : PNK_FALSE);
+ node->setArity(PN_NULLARY);
+ node->setOp(newval ? JSOP_TRUE : JSOP_FALSE);
+ }
+
+ return true;
+}
+
+static bool
+FoldUnaryArithmetic(ExclusiveContext* cx, ParseNode* node, Parser<FullParseHandler>& parser,
+ bool inGenexpLambda)
+{
+ MOZ_ASSERT(node->isKind(PNK_BITNOT) || node->isKind(PNK_POS) || node->isKind(PNK_NEG),
+ "need a different method for this node kind");
+ MOZ_ASSERT(node->isArity(PN_UNARY));
+
+ ParseNode*& expr = node->pn_kid;
+ if (!Fold(cx, &expr, parser, inGenexpLambda))
+ return false;
+
+ if (expr->isKind(PNK_NUMBER) || expr->isKind(PNK_TRUE) || expr->isKind(PNK_FALSE)) {
+ double d = expr->isKind(PNK_NUMBER)
+ ? expr->pn_dval
+ : double(expr->isKind(PNK_TRUE));
+
+ if (node->isKind(PNK_BITNOT))
+ d = ~ToInt32(d);
+ else if (node->isKind(PNK_NEG))
+ d = -d;
+ else
+ MOZ_ASSERT(node->isKind(PNK_POS)); // nothing to do
+
+ parser.prepareNodeForMutation(node);
+ node->setKind(PNK_NUMBER);
+ node->setOp(JSOP_DOUBLE);
+ node->setArity(PN_NULLARY);
+ node->pn_dval = d;
+ }
+
+ return true;
+}
+
+static bool
+FoldIncrementDecrement(ExclusiveContext* cx, ParseNode* node, Parser<FullParseHandler>& parser,
+ bool inGenexpLambda)
+{
+ MOZ_ASSERT(node->isKind(PNK_PREINCREMENT) ||
+ node->isKind(PNK_POSTINCREMENT) ||
+ node->isKind(PNK_PREDECREMENT) ||
+ node->isKind(PNK_POSTDECREMENT));
+ MOZ_ASSERT(node->isArity(PN_UNARY));
+
+ ParseNode*& target = node->pn_kid;
+ MOZ_ASSERT(parser.isValidSimpleAssignmentTarget(target, Parser<FullParseHandler>::PermitAssignmentToFunctionCalls));
+
+ if (!Fold(cx, &target, parser, inGenexpLambda))
+ return false;
+
+ MOZ_ASSERT(parser.isValidSimpleAssignmentTarget(target, Parser<FullParseHandler>::PermitAssignmentToFunctionCalls));
+
+ return true;
+}
+
+static bool
+FoldAndOr(ExclusiveContext* cx, ParseNode** nodePtr, Parser<FullParseHandler>& parser,
+ bool inGenexpLambda)
+{
+ ParseNode* node = *nodePtr;
+
+ MOZ_ASSERT(node->isKind(PNK_AND) || node->isKind(PNK_OR));
+ MOZ_ASSERT(node->isArity(PN_LIST));
+
+ bool isOrNode = node->isKind(PNK_OR);
+ ParseNode** elem = &node->pn_head;
+ do {
+ if (!Fold(cx, elem, parser, inGenexpLambda))
+ return false;
+
+ 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;
+ }
+
+ // If the constant-folded node's truthiness will terminate the
+ // condition -- `a || true || expr` or |b && false && expr| -- then
+ // trailing nodes will never be evaluated. Truncate the list after
+ // the known-truthiness node, as it's the overall result.
+ if ((t == Truthy) == isOrNode) {
+ ParseNode* afterNext;
+ for (ParseNode* next = (*elem)->pn_next; next; next = afterNext) {
+ afterNext = next->pn_next;
+ parser.handler.freeTree(next);
+ --node->pn_count;
+ }
+
+ // Terminate the original and/or list at the known-truthiness
+ // node.
+ (*elem)->pn_next = nullptr;
+ elem = &(*elem)->pn_next;
+ break;
+ }
+
+ MOZ_ASSERT((t == Truthy) == !isOrNode);
+
+ // 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;
+ parser.handler.freeTree(elt);
+ --node->pn_count;
+ } 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);
+
+ // If the last node in the list was replaced, we need to update the
+ // tail pointer in the original and/or node.
+ node->pn_tail = elem;
+
+ node->checkListConsistency();
+
+ // If we removed nodes, we may have to replace a one-element list with
+ // its element.
+ if (node->pn_count == 1) {
+ ParseNode* first = node->pn_head;
+ ReplaceNode(nodePtr, first);
+
+ node->setKind(PNK_NULL);
+ node->setArity(PN_NULLARY);
+ parser.freeTree(node);
+ }
+
+ return true;
+}
+
+static bool
+FoldConditional(ExclusiveContext* cx, ParseNode** nodePtr, Parser<FullParseHandler>& parser,
+ bool inGenexpLambda)
+{
+ 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;
+
+ ParseNode* node = *nodePtr;
+ MOZ_ASSERT(node->isKind(PNK_CONDITIONAL));
+ MOZ_ASSERT(node->isArity(PN_TERNARY));
+
+ ParseNode*& expr = node->pn_kid1;
+ if (!FoldCondition(cx, &expr, parser, inGenexpLambda))
+ return false;
+
+ ParseNode*& ifTruthy = node->pn_kid2;
+ if (!Fold(cx, &ifTruthy, parser, inGenexpLambda))
+ return false;
+
+ ParseNode*& ifFalsy = node->pn_kid3;
+
+ // 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(PNK_CONDITIONAL)) {
+ nextNode = &ifFalsy;
+ } else {
+ if (!Fold(cx, &ifFalsy, parser, inGenexpLambda))
+ 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;
+ ParseNode* discarded;
+ if (t == Truthy) {
+ replacement = ifTruthy;
+ discarded = ifFalsy;
+ } else {
+ replacement = ifFalsy;
+ discarded = ifTruthy;
+ }
+
+ // 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);
+
+ parser.freeTree(discarded);
+ } while (nextNode);
+
+ return true;
+}
+
+static bool
+FoldIf(ExclusiveContext* cx, ParseNode** nodePtr, Parser<FullParseHandler>& parser,
+ bool inGenexpLambda)
+{
+ 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;
+
+ ParseNode* node = *nodePtr;
+ MOZ_ASSERT(node->isKind(PNK_IF));
+ MOZ_ASSERT(node->isArity(PN_TERNARY));
+
+ ParseNode*& expr = node->pn_kid1;
+ if (!FoldCondition(cx, &expr, parser, inGenexpLambda))
+ return false;
+
+ ParseNode*& consequent = node->pn_kid2;
+ if (!Fold(cx, &consequent, parser, inGenexpLambda))
+ return false;
+
+ ParseNode*& alternative = node->pn_kid3;
+ 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(PNK_IF)) {
+ nextNode = &alternative;
+ } else {
+ if (!Fold(cx, &alternative, parser, inGenexpLambda))
+ return false;
+ }
+ }
+
+ // Eliminate the consequent or alternative if the condition has
+ // constant truthiness. Don't eliminate if we have an |if (0)| in
+ // trailing position in a generator expression, as this is a special
+ // form we can't fold away.
+ Truthiness t = Boolish(expr);
+ if (t == Unknown || inGenexpLambda)
+ 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(cx, discarded, &containsHoistedDecls))
+ return false;
+
+ performReplacement = !containsHoistedDecls;
+ }
+
+ if (!performReplacement)
+ continue;
+
+ if (!replacement) {
+ // If there's no replacement node, we have a constantly-false |if|
+ // with no |else|. Replace the entire thing with an empty
+ // statement list.
+ parser.prepareNodeForMutation(node);
+ node->setKind(PNK_STATEMENTLIST);
+ node->setArity(PN_LIST);
+ node->makeEmpty();
+ } 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);
+
+ // Morph the original node into a discardable node, then
+ // aggressively free it and the discarded arm (if any) to suss out
+ // any bugs in the preceding logic.
+ node->setKind(PNK_STATEMENTLIST);
+ node->setArity(PN_LIST);
+ node->makeEmpty();
+ if (discarded)
+ node->append(discarded);
+ parser.freeTree(node);
+ }
+ } while (nextNode);
+
+ return true;
+}
+
+static bool
+FoldFunction(ExclusiveContext* cx, ParseNode* node, Parser<FullParseHandler>& parser,
+ bool inGenexpLambda)
+{
+ MOZ_ASSERT(node->isKind(PNK_FUNCTION));
+ MOZ_ASSERT(node->isArity(PN_CODE));
+
+ // 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->pn_funbox->useAsmOrInsideUseAsm())
+ return true;
+
+ // Note: pn_body is null for lazily-parsed functions.
+ if (ParseNode*& functionBody = node->pn_body) {
+ if (!Fold(cx, &functionBody, parser, node->pn_funbox->isGenexpLambda))
+ return false;
+ }
+
+ return true;
+}
+
+static double
+ComputeBinary(ParseNodeKind kind, double left, double right)
+{
+ if (kind == PNK_ADD)
+ return left + right;
+
+ if (kind == PNK_SUB)
+ return left - right;
+
+ if (kind == PNK_STAR)
+ return left * right;
+
+ if (kind == PNK_MOD)
+ return right == 0 ? GenericNaN() : js_fmod(left, right);
+
+ if (kind == PNK_URSH)
+ return ToUint32(left) >> (ToUint32(right) & 31);
+
+ if (kind == PNK_DIV) {
+ if (right == 0) {
+#if defined(XP_WIN)
+ /* XXX MSVC miscompiles such that (NaN == 0) */
+ if (IsNaN(right))
+ return GenericNaN();
+#endif
+ if (left == 0 || IsNaN(left))
+ return GenericNaN();
+ if (IsNegative(left) != IsNegative(right))
+ return NegativeInfinity<double>();
+ return PositiveInfinity<double>();
+ }
+
+ return left / right;
+ }
+
+ MOZ_ASSERT(kind == PNK_LSH || kind == PNK_RSH);
+
+ int32_t i = ToInt32(left);
+ uint32_t j = ToUint32(right) & 31;
+ return int32_t((kind == PNK_LSH) ? uint32_t(i) << j : i >> j);
+}
+
+static bool
+FoldModule(ExclusiveContext* cx, ParseNode* node, Parser<FullParseHandler>& parser)
+{
+ MOZ_ASSERT(node->isKind(PNK_MODULE));
+ MOZ_ASSERT(node->isArity(PN_CODE));
+
+ ParseNode*& moduleBody = node->pn_body;
+ MOZ_ASSERT(moduleBody);
+ return Fold(cx, &moduleBody, parser, false);
+}
+
+static bool
+FoldBinaryArithmetic(ExclusiveContext* cx, ParseNode* node, Parser<FullParseHandler>& parser,
+ bool inGenexpLambda)
+{
+ MOZ_ASSERT(node->isKind(PNK_SUB) ||
+ node->isKind(PNK_STAR) ||
+ node->isKind(PNK_LSH) ||
+ node->isKind(PNK_RSH) ||
+ node->isKind(PNK_URSH) ||
+ node->isKind(PNK_DIV) ||
+ node->isKind(PNK_MOD));
+ MOZ_ASSERT(node->isArity(PN_LIST));
+ MOZ_ASSERT(node->pn_count >= 2);
+
+ // Fold each operand, ideally into a number.
+ ParseNode** listp = &node->pn_head;
+ for (; *listp; listp = &(*listp)->pn_next) {
+ if (!Fold(cx, listp, parser, inGenexpLambda))
+ return false;
+
+ if (!FoldType(cx, *listp, PNK_NUMBER))
+ return false;
+ }
+
+ // Repoint the list's tail pointer.
+ node->pn_tail = 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->pn_head;
+ ParseNode* next = elem->pn_next;
+ if (elem->isKind(PNK_NUMBER)) {
+ ParseNodeKind kind = node->getKind();
+ while (true) {
+ if (!next || !next->isKind(PNK_NUMBER))
+ break;
+
+ double d = ComputeBinary(kind, elem->pn_dval, next->pn_dval);
+
+ ParseNode* afterNext = next->pn_next;
+ parser.freeTree(next);
+ next = afterNext;
+ elem->pn_next = next;
+
+ elem->setKind(PNK_NUMBER);
+ elem->setOp(JSOP_DOUBLE);
+ elem->setArity(PN_NULLARY);
+ elem->pn_dval = d;
+
+ node->pn_count--;
+ }
+
+ if (node->pn_count == 1) {
+ MOZ_ASSERT(node->pn_head == elem);
+ MOZ_ASSERT(elem->isKind(PNK_NUMBER));
+
+ double d = elem->pn_dval;
+ node->setKind(PNK_NUMBER);
+ node->setArity(PN_NULLARY);
+ node->setOp(JSOP_DOUBLE);
+ node->pn_dval = d;
+
+ parser.freeTree(elem);
+ }
+ }
+
+ return true;
+}
+
+static bool
+FoldExponentiation(ExclusiveContext* cx, ParseNode* node, Parser<FullParseHandler>& parser,
+ bool inGenexpLambda)
+{
+ MOZ_ASSERT(node->isKind(PNK_POW));
+ MOZ_ASSERT(node->isArity(PN_LIST));
+ MOZ_ASSERT(node->pn_count >= 2);
+
+ // Fold each operand, ideally into a number.
+ ParseNode** listp = &node->pn_head;
+ for (; *listp; listp = &(*listp)->pn_next) {
+ if (!Fold(cx, listp, parser, inGenexpLambda))
+ return false;
+
+ if (!FoldType(cx, *listp, PNK_NUMBER))
+ return false;
+ }
+
+ // Repoint the list's tail pointer.
+ node->pn_tail = 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->pn_count > 2)
+ return true;
+
+ ParseNode* base = node->pn_head;
+ ParseNode* exponent = base->pn_next;
+ if (!base->isKind(PNK_NUMBER) || !exponent->isKind(PNK_NUMBER))
+ return true;
+
+ double d1 = base->pn_dval, d2 = exponent->pn_dval;
+
+ parser.prepareNodeForMutation(node);
+ node->setKind(PNK_NUMBER);
+ node->setArity(PN_NULLARY);
+ node->setOp(JSOP_DOUBLE);
+ node->pn_dval = ecmaPow(d1, d2);
+ return true;
+}
+
+static bool
+FoldList(ExclusiveContext* cx, ParseNode* list, Parser<FullParseHandler>& parser,
+ bool inGenexpLambda)
+{
+ MOZ_ASSERT(list->isArity(PN_LIST));
+
+ ParseNode** elem = &list->pn_head;
+ for (; *elem; elem = &(*elem)->pn_next) {
+ if (!Fold(cx, elem, parser, inGenexpLambda))
+ return false;
+ }
+
+ // Repoint the list's tail pointer if the final element was replaced.
+ list->pn_tail = elem;
+
+ list->checkListConsistency();
+
+ return true;
+}
+
+static bool
+FoldReturn(ExclusiveContext* cx, ParseNode* node, Parser<FullParseHandler>& parser,
+ bool inGenexpLambda)
+{
+ MOZ_ASSERT(node->isKind(PNK_RETURN));
+ MOZ_ASSERT(node->isArity(PN_UNARY));
+
+ if (ParseNode*& expr = node->pn_kid) {
+ if (!Fold(cx, &expr, parser, inGenexpLambda))
+ return false;
+ }
+
+ return true;
+}
+
+static bool
+FoldTry(ExclusiveContext* cx, ParseNode* node, Parser<FullParseHandler>& parser,
+ bool inGenexpLambda)
+{
+ MOZ_ASSERT(node->isKind(PNK_TRY));
+ MOZ_ASSERT(node->isArity(PN_TERNARY));
+
+ ParseNode*& statements = node->pn_kid1;
+ if (!Fold(cx, &statements, parser, inGenexpLambda))
+ return false;
+
+ if (ParseNode*& catchList = node->pn_kid2) {
+ if (!Fold(cx, &catchList, parser, inGenexpLambda))
+ return false;
+ }
+
+ if (ParseNode*& finally = node->pn_kid3) {
+ if (!Fold(cx, &finally, parser, inGenexpLambda))
+ return false;
+ }
+
+ return true;
+}
+
+static bool
+FoldCatch(ExclusiveContext* cx, ParseNode* node, Parser<FullParseHandler>& parser,
+ bool inGenexpLambda)
+{
+ MOZ_ASSERT(node->isKind(PNK_CATCH));
+ MOZ_ASSERT(node->isArity(PN_TERNARY));
+
+ ParseNode*& declPattern = node->pn_kid1;
+ if (!Fold(cx, &declPattern, parser, inGenexpLambda))
+ return false;
+
+ if (ParseNode*& cond = node->pn_kid2) {
+ if (!FoldCondition(cx, &cond, parser, inGenexpLambda))
+ return false;
+ }
+
+ if (ParseNode*& statements = node->pn_kid3) {
+ if (!Fold(cx, &statements, parser, inGenexpLambda))
+ return false;
+ }
+
+ return true;
+}
+
+static bool
+FoldClass(ExclusiveContext* cx, ParseNode* node, Parser<FullParseHandler>& parser,
+ bool inGenexpLambda)
+{
+ MOZ_ASSERT(node->isKind(PNK_CLASS));
+ MOZ_ASSERT(node->isArity(PN_TERNARY));
+
+ if (ParseNode*& classNames = node->pn_kid1) {
+ if (!Fold(cx, &classNames, parser, inGenexpLambda))
+ return false;
+ }
+
+ if (ParseNode*& heritage = node->pn_kid2) {
+ if (!Fold(cx, &heritage, parser, inGenexpLambda))
+ return false;
+ }
+
+ ParseNode*& body = node->pn_kid3;
+ return Fold(cx, &body, parser, inGenexpLambda);
+}
+
+static bool
+FoldElement(ExclusiveContext* cx, ParseNode** nodePtr, Parser<FullParseHandler>& parser,
+ bool inGenexpLambda)
+{
+ ParseNode* node = *nodePtr;
+
+ MOZ_ASSERT(node->isKind(PNK_ELEM));
+ MOZ_ASSERT(node->isArity(PN_BINARY));
+
+ ParseNode*& expr = node->pn_left;
+ if (!Fold(cx, &expr, parser, inGenexpLambda))
+ return false;
+
+ ParseNode*& key = node->pn_right;
+ if (!Fold(cx, &key, parser, inGenexpLambda))
+ return false;
+
+ PropertyName* name = nullptr;
+ if (key->isKind(PNK_STRING)) {
+ JSAtom* atom = key->pn_atom;
+ uint32_t index;
+
+ if (atom->isIndex(&index)) {
+ // Optimization 1: We have something like expr["100"]. This is
+ // equivalent to expr[100] which is faster.
+ key->setKind(PNK_NUMBER);
+ key->setOp(JSOP_DOUBLE);
+ key->pn_dval = index;
+ } else {
+ name = atom->asPropertyName();
+ }
+ } else if (key->isKind(PNK_NUMBER)) {
+ double number = key->pn_dval;
+ 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.
+ JSAtom* atom = ToAtom<NoGC>(cx, DoubleValue(number));
+ if (!atom)
+ return false;
+ name = atom->asPropertyName();
+ }
+ }
+
+ // If we don't have a name, we can't optimize to getprop.
+ if (!name)
+ return true;
+
+ // Also don't optimize if the name doesn't map directly to its id for TI's
+ // purposes.
+ if (NameToId(name) != IdToTypeId(NameToId(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.
+ // Don't bother with this for names that TI considers to be indexes, to
+ // simplify downstream analysis.
+ ParseNode* dottedAccess = parser.handler.newPropertyAccess(expr, name, node->pn_pos.end);
+ if (!dottedAccess)
+ return false;
+ dottedAccess->setInParens(node->isInParens());
+ ReplaceNode(nodePtr, dottedAccess);
+
+ // If we've replaced |expr["prop"]| with |expr.prop|, we can now free the
+ // |"prop"| and |expr["prop"]| nodes -- but not the |expr| node that we're
+ // now using as a sub-node of |dottedAccess|. Munge |expr["prop"]| into a
+ // node with |"prop"| as its only child, that'll pass AST sanity-checking
+ // assertions during freeing, then free it.
+ node->setKind(PNK_TYPEOFEXPR);
+ node->setArity(PN_UNARY);
+ node->pn_kid = key;
+ parser.freeTree(node);
+
+ return true;
+}
+
+static bool
+FoldAdd(ExclusiveContext* cx, ParseNode** nodePtr, Parser<FullParseHandler>& parser,
+ bool inGenexpLambda)
+{
+ ParseNode* node = *nodePtr;
+
+ MOZ_ASSERT(node->isKind(PNK_ADD));
+ MOZ_ASSERT(node->isArity(PN_LIST));
+ MOZ_ASSERT(node->pn_count >= 2);
+
+ // Generically fold all operands first.
+ if (!FoldList(cx, node, parser, inGenexpLambda))
+ return false;
+
+ // 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->pn_head;
+ ParseNode* next = current->pn_next;
+ if (current->isKind(PNK_NUMBER)) {
+ do {
+ if (!next->isKind(PNK_NUMBER))
+ break;
+
+ current->pn_dval += next->pn_dval;
+ current->pn_next = next->pn_next;
+ parser.freeTree(next);
+ next = current->pn_next;
+
+ MOZ_ASSERT(node->pn_count > 1);
+ node->pn_count--;
+ } 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(PNK_NUMBER) && next->isKind(PNK_STRING)) {
+ if (!FoldType(cx, current, PNK_STRING))
+ return false;
+ next = current->pn_next;
+ }
+
+ // The first string forces all subsequent additions to be
+ // string concatenations.
+ do {
+ if (current->isKind(PNK_STRING))
+ break;
+
+ current = next;
+ next = next->pn_next;
+ } while (next);
+
+ // If there's nothing left to fold, we're done.
+ if (!next)
+ break;
+
+ RootedString combination(cx);
+ RootedString tmp(cx);
+ do {
+ // Create a rope of the current string and all succeeding
+ // constants that we can convert to strings, then atomize it
+ // and replace them all with that fresh string.
+ MOZ_ASSERT(current->isKind(PNK_STRING));
+
+ combination = current->pn_atom;
+
+ do {
+ // Try folding the next operand to a string.
+ if (!FoldType(cx, next, PNK_STRING))
+ return false;
+
+ // Stop glomming once folding doesn't produce a string.
+ if (!next->isKind(PNK_STRING))
+ break;
+
+ // Add this string to the combination and remove the node.
+ tmp = next->pn_atom;
+ combination = ConcatStrings<CanGC>(cx, combination, tmp);
+ if (!combination)
+ return false;
+
+ current->pn_next = next->pn_next;
+ parser.freeTree(next);
+ next = current->pn_next;
+
+ MOZ_ASSERT(node->pn_count > 1);
+ node->pn_count--;
+ } while (next);
+
+ // Replace |current|'s string with the entire combination.
+ MOZ_ASSERT(current->isKind(PNK_STRING));
+ combination = AtomizeString(cx, combination);
+ if (!combination)
+ return false;
+ current->pn_atom = &combination->asAtom();
+
+
+ // 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;
+ next = current->pn_next;
+
+ if (!FoldType(cx, current, PNK_STRING))
+ return false;
+ next = current->pn_next;
+ } while (!current->isKind(PNK_STRING) && 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->pn_tail = &current->pn_next;
+ node->checkListConsistency();
+
+ if (node->pn_count == 1) {
+ // We reduced the list to a constant. Replace the PNK_ADD node
+ // with that constant.
+ ReplaceNode(nodePtr, current);
+
+ // Free the old node to aggressively verify nothing uses it.
+ node->setKind(PNK_TRUE);
+ node->setArity(PN_NULLARY);
+ node->setOp(JSOP_TRUE);
+ parser.freeTree(node);
+ }
+
+ return true;
+}
+
+static bool
+FoldCall(ExclusiveContext* cx, ParseNode* node, Parser<FullParseHandler>& parser,
+ bool inGenexpLambda)
+{
+ MOZ_ASSERT(node->isKind(PNK_CALL) || node->isKind(PNK_SUPERCALL) ||
+ node->isKind(PNK_TAGGED_TEMPLATE));
+ MOZ_ASSERT(node->isArity(PN_LIST));
+
+ // 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");
+ //
+ // See bug 537673 and bug 1182373.
+ ParseNode** listp = &node->pn_head;
+ if ((*listp)->isInParens())
+ listp = &(*listp)->pn_next;
+
+ for (; *listp; listp = &(*listp)->pn_next) {
+ if (!Fold(cx, listp, parser, inGenexpLambda))
+ return false;
+ }
+
+ // If the last node in the list was replaced, pn_tail points into the wrong node.
+ node->pn_tail = listp;
+
+ node->checkListConsistency();
+ return true;
+}
+
+static bool
+FoldForInOrOf(ExclusiveContext* cx, ParseNode* node, Parser<FullParseHandler>& parser,
+ bool inGenexpLambda)
+{
+ MOZ_ASSERT(node->isKind(PNK_FORIN) || node->isKind(PNK_FOROF));
+ MOZ_ASSERT(node->isArity(PN_TERNARY));
+ MOZ_ASSERT(!node->pn_kid2);
+
+ return Fold(cx, &node->pn_kid1, parser, inGenexpLambda) &&
+ Fold(cx, &node->pn_kid3, parser, inGenexpLambda);
+}
+
+static bool
+FoldForHead(ExclusiveContext* cx, ParseNode* node, Parser<FullParseHandler>& parser,
+ bool inGenexpLambda)
+{
+ MOZ_ASSERT(node->isKind(PNK_FORHEAD));
+ MOZ_ASSERT(node->isArity(PN_TERNARY));
+
+ if (ParseNode*& init = node->pn_kid1) {
+ if (!Fold(cx, &init, parser, inGenexpLambda))
+ return false;
+ }
+
+ if (ParseNode*& test = node->pn_kid2) {
+ if (!FoldCondition(cx, &test, parser, inGenexpLambda))
+ return false;
+
+ if (test->isKind(PNK_TRUE)) {
+ parser.freeTree(test);
+ test = nullptr;
+ }
+ }
+
+ if (ParseNode*& update = node->pn_kid3) {
+ if (!Fold(cx, &update, parser, inGenexpLambda))
+ return false;
+ }
+
+ return true;
+}
+
+static bool
+FoldDottedProperty(ExclusiveContext* cx, ParseNode* node, Parser<FullParseHandler>& parser,
+ bool inGenexpLambda)
+{
+ MOZ_ASSERT(node->isKind(PNK_DOT));
+ MOZ_ASSERT(node->isArity(PN_NAME));
+
+ // Iterate through a long chain of dotted property accesses to find the
+ // most-nested non-dotted property node, then fold that.
+ ParseNode** nested = &node->pn_expr;
+ while ((*nested)->isKind(PNK_DOT)) {
+ MOZ_ASSERT((*nested)->isArity(PN_NAME));
+ nested = &(*nested)->pn_expr;
+ }
+
+ return Fold(cx, nested, parser, inGenexpLambda);
+}
+
+static bool
+FoldName(ExclusiveContext* cx, ParseNode* node, Parser<FullParseHandler>& parser,
+ bool inGenexpLambda)
+{
+ MOZ_ASSERT(node->isKind(PNK_NAME));
+ MOZ_ASSERT(node->isArity(PN_NAME));
+
+ if (!node->pn_expr)
+ return true;
+
+ return Fold(cx, &node->pn_expr, parser, inGenexpLambda);
+}
+
+bool
+Fold(ExclusiveContext* cx, ParseNode** pnp, Parser<FullParseHandler>& parser, bool inGenexpLambda)
+{
+ JS_CHECK_RECURSION(cx, return false);
+
+ ParseNode* pn = *pnp;
+
+ switch (pn->getKind()) {
+ case PNK_NOP:
+ case PNK_REGEXP:
+ case PNK_STRING:
+ case PNK_TRUE:
+ case PNK_FALSE:
+ case PNK_NULL:
+ case PNK_ELISION:
+ case PNK_NUMBER:
+ case PNK_DEBUGGER:
+ case PNK_BREAK:
+ case PNK_CONTINUE:
+ case PNK_TEMPLATE_STRING:
+ case PNK_GENERATOR:
+ case PNK_EXPORT_BATCH_SPEC:
+ case PNK_OBJECT_PROPERTY_NAME:
+ case PNK_POSHOLDER:
+ MOZ_ASSERT(pn->isArity(PN_NULLARY));
+ return true;
+
+ case PNK_SUPERBASE:
+ case PNK_TYPEOFNAME:
+ MOZ_ASSERT(pn->isArity(PN_UNARY));
+ MOZ_ASSERT(pn->pn_kid->isKind(PNK_NAME));
+ MOZ_ASSERT(!pn->pn_kid->expr());
+ return true;
+
+ case PNK_TYPEOFEXPR:
+ return FoldTypeOfExpr(cx, pn, parser, inGenexpLambda);
+
+ case PNK_DELETENAME: {
+ MOZ_ASSERT(pn->isArity(PN_UNARY));
+ MOZ_ASSERT(pn->pn_kid->isKind(PNK_NAME));
+ return true;
+ }
+
+ case PNK_DELETEEXPR:
+ return FoldDeleteExpr(cx, pn, parser, inGenexpLambda);
+
+ case PNK_DELETEELEM:
+ return FoldDeleteElement(cx, pn, parser, inGenexpLambda);
+
+ case PNK_DELETEPROP:
+ return FoldDeleteProperty(cx, pn, parser, inGenexpLambda);
+
+ case PNK_CONDITIONAL:
+ return FoldConditional(cx, pnp, parser, inGenexpLambda);
+
+ case PNK_IF:
+ return FoldIf(cx, pnp, parser, inGenexpLambda);
+
+ case PNK_NOT:
+ return FoldNot(cx, pn, parser, inGenexpLambda);
+
+ case PNK_BITNOT:
+ case PNK_POS:
+ case PNK_NEG:
+ return FoldUnaryArithmetic(cx, pn, parser, inGenexpLambda);
+
+ case PNK_PREINCREMENT:
+ case PNK_POSTINCREMENT:
+ case PNK_PREDECREMENT:
+ case PNK_POSTDECREMENT:
+ return FoldIncrementDecrement(cx, pn, parser, inGenexpLambda);
+
+ case PNK_THROW:
+ case PNK_ARRAYPUSH:
+ case PNK_MUTATEPROTO:
+ case PNK_COMPUTED_NAME:
+ case PNK_SPREAD:
+ case PNK_EXPORT:
+ case PNK_VOID:
+ MOZ_ASSERT(pn->isArity(PN_UNARY));
+ return Fold(cx, &pn->pn_kid, parser, inGenexpLambda);
+
+ case PNK_EXPORT_DEFAULT:
+ MOZ_ASSERT(pn->isArity(PN_BINARY));
+ return Fold(cx, &pn->pn_left, parser, inGenexpLambda);
+
+ case PNK_SEMI:
+ case PNK_THIS:
+ MOZ_ASSERT(pn->isArity(PN_UNARY));
+ if (ParseNode*& expr = pn->pn_kid)
+ return Fold(cx, &expr, parser, inGenexpLambda);
+ return true;
+
+ case PNK_AND:
+ case PNK_OR:
+ return FoldAndOr(cx, pnp, parser, inGenexpLambda);
+
+ case PNK_FUNCTION:
+ return FoldFunction(cx, pn, parser, inGenexpLambda);
+
+ case PNK_MODULE:
+ return FoldModule(cx, pn, parser);
+
+ case PNK_SUB:
+ case PNK_STAR:
+ case PNK_LSH:
+ case PNK_RSH:
+ case PNK_URSH:
+ case PNK_DIV:
+ case PNK_MOD:
+ return FoldBinaryArithmetic(cx, pn, parser, inGenexpLambda);
+
+ case PNK_POW:
+ return FoldExponentiation(cx, pn, parser, inGenexpLambda);
+
+ // Various list nodes not requiring care to minimally fold. Some of
+ // these could be further folded/optimized, but we don't make the effort.
+ case PNK_BITOR:
+ case PNK_BITXOR:
+ case PNK_BITAND:
+ case PNK_STRICTEQ:
+ case PNK_EQ:
+ case PNK_STRICTNE:
+ case PNK_NE:
+ case PNK_LT:
+ case PNK_LE:
+ case PNK_GT:
+ case PNK_GE:
+ case PNK_INSTANCEOF:
+ case PNK_IN:
+ case PNK_COMMA:
+ case PNK_NEW:
+ case PNK_ARRAY:
+ case PNK_OBJECT:
+ case PNK_ARRAYCOMP:
+ case PNK_STATEMENTLIST:
+ case PNK_CLASSMETHODLIST:
+ case PNK_CATCHLIST:
+ case PNK_TEMPLATE_STRING_LIST:
+ case PNK_VAR:
+ case PNK_CONST:
+ case PNK_LET:
+ case PNK_PARAMSBODY:
+ case PNK_CALLSITEOBJ:
+ case PNK_EXPORT_SPEC_LIST:
+ case PNK_IMPORT_SPEC_LIST:
+ case PNK_GENEXP:
+ return FoldList(cx, pn, parser, inGenexpLambda);
+
+ case PNK_YIELD_STAR:
+ MOZ_ASSERT(pn->isArity(PN_BINARY));
+ MOZ_ASSERT(pn->pn_right->isKind(PNK_NAME));
+ return Fold(cx, &pn->pn_left, parser, inGenexpLambda);
+
+ case PNK_YIELD:
+ case PNK_AWAIT:
+ MOZ_ASSERT(pn->isArity(PN_BINARY));
+ MOZ_ASSERT(pn->pn_right->isKind(PNK_NAME) ||
+ (pn->pn_right->isKind(PNK_ASSIGN) &&
+ pn->pn_right->pn_left->isKind(PNK_NAME) &&
+ pn->pn_right->pn_right->isKind(PNK_GENERATOR)));
+ if (!pn->pn_left)
+ return true;
+ return Fold(cx, &pn->pn_left, parser, inGenexpLambda);
+
+ case PNK_RETURN:
+ return FoldReturn(cx, pn, parser, inGenexpLambda);
+
+ case PNK_TRY:
+ return FoldTry(cx, pn, parser, inGenexpLambda);
+
+ case PNK_CATCH:
+ return FoldCatch(cx, pn, parser, inGenexpLambda);
+
+ case PNK_CLASS:
+ return FoldClass(cx, pn, parser, inGenexpLambda);
+
+ case PNK_ELEM:
+ return FoldElement(cx, pnp, parser, inGenexpLambda);
+
+ case PNK_ADD:
+ return FoldAdd(cx, pnp, parser, inGenexpLambda);
+
+ case PNK_CALL:
+ case PNK_SUPERCALL:
+ case PNK_TAGGED_TEMPLATE:
+ return FoldCall(cx, pn, parser, inGenexpLambda);
+
+ case PNK_SWITCH:
+ case PNK_COLON:
+ case PNK_ASSIGN:
+ case PNK_ADDASSIGN:
+ case PNK_SUBASSIGN:
+ case PNK_BITORASSIGN:
+ case PNK_BITANDASSIGN:
+ case PNK_BITXORASSIGN:
+ case PNK_LSHASSIGN:
+ case PNK_RSHASSIGN:
+ case PNK_URSHASSIGN:
+ case PNK_DIVASSIGN:
+ case PNK_MODASSIGN:
+ case PNK_MULASSIGN:
+ case PNK_POWASSIGN:
+ case PNK_IMPORT:
+ case PNK_EXPORT_FROM:
+ case PNK_SHORTHAND:
+ case PNK_FOR:
+ case PNK_COMPREHENSIONFOR:
+ case PNK_CLASSMETHOD:
+ case PNK_IMPORT_SPEC:
+ case PNK_EXPORT_SPEC:
+ case PNK_SETTHIS:
+ MOZ_ASSERT(pn->isArity(PN_BINARY));
+ return Fold(cx, &pn->pn_left, parser, inGenexpLambda) &&
+ Fold(cx, &pn->pn_right, parser, inGenexpLambda);
+
+ case PNK_NEWTARGET:
+ MOZ_ASSERT(pn->isArity(PN_BINARY));
+ MOZ_ASSERT(pn->pn_left->isKind(PNK_POSHOLDER));
+ MOZ_ASSERT(pn->pn_right->isKind(PNK_POSHOLDER));
+ return true;
+
+ case PNK_CLASSNAMES:
+ MOZ_ASSERT(pn->isArity(PN_BINARY));
+ if (ParseNode*& outerBinding = pn->pn_left) {
+ if (!Fold(cx, &outerBinding, parser, inGenexpLambda))
+ return false;
+ }
+ return Fold(cx, &pn->pn_right, parser, inGenexpLambda);
+
+ case PNK_DOWHILE:
+ MOZ_ASSERT(pn->isArity(PN_BINARY));
+ return Fold(cx, &pn->pn_left, parser, inGenexpLambda) &&
+ FoldCondition(cx, &pn->pn_right, parser, inGenexpLambda);
+
+ case PNK_WHILE:
+ MOZ_ASSERT(pn->isArity(PN_BINARY));
+ return FoldCondition(cx, &pn->pn_left, parser, inGenexpLambda) &&
+ Fold(cx, &pn->pn_right, parser, inGenexpLambda);
+
+ case PNK_CASE: {
+ MOZ_ASSERT(pn->isArity(PN_BINARY));
+
+ // pn_left is null for DefaultClauses.
+ if (pn->pn_left) {
+ if (!Fold(cx, &pn->pn_left, parser, inGenexpLambda))
+ return false;
+ }
+ return Fold(cx, &pn->pn_right, parser, inGenexpLambda);
+ }
+
+ case PNK_WITH:
+ MOZ_ASSERT(pn->isArity(PN_BINARY));
+ return Fold(cx, &pn->pn_left, parser, inGenexpLambda) &&
+ Fold(cx, &pn->pn_right, parser, inGenexpLambda);
+
+ case PNK_FORIN:
+ case PNK_FOROF:
+ return FoldForInOrOf(cx, pn, parser, inGenexpLambda);
+
+ case PNK_FORHEAD:
+ return FoldForHead(cx, pn, parser, inGenexpLambda);
+
+ case PNK_LABEL:
+ MOZ_ASSERT(pn->isArity(PN_NAME));
+ return Fold(cx, &pn->pn_expr, parser, inGenexpLambda);
+
+ case PNK_DOT:
+ return FoldDottedProperty(cx, pn, parser, inGenexpLambda);
+
+ case PNK_LEXICALSCOPE:
+ MOZ_ASSERT(pn->isArity(PN_SCOPE));
+ if (!pn->scopeBody())
+ return true;
+ return Fold(cx, &pn->pn_u.scope.body, parser, inGenexpLambda);
+
+ case PNK_NAME:
+ return FoldName(cx, pn, parser, inGenexpLambda);
+
+ case PNK_LIMIT: // invalid sentinel value
+ MOZ_CRASH("invalid node kind");
+ }
+
+ MOZ_CRASH("shouldn't reach here");
+ return false;
+}
+
+bool
+frontend::FoldConstants(ExclusiveContext* cx, ParseNode** pnp, Parser<FullParseHandler>* parser)
+{
+ // Don't constant-fold inside "use asm" code, as this could create a parse
+ // tree that doesn't type-check as asm.js.
+ if (parser->pc->useAsmOrInsideUseAsm())
+ return true;
+
+ return Fold(cx, pnp, *parser, false);
+}
diff --git a/js/src/frontend/FoldConstants.h b/js/src/frontend/FoldConstants.h
new file mode 100644
index 0000000000..274daaaaef
--- /dev/null
+++ b/js/src/frontend/FoldConstants.h
@@ -0,0 +1,41 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sts=4 et sw=4 tw=99:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef frontend_FoldConstants_h
+#define frontend_FoldConstants_h
+
+#include "frontend/SyntaxParseHandler.h"
+
+namespace js {
+namespace frontend {
+
+// Perform constant folding on the given AST. For example, the program
+// `print(2 + 2)` would become `print(4)`.
+//
+// pnp is the address of a pointer variable that points to the root node of the
+// AST. On success, *pnp points to the root node of the new tree, which may be
+// the same node (unchanged or modified in place) or a new node.
+//
+// Usage:
+// pn = parser->statement();
+// if (!pn)
+// return false;
+// if (!FoldConstants(cx, &pn, parser))
+// return false;
+MOZ_MUST_USE bool
+FoldConstants(ExclusiveContext* cx, ParseNode** pnp, Parser<FullParseHandler>* parser);
+
+inline MOZ_MUST_USE bool
+FoldConstants(ExclusiveContext* cx, SyntaxParseHandler::Node* pnp,
+ Parser<SyntaxParseHandler>* parser)
+{
+ return true;
+}
+
+} /* namespace frontend */
+} /* namespace js */
+
+#endif /* frontend_FoldConstants_h */
diff --git a/js/src/frontend/FullParseHandler.h b/js/src/frontend/FullParseHandler.h
new file mode 100644
index 0000000000..add881900a
--- /dev/null
+++ b/js/src/frontend/FullParseHandler.h
@@ -0,0 +1,977 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sts=4 et sw=4 tw=99:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef frontend_FullParseHandler_h
+#define frontend_FullParseHandler_h
+
+#include "mozilla/Attributes.h"
+#include "mozilla/PodOperations.h"
+
+#include "frontend/ParseNode.h"
+#include "frontend/SharedContext.h"
+
+namespace js {
+namespace frontend {
+
+template <typename ParseHandler>
+class Parser;
+
+class SyntaxParseHandler;
+
+// Parse handler used when generating a full parse tree for all code which the
+// parser encounters.
+class FullParseHandler
+{
+ ParseNodeAllocator allocator;
+ TokenStream& tokenStream;
+
+ ParseNode* allocParseNode(size_t size) {
+ MOZ_ASSERT(size == sizeof(ParseNode));
+ return static_cast<ParseNode*>(allocator.allocNode());
+ }
+
+ ParseNode* cloneNode(const ParseNode& other) {
+ ParseNode* node = allocParseNode(sizeof(ParseNode));
+ if (!node)
+ return nullptr;
+ mozilla::PodAssign(node, &other);
+ return node;
+ }
+
+ /*
+ * If this is a full parse to construct the bytecode for a function that
+ * was previously lazily parsed, that lazy function and the current index
+ * into its inner functions. We do not want to reparse the inner functions.
+ */
+ const Rooted<LazyScript*> lazyOuterFunction_;
+ size_t lazyInnerFunctionIndex;
+ size_t lazyClosedOverBindingIndex;
+
+ const TokenPos& pos() {
+ return tokenStream.currentToken().pos;
+ }
+
+ public:
+
+ /*
+ * If non-nullptr, points to a syntax parser which can be used for inner
+ * functions. Cleared if language features not handled by the syntax parser
+ * are encountered, in which case all future activity will use the full
+ * parser.
+ */
+ Parser<SyntaxParseHandler>* syntaxParser;
+
+ /* new_ methods for creating parse nodes. These report OOM on context. */
+ JS_DECLARE_NEW_METHODS(new_, allocParseNode, inline)
+
+ typedef ParseNode* Node;
+
+ bool isPropertyAccess(ParseNode* node) {
+ return node->isKind(PNK_DOT) || node->isKind(PNK_ELEM);
+ }
+
+ bool isFunctionCall(ParseNode* node) {
+ // Note: super() is a special form, *not* a function call.
+ return node->isKind(PNK_CALL);
+ }
+
+ static bool isUnparenthesizedDestructuringPattern(ParseNode* node) {
+ return !node->isInParens() && (node->isKind(PNK_OBJECT) || node->isKind(PNK_ARRAY));
+ }
+
+ static bool isParenthesizedDestructuringPattern(ParseNode* 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(PNK_OBJECT) || node->isKind(PNK_ARRAY));
+ }
+
+ static bool isDestructuringPatternAnyParentheses(ParseNode* node) {
+ return isUnparenthesizedDestructuringPattern(node) ||
+ isParenthesizedDestructuringPattern(node);
+ }
+
+ FullParseHandler(ExclusiveContext* cx, LifoAlloc& alloc,
+ TokenStream& tokenStream, Parser<SyntaxParseHandler>* syntaxParser,
+ LazyScript* lazyOuterFunction)
+ : allocator(cx, alloc),
+ tokenStream(tokenStream),
+ lazyOuterFunction_(cx, lazyOuterFunction),
+ lazyInnerFunctionIndex(0),
+ lazyClosedOverBindingIndex(0),
+ syntaxParser(syntaxParser)
+ {}
+
+ static ParseNode* null() { return nullptr; }
+
+ ParseNode* freeTree(ParseNode* pn) { return allocator.freeTree(pn); }
+ void prepareNodeForMutation(ParseNode* pn) { return allocator.prepareNodeForMutation(pn); }
+ const Token& currentToken() { return tokenStream.currentToken(); }
+
+ ParseNode* newName(PropertyName* name, const TokenPos& pos, ExclusiveContext* cx)
+ {
+ return new_<NameNode>(PNK_NAME, JSOP_GETNAME, name, pos);
+ }
+
+ ParseNode* newComputedName(ParseNode* expr, uint32_t begin, uint32_t end) {
+ TokenPos pos(begin, end);
+ return new_<UnaryNode>(PNK_COMPUTED_NAME, JSOP_NOP, pos, expr);
+ }
+
+ ParseNode* newObjectLiteralPropertyName(JSAtom* atom, const TokenPos& pos) {
+ return new_<NullaryNode>(PNK_OBJECT_PROPERTY_NAME, JSOP_NOP, pos, atom);
+ }
+
+ ParseNode* newNumber(double value, DecimalPoint decimalPoint, const TokenPos& pos) {
+ ParseNode* pn = new_<NullaryNode>(PNK_NUMBER, pos);
+ if (!pn)
+ return nullptr;
+ pn->initNumber(value, decimalPoint);
+ return pn;
+ }
+
+ ParseNode* newBooleanLiteral(bool cond, const TokenPos& pos) {
+ return new_<BooleanLiteral>(cond, pos);
+ }
+
+ ParseNode* newStringLiteral(JSAtom* atom, const TokenPos& pos) {
+ return new_<NullaryNode>(PNK_STRING, JSOP_NOP, pos, atom);
+ }
+
+ ParseNode* newTemplateStringLiteral(JSAtom* atom, const TokenPos& pos) {
+ return new_<NullaryNode>(PNK_TEMPLATE_STRING, JSOP_NOP, pos, atom);
+ }
+
+ ParseNode* newCallSiteObject(uint32_t begin) {
+ ParseNode* callSite = new_<CallSiteNode>(begin);
+ if (!callSite)
+ return null();
+
+ Node propExpr = newArrayLiteral(getPosition(callSite).begin);
+ if (!propExpr)
+ return null();
+
+ addArrayElement(callSite, propExpr);
+
+ return callSite;
+ }
+
+ void addToCallSiteObject(ParseNode* callSiteObj, ParseNode* rawNode, ParseNode* cookedNode) {
+ MOZ_ASSERT(callSiteObj->isKind(PNK_CALLSITEOBJ));
+
+ addArrayElement(callSiteObj, cookedNode);
+ addArrayElement(callSiteObj->pn_head, 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->pn_head);
+ }
+
+ ParseNode* newThisLiteral(const TokenPos& pos, ParseNode* thisName) {
+ return new_<ThisLiteral>(pos, thisName);
+ }
+
+ ParseNode* newNullLiteral(const TokenPos& pos) {
+ return new_<NullLiteral>(pos);
+ }
+
+ // The Boxer object here is any object that can allocate ObjectBoxes.
+ // Specifically, a Boxer has a .newObjectBox(T) method that accepts a
+ // Rooted<RegExpObject*> argument and returns an ObjectBox*.
+ template <class Boxer>
+ ParseNode* newRegExp(RegExpObject* reobj, const TokenPos& pos, Boxer& boxer) {
+ ObjectBox* objbox = boxer.newObjectBox(reobj);
+ if (!objbox)
+ return null();
+ return new_<RegExpLiteral>(objbox, pos);
+ }
+
+ ParseNode* newConditional(ParseNode* cond, ParseNode* thenExpr, ParseNode* elseExpr) {
+ return new_<ConditionalExpression>(cond, thenExpr, elseExpr);
+ }
+
+ ParseNode* newDelete(uint32_t begin, ParseNode* expr) {
+ if (expr->isKind(PNK_NAME)) {
+ expr->setOp(JSOP_DELNAME);
+ return newUnary(PNK_DELETENAME, JSOP_NOP, begin, expr);
+ }
+
+ if (expr->isKind(PNK_DOT))
+ return newUnary(PNK_DELETEPROP, JSOP_NOP, begin, expr);
+
+ if (expr->isKind(PNK_ELEM))
+ return newUnary(PNK_DELETEELEM, JSOP_NOP, begin, expr);
+
+ return newUnary(PNK_DELETEEXPR, JSOP_NOP, begin, expr);
+ }
+
+ ParseNode* newTypeof(uint32_t begin, ParseNode* kid) {
+ TokenPos pos(begin, kid->pn_pos.end);
+ ParseNodeKind kind = kid->isKind(PNK_NAME) ? PNK_TYPEOFNAME : PNK_TYPEOFEXPR;
+ return new_<UnaryNode>(kind, JSOP_NOP, pos, kid);
+ }
+
+ ParseNode* newNullary(ParseNodeKind kind, JSOp op, const TokenPos& pos) {
+ return new_<NullaryNode>(kind, op, pos);
+ }
+
+ ParseNode* newUnary(ParseNodeKind kind, JSOp op, uint32_t begin, ParseNode* kid) {
+ TokenPos pos(begin, kid ? kid->pn_pos.end : begin + 1);
+ return new_<UnaryNode>(kind, op, pos, kid);
+ }
+
+ ParseNode* newUpdate(ParseNodeKind kind, uint32_t begin, ParseNode* kid) {
+ TokenPos pos(begin, kid->pn_pos.end);
+ return new_<UnaryNode>(kind, JSOP_NOP, pos, kid);
+ }
+
+ ParseNode* newSpread(uint32_t begin, ParseNode* kid) {
+ TokenPos pos(begin, kid->pn_pos.end);
+ return new_<UnaryNode>(PNK_SPREAD, JSOP_NOP, pos, kid);
+ }
+
+ ParseNode* newArrayPush(uint32_t begin, ParseNode* kid) {
+ TokenPos pos(begin, kid->pn_pos.end);
+ return new_<UnaryNode>(PNK_ARRAYPUSH, JSOP_ARRAYPUSH, pos, kid);
+ }
+
+ ParseNode* newBinary(ParseNodeKind kind, JSOp op = JSOP_NOP) {
+ return new_<BinaryNode>(kind, op, pos(), (ParseNode*) nullptr, (ParseNode*) nullptr);
+ }
+ ParseNode* newBinary(ParseNodeKind kind, ParseNode* left,
+ JSOp op = JSOP_NOP) {
+ return new_<BinaryNode>(kind, op, left->pn_pos, left, (ParseNode*) nullptr);
+ }
+ ParseNode* newBinary(ParseNodeKind kind, ParseNode* left, ParseNode* right,
+ JSOp op = JSOP_NOP) {
+ TokenPos pos(left->pn_pos.begin, right->pn_pos.end);
+ return new_<BinaryNode>(kind, op, pos, left, right);
+ }
+ ParseNode* appendOrCreateList(ParseNodeKind kind, ParseNode* left, ParseNode* right,
+ ParseContext* pc, JSOp op = JSOP_NOP)
+ {
+ return ParseNode::appendOrCreateList(kind, op, left, right, this, pc);
+ }
+
+ ParseNode* newTernary(ParseNodeKind kind,
+ ParseNode* first, ParseNode* second, ParseNode* third,
+ JSOp op = JSOP_NOP)
+ {
+ return new_<TernaryNode>(kind, op, first, second, third);
+ }
+
+ // Expressions
+
+ ParseNode* newArrayComprehension(ParseNode* body, const TokenPos& pos) {
+ MOZ_ASSERT(pos.begin <= body->pn_pos.begin);
+ MOZ_ASSERT(body->pn_pos.end <= pos.end);
+ ParseNode* pn = new_<ListNode>(PNK_ARRAYCOMP, pos);
+ if (!pn)
+ return nullptr;
+ pn->append(body);
+ return pn;
+ }
+
+ ParseNode* newArrayLiteral(uint32_t begin) {
+ ParseNode* literal = new_<ListNode>(PNK_ARRAY, TokenPos(begin, begin + 1));
+ // Later in this stack: remove dependency on this opcode.
+ if (literal)
+ literal->setOp(JSOP_NEWINIT);
+ return literal;
+ }
+
+ MOZ_MUST_USE bool addElision(ParseNode* literal, const TokenPos& pos) {
+ ParseNode* elision = new_<NullaryNode>(PNK_ELISION, pos);
+ if (!elision)
+ return false;
+ literal->append(elision);
+ literal->pn_xflags |= PNX_ARRAYHOLESPREAD | PNX_NONCONST;
+ return true;
+ }
+
+ MOZ_MUST_USE bool addSpreadElement(ParseNode* literal, uint32_t begin, ParseNode* inner) {
+ TokenPos pos(begin, inner->pn_pos.end);
+ ParseNode* spread = new_<UnaryNode>(PNK_SPREAD, JSOP_NOP, pos, inner);
+ if (!spread)
+ return null();
+ literal->append(spread);
+ literal->pn_xflags |= PNX_ARRAYHOLESPREAD | PNX_NONCONST;
+ return true;
+ }
+
+ void addArrayElement(ParseNode* literal, ParseNode* element) {
+ if (!element->isConstant())
+ literal->pn_xflags |= PNX_NONCONST;
+ literal->append(element);
+ }
+
+ ParseNode* newCall() {
+ return newList(PNK_CALL, JSOP_CALL);
+ }
+
+ ParseNode* newTaggedTemplate() {
+ return newList(PNK_TAGGED_TEMPLATE, JSOP_CALL);
+ }
+
+ ParseNode* newObjectLiteral(uint32_t begin) {
+ ParseNode* literal = new_<ListNode>(PNK_OBJECT, TokenPos(begin, begin + 1));
+ // Later in this stack: remove dependency on this opcode.
+ if (literal)
+ literal->setOp(JSOP_NEWINIT);
+ return literal;
+ }
+
+ ParseNode* newClass(ParseNode* name, ParseNode* heritage, ParseNode* methodBlock) {
+ return new_<ClassNode>(name, heritage, methodBlock);
+ }
+ ParseNode* newClassMethodList(uint32_t begin) {
+ return new_<ListNode>(PNK_CLASSMETHODLIST, TokenPos(begin, begin + 1));
+ }
+ ParseNode* newClassNames(ParseNode* outer, ParseNode* inner, const TokenPos& pos) {
+ return new_<ClassNames>(outer, inner, pos);
+ }
+ ParseNode* newNewTarget(ParseNode* newHolder, ParseNode* targetHolder) {
+ return new_<BinaryNode>(PNK_NEWTARGET, JSOP_NOP, newHolder, targetHolder);
+ }
+ ParseNode* newPosHolder(const TokenPos& pos) {
+ return new_<NullaryNode>(PNK_POSHOLDER, pos);
+ }
+ ParseNode* newSuperBase(ParseNode* thisName, const TokenPos& pos) {
+ return new_<UnaryNode>(PNK_SUPERBASE, JSOP_NOP, pos, thisName);
+ }
+
+ MOZ_MUST_USE bool addPrototypeMutation(ParseNode* literal, uint32_t begin, ParseNode* expr) {
+ // Object literals with mutated [[Prototype]] are non-constant so that
+ // singleton objects will have Object.prototype as their [[Prototype]].
+ setListFlag(literal, PNX_NONCONST);
+
+ ParseNode* mutation = newUnary(PNK_MUTATEPROTO, JSOP_NOP, begin, expr);
+ if (!mutation)
+ return false;
+ literal->append(mutation);
+ return true;
+ }
+
+ MOZ_MUST_USE bool addPropertyDefinition(ParseNode* literal, ParseNode* key, ParseNode* val) {
+ MOZ_ASSERT(literal->isKind(PNK_OBJECT));
+ MOZ_ASSERT(literal->isArity(PN_LIST));
+ MOZ_ASSERT(key->isKind(PNK_NUMBER) ||
+ key->isKind(PNK_OBJECT_PROPERTY_NAME) ||
+ key->isKind(PNK_STRING) ||
+ key->isKind(PNK_COMPUTED_NAME));
+
+ ParseNode* propdef = newBinary(PNK_COLON, key, val, JSOP_INITPROP);
+ if (!propdef)
+ return false;
+ literal->append(propdef);
+ return true;
+ }
+
+ MOZ_MUST_USE bool addShorthand(ParseNode* literal, ParseNode* name, ParseNode* expr) {
+ MOZ_ASSERT(literal->isKind(PNK_OBJECT));
+ MOZ_ASSERT(literal->isArity(PN_LIST));
+ MOZ_ASSERT(name->isKind(PNK_OBJECT_PROPERTY_NAME));
+ MOZ_ASSERT(expr->isKind(PNK_NAME));
+ MOZ_ASSERT(name->pn_atom == expr->pn_atom);
+
+ setListFlag(literal, PNX_NONCONST);
+ ParseNode* propdef = newBinary(PNK_SHORTHAND, name, expr, JSOP_INITPROP);
+ if (!propdef)
+ return false;
+ literal->append(propdef);
+ return true;
+ }
+
+ MOZ_MUST_USE bool addObjectMethodDefinition(ParseNode* literal, ParseNode* key, ParseNode* fn,
+ JSOp op)
+ {
+ MOZ_ASSERT(literal->isArity(PN_LIST));
+ MOZ_ASSERT(key->isKind(PNK_NUMBER) ||
+ key->isKind(PNK_OBJECT_PROPERTY_NAME) ||
+ key->isKind(PNK_STRING) ||
+ key->isKind(PNK_COMPUTED_NAME));
+ literal->pn_xflags |= PNX_NONCONST;
+
+ ParseNode* propdef = newBinary(PNK_COLON, key, fn, op);
+ if (!propdef)
+ return false;
+ literal->append(propdef);
+ return true;
+ }
+
+ MOZ_MUST_USE bool addClassMethodDefinition(ParseNode* methodList, ParseNode* key, ParseNode* fn,
+ JSOp op, bool isStatic)
+ {
+ MOZ_ASSERT(methodList->isKind(PNK_CLASSMETHODLIST));
+ MOZ_ASSERT(key->isKind(PNK_NUMBER) ||
+ key->isKind(PNK_OBJECT_PROPERTY_NAME) ||
+ key->isKind(PNK_STRING) ||
+ key->isKind(PNK_COMPUTED_NAME));
+
+ ParseNode* classMethod = new_<ClassMethod>(key, fn, op, isStatic);
+ if (!classMethod)
+ return false;
+ methodList->append(classMethod);
+ return true;
+ }
+
+ ParseNode* newYieldExpression(uint32_t begin, ParseNode* value, ParseNode* gen,
+ JSOp op = JSOP_YIELD) {
+ TokenPos pos(begin, value ? value->pn_pos.end : begin + 1);
+ return new_<BinaryNode>(PNK_YIELD, op, pos, value, gen);
+ }
+
+ ParseNode* newYieldStarExpression(uint32_t begin, ParseNode* value, ParseNode* gen) {
+ TokenPos pos(begin, value->pn_pos.end);
+ return new_<BinaryNode>(PNK_YIELD_STAR, JSOP_NOP, pos, value, gen);
+ }
+
+ ParseNode* newAwaitExpression(uint32_t begin, ParseNode* value, ParseNode* gen) {
+ TokenPos pos(begin, value ? value->pn_pos.end : begin + 1);
+ return new_<BinaryNode>(PNK_AWAIT, JSOP_YIELD, pos, value, gen);
+ }
+
+ // Statements
+
+ ParseNode* newStatementList(const TokenPos& pos) {
+ return new_<ListNode>(PNK_STATEMENTLIST, pos);
+ }
+
+ MOZ_MUST_USE bool isFunctionStmt(ParseNode* stmt) {
+ while (stmt->isKind(PNK_LABEL))
+ stmt = stmt->as<LabeledStatement>().statement();
+ return stmt->isKind(PNK_FUNCTION);
+ }
+
+ void addStatementToList(ParseNode* list, ParseNode* stmt) {
+ MOZ_ASSERT(list->isKind(PNK_STATEMENTLIST));
+
+ list->append(stmt);
+
+ if (isFunctionStmt(stmt)) {
+ // PNX_FUNCDEFS notifies the emitter that the block contains
+ // body-level function definitions that should be processed
+ // before the rest of nodes.
+ list->pn_xflags |= PNX_FUNCDEFS;
+ }
+ }
+
+ void addCaseStatementToList(ParseNode* list, ParseNode* casepn) {
+ MOZ_ASSERT(list->isKind(PNK_STATEMENTLIST));
+ MOZ_ASSERT(casepn->isKind(PNK_CASE));
+ MOZ_ASSERT(casepn->pn_right->isKind(PNK_STATEMENTLIST));
+
+ list->append(casepn);
+
+ if (casepn->pn_right->pn_xflags & PNX_FUNCDEFS)
+ list->pn_xflags |= PNX_FUNCDEFS;
+ }
+
+ MOZ_MUST_USE bool prependInitialYield(ParseNode* stmtList, ParseNode* genName) {
+ MOZ_ASSERT(stmtList->isKind(PNK_STATEMENTLIST));
+
+ TokenPos yieldPos(stmtList->pn_pos.begin, stmtList->pn_pos.begin + 1);
+ ParseNode* makeGen = new_<NullaryNode>(PNK_GENERATOR, yieldPos);
+ if (!makeGen)
+ return false;
+
+ MOZ_ASSERT(genName->getOp() == JSOP_GETNAME);
+ genName->setOp(JSOP_SETNAME);
+ ParseNode* genInit = newBinary(PNK_ASSIGN, genName, makeGen);
+ if (!genInit)
+ return false;
+
+ ParseNode* initialYield = newYieldExpression(yieldPos.begin, nullptr, genInit,
+ JSOP_INITIALYIELD);
+ if (!initialYield)
+ return false;
+
+ stmtList->prepend(initialYield);
+ return true;
+ }
+
+ ParseNode* newSetThis(ParseNode* thisName, ParseNode* val) {
+ MOZ_ASSERT(thisName->getOp() == JSOP_GETNAME);
+ thisName->setOp(JSOP_SETNAME);
+ return newBinary(PNK_SETTHIS, thisName, val);
+ }
+
+ ParseNode* newEmptyStatement(const TokenPos& pos) {
+ return new_<UnaryNode>(PNK_SEMI, JSOP_NOP, pos, (ParseNode*) nullptr);
+ }
+
+ ParseNode* newImportDeclaration(ParseNode* importSpecSet,
+ ParseNode* moduleSpec, const TokenPos& pos)
+ {
+ ParseNode* pn = new_<BinaryNode>(PNK_IMPORT, JSOP_NOP, pos,
+ importSpecSet, moduleSpec);
+ if (!pn)
+ return null();
+ return pn;
+ }
+
+ ParseNode* newExportDeclaration(ParseNode* kid, const TokenPos& pos) {
+ return new_<UnaryNode>(PNK_EXPORT, JSOP_NOP, pos, kid);
+ }
+
+ ParseNode* newExportFromDeclaration(uint32_t begin, ParseNode* exportSpecSet,
+ ParseNode* moduleSpec)
+ {
+ ParseNode* pn = new_<BinaryNode>(PNK_EXPORT_FROM, JSOP_NOP, exportSpecSet, moduleSpec);
+ if (!pn)
+ return null();
+ pn->pn_pos.begin = begin;
+ return pn;
+ }
+
+ ParseNode* newExportDefaultDeclaration(ParseNode* kid, ParseNode* maybeBinding,
+ const TokenPos& pos) {
+ return new_<BinaryNode>(PNK_EXPORT_DEFAULT, JSOP_NOP, pos, kid, maybeBinding);
+ }
+
+ ParseNode* newExprStatement(ParseNode* expr, uint32_t end) {
+ MOZ_ASSERT(expr->pn_pos.end <= end);
+ return new_<UnaryNode>(PNK_SEMI, JSOP_NOP, TokenPos(expr->pn_pos.begin, end), expr);
+ }
+
+ ParseNode* newIfStatement(uint32_t begin, ParseNode* cond, ParseNode* thenBranch,
+ ParseNode* elseBranch)
+ {
+ ParseNode* pn = new_<TernaryNode>(PNK_IF, JSOP_NOP, cond, thenBranch, elseBranch);
+ if (!pn)
+ return null();
+ pn->pn_pos.begin = begin;
+ return pn;
+ }
+
+ ParseNode* newDoWhileStatement(ParseNode* body, ParseNode* cond, const TokenPos& pos) {
+ return new_<BinaryNode>(PNK_DOWHILE, JSOP_NOP, pos, body, cond);
+ }
+
+ ParseNode* newWhileStatement(uint32_t begin, ParseNode* cond, ParseNode* body) {
+ TokenPos pos(begin, body->pn_pos.end);
+ return new_<BinaryNode>(PNK_WHILE, JSOP_NOP, pos, cond, body);
+ }
+
+ ParseNode* newForStatement(uint32_t begin, ParseNode* forHead, ParseNode* body,
+ unsigned iflags)
+ {
+ /* A FOR node is binary, left is loop control and right is the body. */
+ JSOp op = forHead->isKind(PNK_FORIN) ? JSOP_ITER : JSOP_NOP;
+ BinaryNode* pn = new_<BinaryNode>(PNK_FOR, op, TokenPos(begin, body->pn_pos.end),
+ forHead, body);
+ if (!pn)
+ return null();
+ pn->pn_iflags = iflags;
+ return pn;
+ }
+
+ ParseNode* newComprehensionFor(uint32_t begin, ParseNode* forHead, ParseNode* body) {
+ // A PNK_COMPREHENSIONFOR node is binary: left is loop control, right
+ // is the body.
+ MOZ_ASSERT(forHead->isKind(PNK_FORIN) || forHead->isKind(PNK_FOROF));
+ JSOp op = forHead->isKind(PNK_FORIN) ? JSOP_ITER : JSOP_NOP;
+ BinaryNode* pn = new_<BinaryNode>(PNK_COMPREHENSIONFOR, op,
+ TokenPos(begin, body->pn_pos.end), forHead, body);
+ if (!pn)
+ return null();
+ pn->pn_iflags = JSOP_ITER;
+ return pn;
+ }
+
+ ParseNode* newComprehensionBinding(ParseNode* kid) {
+ MOZ_ASSERT(kid->isKind(PNK_NAME));
+ return new_<ListNode>(PNK_LET, JSOP_NOP, kid);
+ }
+
+ ParseNode* newForHead(ParseNode* init, ParseNode* test, ParseNode* update,
+ const TokenPos& pos)
+ {
+ return new_<TernaryNode>(PNK_FORHEAD, JSOP_NOP, init, test, update, pos);
+ }
+
+ ParseNode* newForInOrOfHead(ParseNodeKind kind, ParseNode* target, ParseNode* iteratedExpr,
+ const TokenPos& pos)
+ {
+ MOZ_ASSERT(kind == PNK_FORIN || kind == PNK_FOROF);
+ return new_<TernaryNode>(kind, JSOP_NOP, target, nullptr, iteratedExpr, pos);
+ }
+
+ ParseNode* newSwitchStatement(uint32_t begin, ParseNode* discriminant, ParseNode* caseList) {
+ TokenPos pos(begin, caseList->pn_pos.end);
+ return new_<BinaryNode>(PNK_SWITCH, JSOP_NOP, pos, discriminant, caseList);
+ }
+
+ ParseNode* newCaseOrDefault(uint32_t begin, ParseNode* expr, ParseNode* body) {
+ return new_<CaseClause>(expr, body, begin);
+ }
+
+ ParseNode* newContinueStatement(PropertyName* label, const TokenPos& pos) {
+ return new_<ContinueStatement>(label, pos);
+ }
+
+ ParseNode* newBreakStatement(PropertyName* label, const TokenPos& pos) {
+ return new_<BreakStatement>(label, pos);
+ }
+
+ ParseNode* newReturnStatement(ParseNode* expr, const TokenPos& pos) {
+ MOZ_ASSERT_IF(expr, pos.encloses(expr->pn_pos));
+ return new_<UnaryNode>(PNK_RETURN, JSOP_RETURN, pos, expr);
+ }
+
+ ParseNode* newWithStatement(uint32_t begin, ParseNode* expr, ParseNode* body) {
+ return new_<BinaryNode>(PNK_WITH, JSOP_NOP, TokenPos(begin, body->pn_pos.end),
+ expr, body);
+ }
+
+ ParseNode* newLabeledStatement(PropertyName* label, ParseNode* stmt, uint32_t begin) {
+ return new_<LabeledStatement>(label, stmt, begin);
+ }
+
+ ParseNode* newThrowStatement(ParseNode* expr, const TokenPos& pos) {
+ MOZ_ASSERT(pos.encloses(expr->pn_pos));
+ return new_<UnaryNode>(PNK_THROW, JSOP_THROW, pos, expr);
+ }
+
+ ParseNode* newTryStatement(uint32_t begin, ParseNode* body, ParseNode* catchList,
+ ParseNode* finallyBlock) {
+ TokenPos pos(begin, (finallyBlock ? finallyBlock : catchList)->pn_pos.end);
+ return new_<TernaryNode>(PNK_TRY, JSOP_NOP, body, catchList, finallyBlock, pos);
+ }
+
+ ParseNode* newDebuggerStatement(const TokenPos& pos) {
+ return new_<DebuggerStatement>(pos);
+ }
+
+ ParseNode* newPropertyAccess(ParseNode* pn, PropertyName* name, uint32_t end) {
+ return new_<PropertyAccess>(pn, name, pn->pn_pos.begin, end);
+ }
+
+ ParseNode* newPropertyByValue(ParseNode* lhs, ParseNode* index, uint32_t end) {
+ return new_<PropertyByValue>(lhs, index, lhs->pn_pos.begin, end);
+ }
+
+ inline MOZ_MUST_USE bool addCatchBlock(ParseNode* catchList, ParseNode* lexicalScope,
+ ParseNode* catchName, ParseNode* catchGuard,
+ ParseNode* catchBody);
+
+ inline MOZ_MUST_USE bool setLastFunctionFormalParameterDefault(ParseNode* funcpn,
+ ParseNode* pn);
+ inline void setLastFunctionFormalParameterDestructuring(ParseNode* funcpn, ParseNode* pn);
+
+ ParseNode* newFunctionDefinition() {
+ return new_<CodeNode>(PNK_FUNCTION, pos());
+ }
+ bool setComprehensionLambdaBody(ParseNode* pn, ParseNode* body) {
+ MOZ_ASSERT(body->isKind(PNK_STATEMENTLIST));
+ ParseNode* paramsBody = newList(PNK_PARAMSBODY, body);
+ if (!paramsBody)
+ return false;
+ setFunctionFormalParametersAndBody(pn, paramsBody);
+ return true;
+ }
+ void setFunctionFormalParametersAndBody(ParseNode* pn, ParseNode* kid) {
+ MOZ_ASSERT_IF(kid, kid->isKind(PNK_PARAMSBODY));
+ pn->pn_body = kid;
+ }
+ void setFunctionBox(ParseNode* pn, FunctionBox* funbox) {
+ MOZ_ASSERT(pn->isKind(PNK_FUNCTION));
+ pn->pn_funbox = funbox;
+ funbox->functionNode = pn;
+ }
+ void addFunctionFormalParameter(ParseNode* pn, ParseNode* argpn) {
+ pn->pn_body->append(argpn);
+ }
+ void setFunctionBody(ParseNode* fn, ParseNode* body) {
+ MOZ_ASSERT(fn->pn_body->isKind(PNK_PARAMSBODY));
+ fn->pn_body->append(body);
+ }
+
+ ParseNode* newModule() {
+ return new_<CodeNode>(PNK_MODULE, pos());
+ }
+
+ ParseNode* newLexicalScope(LexicalScope::Data* bindings, ParseNode* body) {
+ return new_<LexicalScopeNode>(bindings, body);
+ }
+
+ ParseNode* newAssignment(ParseNodeKind kind, ParseNode* lhs, ParseNode* rhs,
+ JSOp op)
+ {
+ return newBinary(kind, lhs, rhs, op);
+ }
+
+ bool isUnparenthesizedYieldExpression(ParseNode* node) {
+ return node->isKind(PNK_YIELD) && !node->isInParens();
+ }
+
+ bool isUnparenthesizedCommaExpression(ParseNode* node) {
+ return node->isKind(PNK_COMMA) && !node->isInParens();
+ }
+
+ bool isUnparenthesizedAssignment(Node node) {
+ if (node->isKind(PNK_ASSIGN) && !node->isInParens()) {
+ // PNK_ASSIGN is also (mis)used for things like |var name = expr;|.
+ // But this method is only called on actual expressions, so we can
+ // just assert the node's op is the one used for plain assignment.
+ MOZ_ASSERT(node->isOp(JSOP_NOP));
+ return true;
+ }
+
+ return false;
+ }
+
+ bool isUnparenthesizedUnaryExpression(ParseNode* node) {
+ if (!node->isInParens()) {
+ ParseNodeKind kind = node->getKind();
+ return kind == PNK_VOID || kind == PNK_NOT || kind == PNK_BITNOT || kind == PNK_POS ||
+ kind == PNK_NEG || IsTypeofKind(kind) || IsDeleteKind(kind);
+ }
+ return false;
+ }
+
+ bool isReturnStatement(ParseNode* node) {
+ return node->isKind(PNK_RETURN);
+ }
+
+ bool isStatementPermittedAfterReturnStatement(ParseNode *node) {
+ ParseNodeKind kind = node->getKind();
+ return kind == PNK_FUNCTION || kind == PNK_VAR || kind == PNK_BREAK || kind == PNK_THROW ||
+ (kind == PNK_SEMI && !node->pn_kid);
+ }
+
+ bool isSuperBase(ParseNode* node) {
+ return node->isKind(PNK_SUPERBASE);
+ }
+
+ inline MOZ_MUST_USE bool finishInitializerAssignment(ParseNode* pn, ParseNode* init);
+
+ void setBeginPosition(ParseNode* pn, ParseNode* oth) {
+ setBeginPosition(pn, oth->pn_pos.begin);
+ }
+ void setBeginPosition(ParseNode* pn, uint32_t begin) {
+ pn->pn_pos.begin = begin;
+ MOZ_ASSERT(pn->pn_pos.begin <= pn->pn_pos.end);
+ }
+
+ void setEndPosition(ParseNode* pn, ParseNode* oth) {
+ setEndPosition(pn, oth->pn_pos.end);
+ }
+ void setEndPosition(ParseNode* pn, uint32_t end) {
+ pn->pn_pos.end = end;
+ MOZ_ASSERT(pn->pn_pos.begin <= pn->pn_pos.end);
+ }
+
+ void setPosition(ParseNode* pn, const TokenPos& pos) {
+ pn->pn_pos = pos;
+ }
+ TokenPos getPosition(ParseNode* pn) {
+ return pn->pn_pos;
+ }
+
+ bool isDeclarationKind(ParseNodeKind kind) {
+ return kind == PNK_VAR || kind == PNK_LET || kind == PNK_CONST;
+ }
+
+ ParseNode* newList(ParseNodeKind kind, JSOp op = JSOP_NOP) {
+ MOZ_ASSERT(!isDeclarationKind(kind));
+ return new_<ListNode>(kind, op, pos());
+ }
+
+ ParseNode* newList(ParseNodeKind kind, uint32_t begin, JSOp op = JSOP_NOP) {
+ MOZ_ASSERT(!isDeclarationKind(kind));
+ return new_<ListNode>(kind, op, TokenPos(begin, begin + 1));
+ }
+
+ ParseNode* newList(ParseNodeKind kind, ParseNode* kid, JSOp op = JSOP_NOP) {
+ MOZ_ASSERT(!isDeclarationKind(kind));
+ return new_<ListNode>(kind, op, kid);
+ }
+
+ ParseNode* newDeclarationList(ParseNodeKind kind, JSOp op = JSOP_NOP) {
+ MOZ_ASSERT(isDeclarationKind(kind));
+ return new_<ListNode>(kind, op, pos());
+ }
+
+ ParseNode* newDeclarationList(ParseNodeKind kind, ParseNode* kid, JSOp op = JSOP_NOP) {
+ MOZ_ASSERT(isDeclarationKind(kind));
+ return new_<ListNode>(kind, op, kid);
+ }
+
+ bool isDeclarationList(ParseNode* node) {
+ return isDeclarationKind(node->getKind());
+ }
+
+ ParseNode* singleBindingFromDeclaration(ParseNode* decl) {
+ MOZ_ASSERT(isDeclarationList(decl));
+ MOZ_ASSERT(decl->pn_count == 1);
+ return decl->pn_head;
+ }
+
+ ParseNode* newCatchList() {
+ return new_<ListNode>(PNK_CATCHLIST, JSOP_NOP, pos());
+ }
+
+ ParseNode* newCommaExpressionList(ParseNode* kid) {
+ return newList(PNK_COMMA, kid, JSOP_NOP);
+ }
+
+ void addList(ParseNode* list, ParseNode* kid) {
+ list->append(kid);
+ }
+
+ void setOp(ParseNode* pn, JSOp op) {
+ pn->setOp(op);
+ }
+ void setListFlag(ParseNode* pn, unsigned flag) {
+ MOZ_ASSERT(pn->isArity(PN_LIST));
+ pn->pn_xflags |= flag;
+ }
+ MOZ_MUST_USE ParseNode* parenthesize(ParseNode* pn) {
+ pn->setInParens(true);
+ return pn;
+ }
+ MOZ_MUST_USE ParseNode* setLikelyIIFE(ParseNode* pn) {
+ return parenthesize(pn);
+ }
+ void setPrologue(ParseNode* pn) {
+ pn->pn_prologue = true;
+ }
+
+ bool isConstant(ParseNode* pn) {
+ return pn->isConstant();
+ }
+
+ bool isUnparenthesizedName(ParseNode* node) {
+ return node->isKind(PNK_NAME) && !node->isInParens();
+ }
+
+ bool isNameAnyParentheses(ParseNode* node) {
+ return node->isKind(PNK_NAME);
+ }
+
+ bool nameIsEvalAnyParentheses(ParseNode* node, ExclusiveContext* cx) {
+ MOZ_ASSERT(isNameAnyParentheses(node),
+ "must only call this function on known names");
+
+ return node->pn_atom == cx->names().eval;
+ }
+
+ const char* nameIsArgumentsEvalAnyParentheses(ParseNode* node, ExclusiveContext* cx) {
+ MOZ_ASSERT(isNameAnyParentheses(node),
+ "must only call this function on known names");
+
+ if (nameIsEvalAnyParentheses(node, cx))
+ return js_eval_str;
+ if (node->pn_atom == cx->names().arguments)
+ return js_arguments_str;
+ return nullptr;
+ }
+
+ bool nameIsUnparenthesizedAsync(ParseNode* node, ExclusiveContext* cx) {
+ MOZ_ASSERT(isNameAnyParentheses(node),
+ "must only call this function on known names");
+
+ return node->pn_atom == cx->names().async;
+ }
+
+ bool isCall(ParseNode* pn) {
+ return pn->isKind(PNK_CALL);
+ }
+ PropertyName* maybeDottedProperty(ParseNode* pn) {
+ return pn->is<PropertyAccess>() ? &pn->as<PropertyAccess>().name() : nullptr;
+ }
+ JSAtom* isStringExprStatement(ParseNode* pn, TokenPos* pos) {
+ if (JSAtom* atom = pn->isStringExprStatement()) {
+ *pos = pn->pn_kid->pn_pos;
+ return atom;
+ }
+ return nullptr;
+ }
+
+ void adjustGetToSet(ParseNode* node) {
+ node->setOp(node->isOp(JSOP_GETLOCAL) ? JSOP_SETLOCAL : JSOP_SETNAME);
+ }
+
+ void disableSyntaxParser() {
+ syntaxParser = nullptr;
+ }
+
+ bool canSkipLazyInnerFunctions() {
+ return !!lazyOuterFunction_;
+ }
+ bool canSkipLazyClosedOverBindings() {
+ return !!lazyOuterFunction_;
+ }
+ LazyScript* lazyOuterFunction() {
+ return lazyOuterFunction_;
+ }
+ JSFunction* nextLazyInnerFunction() {
+ MOZ_ASSERT(lazyInnerFunctionIndex < lazyOuterFunction()->numInnerFunctions());
+ return lazyOuterFunction()->innerFunctions()[lazyInnerFunctionIndex++];
+ }
+ JSAtom* nextLazyClosedOverBinding() {
+ MOZ_ASSERT(lazyClosedOverBindingIndex < lazyOuterFunction()->numClosedOverBindings());
+ return lazyOuterFunction()->closedOverBindings()[lazyClosedOverBindingIndex++];
+ }
+};
+
+inline bool
+FullParseHandler::addCatchBlock(ParseNode* catchList, ParseNode* lexicalScope,
+ ParseNode* catchName, ParseNode* catchGuard, ParseNode* catchBody)
+{
+ ParseNode* catchpn = newTernary(PNK_CATCH, catchName, catchGuard, catchBody);
+ if (!catchpn)
+ return false;
+ catchList->append(lexicalScope);
+ lexicalScope->setScopeBody(catchpn);
+ return true;
+}
+
+inline bool
+FullParseHandler::setLastFunctionFormalParameterDefault(ParseNode* funcpn, ParseNode* defaultValue)
+{
+ ParseNode* arg = funcpn->pn_body->last();
+ ParseNode* pn = newBinary(PNK_ASSIGN, arg, defaultValue, JSOP_NOP);
+ if (!pn)
+ return false;
+
+ funcpn->pn_body->pn_pos.end = pn->pn_pos.end;
+ ParseNode* pnchild = funcpn->pn_body->pn_head;
+ ParseNode* pnlast = funcpn->pn_body->last();
+ MOZ_ASSERT(pnchild);
+ if (pnchild == pnlast) {
+ funcpn->pn_body->pn_head = pn;
+ } else {
+ while (pnchild->pn_next != pnlast) {
+ MOZ_ASSERT(pnchild->pn_next);
+ pnchild = pnchild->pn_next;
+ }
+ pnchild->pn_next = pn;
+ }
+ funcpn->pn_body->pn_tail = &pn->pn_next;
+
+ return true;
+}
+
+inline bool
+FullParseHandler::finishInitializerAssignment(ParseNode* pn, ParseNode* init)
+{
+ pn->pn_expr = init;
+ pn->setOp(JSOP_SETNAME);
+
+ /* The declarator's position must include the initializer. */
+ pn->pn_pos.end = init->pn_pos.end;
+ return true;
+}
+
+} // namespace frontend
+} // namespace js
+
+#endif /* frontend_FullParseHandler_h */
diff --git a/js/src/frontend/NameAnalysisTypes.h b/js/src/frontend/NameAnalysisTypes.h
new file mode 100644
index 0000000000..d39e177fba
--- /dev/null
+++ b/js/src/frontend/NameAnalysisTypes.h
@@ -0,0 +1,366 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sts=4 et sw=4 tw=99:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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_NameAnalysis_h
+#define frontend_NameAnalysis_h
+
+#include "jsopcode.h"
+
+#include "vm/Scope.h"
+
+namespace js {
+
+// An "environment coordinate" describes how to get from head of the
+// environment chain to a given lexically-enclosing variable. An environment
+// coordinate has two dimensions:
+// - hops: the number of environment objects on the scope chain to skip
+// - slot: the slot on the environment object holding the variable's value
+class EnvironmentCoordinate
+{
+ uint32_t hops_;
+ uint32_t slot_;
+
+ // Technically, hops_/slot_ are ENVCOORD_(HOPS|SLOT)_BITS wide. Since
+ // EnvironmentCoordinate is a temporary value, don't bother with a bitfield as
+ // this only adds overhead.
+ static_assert(ENVCOORD_HOPS_BITS <= 32, "We have enough bits below");
+ static_assert(ENVCOORD_SLOT_BITS <= 32, "We have enough bits below");
+
+ public:
+ explicit inline EnvironmentCoordinate(jsbytecode* pc)
+ : hops_(GET_ENVCOORD_HOPS(pc)), slot_(GET_ENVCOORD_SLOT(pc + ENVCOORD_HOPS_LEN))
+ {
+ MOZ_ASSERT(JOF_OPTYPE(JSOp(*pc)) == JOF_ENVCOORD);
+ }
+
+ EnvironmentCoordinate() {}
+
+ 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 {
+
+// 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,
+ ForOfVar,
+ Let,
+ Const,
+ Import,
+ BodyLevelFunction,
+ LexicalFunction,
+ VarForAnnexBLexicalFunction,
+ SimpleCatchParameter,
+ CatchParameter
+};
+
+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::VarForAnnexBLexicalFunction:
+ case DeclarationKind::ForOfVar:
+ return BindingKind::Var;
+
+ case DeclarationKind::Let:
+ case DeclarationKind::LexicalFunction:
+ case DeclarationKind::SimpleCatchParameter:
+ case DeclarationKind::CatchParameter:
+ return BindingKind::Let;
+
+ case DeclarationKind::Const:
+ return BindingKind::Const;
+
+ case DeclarationKind::Import:
+ return BindingKind::Import;
+ }
+
+ MOZ_CRASH("Bad DeclarationKind");
+}
+
+static inline bool
+DeclarationKindIsLexical(DeclarationKind kind)
+{
+ return BindingKindIsLexical(DeclarationKindToBindingKind(kind));
+}
+
+// Used in Parser to track declared names.
+class DeclaredNameInfo
+{
+ 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_;
+
+ public:
+ explicit DeclaredNameInfo(DeclarationKind kind)
+ : kind_(kind),
+ closedOver_(false)
+ { }
+
+ // Needed for InlineMap.
+ DeclaredNameInfo() = default;
+
+ DeclarationKind kind() const {
+ return kind_;
+ }
+
+ void alterKind(DeclarationKind kind) {
+ kind_ = kind;
+ }
+
+ void setClosedOver() {
+ closedOver_ = true;
+ }
+
+ bool closedOver() const {
+ return closedOver_;
+ }
+};
+
+// Used in BytecodeEmitter to map names to locations.
+class NameLocation
+{
+ public:
+ enum class Kind : uint8_t
+ {
+ // Cannot statically determine where the name lives. Needs to walk the
+ // environment chain to search for the name.
+ Dynamic,
+
+ // The name lives on the global or is a global lexical binding. Search
+ // for the name on the global scope.
+ Global,
+
+ // Special mode used only when emitting self-hosted scripts. See
+ // BytecodeEmitter::lookupName.
+ Intrinsic,
+
+ // In a named lambda, the name is the callee itself.
+ NamedLambdaCallee,
+
+ // The name is a positional formal parameter name and can be retrieved
+ // directly from the stack using slot_.
+ ArgumentSlot,
+
+ // The name is not closed over and lives on the frame in slot_.
+ FrameSlot,
+
+ // The name is closed over and lives on an environment hops_ away in slot_.
+ EnvironmentCoordinate,
+
+ // An imported name in a module.
+ Import,
+
+ // Cannot statically determine where the synthesized var for Annex
+ // B.3.3 lives.
+ DynamicAnnexBVar
+ };
+
+ private:
+ // Where the name lives.
+ Kind kind_;
+
+ // If the name is not Dynamic or DynamicAnnexBVar, the kind of the
+ // binding.
+ BindingKind bindingKind_;
+
+ // If the name is closed over and accessed via EnvironmentCoordinate, the
+ // number of dynamic environments to skip.
+ //
+ // Otherwise UINT8_MAX.
+ uint8_t hops_;
+
+ // If the name lives on the frame, the slot frame.
+ //
+ // If the name is closed over and accessed via EnvironmentCoordinate, the
+ // slot on the environment.
+ //
+ // Otherwise LOCALNO_LIMIT/ENVCOORD_SLOT_LIMIT.
+ uint32_t slot_ : ENVCOORD_SLOT_BITS;
+
+ static_assert(LOCALNO_BITS == ENVCOORD_SLOT_BITS,
+ "Frame and environment slots must be same sized.");
+
+ NameLocation(Kind kind, BindingKind bindingKind,
+ uint8_t hops = UINT8_MAX, uint32_t slot = ENVCOORD_SLOT_LIMIT)
+ : kind_(kind),
+ bindingKind_(bindingKind),
+ hops_(hops),
+ slot_(slot)
+ { }
+
+ public:
+ // Default constructor for InlineMap.
+ NameLocation() = default;
+
+ static NameLocation Dynamic() {
+ return NameLocation();
+ }
+
+ static NameLocation Global(BindingKind bindKind) {
+ MOZ_ASSERT(bindKind != BindingKind::FormalParameter);
+ return NameLocation(Kind::Global, bindKind);
+ }
+
+ static NameLocation Intrinsic() {
+ return NameLocation(Kind::Intrinsic, BindingKind::Var);
+ }
+
+ static NameLocation NamedLambdaCallee() {
+ return NameLocation(Kind::NamedLambdaCallee, BindingKind::NamedLambdaCallee);
+ }
+
+ static NameLocation ArgumentSlot(uint16_t slot) {
+ return NameLocation(Kind::ArgumentSlot, BindingKind::FormalParameter, 0, slot);
+ }
+
+ static NameLocation FrameSlot(BindingKind bindKind, uint32_t slot) {
+ MOZ_ASSERT(slot < LOCALNO_LIMIT);
+ return NameLocation(Kind::FrameSlot, bindKind, 0, slot);
+ }
+
+ static NameLocation EnvironmentCoordinate(BindingKind bindKind, uint8_t hops, uint32_t slot) {
+ MOZ_ASSERT(slot < ENVCOORD_SLOT_LIMIT);
+ return NameLocation(Kind::EnvironmentCoordinate, bindKind, hops, slot);
+ }
+
+ static NameLocation Import() {
+ return NameLocation(Kind::Import, BindingKind::Import);
+ }
+
+ static NameLocation DynamicAnnexBVar() {
+ return NameLocation(Kind::DynamicAnnexBVar, BindingKind::Var);
+ }
+
+ static NameLocation fromBinding(BindingKind bindKind, const BindingLocation& bl) {
+ switch (bl.kind()) {
+ case BindingLocation::Kind::Global:
+ return Global(bindKind);
+ case BindingLocation::Kind::Argument:
+ return ArgumentSlot(bl.argumentSlot());
+ case BindingLocation::Kind::Frame:
+ return FrameSlot(bindKind, bl.slot());
+ case BindingLocation::Kind::Environment:
+ return EnvironmentCoordinate(bindKind, 0, bl.slot());
+ case BindingLocation::Kind::Import:
+ return Import();
+ case BindingLocation::Kind::NamedLambdaCallee:
+ return NamedLambdaCallee();
+ }
+ MOZ_CRASH("Bad BindingKind");
+ }
+
+ bool operator==(const NameLocation& other) const {
+ return kind_ == other.kind_ && bindingKind_ == other.bindingKind_ &&
+ hops_ == other.hops_ && slot_ == other.slot_;
+ }
+
+ bool operator!=(const NameLocation& other) const {
+ return !(*this == other);
+ }
+
+ Kind kind() const {
+ return kind_;
+ }
+
+ uint16_t argumentSlot() const {
+ MOZ_ASSERT(kind_ == Kind::ArgumentSlot);
+ return mozilla::AssertedCast<uint16_t>(slot_);
+ }
+
+ uint32_t frameSlot() const {
+ MOZ_ASSERT(kind_ == Kind::FrameSlot);
+ return slot_;
+ }
+
+ NameLocation addHops(uint8_t more) {
+ MOZ_ASSERT(hops_ < ENVCOORD_HOPS_LIMIT - more);
+ MOZ_ASSERT(kind_ == Kind::EnvironmentCoordinate);
+ return NameLocation(kind_, bindingKind_, hops_ + more, slot_);
+ }
+
+ class EnvironmentCoordinate environmentCoordinate() const {
+ MOZ_ASSERT(kind_ == Kind::EnvironmentCoordinate);
+ class EnvironmentCoordinate coord;
+ coord.setHops(hops_);
+ coord.setSlot(slot_);
+ return coord;
+ }
+
+ BindingKind bindingKind() const {
+ MOZ_ASSERT(kind_ != Kind::Dynamic);
+ return bindingKind_;
+ }
+
+ bool isLexical() const {
+ return BindingKindIsLexical(bindingKind());
+ }
+
+ bool isConst() const {
+ return bindingKind() == BindingKind::Const;
+ }
+
+ bool hasKnownSlot() const {
+ return kind_ == Kind::ArgumentSlot ||
+ kind_ == Kind::FrameSlot ||
+ kind_ == Kind::EnvironmentCoordinate;
+ }
+};
+
+// This type is declared here for LazyScript::Create.
+using AtomVector = Vector<JSAtom*, 24, SystemAllocPolicy>;
+
+} // namespace frontend
+} // namespace js
+
+namespace mozilla {
+
+template <>
+struct IsPod<js::frontend::DeclaredNameInfo> : TrueType {};
+
+template <>
+struct IsPod<js::frontend::NameLocation> : TrueType {};
+
+} // namespace mozilla
+
+#endif // frontend_NameAnalysis_h
diff --git a/js/src/frontend/NameCollections.h b/js/src/frontend/NameCollections.h
new file mode 100644
index 0000000000..58c5d0ac04
--- /dev/null
+++ b/js/src/frontend/NameCollections.h
@@ -0,0 +1,338 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sts=4 et sw=4 tw=99:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "ds/InlineTable.h"
+#include "frontend/NameAnalysisTypes.h"
+#include "js/Vector.h"
+#include "vm/Stack.h"
+
+namespace js {
+namespace frontend {
+
+// 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();
+ }
+
+ bool empty() const {
+ return all_.empty();
+ }
+
+ 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(ExclusiveContext* cx) {
+ ConcreteCollectionPool::template assertInvariants<Collection>();
+
+ RepresentativeCollection* collection;
+ if (recyclable_.empty()) {
+ collection = allocate();
+ if (!collection)
+ ReportOutOfMemory(cx);
+ } else {
+ collection = asRepresentative(recyclable_.popCopy());
+ collection->clear();
+ }
+ return reinterpret_cast<Collection*>(collection);
+ }
+
+ // Release a collection back to the pool.
+ template <typename Collection>
+ void release(Collection** collection) {
+ ConcreteCollectionPool::template assertInvariants<Collection>();
+ MOZ_ASSERT(*collection);
+
+#ifdef DEBUG
+ bool ok = false;
+ // Make sure the collection is in |all_| but not already in |recyclable_|.
+ for (void** it = all_.begin(); it != all_.end(); ++it) {
+ if (*it == *collection) {
+ ok = true;
+ break;
+ }
+ }
+ MOZ_ASSERT(ok);
+ for (void** it = recyclable_.begin(); it != recyclable_.end(); ++it)
+ MOZ_ASSERT(*it != *collection);
+#endif
+
+ MOZ_ASSERT(recyclable_.length() < all_.length());
+ // Reserved in allocateFresh.
+ recyclable_.infallibleAppend(*collection);
+ *collection = nullptr;
+ }
+};
+
+template <typename Wrapped>
+struct RecyclableAtomMapValueWrapper
+{
+ 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() {
+ 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 RecyclableNameMap = InlineMap<JSAtom*,
+ RecyclableAtomMapValueWrapper<MapValue>,
+ 24,
+ DefaultHasher<JSAtom*>,
+ SystemAllocPolicy>;
+
+using DeclaredNameMap = RecyclableNameMap<DeclaredNameInfo>;
+using CheckTDZMap = RecyclableNameMap<MaybeCheckTDZ>;
+using NameLocationMap = RecyclableNameMap<NameLocation>;
+using AtomIndexMap = RecyclableNameMap<uint32_t>;
+
+#undef RECYCLABLE_NAME_MAP_TYPE
+
+template <typename RepresentativeTable>
+class InlineTablePool
+ : public CollectionPool<RepresentativeTable, InlineTablePool<RepresentativeTable>>
+{
+ 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.");
+ static_assert(mozilla::IsPod<typename Table::Table::Entry>::value,
+ "Only tables with POD values are usable in the pool.");
+ }
+};
+
+using FunctionBoxVector = Vector<FunctionBox*, 24, SystemAllocPolicy>;
+
+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.");
+ static_assert(mozilla::IsPod<typename Vector::ElementType>::value,
+ "Only vectors of POD values are usable in the pool.");
+ static_assert(sizeof(typename Vector::ElementType) ==
+ sizeof(typename RepresentativeVector::ElementType),
+ "Only vectors with same-sized elements are usable in the pool.");
+ }
+};
+
+class NameCollectionPool
+{
+ InlineTablePool<AtomIndexMap> mapPool_;
+ VectorPool<AtomVector> vectorPool_;
+ uint32_t activeCompilations_;
+
+ public:
+ NameCollectionPool()
+ : activeCompilations_(0)
+ { }
+
+ bool hasActiveCompilation() const {
+ return activeCompilations_ != 0;
+ }
+
+ void addActiveCompilation() {
+ activeCompilations_++;
+ }
+
+ void removeActiveCompilation() {
+ MOZ_ASSERT(hasActiveCompilation());
+ activeCompilations_--;
+ }
+
+ template <typename Map>
+ Map* acquireMap(ExclusiveContext* cx) {
+ MOZ_ASSERT(hasActiveCompilation());
+ return mapPool_.acquire<Map>(cx);
+ }
+
+ template <typename Map>
+ void releaseMap(Map** map) {
+ MOZ_ASSERT(hasActiveCompilation());
+ MOZ_ASSERT(map);
+ if (*map)
+ mapPool_.release(map);
+ }
+
+ template <typename Vector>
+ Vector* acquireVector(ExclusiveContext* cx) {
+ MOZ_ASSERT(hasActiveCompilation());
+ return vectorPool_.acquire<Vector>(cx);
+ }
+
+ template <typename Vector>
+ void releaseVector(Vector** vec) {
+ MOZ_ASSERT(hasActiveCompilation());
+ MOZ_ASSERT(vec);
+ if (*vec)
+ vectorPool_.release(vec);
+ }
+
+ void purge() {
+ if (!hasActiveCompilation()) {
+ mapPool_.purgeAll();
+ vectorPool_.purgeAll();
+ }
+ }
+};
+
+#define POOLED_COLLECTION_PTR_METHODS(N, T) \
+ NameCollectionPool& pool_; \
+ T* collection_; \
+ \
+ T& collection() { \
+ MOZ_ASSERT(collection_); \
+ return *collection_; \
+ } \
+ \
+ const T& collection() const { \
+ MOZ_ASSERT(collection_); \
+ return *collection_; \
+ } \
+ \
+ public: \
+ explicit N(NameCollectionPool& pool) \
+ : pool_(pool), \
+ collection_(nullptr) \
+ { } \
+ \
+ ~N() { \
+ pool_.release##T(&collection_); \
+ } \
+ \
+ bool acquire(ExclusiveContext* cx) { \
+ MOZ_ASSERT(!collection_); \
+ collection_ = pool_.acquire##T<T>(cx); \
+ 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
+{
+ POOLED_COLLECTION_PTR_METHODS(PooledMapPtr, Map)
+};
+
+template <typename Vector>
+class PooledVectorPtr
+{
+ POOLED_COLLECTION_PTR_METHODS(PooledVectorPtr, Vector)
+
+ typename Vector::ElementType& operator[](size_t index) {
+ return collection()[index];
+ }
+
+ const typename Vector::ElementType& operator[](size_t index) const {
+ return collection()[index];
+ }
+};
+
+#undef POOLED_COLLECTION_PTR_METHODS
+
+} // namespace frontend
+} // namespace js
+
+namespace mozilla {
+
+template <>
+struct IsPod<js::MaybeCheckTDZ> : TrueType {};
+
+template <typename T>
+struct IsPod<js::frontend::RecyclableAtomMapValueWrapper<T>> : IsPod<T> {};
+
+} // namespace mozilla
+
+#endif // frontend_NameCollections_h
diff --git a/js/src/frontend/NameFunctions.cpp b/js/src/frontend/NameFunctions.cpp
new file mode 100644
index 0000000000..ce1318f0b1
--- /dev/null
+++ b/js/src/frontend/NameFunctions.cpp
@@ -0,0 +1,838 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sts=4 et sw=4 tw=99:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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/Sprintf.h"
+
+#include "jsfun.h"
+#include "jsprf.h"
+
+#include "frontend/BytecodeCompiler.h"
+#include "frontend/ParseNode.h"
+#include "frontend/SharedContext.h"
+#include "vm/StringBuffer.h"
+
+using namespace js;
+using namespace js::frontend;
+
+namespace {
+
+class NameResolver
+{
+ static const size_t MaxParents = 100;
+
+ ExclusiveContext* cx;
+ size_t nparents; /* number of parents in the parents array */
+ ParseNode* parents[MaxParents]; /* history of ParseNodes we've been looking at */
+ StringBuffer* buf; /* when resolving, buffer to append to */
+
+ /* Test whether a ParseNode represents a function invocation */
+ bool call(ParseNode* pn) {
+ return pn && pn->isKind(PNK_CALL);
+ }
+
+ /*
+ * 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
+ * PNK_NAME nodes and PNK_STRING nodes: given code like a["b c"], the
+ * front end will produce a PNK_DOT with a PNK_NAME child whose name
+ * contains spaces.
+ */
+ bool appendPropertyReference(JSAtom* name) {
+ if (IsIdentifier(name))
+ return buf->append('.') && buf->append(name);
+
+ /* Quote the string as needed. */
+ JSString* source = QuoteString(cx, name, '"');
+ return source && buf->append('[') && buf->append(source) && buf->append(']');
+ }
+
+ /* Append a number to buf. */
+ bool appendNumber(double n) {
+ char number[30];
+ int digits = SprintfLiteral(number, "%g", n);
+ return buf->append(number, digits);
+ }
+
+ /* Append "[<n>]" to buf, referencing a property named by a numeric literal. */
+ bool appendNumericPropertyReference(double n) {
+ return buf->append("[") && appendNumber(n) && buf->append(']');
+ }
+
+ /*
+ * Walk over the given ParseNode, attempting to convert it to a stringified
+ * name that respresents where the function is being assigned to.
+ *
+ * |*foundName| is set to true if a name is found for the expression.
+ */
+ bool nameExpression(ParseNode* n, bool* foundName) {
+ switch (n->getKind()) {
+ case PNK_DOT:
+ if (!nameExpression(n->expr(), foundName))
+ return false;
+ if (!*foundName)
+ return true;
+ return appendPropertyReference(n->pn_atom);
+
+ case PNK_NAME:
+ *foundName = true;
+ return buf->append(n->pn_atom);
+
+ case PNK_THIS:
+ *foundName = true;
+ return buf->append("this");
+
+ case PNK_ELEM:
+ if (!nameExpression(n->pn_left, foundName))
+ return false;
+ if (!*foundName)
+ return true;
+ if (!buf->append('[') || !nameExpression(n->pn_right, foundName))
+ return false;
+ if (!*foundName)
+ return true;
+ return buf->append(']');
+
+ case PNK_NUMBER:
+ *foundName = true;
+ return appendNumber(n->pn_dval);
+
+ 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) {
+ *size = 0;
+
+ for (int pos = nparents - 1; pos >= 0; pos--) {
+ ParseNode* cur = parents[pos];
+ if (cur->isAssignment())
+ return cur;
+
+ switch (cur->getKind()) {
+ case PNK_NAME: return cur; /* found the initialized declaration */
+ case PNK_THIS: return cur; /* Setting a property of 'this'. */
+ case PNK_FUNCTION: return nullptr; /* won't find an assignment or declaration */
+
+ case PNK_RETURN:
+ /*
+ * 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
+ * PNK_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;
+ } else if (call(cur)) {
+ /* Don't skip too high in the tree */
+ break;
+ }
+ cur = parents[tmp];
+ }
+ break;
+
+ case PNK_COLON:
+ case PNK_SHORTHAND:
+ /*
+ * Record the PNK_COLON/SHORTHAND but skip the PNK_OBJECT so we're not
+ * flagged as a contributor.
+ */
+ pos--;
+ MOZ_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.
+ */
+ bool resolveFun(ParseNode* pn, HandleAtom prefix, MutableHandleAtom retAtom) {
+ MOZ_ASSERT(pn != nullptr);
+ MOZ_ASSERT(pn->isKind(PNK_FUNCTION));
+ MOZ_ASSERT(pn->isArity(PN_CODE));
+ RootedFunction fun(cx, pn->pn_funbox->function());
+
+ StringBuffer buf(cx);
+ this->buf = &buf;
+
+ retAtom.set(nullptr);
+
+ /* If the function already has a name, use that */
+ if (fun->displayAtom() != nullptr) {
+ if (prefix == nullptr) {
+ retAtom.set(fun->displayAtom());
+ return true;
+ }
+ if (!buf.append(prefix) ||
+ !buf.append('/') ||
+ !buf.append(fun->displayAtom()))
+ return false;
+ retAtom.set(buf.finishAtom());
+ return !!retAtom;
+ }
+
+ /* If a prefix is specified, then it is a form of namespace */
+ if (prefix != nullptr && (!buf.append(prefix) || !buf.append('/')))
+ return false;
+
+ /* Gather all nodes relevant to naming */
+ ParseNode* toName[MaxParents];
+ size_t size;
+ ParseNode* assignment = gatherNameable(toName, &size);
+
+ /* If the function is assigned to something, then that is very relevant */
+ if (assignment) {
+ if (assignment->isAssignment())
+ assignment = assignment->pn_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(PNK_COLON) || node->isKind(PNK_SHORTHAND)) {
+ ParseNode* left = node->pn_left;
+ if (left->isKind(PNK_OBJECT_PROPERTY_NAME) || left->isKind(PNK_STRING)) {
+ if (!appendPropertyReference(left->pn_atom))
+ return false;
+ } else if (left->isKind(PNK_NUMBER)) {
+ if (!appendNumericPropertyReference(left->pn_dval))
+ return false;
+ } else {
+ MOZ_ASSERT(left->isKind(PNK_COMPUTED_NAME));
+ }
+ } 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;
+
+ retAtom.set(buf.finishAtom());
+ if (!retAtom)
+ return false;
+ fun->setGuessedAtom(retAtom);
+ 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 && call(parents[pos]) && parents[pos]->pn_head == cur;
+ }
+
+ bool resolveTemplateLiteral(ParseNode* node, HandleAtom prefix) {
+ MOZ_ASSERT(node->isKind(PNK_TEMPLATE_STRING_LIST));
+ ParseNode* element = node->pn_head;
+ while (true) {
+ MOZ_ASSERT(element->isKind(PNK_TEMPLATE_STRING));
+
+ element = element->pn_next;
+ if (!element)
+ return true;
+
+ if (!resolve(element, prefix))
+ return false;
+
+ element = element->pn_next;
+ }
+ }
+
+ bool resolveTaggedTemplate(ParseNode* node, HandleAtom prefix) {
+ MOZ_ASSERT(node->isKind(PNK_TAGGED_TEMPLATE));
+
+ ParseNode* element = node->pn_head;
+
+ // The list head is a leading expression, e.g. |tag| in |tag`foo`|,
+ // that might contain functions.
+ if (!resolve(element, prefix))
+ return false;
+
+ // Next is the callsite object node. This node only contains
+ // internal strings and an array -- no user-controlled expressions.
+ element = element->pn_next;
+#ifdef DEBUG
+ {
+ MOZ_ASSERT(element->isKind(PNK_CALLSITEOBJ));
+ ParseNode* array = element->pn_head;
+ MOZ_ASSERT(array->isKind(PNK_ARRAY));
+ for (ParseNode* kid = array->pn_head; kid; kid = kid->pn_next)
+ MOZ_ASSERT(kid->isKind(PNK_TEMPLATE_STRING));
+ for (ParseNode* next = array->pn_next; next; next = next->pn_next)
+ MOZ_ASSERT(next->isKind(PNK_TEMPLATE_STRING));
+ }
+#endif
+
+ // Next come any interpolated expressions in the tagged template.
+ ParseNode* interpolated = element->pn_next;
+ for (; interpolated; interpolated = interpolated->pn_next) {
+ if (!resolve(interpolated, prefix))
+ return false;
+ }
+
+ return true;
+ }
+
+ public:
+ explicit NameResolver(ExclusiveContext* cx) : cx(cx), nparents(0), buf(nullptr) {}
+
+ /*
+ * Resolve all names for anonymous functions recursively within the
+ * ParseNode instance given. The prefix is for each subsequent name, and
+ * should initially be nullptr.
+ */
+ bool resolve(ParseNode* cur, HandleAtom prefixArg = nullptr) {
+ RootedAtom prefix(cx, prefixArg);
+ if (cur == nullptr)
+ return true;
+
+ MOZ_ASSERT((cur->isKind(PNK_FUNCTION) || cur->isKind(PNK_MODULE)) == cur->isArity(PN_CODE));
+ if (cur->isKind(PNK_FUNCTION)) {
+ RootedAtom prefix2(cx);
+ if (!resolveFun(cur, prefix, &prefix2))
+ 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 - 1, cur))
+ prefix = prefix2;
+ }
+ if (nparents >= MaxParents)
+ return true;
+ parents[nparents++] = cur;
+
+ switch (cur->getKind()) {
+ // Nodes with no children that might require name resolution need no
+ // further work.
+ case PNK_NOP:
+ case PNK_STRING:
+ case PNK_TEMPLATE_STRING:
+ case PNK_REGEXP:
+ case PNK_TRUE:
+ case PNK_FALSE:
+ case PNK_NULL:
+ case PNK_ELISION:
+ case PNK_GENERATOR:
+ case PNK_NUMBER:
+ case PNK_BREAK:
+ case PNK_CONTINUE:
+ case PNK_DEBUGGER:
+ case PNK_EXPORT_BATCH_SPEC:
+ case PNK_OBJECT_PROPERTY_NAME:
+ case PNK_POSHOLDER:
+ MOZ_ASSERT(cur->isArity(PN_NULLARY));
+ break;
+
+ case PNK_TYPEOFNAME:
+ case PNK_SUPERBASE:
+ MOZ_ASSERT(cur->isArity(PN_UNARY));
+ MOZ_ASSERT(cur->pn_kid->isKind(PNK_NAME));
+ MOZ_ASSERT(!cur->pn_kid->expr());
+ break;
+
+ case PNK_NEWTARGET:
+ MOZ_ASSERT(cur->isArity(PN_BINARY));
+ MOZ_ASSERT(cur->pn_left->isKind(PNK_POSHOLDER));
+ MOZ_ASSERT(cur->pn_right->isKind(PNK_POSHOLDER));
+ break;
+
+ // Nodes with a single non-null child requiring name resolution.
+ case PNK_TYPEOFEXPR:
+ case PNK_VOID:
+ case PNK_NOT:
+ case PNK_BITNOT:
+ case PNK_THROW:
+ case PNK_DELETENAME:
+ case PNK_DELETEPROP:
+ case PNK_DELETEELEM:
+ case PNK_DELETEEXPR:
+ case PNK_NEG:
+ case PNK_POS:
+ case PNK_PREINCREMENT:
+ case PNK_POSTINCREMENT:
+ case PNK_PREDECREMENT:
+ case PNK_POSTDECREMENT:
+ case PNK_COMPUTED_NAME:
+ case PNK_ARRAYPUSH:
+ case PNK_SPREAD:
+ case PNK_MUTATEPROTO:
+ case PNK_EXPORT:
+ MOZ_ASSERT(cur->isArity(PN_UNARY));
+ if (!resolve(cur->pn_kid, prefix))
+ return false;
+ break;
+
+ // Nodes with a single nullable child.
+ case PNK_SEMI:
+ case PNK_THIS:
+ MOZ_ASSERT(cur->isArity(PN_UNARY));
+ if (ParseNode* expr = cur->pn_kid) {
+ if (!resolve(expr, prefix))
+ return false;
+ }
+ break;
+
+ // Binary nodes with two non-null children.
+ case PNK_ASSIGN:
+ case PNK_ADDASSIGN:
+ case PNK_SUBASSIGN:
+ case PNK_BITORASSIGN:
+ case PNK_BITXORASSIGN:
+ case PNK_BITANDASSIGN:
+ case PNK_LSHASSIGN:
+ case PNK_RSHASSIGN:
+ case PNK_URSHASSIGN:
+ case PNK_MULASSIGN:
+ case PNK_DIVASSIGN:
+ case PNK_MODASSIGN:
+ case PNK_POWASSIGN:
+ case PNK_COLON:
+ case PNK_SHORTHAND:
+ case PNK_DOWHILE:
+ case PNK_WHILE:
+ case PNK_SWITCH:
+ case PNK_FOR:
+ case PNK_COMPREHENSIONFOR:
+ case PNK_CLASSMETHOD:
+ case PNK_SETTHIS:
+ MOZ_ASSERT(cur->isArity(PN_BINARY));
+ if (!resolve(cur->pn_left, prefix))
+ return false;
+ if (!resolve(cur->pn_right, prefix))
+ return false;
+ break;
+
+ case PNK_ELEM:
+ MOZ_ASSERT(cur->isArity(PN_BINARY));
+ if (!cur->as<PropertyByValue>().isSuper() && !resolve(cur->pn_left, prefix))
+ return false;
+ if (!resolve(cur->pn_right, prefix))
+ return false;
+ break;
+
+ case PNK_WITH:
+ MOZ_ASSERT(cur->isArity(PN_BINARY));
+ if (!resolve(cur->pn_left, prefix))
+ return false;
+ if (!resolve(cur->pn_right, prefix))
+ return false;
+ break;
+
+ case PNK_CASE:
+ MOZ_ASSERT(cur->isArity(PN_BINARY));
+ if (ParseNode* caseExpr = cur->pn_left) {
+ if (!resolve(caseExpr, prefix))
+ return false;
+ }
+ if (!resolve(cur->pn_right, prefix))
+ return false;
+ break;
+
+ case PNK_YIELD_STAR:
+ MOZ_ASSERT(cur->isArity(PN_BINARY));
+ MOZ_ASSERT(cur->pn_right->isKind(PNK_NAME));
+ if (!resolve(cur->pn_left, prefix))
+ return false;
+ break;
+
+ case PNK_YIELD:
+ case PNK_AWAIT:
+ MOZ_ASSERT(cur->isArity(PN_BINARY));
+ if (cur->pn_left) {
+ if (!resolve(cur->pn_left, prefix))
+ return false;
+ }
+ MOZ_ASSERT(cur->pn_right->isKind(PNK_NAME) ||
+ (cur->pn_right->isKind(PNK_ASSIGN) &&
+ cur->pn_right->pn_left->isKind(PNK_NAME) &&
+ cur->pn_right->pn_right->isKind(PNK_GENERATOR)));
+ break;
+
+ case PNK_RETURN:
+ MOZ_ASSERT(cur->isArity(PN_UNARY));
+ if (ParseNode* returnValue = cur->pn_kid) {
+ if (!resolve(returnValue, prefix))
+ return false;
+ }
+ break;
+
+ case PNK_IMPORT:
+ case PNK_EXPORT_FROM:
+ case PNK_EXPORT_DEFAULT:
+ MOZ_ASSERT(cur->isArity(PN_BINARY));
+ // The left halves of these nodes don't contain any unconstrained
+ // expressions, but it's very hard to assert this to safely rely on
+ // it. So recur anyway.
+ if (!resolve(cur->pn_left, prefix))
+ return false;
+ MOZ_ASSERT_IF(!cur->isKind(PNK_EXPORT_DEFAULT),
+ cur->pn_right->isKind(PNK_STRING));
+ break;
+
+ // Ternary nodes with three expression children.
+ case PNK_CONDITIONAL:
+ MOZ_ASSERT(cur->isArity(PN_TERNARY));
+ if (!resolve(cur->pn_kid1, prefix))
+ return false;
+ if (!resolve(cur->pn_kid2, prefix))
+ return false;
+ if (!resolve(cur->pn_kid3, prefix))
+ return false;
+ break;
+
+ // The first part of a for-in/of is the declaration in the loop (or
+ // null if no declaration). The latter two parts are the location
+ // assigned each loop and the value being looped over; obviously,
+ // either might contain functions to name. Declarations may (through
+ // computed property names, and possibly through [deprecated!]
+ // initializers) also contain functions to name.
+ case PNK_FORIN:
+ case PNK_FOROF:
+ MOZ_ASSERT(cur->isArity(PN_TERNARY));
+ if (ParseNode* decl = cur->pn_kid1) {
+ if (!resolve(decl, prefix))
+ return false;
+ }
+ if (!resolve(cur->pn_kid2, prefix))
+ return false;
+ if (!resolve(cur->pn_kid3, prefix))
+ return false;
+ break;
+
+ // Every part of a for(;;) head may contain a function needing name
+ // resolution.
+ case PNK_FORHEAD:
+ MOZ_ASSERT(cur->isArity(PN_TERNARY));
+ if (ParseNode* init = cur->pn_kid1) {
+ if (!resolve(init, prefix))
+ return false;
+ }
+ if (ParseNode* cond = cur->pn_kid2) {
+ if (!resolve(cond, prefix))
+ return false;
+ }
+ if (ParseNode* step = cur->pn_kid3) {
+ if (!resolve(step, prefix))
+ return false;
+ }
+ break;
+
+ // The first child of a class is a pair of names referring to it,
+ // inside and outside the class. The second is the class's heritage,
+ // if any. The third is the class body.
+ case PNK_CLASS:
+ MOZ_ASSERT(cur->isArity(PN_TERNARY));
+ MOZ_ASSERT_IF(cur->pn_kid1, cur->pn_kid1->isKind(PNK_CLASSNAMES));
+ MOZ_ASSERT_IF(cur->pn_kid1, cur->pn_kid1->isArity(PN_BINARY));
+ MOZ_ASSERT_IF(cur->pn_kid1 && cur->pn_kid1->pn_left,
+ cur->pn_kid1->pn_left->isKind(PNK_NAME));
+ MOZ_ASSERT_IF(cur->pn_kid1 && cur->pn_kid1->pn_left,
+ !cur->pn_kid1->pn_left->expr());
+ MOZ_ASSERT_IF(cur->pn_kid1, cur->pn_kid1->pn_right->isKind(PNK_NAME));
+ MOZ_ASSERT_IF(cur->pn_kid1, !cur->pn_kid1->pn_right->expr());
+ if (cur->pn_kid2) {
+ if (!resolve(cur->pn_kid2, prefix))
+ return false;
+ }
+ if (!resolve(cur->pn_kid3, prefix))
+ return false;
+ break;
+
+ // The condition and consequent are non-optional, but the alternative
+ // might be omitted.
+ case PNK_IF:
+ MOZ_ASSERT(cur->isArity(PN_TERNARY));
+ if (!resolve(cur->pn_kid1, prefix))
+ return false;
+ if (!resolve(cur->pn_kid2, prefix))
+ return false;
+ if (cur->pn_kid3) {
+ if (!resolve(cur->pn_kid3, prefix))
+ return false;
+ }
+ break;
+
+ // The statements in the try-block are mandatory. The catch-blocks
+ // and finally block are optional (but at least one or the other must
+ // be present).
+ case PNK_TRY:
+ MOZ_ASSERT(cur->isArity(PN_TERNARY));
+ if (!resolve(cur->pn_kid1, prefix))
+ return false;
+ MOZ_ASSERT(cur->pn_kid2 || cur->pn_kid3);
+ if (ParseNode* catchList = cur->pn_kid2) {
+ MOZ_ASSERT(catchList->isKind(PNK_CATCHLIST));
+ if (!resolve(catchList, prefix))
+ return false;
+ }
+ if (ParseNode* finallyBlock = cur->pn_kid3) {
+ if (!resolve(finallyBlock, prefix))
+ return false;
+ }
+ break;
+
+ // The first child, the catch-pattern, may contain functions via
+ // computed property names. The optional catch-conditions may
+ // contain any expression. The catch statements, of course, may
+ // contain arbitrary expressions.
+ case PNK_CATCH:
+ MOZ_ASSERT(cur->isArity(PN_TERNARY));
+ if (!resolve(cur->pn_kid1, prefix))
+ return false;
+ if (cur->pn_kid2) {
+ if (!resolve(cur->pn_kid2, prefix))
+ return false;
+ }
+ if (!resolve(cur->pn_kid3, prefix))
+ return false;
+ break;
+
+ // Nodes with arbitrary-expression children.
+ case PNK_OR:
+ case PNK_AND:
+ case PNK_BITOR:
+ case PNK_BITXOR:
+ case PNK_BITAND:
+ case PNK_STRICTEQ:
+ case PNK_EQ:
+ case PNK_STRICTNE:
+ case PNK_NE:
+ case PNK_LT:
+ case PNK_LE:
+ case PNK_GT:
+ case PNK_GE:
+ case PNK_INSTANCEOF:
+ case PNK_IN:
+ case PNK_LSH:
+ case PNK_RSH:
+ case PNK_URSH:
+ case PNK_ADD:
+ case PNK_SUB:
+ case PNK_STAR:
+ case PNK_DIV:
+ case PNK_MOD:
+ case PNK_POW:
+ case PNK_COMMA:
+ case PNK_NEW:
+ case PNK_CALL:
+ case PNK_SUPERCALL:
+ case PNK_GENEXP:
+ case PNK_ARRAY:
+ case PNK_STATEMENTLIST:
+ case PNK_PARAMSBODY:
+ // Initializers for individual variables, and computed property names
+ // within destructuring patterns, may contain unnamed functions.
+ case PNK_VAR:
+ case PNK_CONST:
+ case PNK_LET:
+ MOZ_ASSERT(cur->isArity(PN_LIST));
+ for (ParseNode* element = cur->pn_head; element; element = element->pn_next) {
+ if (!resolve(element, prefix))
+ return false;
+ }
+ break;
+
+ // Array comprehension nodes are lists with a single child:
+ // PNK_COMPREHENSIONFOR for comprehensions, PNK_LEXICALSCOPE for
+ // legacy comprehensions. Probably this should be a non-list
+ // eventually.
+ case PNK_ARRAYCOMP:
+ MOZ_ASSERT(cur->isArity(PN_LIST));
+ MOZ_ASSERT(cur->pn_count == 1);
+ MOZ_ASSERT(cur->pn_head->isKind(PNK_LEXICALSCOPE) ||
+ cur->pn_head->isKind(PNK_COMPREHENSIONFOR));
+ if (!resolve(cur->pn_head, prefix))
+ return false;
+ break;
+
+ case PNK_OBJECT:
+ case PNK_CLASSMETHODLIST:
+ MOZ_ASSERT(cur->isArity(PN_LIST));
+ for (ParseNode* element = cur->pn_head; element; element = element->pn_next) {
+ if (!resolve(element, prefix))
+ return false;
+ }
+ break;
+
+ // A template string list's contents alternate raw template string
+ // contents with expressions interpolated into the overall literal.
+ case PNK_TEMPLATE_STRING_LIST:
+ MOZ_ASSERT(cur->isArity(PN_LIST));
+ if (!resolveTemplateLiteral(cur, prefix))
+ return false;
+ break;
+
+ case PNK_TAGGED_TEMPLATE:
+ MOZ_ASSERT(cur->isArity(PN_LIST));
+ if (!resolveTaggedTemplate(cur, prefix))
+ return false;
+ break;
+
+ // Import/export spec lists contain import/export specs containing
+ // only pairs of names. Alternatively, an export spec lists may
+ // contain a single export batch specifier.
+ case PNK_EXPORT_SPEC_LIST:
+ case PNK_IMPORT_SPEC_LIST: {
+ MOZ_ASSERT(cur->isArity(PN_LIST));
+#ifdef DEBUG
+ bool isImport = cur->isKind(PNK_IMPORT_SPEC_LIST);
+ ParseNode* item = cur->pn_head;
+ if (!isImport && item && item->isKind(PNK_EXPORT_BATCH_SPEC)) {
+ MOZ_ASSERT(item->isArity(PN_NULLARY));
+ break;
+ }
+ for (; item; item = item->pn_next) {
+ MOZ_ASSERT(item->isKind(isImport ? PNK_IMPORT_SPEC : PNK_EXPORT_SPEC));
+ MOZ_ASSERT(item->isArity(PN_BINARY));
+ MOZ_ASSERT(item->pn_left->isKind(PNK_NAME));
+ MOZ_ASSERT(!item->pn_left->expr());
+ MOZ_ASSERT(item->pn_right->isKind(PNK_NAME));
+ MOZ_ASSERT(!item->pn_right->expr());
+ }
+#endif
+ break;
+ }
+
+ case PNK_CATCHLIST: {
+ MOZ_ASSERT(cur->isArity(PN_LIST));
+ for (ParseNode* catchNode = cur->pn_head; catchNode; catchNode = catchNode->pn_next) {
+ MOZ_ASSERT(catchNode->isKind(PNK_LEXICALSCOPE));
+ MOZ_ASSERT(catchNode->scopeBody()->isKind(PNK_CATCH));
+ MOZ_ASSERT(catchNode->scopeBody()->isArity(PN_TERNARY));
+ if (!resolve(catchNode->scopeBody(), prefix))
+ return false;
+ }
+ break;
+ }
+
+ case PNK_DOT:
+ MOZ_ASSERT(cur->isArity(PN_NAME));
+
+ // Super prop nodes do not have a meaningful LHS
+ if (cur->as<PropertyAccess>().isSuper())
+ break;
+ if (!resolve(cur->expr(), prefix))
+ return false;
+ break;
+
+ case PNK_LABEL:
+ MOZ_ASSERT(cur->isArity(PN_NAME));
+ if (!resolve(cur->expr(), prefix))
+ return false;
+ break;
+
+ case PNK_NAME:
+ MOZ_ASSERT(cur->isArity(PN_NAME));
+ if (!resolve(cur->expr(), prefix))
+ return false;
+ break;
+
+ case PNK_LEXICALSCOPE:
+ MOZ_ASSERT(cur->isArity(PN_SCOPE));
+ if (!resolve(cur->scopeBody(), prefix))
+ return false;
+ break;
+
+ case PNK_FUNCTION:
+ case PNK_MODULE:
+ MOZ_ASSERT(cur->isArity(PN_CODE));
+ if (!resolve(cur->pn_body, prefix))
+ return false;
+ break;
+
+ // Kinds that should be handled by parent node resolution.
+
+ case PNK_IMPORT_SPEC: // by PNK_IMPORT_SPEC_LIST
+ case PNK_EXPORT_SPEC: // by PNK_EXPORT_SPEC_LIST
+ case PNK_CALLSITEOBJ: // by PNK_TAGGED_TEMPLATE
+ case PNK_CLASSNAMES: // by PNK_CLASS
+ MOZ_CRASH("should have been handled by a parent node");
+
+ case PNK_LIMIT: // invalid sentinel value
+ MOZ_CRASH("invalid node kind");
+ }
+
+ nparents--;
+ return true;
+ }
+};
+
+} /* anonymous namespace */
+
+bool
+frontend::NameFunctions(ExclusiveContext* cx, ParseNode* pn)
+{
+ NameResolver nr(cx);
+ return nr.resolve(pn);
+}
diff --git a/js/src/frontend/NameFunctions.h b/js/src/frontend/NameFunctions.h
new file mode 100644
index 0000000000..a04e7040ec
--- /dev/null
+++ b/js/src/frontend/NameFunctions.h
@@ -0,0 +1,28 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sts=4 et sw=4 tw=99:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef frontend_NameFunctions_h
+#define frontend_NameFunctions_h
+
+#include "mozilla/Attributes.h"
+
+#include "js/TypeDecls.h"
+
+namespace js {
+
+class ExclusiveContext;
+
+namespace frontend {
+
+class ParseNode;
+
+MOZ_MUST_USE bool
+NameFunctions(ExclusiveContext* cx, ParseNode* pn);
+
+} /* namespace frontend */
+} /* namespace js */
+
+#endif /* frontend_NameFunctions_h */
diff --git a/js/src/frontend/ParseNode-inl.h b/js/src/frontend/ParseNode-inl.h
new file mode 100644
index 0000000000..395d09b5b9
--- /dev/null
+++ b/js/src/frontend/ParseNode-inl.h
@@ -0,0 +1,35 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sts=4 et sw=4 tw=99:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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_inl_h
+#define frontend_ParseNode_inl_h
+
+#include "frontend/ParseNode.h"
+
+#include "frontend/SharedContext.h"
+
+namespace js {
+namespace frontend {
+
+inline PropertyName*
+ParseNode::name() const
+{
+ MOZ_ASSERT(isKind(PNK_FUNCTION) || isKind(PNK_NAME));
+ JSAtom* atom = isKind(PNK_FUNCTION) ? pn_funbox->function()->name() : pn_atom;
+ return atom->asPropertyName();
+}
+
+inline JSAtom*
+ParseNode::atom() const
+{
+ MOZ_ASSERT(isKind(PNK_STRING));
+ return pn_atom;
+}
+
+} /* namespace frontend */
+} /* namespace js */
+
+#endif /* frontend_ParseNode_inl_h */
diff --git a/js/src/frontend/ParseNode.cpp b/js/src/frontend/ParseNode.cpp
new file mode 100644
index 0000000000..f79baba9ef
--- /dev/null
+++ b/js/src/frontend/ParseNode.cpp
@@ -0,0 +1,904 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sts=4 et sw=4 tw=99:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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-inl.h"
+
+#include "frontend/Parser.h"
+
+#include "jscntxtinlines.h"
+
+using namespace js;
+using namespace js::frontend;
+
+using mozilla::ArrayLength;
+using mozilla::IsFinite;
+
+#ifdef DEBUG
+void
+ParseNode::checkListConsistency()
+{
+ MOZ_ASSERT(isArity(PN_LIST));
+ ParseNode** tail;
+ uint32_t count = 0;
+ if (pn_head) {
+ ParseNode* last = pn_head;
+ ParseNode* pn = last;
+ while (pn) {
+ last = pn;
+ pn = pn->pn_next;
+ count++;
+ }
+
+ tail = &last->pn_next;
+ } else {
+ tail = &pn_head;
+ }
+ MOZ_ASSERT(pn_tail == tail);
+ MOZ_ASSERT(pn_count == count);
+}
+#endif
+
+/* Add |node| to |parser|'s free node list. */
+void
+ParseNodeAllocator::freeNode(ParseNode* pn)
+{
+ /* Catch back-to-back dup recycles. */
+ MOZ_ASSERT(pn != freelist);
+
+#ifdef DEBUG
+ /* Poison the node, to catch attempts to use it without initializing it. */
+ memset(pn, 0xab, sizeof(*pn));
+#endif
+
+ pn->pn_next = freelist;
+ freelist = pn;
+}
+
+namespace {
+
+/*
+ * A work pool of ParseNodes. The work pool is a stack, chained together
+ * by nodes' pn_next fields. We use this to avoid creating deep C++ stacks
+ * when recycling deep parse trees.
+ *
+ * Since parse nodes are probably allocated in something close to the order
+ * they appear in a depth-first traversal of the tree, making the work pool
+ * a stack should give us pretty good locality.
+ */
+class NodeStack {
+ public:
+ NodeStack() : top(nullptr) { }
+ bool empty() { return top == nullptr; }
+ void push(ParseNode* pn) {
+ pn->pn_next = top;
+ top = pn;
+ }
+ /* Push the children of the PN_LIST node |pn| on the stack. */
+ void pushList(ParseNode* pn) {
+ /* This clobbers pn->pn_head if the list is empty; should be okay. */
+ *pn->pn_tail = top;
+ top = pn->pn_head;
+ }
+ ParseNode* pop() {
+ MOZ_ASSERT(!empty());
+ ParseNode* hold = top; /* my kingdom for a prog1 */
+ top = top->pn_next;
+ return hold;
+ }
+ private:
+ ParseNode* top;
+};
+
+} /* anonymous namespace */
+
+enum class PushResult { Recyclable, CleanUpLater };
+
+static PushResult
+PushCodeNodeChildren(ParseNode* node, NodeStack* stack)
+{
+ MOZ_ASSERT(node->isArity(PN_CODE));
+
+ /*
+ * Function nodes are linked into the function box tree, and may appear
+ * on method lists. Both of those lists are singly-linked, so trying to
+ * update them now could result in quadratic behavior when recycling
+ * trees containing many functions; and the lists can be very long. So
+ * we put off cleaning the lists up until just before function
+ * analysis, when we call CleanFunctionList.
+ *
+ * In fact, we can't recycle the parse node yet, either: it may appear
+ * on a method list, and reusing the node would corrupt that. Instead,
+ * we clear its pn_funbox pointer to mark it as deleted;
+ * CleanFunctionList recycles it as well.
+ *
+ * We do recycle the nodes around it, though, so we must clear pointers
+ * to them to avoid leaving dangling references where someone can find
+ * them.
+ */
+ node->pn_funbox = nullptr;
+ if (node->pn_body)
+ stack->push(node->pn_body);
+ node->pn_body = nullptr;
+
+ return PushResult::CleanUpLater;
+}
+
+static PushResult
+PushNameNodeChildren(ParseNode* node, NodeStack* stack)
+{
+ MOZ_ASSERT(node->isArity(PN_NAME));
+
+ if (node->pn_expr)
+ stack->push(node->pn_expr);
+ node->pn_expr = nullptr;
+ return PushResult::Recyclable;
+}
+
+static PushResult
+PushScopeNodeChildren(ParseNode* node, NodeStack* stack)
+{
+ MOZ_ASSERT(node->isArity(PN_SCOPE));
+
+ if (node->scopeBody())
+ stack->push(node->scopeBody());
+ node->setScopeBody(nullptr);
+ return PushResult::Recyclable;
+}
+
+static PushResult
+PushListNodeChildren(ParseNode* node, NodeStack* stack)
+{
+ MOZ_ASSERT(node->isArity(PN_LIST));
+ node->checkListConsistency();
+
+ stack->pushList(node);
+
+ return PushResult::Recyclable;
+}
+
+static PushResult
+PushUnaryNodeChild(ParseNode* node, NodeStack* stack)
+{
+ MOZ_ASSERT(node->isArity(PN_UNARY));
+
+ stack->push(node->pn_kid);
+
+ return PushResult::Recyclable;
+}
+
+/*
+ * Push the children of |pn| on |stack|. Return true if |pn| itself could be
+ * safely recycled, or false if it must be cleaned later (pn_used and pn_defn
+ * nodes, and all function nodes; see comments for CleanFunctionList in
+ * SemanticAnalysis.cpp). Some callers want to free |pn|; others
+ * (js::ParseNodeAllocator::prepareNodeForMutation) don't care about |pn|, and
+ * just need to take care of its children.
+ */
+static PushResult
+PushNodeChildren(ParseNode* pn, NodeStack* stack)
+{
+ switch (pn->getKind()) {
+ // Trivial nodes that refer to no nodes, are referred to by nothing
+ // but their parents, are never used, and are never a definition.
+ case PNK_NOP:
+ case PNK_STRING:
+ case PNK_TEMPLATE_STRING:
+ case PNK_REGEXP:
+ case PNK_TRUE:
+ case PNK_FALSE:
+ case PNK_NULL:
+ case PNK_ELISION:
+ case PNK_GENERATOR:
+ case PNK_NUMBER:
+ case PNK_BREAK:
+ case PNK_CONTINUE:
+ case PNK_DEBUGGER:
+ case PNK_EXPORT_BATCH_SPEC:
+ case PNK_OBJECT_PROPERTY_NAME:
+ case PNK_POSHOLDER:
+ MOZ_ASSERT(pn->isArity(PN_NULLARY));
+ return PushResult::Recyclable;
+
+ // Nodes with a single non-null child.
+ case PNK_TYPEOFNAME:
+ case PNK_TYPEOFEXPR:
+ case PNK_VOID:
+ case PNK_NOT:
+ case PNK_BITNOT:
+ case PNK_THROW:
+ case PNK_DELETENAME:
+ case PNK_DELETEPROP:
+ case PNK_DELETEELEM:
+ case PNK_DELETEEXPR:
+ case PNK_POS:
+ case PNK_NEG:
+ case PNK_PREINCREMENT:
+ case PNK_POSTINCREMENT:
+ case PNK_PREDECREMENT:
+ case PNK_POSTDECREMENT:
+ case PNK_COMPUTED_NAME:
+ case PNK_ARRAYPUSH:
+ case PNK_SPREAD:
+ case PNK_MUTATEPROTO:
+ case PNK_EXPORT:
+ case PNK_SUPERBASE:
+ return PushUnaryNodeChild(pn, stack);
+
+ // Nodes with a single nullable child.
+ case PNK_THIS:
+ case PNK_SEMI: {
+ MOZ_ASSERT(pn->isArity(PN_UNARY));
+ if (pn->pn_kid)
+ stack->push(pn->pn_kid);
+ return PushResult::Recyclable;
+ }
+
+ // Binary nodes with two non-null children.
+
+ // All assignment and compound assignment nodes qualify.
+ case PNK_ASSIGN:
+ case PNK_ADDASSIGN:
+ case PNK_SUBASSIGN:
+ case PNK_BITORASSIGN:
+ case PNK_BITXORASSIGN:
+ case PNK_BITANDASSIGN:
+ case PNK_LSHASSIGN:
+ case PNK_RSHASSIGN:
+ case PNK_URSHASSIGN:
+ case PNK_MULASSIGN:
+ case PNK_DIVASSIGN:
+ case PNK_MODASSIGN:
+ case PNK_POWASSIGN:
+ // ...and a few others.
+ case PNK_ELEM:
+ case PNK_IMPORT_SPEC:
+ case PNK_EXPORT_SPEC:
+ case PNK_COLON:
+ case PNK_SHORTHAND:
+ case PNK_DOWHILE:
+ case PNK_WHILE:
+ case PNK_SWITCH:
+ case PNK_CLASSMETHOD:
+ case PNK_NEWTARGET:
+ case PNK_SETTHIS:
+ case PNK_FOR:
+ case PNK_COMPREHENSIONFOR:
+ case PNK_WITH: {
+ MOZ_ASSERT(pn->isArity(PN_BINARY));
+ stack->push(pn->pn_left);
+ stack->push(pn->pn_right);
+ return PushResult::Recyclable;
+ }
+
+ // Default clauses are PNK_CASE but do not have case expressions.
+ // Named class expressions do not have outer binding nodes.
+ // So both are binary nodes with a possibly-null pn_left.
+ case PNK_CASE:
+ case PNK_CLASSNAMES: {
+ MOZ_ASSERT(pn->isArity(PN_BINARY));
+ if (pn->pn_left)
+ stack->push(pn->pn_left);
+ stack->push(pn->pn_right);
+ return PushResult::Recyclable;
+ }
+
+ // The left half is the expression being yielded. The right half is
+ // internal goop: a name reference to the invisible '.generator' local
+ // variable, or an assignment of a PNK_GENERATOR node to the '.generator'
+ // local, for a synthesized, prepended initial yield. Yum!
+ case PNK_YIELD_STAR:
+ case PNK_YIELD:
+ case PNK_AWAIT: {
+ MOZ_ASSERT(pn->isArity(PN_BINARY));
+ MOZ_ASSERT(pn->pn_right);
+ MOZ_ASSERT(pn->pn_right->isKind(PNK_NAME) ||
+ (pn->pn_right->isKind(PNK_ASSIGN) &&
+ pn->pn_right->pn_left->isKind(PNK_NAME) &&
+ pn->pn_right->pn_right->isKind(PNK_GENERATOR)));
+ if (pn->pn_left)
+ stack->push(pn->pn_left);
+ stack->push(pn->pn_right);
+ return PushResult::Recyclable;
+ }
+
+ // A return node's child is what you'd expect: the return expression,
+ // if any.
+ case PNK_RETURN: {
+ MOZ_ASSERT(pn->isArity(PN_UNARY));
+ if (pn->pn_kid)
+ stack->push(pn->pn_kid);
+ return PushResult::Recyclable;
+ }
+
+ // Import and export-from nodes have a list of specifiers on the left
+ // and a module string on the right.
+ case PNK_IMPORT:
+ case PNK_EXPORT_FROM: {
+ MOZ_ASSERT(pn->isArity(PN_BINARY));
+ MOZ_ASSERT_IF(pn->isKind(PNK_IMPORT), pn->pn_left->isKind(PNK_IMPORT_SPEC_LIST));
+ MOZ_ASSERT_IF(pn->isKind(PNK_EXPORT_FROM), pn->pn_left->isKind(PNK_EXPORT_SPEC_LIST));
+ MOZ_ASSERT(pn->pn_left->isArity(PN_LIST));
+ MOZ_ASSERT(pn->pn_right->isKind(PNK_STRING));
+ stack->pushList(pn->pn_left);
+ stack->push(pn->pn_right);
+ return PushResult::Recyclable;
+ }
+
+ case PNK_EXPORT_DEFAULT: {
+ MOZ_ASSERT(pn->isArity(PN_BINARY));
+ MOZ_ASSERT_IF(pn->pn_right, pn->pn_right->isKind(PNK_NAME));
+ stack->push(pn->pn_left);
+ if (pn->pn_right)
+ stack->push(pn->pn_right);
+ return PushResult::Recyclable;
+ }
+
+ // Ternary nodes with all children non-null.
+ case PNK_CONDITIONAL: {
+ MOZ_ASSERT(pn->isArity(PN_TERNARY));
+ stack->push(pn->pn_kid1);
+ stack->push(pn->pn_kid2);
+ stack->push(pn->pn_kid3);
+ return PushResult::Recyclable;
+ }
+
+ // For for-in and for-of, the first child is the left-hand side of the
+ // 'in' or 'of' (a declaration or an assignment target). The second
+ // child is always null, and the third child is the expression looped
+ // over. For example, in |for (var p in obj)|, the first child is |var
+ // p|, the second child is null, and the third child is |obj|.
+ case PNK_FORIN:
+ case PNK_FOROF: {
+ MOZ_ASSERT(pn->isArity(PN_TERNARY));
+ MOZ_ASSERT(!pn->pn_kid2);
+ stack->push(pn->pn_kid1);
+ stack->push(pn->pn_kid3);
+ return PushResult::Recyclable;
+ }
+
+ // for (;;) nodes have one child per optional component of the loop head.
+ case PNK_FORHEAD: {
+ MOZ_ASSERT(pn->isArity(PN_TERNARY));
+ if (pn->pn_kid1)
+ stack->push(pn->pn_kid1);
+ if (pn->pn_kid2)
+ stack->push(pn->pn_kid2);
+ if (pn->pn_kid3)
+ stack->push(pn->pn_kid3);
+ return PushResult::Recyclable;
+ }
+
+ // classes might have an optional node for the heritage, as well as the names
+ case PNK_CLASS: {
+ MOZ_ASSERT(pn->isArity(PN_TERNARY));
+ if (pn->pn_kid1)
+ stack->push(pn->pn_kid1);
+ if (pn->pn_kid2)
+ stack->push(pn->pn_kid2);
+ stack->push(pn->pn_kid3);
+ return PushResult::Recyclable;
+ }
+
+ // if-statement nodes have condition and consequent children and a
+ // possibly-null alternative.
+ case PNK_IF: {
+ MOZ_ASSERT(pn->isArity(PN_TERNARY));
+ stack->push(pn->pn_kid1);
+ stack->push(pn->pn_kid2);
+ if (pn->pn_kid3)
+ stack->push(pn->pn_kid3);
+ return PushResult::Recyclable;
+ }
+
+ // try-statements have statements to execute, and one or both of a
+ // catch-list and a finally-block.
+ case PNK_TRY: {
+ MOZ_ASSERT(pn->isArity(PN_TERNARY));
+ MOZ_ASSERT(pn->pn_kid2 || pn->pn_kid3);
+ stack->push(pn->pn_kid1);
+ if (pn->pn_kid2)
+ stack->push(pn->pn_kid2);
+ if (pn->pn_kid3)
+ stack->push(pn->pn_kid3);
+ return PushResult::Recyclable;
+ }
+
+ // A catch node has first kid as catch-variable pattern, the second kid
+ // as catch condition (which, if non-null, records the |<cond>| in
+ // SpiderMonkey's |catch (e if <cond>)| extension), and third kid as the
+ // statements in the catch block.
+ case PNK_CATCH: {
+ MOZ_ASSERT(pn->isArity(PN_TERNARY));
+ stack->push(pn->pn_kid1);
+ if (pn->pn_kid2)
+ stack->push(pn->pn_kid2);
+ stack->push(pn->pn_kid3);
+ return PushResult::Recyclable;
+ }
+
+ // List nodes with all non-null children.
+ case PNK_OR:
+ case PNK_AND:
+ case PNK_BITOR:
+ case PNK_BITXOR:
+ case PNK_BITAND:
+ case PNK_STRICTEQ:
+ case PNK_EQ:
+ case PNK_STRICTNE:
+ case PNK_NE:
+ case PNK_LT:
+ case PNK_LE:
+ case PNK_GT:
+ case PNK_GE:
+ case PNK_INSTANCEOF:
+ case PNK_IN:
+ case PNK_LSH:
+ case PNK_RSH:
+ case PNK_URSH:
+ case PNK_ADD:
+ case PNK_SUB:
+ case PNK_STAR:
+ case PNK_DIV:
+ case PNK_MOD:
+ case PNK_POW:
+ case PNK_COMMA:
+ case PNK_NEW:
+ case PNK_CALL:
+ case PNK_SUPERCALL:
+ case PNK_GENEXP:
+ case PNK_ARRAY:
+ case PNK_OBJECT:
+ case PNK_TEMPLATE_STRING_LIST:
+ case PNK_TAGGED_TEMPLATE:
+ case PNK_CALLSITEOBJ:
+ case PNK_VAR:
+ case PNK_CONST:
+ case PNK_LET:
+ case PNK_CATCHLIST:
+ case PNK_STATEMENTLIST:
+ case PNK_IMPORT_SPEC_LIST:
+ case PNK_EXPORT_SPEC_LIST:
+ case PNK_PARAMSBODY:
+ case PNK_CLASSMETHODLIST:
+ return PushListNodeChildren(pn, stack);
+
+ // Array comprehension nodes are lists with a single child:
+ // PNK_COMPREHENSIONFOR for comprehensions, PNK_LEXICALSCOPE for legacy
+ // comprehensions. Probably this should be a non-list eventually.
+ case PNK_ARRAYCOMP: {
+#ifdef DEBUG
+ MOZ_ASSERT(pn->isKind(PNK_ARRAYCOMP));
+ MOZ_ASSERT(pn->isArity(PN_LIST));
+ MOZ_ASSERT(pn->pn_count == 1);
+ MOZ_ASSERT(pn->pn_head->isKind(PNK_LEXICALSCOPE) ||
+ pn->pn_head->isKind(PNK_COMPREHENSIONFOR));
+#endif
+ return PushListNodeChildren(pn, stack);
+ }
+
+ case PNK_LABEL:
+ case PNK_DOT:
+ case PNK_NAME:
+ return PushNameNodeChildren(pn, stack);
+
+ case PNK_LEXICALSCOPE:
+ return PushScopeNodeChildren(pn, stack);
+
+ case PNK_FUNCTION:
+ case PNK_MODULE:
+ return PushCodeNodeChildren(pn, stack);
+
+ case PNK_LIMIT: // invalid sentinel value
+ MOZ_CRASH("invalid node kind");
+ }
+
+ MOZ_CRASH("bad ParseNodeKind");
+ return PushResult::CleanUpLater;
+}
+
+/*
+ * Prepare |pn| to be mutated in place into a new kind of node. Recycle all
+ * |pn|'s recyclable children (but not |pn| itself!), and disconnect it from
+ * metadata structures (the function box tree).
+ */
+void
+ParseNodeAllocator::prepareNodeForMutation(ParseNode* pn)
+{
+ // Nothing to do for nullary nodes.
+ if (pn->isArity(PN_NULLARY))
+ return;
+
+ // Put |pn|'s children (but not |pn| itself) on a work stack.
+ NodeStack stack;
+ PushNodeChildren(pn, &stack);
+
+ // For each node on the work stack, push its children on the work stack,
+ // and free the node if we can.
+ while (!stack.empty()) {
+ pn = stack.pop();
+ if (PushNodeChildren(pn, &stack) == PushResult::Recyclable)
+ freeNode(pn);
+ }
+}
+
+/*
+ * Return the nodes in the subtree |pn| to the parser's free node list, for
+ * reallocation.
+ */
+ParseNode*
+ParseNodeAllocator::freeTree(ParseNode* pn)
+{
+ if (!pn)
+ return nullptr;
+
+ ParseNode* savedNext = pn->pn_next;
+
+ NodeStack stack;
+ for (;;) {
+ if (PushNodeChildren(pn, &stack) == PushResult::Recyclable)
+ freeNode(pn);
+ if (stack.empty())
+ break;
+ pn = stack.pop();
+ }
+
+ return savedNext;
+}
+
+/*
+ * Allocate a ParseNode from parser's node freelist or, failing that, from
+ * cx's temporary arena.
+ */
+void*
+ParseNodeAllocator::allocNode()
+{
+ if (ParseNode* pn = freelist) {
+ freelist = pn->pn_next;
+ return pn;
+ }
+
+ LifoAlloc::AutoFallibleScope fallibleAllocator(&alloc);
+ void* p = alloc.alloc(sizeof (ParseNode));
+ if (!p)
+ ReportOutOfMemory(cx);
+ return p;
+}
+
+ParseNode*
+ParseNode::appendOrCreateList(ParseNodeKind kind, JSOp op, 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) &&
+ left->isOp(op) &&
+ (CodeSpec[op].format & JOF_LEFTASSOC ||
+ (kind == PNK_POW && !left->pn_parens)))
+ {
+ ListNode* list = &left->as<ListNode>();
+
+ list->append(right);
+ list->pn_pos.end = right->pn_pos.end;
+
+ return list;
+ }
+ }
+
+ ParseNode* list = handler->new_<ListNode>(kind, op, left);
+ if (!list)
+ return nullptr;
+
+ list->append(right);
+ return list;
+}
+
+#ifdef DEBUG
+
+static const char * const parseNodeNames[] = {
+#define STRINGIFY(name) #name,
+ FOR_EACH_PARSE_NODE_KIND(STRINGIFY)
+#undef STRINGIFY
+};
+
+void
+frontend::DumpParseTree(ParseNode* pn, int indent)
+{
+ if (pn == nullptr)
+ fprintf(stderr, "#NULL");
+ else
+ pn->dump(indent);
+}
+
+static void
+IndentNewLine(int indent)
+{
+ fputc('\n', stderr);
+ for (int i = 0; i < indent; ++i)
+ fputc(' ', stderr);
+}
+
+void
+ParseNode::dump()
+{
+ dump(0);
+ fputc('\n', stderr);
+}
+
+void
+ParseNode::dump(int indent)
+{
+ switch (pn_arity) {
+ case PN_NULLARY:
+ ((NullaryNode*) this)->dump();
+ break;
+ case PN_UNARY:
+ ((UnaryNode*) this)->dump(indent);
+ break;
+ case PN_BINARY:
+ ((BinaryNode*) this)->dump(indent);
+ break;
+ case PN_TERNARY:
+ ((TernaryNode*) this)->dump(indent);
+ break;
+ case PN_CODE:
+ ((CodeNode*) this)->dump(indent);
+ break;
+ case PN_LIST:
+ ((ListNode*) this)->dump(indent);
+ break;
+ case PN_NAME:
+ ((NameNode*) this)->dump(indent);
+ break;
+ case PN_SCOPE:
+ ((LexicalScopeNode*) this)->dump(indent);
+ break;
+ default:
+ fprintf(stderr, "#<BAD NODE %p, kind=%u, arity=%u>",
+ (void*) this, unsigned(getKind()), unsigned(pn_arity));
+ break;
+ }
+}
+
+void
+NullaryNode::dump()
+{
+ switch (getKind()) {
+ case PNK_TRUE: fprintf(stderr, "#true"); break;
+ case PNK_FALSE: fprintf(stderr, "#false"); break;
+ case PNK_NULL: fprintf(stderr, "#null"); break;
+
+ case PNK_NUMBER: {
+ ToCStringBuf cbuf;
+ const char* cstr = NumberToCString(nullptr, &cbuf, pn_dval);
+ if (!IsFinite(pn_dval))
+ fputc('#', stderr);
+ if (cstr)
+ fprintf(stderr, "%s", cstr);
+ else
+ fprintf(stderr, "%g", pn_dval);
+ break;
+ }
+
+ case PNK_STRING:
+ pn_atom->dumpCharsNoNewline();
+ break;
+
+ default:
+ fprintf(stderr, "(%s)", parseNodeNames[getKind()]);
+ }
+}
+
+void
+UnaryNode::dump(int indent)
+{
+ const char* name = parseNodeNames[getKind()];
+ fprintf(stderr, "(%s ", name);
+ indent += strlen(name) + 2;
+ DumpParseTree(pn_kid, indent);
+ fprintf(stderr, ")");
+}
+
+void
+BinaryNode::dump(int indent)
+{
+ const char* name = parseNodeNames[getKind()];
+ fprintf(stderr, "(%s ", name);
+ indent += strlen(name) + 2;
+ DumpParseTree(pn_left, indent);
+ IndentNewLine(indent);
+ DumpParseTree(pn_right, indent);
+ fprintf(stderr, ")");
+}
+
+void
+TernaryNode::dump(int indent)
+{
+ const char* name = parseNodeNames[getKind()];
+ fprintf(stderr, "(%s ", name);
+ indent += strlen(name) + 2;
+ DumpParseTree(pn_kid1, indent);
+ IndentNewLine(indent);
+ DumpParseTree(pn_kid2, indent);
+ IndentNewLine(indent);
+ DumpParseTree(pn_kid3, indent);
+ fprintf(stderr, ")");
+}
+
+void
+CodeNode::dump(int indent)
+{
+ const char* name = parseNodeNames[getKind()];
+ fprintf(stderr, "(%s ", name);
+ indent += strlen(name) + 2;
+ DumpParseTree(pn_body, indent);
+ fprintf(stderr, ")");
+}
+
+void
+ListNode::dump(int indent)
+{
+ const char* name = parseNodeNames[getKind()];
+ fprintf(stderr, "(%s [", name);
+ if (pn_head != nullptr) {
+ indent += strlen(name) + 3;
+ DumpParseTree(pn_head, indent);
+ ParseNode* pn = pn_head->pn_next;
+ while (pn != nullptr) {
+ IndentNewLine(indent);
+ DumpParseTree(pn, indent);
+ pn = pn->pn_next;
+ }
+ }
+ fprintf(stderr, "])");
+}
+
+template <typename CharT>
+static void
+DumpName(const CharT* s, size_t len)
+{
+ if (len == 0)
+ fprintf(stderr, "#<zero-length name>");
+
+ for (size_t i = 0; i < len; i++) {
+ char16_t c = s[i];
+ if (c > 32 && c < 127)
+ fputc(c, stderr);
+ else if (c <= 255)
+ fprintf(stderr, "\\x%02x", unsigned(c));
+ else
+ fprintf(stderr, "\\u%04x", unsigned(c));
+ }
+}
+
+void
+NameNode::dump(int indent)
+{
+ if (isKind(PNK_NAME) || isKind(PNK_DOT)) {
+ if (isKind(PNK_DOT))
+ fprintf(stderr, "(.");
+
+ if (!pn_atom) {
+ fprintf(stderr, "#<null name>");
+ } else if (getOp() == JSOP_GETARG && pn_atom->length() == 0) {
+ // Dump destructuring parameter.
+ fprintf(stderr, "(#<zero-length name> ");
+ DumpParseTree(expr(), indent + 21);
+ fputc(')', stderr);
+ } else {
+ JS::AutoCheckCannotGC nogc;
+ if (pn_atom->hasLatin1Chars())
+ DumpName(pn_atom->latin1Chars(nogc), pn_atom->length());
+ else
+ DumpName(pn_atom->twoByteChars(nogc), pn_atom->length());
+ }
+
+ if (isKind(PNK_DOT)) {
+ fputc(' ', stderr);
+ if (as<PropertyAccess>().isSuper())
+ fprintf(stderr, "super");
+ else
+ DumpParseTree(expr(), indent + 2);
+ fputc(')', stderr);
+ }
+ return;
+ }
+
+ const char* name = parseNodeNames[getKind()];
+ fprintf(stderr, "(%s ", name);
+ indent += strlen(name) + 2;
+ DumpParseTree(expr(), indent);
+ fprintf(stderr, ")");
+}
+
+void
+LexicalScopeNode::dump(int indent)
+{
+ const char* name = parseNodeNames[getKind()];
+ fprintf(stderr, "(%s [", name);
+ int nameIndent = indent + strlen(name) + 3;
+ if (!isEmptyScope()) {
+ LexicalScope::Data* bindings = scopeBindings();
+ for (uint32_t i = 0; i < bindings->length; i++) {
+ JSAtom* name = bindings->names[i].name();
+ JS::AutoCheckCannotGC nogc;
+ if (name->hasLatin1Chars())
+ DumpName(name->latin1Chars(nogc), name->length());
+ else
+ DumpName(name->twoByteChars(nogc), name->length());
+ if (i < bindings->length - 1)
+ IndentNewLine(nameIndent);
+ }
+ }
+ fprintf(stderr, "]");
+ indent += 2;
+ IndentNewLine(indent);
+ DumpParseTree(scopeBody(), indent);
+ fprintf(stderr, ")");
+}
+#endif
+
+ObjectBox::ObjectBox(JSObject* object, ObjectBox* traceLink)
+ : object(object),
+ traceLink(traceLink),
+ emitLink(nullptr)
+{
+ MOZ_ASSERT(!object->is<JSFunction>());
+ MOZ_ASSERT(object->isTenured());
+}
+
+ObjectBox::ObjectBox(JSFunction* function, ObjectBox* traceLink)
+ : object(function),
+ traceLink(traceLink),
+ emitLink(nullptr)
+{
+ MOZ_ASSERT(object->is<JSFunction>());
+ MOZ_ASSERT(asFunctionBox()->function() == function);
+ MOZ_ASSERT(object->isTenured());
+}
+
+FunctionBox*
+ObjectBox::asFunctionBox()
+{
+ MOZ_ASSERT(isFunctionBox());
+ return static_cast<FunctionBox*>(this);
+}
+
+/* static */ void
+ObjectBox::TraceList(JSTracer* trc, ObjectBox* listHead)
+{
+ for (ObjectBox* box = listHead; box; box = box->traceLink)
+ box->trace(trc);
+}
+
+void
+ObjectBox::trace(JSTracer* trc)
+{
+ TraceRoot(trc, &object, "parser.object");
+}
+
+void
+FunctionBox::trace(JSTracer* trc)
+{
+ ObjectBox::trace(trc);
+ if (enclosingScope_)
+ TraceRoot(trc, &enclosingScope_, "funbox-enclosingScope");
+}
diff --git a/js/src/frontend/ParseNode.h b/js/src/frontend/ParseNode.h
new file mode 100644
index 0000000000..d37aaaae08
--- /dev/null
+++ b/js/src/frontend/ParseNode.h
@@ -0,0 +1,1450 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sts=4 et sw=4 tw=99:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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/Attributes.h"
+
+#include "builtin/ModuleObject.h"
+#include "frontend/TokenStream.h"
+
+namespace js {
+namespace frontend {
+
+class ParseContext;
+class FullParseHandler;
+class FunctionBox;
+class ObjectBox;
+
+#define FOR_EACH_PARSE_NODE_KIND(F) \
+ F(NOP) \
+ F(SEMI) \
+ F(COMMA) \
+ F(CONDITIONAL) \
+ F(COLON) \
+ F(SHORTHAND) \
+ F(POS) \
+ F(NEG) \
+ F(PREINCREMENT) \
+ F(POSTINCREMENT) \
+ F(PREDECREMENT) \
+ F(POSTDECREMENT) \
+ F(DOT) \
+ F(ELEM) \
+ F(ARRAY) \
+ F(ELISION) \
+ F(STATEMENTLIST) \
+ F(LABEL) \
+ F(OBJECT) \
+ F(CALL) \
+ F(NAME) \
+ F(OBJECT_PROPERTY_NAME) \
+ F(COMPUTED_NAME) \
+ F(NUMBER) \
+ F(STRING) \
+ F(TEMPLATE_STRING_LIST) \
+ F(TEMPLATE_STRING) \
+ F(TAGGED_TEMPLATE) \
+ F(CALLSITEOBJ) \
+ F(REGEXP) \
+ F(TRUE) \
+ F(FALSE) \
+ F(NULL) \
+ F(THIS) \
+ F(FUNCTION) \
+ F(MODULE) \
+ F(IF) \
+ F(SWITCH) \
+ F(CASE) \
+ F(WHILE) \
+ F(DOWHILE) \
+ F(FOR) \
+ F(COMPREHENSIONFOR) \
+ F(BREAK) \
+ F(CONTINUE) \
+ F(VAR) \
+ F(CONST) \
+ F(WITH) \
+ F(RETURN) \
+ F(NEW) \
+ /* Delete operations. These must be sequential. */ \
+ F(DELETENAME) \
+ F(DELETEPROP) \
+ F(DELETEELEM) \
+ F(DELETEEXPR) \
+ F(TRY) \
+ F(CATCH) \
+ F(CATCHLIST) \
+ F(THROW) \
+ F(DEBUGGER) \
+ F(GENERATOR) \
+ F(YIELD) \
+ F(YIELD_STAR) \
+ F(GENEXP) \
+ F(ARRAYCOMP) \
+ F(ARRAYPUSH) \
+ F(LEXICALSCOPE) \
+ F(LET) \
+ F(IMPORT) \
+ F(IMPORT_SPEC_LIST) \
+ F(IMPORT_SPEC) \
+ F(EXPORT) \
+ F(EXPORT_FROM) \
+ F(EXPORT_DEFAULT) \
+ F(EXPORT_SPEC_LIST) \
+ F(EXPORT_SPEC) \
+ F(EXPORT_BATCH_SPEC) \
+ F(FORIN) \
+ F(FOROF) \
+ F(FORHEAD) \
+ F(PARAMSBODY) \
+ F(SPREAD) \
+ F(MUTATEPROTO) \
+ F(CLASS) \
+ F(CLASSMETHOD) \
+ F(CLASSMETHODLIST) \
+ F(CLASSNAMES) \
+ F(NEWTARGET) \
+ F(POSHOLDER) \
+ F(SUPERBASE) \
+ F(SUPERCALL) \
+ F(SETTHIS) \
+ \
+ /* Unary operators. */ \
+ F(TYPEOFNAME) \
+ F(TYPEOFEXPR) \
+ F(VOID) \
+ F(NOT) \
+ F(BITNOT) \
+ F(AWAIT) \
+ \
+ /* \
+ * Binary operators. \
+ * These must be in the same order as TOK_OR and friends in TokenStream.h. \
+ */ \
+ F(OR) \
+ F(AND) \
+ F(BITOR) \
+ F(BITXOR) \
+ F(BITAND) \
+ F(STRICTEQ) \
+ F(EQ) \
+ F(STRICTNE) \
+ F(NE) \
+ F(LT) \
+ F(LE) \
+ F(GT) \
+ F(GE) \
+ F(INSTANCEOF) \
+ F(IN) \
+ F(LSH) \
+ F(RSH) \
+ F(URSH) \
+ F(ADD) \
+ F(SUB) \
+ F(STAR) \
+ F(DIV) \
+ F(MOD) \
+ F(POW) \
+ \
+ /* Assignment operators (= += -= etc.). */ \
+ /* ParseNode::isAssignment assumes all these are consecutive. */ \
+ F(ASSIGN) \
+ F(ADDASSIGN) \
+ F(SUBASSIGN) \
+ F(BITORASSIGN) \
+ F(BITXORASSIGN) \
+ F(BITANDASSIGN) \
+ F(LSHASSIGN) \
+ F(RSHASSIGN) \
+ F(URSHASSIGN) \
+ F(MULASSIGN) \
+ F(DIVASSIGN) \
+ F(MODASSIGN) \
+ F(POWASSIGN)
+
+/*
+ * 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, with a
+ * secondary JSOp label when needed.
+ *
+ * The long comment after this enum block describes the kinds in detail.
+ */
+enum ParseNodeKind
+{
+#define EMIT_ENUM(name) PNK_##name,
+ FOR_EACH_PARSE_NODE_KIND(EMIT_ENUM)
+#undef EMIT_ENUM
+ PNK_LIMIT, /* domain size */
+ PNK_BINOP_FIRST = PNK_OR,
+ PNK_BINOP_LAST = PNK_POW,
+ PNK_ASSIGNMENT_START = PNK_ASSIGN,
+ PNK_ASSIGNMENT_LAST = PNK_POWASSIGN
+};
+
+inline bool
+IsDeleteKind(ParseNodeKind kind)
+{
+ return PNK_DELETENAME <= kind && kind <= PNK_DELETEEXPR;
+}
+
+inline bool
+IsTypeofKind(ParseNodeKind kind)
+{
+ return PNK_TYPEOFNAME <= kind && kind <= PNK_TYPEOFEXPR;
+}
+
+/*
+ * Label Variant Members
+ * ----- ------- -------
+ * <Definitions>
+ * PNK_FUNCTION name pn_funbox: ptr to js::FunctionBox holding function
+ * object containing arg and var properties. We
+ * create the function object at parse (not emit)
+ * time to specialize arg and var bytecodes early.
+ * pn_body: PNK_PARAMSBODY, ordinarily;
+ * PNK_LEXICALSCOPE for implicit function in genexpr
+ * PNK_PARAMSBODY list list of formal parameters with
+ * PNK_NAME node with non-empty name for
+ * SingleNameBinding without Initializer
+ * PNK_ASSIGN node for SingleNameBinding with
+ * Initializer
+ * PNK_NAME node with empty name for destructuring
+ * pn_expr: PNK_ARRAY, PNK_OBJECT, or PNK_ASSIGN
+ * PNK_ARRAY or PNK_OBJECT for BindingPattern
+ * without Initializer
+ * PNK_ASSIGN for BindingPattern with
+ * Initializer
+ * followed by:
+ * PNK_STATEMENTLIST node for function body
+ * statements,
+ * PNK_RETURN for expression closure
+ * pn_count: 1 + number of formal parameters
+ * pn_tree: PNK_PARAMSBODY or PNK_STATEMENTLIST node
+ * PNK_SPREAD unary pn_kid: expression being spread
+ *
+ * <Statements>
+ * PNK_STATEMENTLIST list pn_head: list of pn_count statements
+ * PNK_IF ternary pn_kid1: cond, pn_kid2: then, pn_kid3: else or null.
+ * In body of a comprehension or desugared generator
+ * expression, pn_kid2 is PNK_YIELD, PNK_ARRAYPUSH,
+ * or (if the push was optimized away) empty
+ * PNK_STATEMENTLIST.
+ * PNK_SWITCH binary pn_left: discriminant
+ * pn_right: list of PNK_CASE nodes, with at most one
+ * default node, or if there are let bindings
+ * in the top level of the switch body's cases, a
+ * PNK_LEXICALSCOPE node that contains the list of
+ * PNK_CASE nodes.
+ * PNK_CASE binary pn_left: case-expression if CaseClause, or
+ * null if DefaultClause
+ * pn_right: PNK_STATEMENTLIST node for this case's
+ * statements
+ * pn_u.binary.offset: scratch space for the emitter
+ * PNK_WHILE binary pn_left: cond, pn_right: body
+ * PNK_DOWHILE binary pn_left: body, pn_right: cond
+ * PNK_FOR binary pn_left: either PNK_FORIN (for-in statement),
+ * PNK_FOROF (for-of) or PNK_FORHEAD (for(;;))
+ * pn_right: body
+ * PNK_COMPREHENSIONFOR pn_left: either PNK_FORIN or PNK_FOROF
+ * binary pn_right: body
+ * PNK_FORIN ternary pn_kid1: declaration or expression to left of 'in'
+ * pn_kid2: null
+ * pn_kid3: object expr to right of 'in'
+ * PNK_FOROF ternary pn_kid1: declaration or expression to left of 'of'
+ * pn_kid2: null
+ * pn_kid3: expr to right of 'of'
+ * PNK_FORHEAD ternary pn_kid1: init expr before first ';' or nullptr
+ * pn_kid2: cond expr before second ';' or nullptr
+ * pn_kid3: update expr after second ';' or nullptr
+ * PNK_THROW unary pn_op: JSOP_THROW, pn_kid: exception
+ * PNK_TRY ternary pn_kid1: try block
+ * pn_kid2: null or PNK_CATCHLIST list
+ * pn_kid3: null or finally block
+ * PNK_CATCHLIST list pn_head: list of PNK_LEXICALSCOPE nodes, one per
+ * catch-block, each with pn_expr pointing
+ * to a PNK_CATCH node
+ * PNK_CATCH ternary pn_kid1: PNK_NAME, PNK_ARRAY, or PNK_OBJECT catch var node
+ * (PNK_ARRAY or PNK_OBJECT if destructuring)
+ * pn_kid2: null or the catch guard expression
+ * pn_kid3: catch block statements
+ * PNK_BREAK name pn_atom: label or null
+ * PNK_CONTINUE name pn_atom: label or null
+ * PNK_WITH binary pn_left: head expr; pn_right: body;
+ * PNK_VAR, list pn_head: list of PNK_NAME or PNK_ASSIGN nodes
+ * PNK_LET, each name node has either
+ * PNK_CONST pn_used: false
+ * pn_atom: variable name
+ * pn_expr: initializer or null
+ * or
+ * pn_used: true
+ * pn_atom: variable name
+ * pn_lexdef: def node
+ * each assignment node has
+ * pn_left: PNK_NAME with pn_used true and
+ * pn_lexdef (NOT pn_expr) set
+ * pn_right: initializer
+ * PNK_RETURN unary pn_kid: return expr or null
+ * PNK_SEMI unary pn_kid: expr or null statement
+ * pn_prologue: true if Directive Prologue member
+ * in original source, not introduced via
+ * constant folding or other tree rewriting
+ * PNK_LABEL name pn_atom: label, pn_expr: labeled statement
+ * PNK_IMPORT binary pn_left: PNK_IMPORT_SPEC_LIST import specifiers
+ * pn_right: PNK_STRING module specifier
+ * PNK_EXPORT unary pn_kid: declaration expression
+ * PNK_EXPORT_FROM binary pn_left: PNK_EXPORT_SPEC_LIST export specifiers
+ * pn_right: PNK_STRING module specifier
+ * PNK_EXPORT_DEFAULT unary pn_kid: export default declaration or expression
+ *
+ * <Expressions>
+ * All left-associated binary trees of the same type are optimized into lists
+ * to avoid recursion when processing expression chains.
+ * PNK_COMMA list pn_head: list of pn_count comma-separated exprs
+ * PNK_ASSIGN binary pn_left: lvalue, pn_right: rvalue
+ * PNK_ADDASSIGN, binary pn_left: lvalue, pn_right: rvalue
+ * PNK_SUBASSIGN, pn_op: JSOP_ADD for +=, etc.
+ * PNK_BITORASSIGN,
+ * PNK_BITXORASSIGN,
+ * PNK_BITANDASSIGN,
+ * PNK_LSHASSIGN,
+ * PNK_RSHASSIGN,
+ * PNK_URSHASSIGN,
+ * PNK_MULASSIGN,
+ * PNK_DIVASSIGN,
+ * PNK_MODASSIGN,
+ * PNK_POWASSIGN
+ * PNK_CONDITIONAL ternary (cond ? trueExpr : falseExpr)
+ * pn_kid1: cond, pn_kid2: then, pn_kid3: else
+ * PNK_OR, list pn_head; list of pn_count subexpressions
+ * PNK_AND, All of these operators are left-associative except (**).
+ * PNK_BITOR,
+ * PNK_BITXOR,
+ * PNK_BITAND,
+ * PNK_EQ,
+ * PNK_NE,
+ * PNK_STRICTEQ,
+ * PNK_STRICTNE,
+ * PNK_LT,
+ * PNK_LE,
+ * PNK_GT,
+ * PNK_GE,
+ * PNK_LSH,
+ * PNK_RSH,
+ * PNK_URSH,
+ * PNK_ADD,
+ * PNK_SUB,
+ * PNK_STAR,
+ * PNK_DIV,
+ * PNK_MOD,
+ * PNK_POW (**) is right-associative, but forms a list
+ * nonetheless. Special hacks everywhere.
+ *
+ * PNK_POS, unary pn_kid: UNARY expr
+ * PNK_NEG
+ * PNK_VOID, unary pn_kid: UNARY expr
+ * PNK_NOT,
+ * PNK_BITNOT,
+ * PNK_AWAIT
+ * PNK_TYPEOFNAME, unary pn_kid: UNARY expr
+ * PNK_TYPEOFEXPR
+ * PNK_PREINCREMENT, unary pn_kid: MEMBER expr
+ * PNK_POSTINCREMENT,
+ * PNK_PREDECREMENT,
+ * PNK_POSTDECREMENT
+ * PNK_NEW list pn_head: list of ctor, arg1, arg2, ... argN
+ * pn_count: 1 + N (where N is number of args)
+ * ctor is a MEMBER expr
+ * PNK_DELETENAME unary pn_kid: PNK_NAME expr
+ * PNK_DELETEPROP unary pn_kid: PNK_DOT expr
+ * PNK_DELETEELEM unary pn_kid: PNK_ELEM expr
+ * PNK_DELETEEXPR unary pn_kid: MEMBER expr that's evaluated, then the
+ * overall delete evaluates to true; can't be a kind
+ * for a more-specific PNK_DELETE* unless constant
+ * folding (or a similar parse tree manipulation) has
+ * occurred
+ * PNK_DOT name pn_expr: MEMBER expr to left of .
+ * pn_atom: name to right of .
+ * PNK_ELEM binary pn_left: MEMBER expr to left of [
+ * pn_right: expr between [ and ]
+ * PNK_CALL list pn_head: list of call, arg1, arg2, ... argN
+ * pn_count: 1 + N (where N is number of args)
+ * call is a MEMBER expr naming a callable object
+ * PNK_GENEXP list Exactly like PNK_CALL, used for the implicit call
+ * in the desugaring of a generator-expression.
+ * PNK_ARRAY list pn_head: list of pn_count array element exprs
+ * [,,] holes are represented by PNK_ELISION nodes
+ * pn_xflags: PN_ENDCOMMA if extra comma at end
+ * PNK_OBJECT list pn_head: list of pn_count binary PNK_COLON nodes
+ * PNK_COLON binary key-value pair in object initializer or
+ * destructuring lhs
+ * pn_left: property id, pn_right: value
+ * PNK_SHORTHAND binary Same fields as PNK_COLON. This is used for object
+ * literal properties using shorthand ({x}).
+ * PNK_COMPUTED_NAME unary ES6 ComputedPropertyName.
+ * pn_kid: the AssignmentExpression inside the square brackets
+ * PNK_NAME, name pn_atom: name, string, or object atom
+ * PNK_STRING pn_op: JSOP_GETNAME, JSOP_STRING, or JSOP_OBJECT
+ * If JSOP_GETNAME, pn_op may be JSOP_*ARG or JSOP_*VAR
+ * telling const-ness and static analysis results
+ * PNK_TEMPLATE_STRING_LIST pn_head: list of alternating expr and template strings
+ * list
+ * PNK_TEMPLATE_STRING pn_atom: template string atom
+ nullary pn_op: JSOP_NOP
+ * PNK_TAGGED_TEMPLATE pn_head: list of call, call site object, arg1, arg2, ... argN
+ * list pn_count: 2 + N (N is the number of substitutions)
+ * PNK_CALLSITEOBJ list pn_head: a PNK_ARRAY node followed by
+ * list of pn_count - 1 PNK_TEMPLATE_STRING nodes
+ * PNK_REGEXP nullary pn_objbox: RegExp model object
+ * PNK_NUMBER dval pn_dval: double value of numeric literal
+ * PNK_TRUE, nullary pn_op: JSOp bytecode
+ * PNK_FALSE,
+ * PNK_NULL
+ *
+ * PNK_THIS, unary pn_kid: '.this' Name if function `this`, else nullptr
+ * PNK_SUPERBASE unary pn_kid: '.this' Name
+ *
+ * PNK_SETTHIS binary pn_left: '.this' Name, pn_right: SuperCall
+ *
+ * PNK_LEXICALSCOPE scope pn_u.scope.bindings: scope bindings
+ * pn_u.scope.body: scope body
+ * PNK_GENERATOR nullary
+ * PNK_YIELD, binary pn_left: expr or null; pn_right: generator object
+ * PNK_YIELD_STAR
+ * PNK_ARRAYCOMP list pn_count: 1
+ * pn_head: list of 1 element, which is block
+ * enclosing for loop(s) and optionally
+ * if-guarded PNK_ARRAYPUSH
+ * PNK_ARRAYPUSH unary pn_op: JSOP_ARRAYCOMP
+ * pn_kid: array comprehension expression
+ * PNK_NOP nullary
+ */
+enum ParseNodeArity
+{
+ PN_NULLARY, /* 0 kids, only pn_atom/pn_dval/etc. */
+ PN_UNARY, /* one kid, plus a couple of scalars */
+ PN_BINARY, /* two kids, plus a couple of scalars */
+ PN_TERNARY, /* three kids */
+ PN_CODE, /* module or function definition node */
+ PN_LIST, /* generic singly linked list */
+ PN_NAME, /* name, label, or regexp */
+ PN_SCOPE /* lexical scope */
+};
+
+class LoopControlStatement;
+class BreakStatement;
+class ContinueStatement;
+class ConditionalExpression;
+class PropertyAccess;
+
+class ParseNode
+{
+ uint16_t pn_type; /* PNK_* type */
+ uint8_t pn_op; /* see JSOp enum and jsopcode.tbl */
+ uint8_t pn_arity:4; /* see ParseNodeArity enum */
+ bool pn_parens:1; /* this expr was enclosed in parens */
+
+ ParseNode(const ParseNode& other) = delete;
+ void operator=(const ParseNode& other) = delete;
+
+ public:
+ ParseNode(ParseNodeKind kind, JSOp op, ParseNodeArity arity)
+ : pn_type(kind),
+ pn_op(op),
+ pn_arity(arity),
+ pn_parens(false),
+ pn_pos(0, 0),
+ pn_next(nullptr)
+ {
+ MOZ_ASSERT(kind < PNK_LIMIT);
+ memset(&pn_u, 0, sizeof pn_u);
+ }
+
+ ParseNode(ParseNodeKind kind, JSOp op, ParseNodeArity arity, const TokenPos& pos)
+ : pn_type(kind),
+ pn_op(op),
+ pn_arity(arity),
+ pn_parens(false),
+ pn_pos(pos),
+ pn_next(nullptr)
+ {
+ MOZ_ASSERT(kind < PNK_LIMIT);
+ memset(&pn_u, 0, sizeof pn_u);
+ }
+
+ JSOp getOp() const { return JSOp(pn_op); }
+ void setOp(JSOp op) { pn_op = op; }
+ bool isOp(JSOp op) const { return getOp() == op; }
+
+ ParseNodeKind getKind() const {
+ MOZ_ASSERT(pn_type < PNK_LIMIT);
+ return ParseNodeKind(pn_type);
+ }
+ void setKind(ParseNodeKind kind) {
+ MOZ_ASSERT(kind < PNK_LIMIT);
+ pn_type = kind;
+ }
+ bool isKind(ParseNodeKind kind) const { return getKind() == kind; }
+
+ ParseNodeArity getArity() const { return ParseNodeArity(pn_arity); }
+ bool isArity(ParseNodeArity a) const { return getArity() == a; }
+ void setArity(ParseNodeArity a) { pn_arity = a; }
+
+ bool isAssignment() const {
+ ParseNodeKind kind = getKind();
+ return PNK_ASSIGNMENT_START <= kind && kind <= PNK_ASSIGNMENT_LAST;
+ }
+
+ bool isBinaryOperation() const {
+ ParseNodeKind kind = getKind();
+ return PNK_BINOP_FIRST <= kind && kind <= PNK_BINOP_LAST;
+ }
+
+ /* Boolean attributes. */
+ bool isInParens() const { return pn_parens; }
+ bool isLikelyIIFE() const { return isInParens(); }
+ void setInParens(bool enabled) { pn_parens = enabled; }
+
+ TokenPos pn_pos; /* two 16-bit pairs here, for 64 bits */
+ ParseNode* pn_next; /* intrinsic link in parent PN_LIST */
+
+ union {
+ struct { /* list of next-linked nodes */
+ ParseNode* head; /* first node in list */
+ ParseNode** tail; /* ptr to ptr to last node in list */
+ uint32_t count; /* number of nodes in list */
+ uint32_t xflags; /* see PNX_* below */
+ } list;
+ struct { /* ternary: if, for(;;), ?: */
+ ParseNode* kid1; /* condition, discriminant, etc. */
+ ParseNode* kid2; /* then-part, case list, etc. */
+ ParseNode* kid3; /* else-part, default case, etc. */
+ } ternary;
+ struct { /* two kids if binary */
+ ParseNode* left;
+ ParseNode* right;
+ union {
+ unsigned iflags; /* JSITER_* flags for PNK_{COMPREHENSION,}FOR node */
+ bool isStatic; /* only for PNK_CLASSMETHOD */
+ uint32_t offset; /* for the emitter's use on PNK_CASE nodes */
+ };
+ } binary;
+ struct { /* one kid if unary */
+ ParseNode* kid;
+ bool prologue; /* directive prologue member (as
+ pn_prologue) */
+ } unary;
+ struct { /* name, labeled statement, etc. */
+ union {
+ JSAtom* atom; /* lexical name or label atom */
+ ObjectBox* objbox; /* regexp object */
+ FunctionBox* funbox; /* function object */
+ };
+ ParseNode* expr; /* module or function body, var
+ initializer, argument default, or
+ base object of PNK_DOT */
+ } name;
+ struct {
+ LexicalScope::Data* bindings;
+ ParseNode* body;
+ } scope;
+ struct {
+ double value; /* aligned numeric literal value */
+ DecimalPoint decimalPoint; /* Whether the number has a decimal point */
+ } number;
+ class {
+ friend class LoopControlStatement;
+ PropertyName* label; /* target of break/continue statement */
+ } loopControl;
+ } pn_u;
+
+#define pn_objbox pn_u.name.objbox
+#define pn_funbox pn_u.name.funbox
+#define pn_body pn_u.name.expr
+#define pn_head pn_u.list.head
+#define pn_tail pn_u.list.tail
+#define pn_count pn_u.list.count
+#define pn_xflags pn_u.list.xflags
+#define pn_kid1 pn_u.ternary.kid1
+#define pn_kid2 pn_u.ternary.kid2
+#define pn_kid3 pn_u.ternary.kid3
+#define pn_left pn_u.binary.left
+#define pn_right pn_u.binary.right
+#define pn_pval pn_u.binary.pval
+#define pn_iflags pn_u.binary.iflags
+#define pn_kid pn_u.unary.kid
+#define pn_prologue pn_u.unary.prologue
+#define pn_atom pn_u.name.atom
+#define pn_objbox pn_u.name.objbox
+#define pn_expr pn_u.name.expr
+#define pn_dval pn_u.number.value
+
+
+ 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, JSOp op, ParseNode* left, ParseNode* right,
+ FullParseHandler* handler, ParseContext* pc);
+
+ inline PropertyName* name() const;
+ inline JSAtom* atom() const;
+
+ ParseNode* expr() const {
+ MOZ_ASSERT(pn_arity == PN_NAME || pn_arity == PN_CODE);
+ return pn_expr;
+ }
+
+ bool isEmptyScope() const {
+ MOZ_ASSERT(pn_arity == PN_SCOPE);
+ return !pn_u.scope.bindings;
+ }
+
+ Handle<LexicalScope::Data*> scopeBindings() const {
+ MOZ_ASSERT(!isEmptyScope());
+ // Bindings' GC safety depend on the presence of an AutoKeepAtoms that
+ // the rest of the frontend also depends on.
+ return Handle<LexicalScope::Data*>::fromMarkedLocation(&pn_u.scope.bindings);
+ }
+
+ ParseNode* scopeBody() const {
+ MOZ_ASSERT(pn_arity == PN_SCOPE);
+ return pn_u.scope.body;
+ }
+
+ void setScopeBody(ParseNode* body) {
+ MOZ_ASSERT(pn_arity == PN_SCOPE);
+ pn_u.scope.body = body;
+ }
+
+/* PN_LIST pn_xflags bits. */
+#define PNX_FUNCDEFS 0x01 /* contains top-level function statements */
+#define PNX_ARRAYHOLESPREAD 0x02 /* one or more of
+ 1. array initialiser has holes
+ 2. array initializer has spread node */
+#define PNX_NONCONST 0x04 /* initialiser has non-constants */
+
+ bool functionIsHoisted() const {
+ MOZ_ASSERT(pn_arity == PN_CODE && getKind() == PNK_FUNCTION);
+ MOZ_ASSERT(isOp(JSOP_LAMBDA) || // lambda, genexpr
+ isOp(JSOP_LAMBDA_ARROW) || // arrow function
+ isOp(JSOP_FUNWITHPROTO) || // already emitted lambda with needsProto
+ isOp(JSOP_DEFFUN) || // non-body-level function statement
+ isOp(JSOP_NOP) || // body-level function stmt in global code
+ isOp(JSOP_GETLOCAL) || // body-level function stmt in function code
+ isOp(JSOP_GETARG) || // body-level function redeclaring formal
+ isOp(JSOP_INITLEXICAL)); // block-level function stmt
+ return !isOp(JSOP_LAMBDA) && !isOp(JSOP_LAMBDA_ARROW) &&
+ !isOp(JSOP_FUNWITHPROTO) && !isOp(JSOP_DEFFUN);
+ }
+
+ /*
+ * True if this statement node 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 pn_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.
+ */
+ JSAtom* isStringExprStatement() const {
+ if (getKind() == PNK_SEMI) {
+ MOZ_ASSERT(pn_arity == PN_UNARY);
+ ParseNode* kid = pn_kid;
+ if (kid && kid->getKind() == PNK_STRING && !kid->pn_parens)
+ return kid->pn_atom;
+ }
+ return nullptr;
+ }
+
+ /* True if pn is a parsenode representing a literal constant. */
+ bool isLiteral() const {
+ return isKind(PNK_NUMBER) ||
+ isKind(PNK_STRING) ||
+ isKind(PNK_TRUE) ||
+ isKind(PNK_FALSE) ||
+ isKind(PNK_NULL);
+ }
+
+ /* Return true if this node appears in a Directive Prologue. */
+ bool isDirectivePrologueMember() const { return pn_prologue; }
+
+ // True iff this is a for-in/of loop variable declaration (var/let/const).
+ bool isForLoopDeclaration() const {
+ if (isKind(PNK_VAR) || isKind(PNK_LET) || isKind(PNK_CONST)) {
+ MOZ_ASSERT(isArity(PN_LIST));
+ MOZ_ASSERT(pn_count > 0);
+ return true;
+ }
+
+ return false;
+ }
+
+ ParseNode* generatorExpr() const {
+ MOZ_ASSERT(isKind(PNK_GENEXP));
+
+ ParseNode* callee = this->pn_head;
+ MOZ_ASSERT(callee->isKind(PNK_FUNCTION));
+
+ ParseNode* paramsBody = callee->pn_body;
+ MOZ_ASSERT(paramsBody->isKind(PNK_PARAMSBODY));
+
+ ParseNode* body = paramsBody->last();
+ MOZ_ASSERT(body->isKind(PNK_STATEMENTLIST));
+ MOZ_ASSERT(body->last()->isKind(PNK_LEXICALSCOPE) ||
+ body->last()->isKind(PNK_COMPREHENSIONFOR));
+ return body->last();
+ }
+
+ /*
+ * Compute a pointer to the last element in a singly-linked list. NB: list
+ * must be non-empty for correct PN_LAST usage -- this is asserted!
+ */
+ ParseNode* last() const {
+ MOZ_ASSERT(pn_arity == PN_LIST);
+ MOZ_ASSERT(pn_count != 0);
+ return (ParseNode*)(uintptr_t(pn_tail) - offsetof(ParseNode, pn_next));
+ }
+
+ void initNumber(double value, DecimalPoint decimalPoint) {
+ MOZ_ASSERT(pn_arity == PN_NULLARY);
+ MOZ_ASSERT(getKind() == PNK_NUMBER);
+ pn_u.number.value = value;
+ pn_u.number.decimalPoint = decimalPoint;
+ }
+
+ void makeEmpty() {
+ MOZ_ASSERT(pn_arity == PN_LIST);
+ pn_head = nullptr;
+ pn_tail = &pn_head;
+ pn_count = 0;
+ pn_xflags = 0;
+ }
+
+ void initList(ParseNode* pn) {
+ MOZ_ASSERT(pn_arity == PN_LIST);
+ if (pn->pn_pos.begin < pn_pos.begin)
+ pn_pos.begin = pn->pn_pos.begin;
+ pn_pos.end = pn->pn_pos.end;
+ pn_head = pn;
+ pn_tail = &pn->pn_next;
+ pn_count = 1;
+ pn_xflags = 0;
+ }
+
+ void append(ParseNode* pn) {
+ MOZ_ASSERT(pn_arity == PN_LIST);
+ MOZ_ASSERT(pn->pn_pos.begin >= pn_pos.begin);
+ pn_pos.end = pn->pn_pos.end;
+ *pn_tail = pn;
+ pn_tail = &pn->pn_next;
+ pn_count++;
+ }
+
+ void prepend(ParseNode* pn) {
+ MOZ_ASSERT(pn_arity == PN_LIST);
+ pn->pn_next = pn_head;
+ pn_head = pn;
+ if (pn_tail == &pn_head)
+ pn_tail = &pn->pn_next;
+ pn_count++;
+ }
+
+ void checkListConsistency()
+#ifndef DEBUG
+ {}
+#endif
+ ;
+
+ enum AllowConstantObjects {
+ DontAllowObjects = 0,
+ AllowObjects,
+ ForCopyOnWriteArray
+ };
+
+ MOZ_MUST_USE bool getConstantValue(ExclusiveContext* cx, AllowConstantObjects allowObjects,
+ MutableHandleValue vp, Value* compare = nullptr,
+ size_t ncompare = 0, NewObjectKind newKind = TenuredObject);
+ 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
+ void dump();
+ void dump(int indent);
+#endif
+};
+
+struct NullaryNode : public ParseNode
+{
+ NullaryNode(ParseNodeKind kind, const TokenPos& pos)
+ : ParseNode(kind, JSOP_NOP, PN_NULLARY, pos) {}
+ NullaryNode(ParseNodeKind kind, JSOp op, const TokenPos& pos)
+ : ParseNode(kind, op, PN_NULLARY, pos) {}
+
+ // This constructor is for a few mad uses in the emitter. It populates
+ // the pn_atom field even though that field belongs to a branch in pn_u
+ // that nullary nodes shouldn't use -- bogus.
+ NullaryNode(ParseNodeKind kind, JSOp op, const TokenPos& pos, JSAtom* atom)
+ : ParseNode(kind, op, PN_NULLARY, pos)
+ {
+ pn_atom = atom;
+ }
+
+#ifdef DEBUG
+ void dump();
+#endif
+};
+
+struct UnaryNode : public ParseNode
+{
+ UnaryNode(ParseNodeKind kind, JSOp op, const TokenPos& pos, ParseNode* kid)
+ : ParseNode(kind, op, PN_UNARY, pos)
+ {
+ pn_kid = kid;
+ }
+
+#ifdef DEBUG
+ void dump(int indent);
+#endif
+};
+
+struct BinaryNode : public ParseNode
+{
+ BinaryNode(ParseNodeKind kind, JSOp op, const TokenPos& pos, ParseNode* left, ParseNode* right)
+ : ParseNode(kind, op, PN_BINARY, pos)
+ {
+ pn_left = left;
+ pn_right = right;
+ }
+
+ BinaryNode(ParseNodeKind kind, JSOp op, ParseNode* left, ParseNode* right)
+ : ParseNode(kind, op, PN_BINARY, TokenPos::box(left->pn_pos, right->pn_pos))
+ {
+ pn_left = left;
+ pn_right = right;
+ }
+
+#ifdef DEBUG
+ void dump(int indent);
+#endif
+};
+
+struct TernaryNode : public ParseNode
+{
+ TernaryNode(ParseNodeKind kind, JSOp op, ParseNode* kid1, ParseNode* kid2, ParseNode* kid3)
+ : ParseNode(kind, op, PN_TERNARY,
+ TokenPos((kid1 ? kid1 : kid2 ? kid2 : kid3)->pn_pos.begin,
+ (kid3 ? kid3 : kid2 ? kid2 : kid1)->pn_pos.end))
+ {
+ pn_kid1 = kid1;
+ pn_kid2 = kid2;
+ pn_kid3 = kid3;
+ }
+
+ TernaryNode(ParseNodeKind kind, JSOp op, ParseNode* kid1, ParseNode* kid2, ParseNode* kid3,
+ const TokenPos& pos)
+ : ParseNode(kind, op, PN_TERNARY, pos)
+ {
+ pn_kid1 = kid1;
+ pn_kid2 = kid2;
+ pn_kid3 = kid3;
+ }
+
+#ifdef DEBUG
+ void dump(int indent);
+#endif
+};
+
+struct ListNode : public ParseNode
+{
+ ListNode(ParseNodeKind kind, const TokenPos& pos)
+ : ParseNode(kind, JSOP_NOP, PN_LIST, pos)
+ {
+ makeEmpty();
+ }
+
+ ListNode(ParseNodeKind kind, JSOp op, const TokenPos& pos)
+ : ParseNode(kind, op, PN_LIST, pos)
+ {
+ makeEmpty();
+ }
+
+ ListNode(ParseNodeKind kind, JSOp op, ParseNode* kid)
+ : ParseNode(kind, op, PN_LIST, kid->pn_pos)
+ {
+ initList(kid);
+ }
+
+ static bool test(const ParseNode& node) {
+ return node.isArity(PN_LIST);
+ }
+
+#ifdef DEBUG
+ void dump(int indent);
+#endif
+};
+
+struct CodeNode : public ParseNode
+{
+ CodeNode(ParseNodeKind kind, const TokenPos& pos)
+ : ParseNode(kind, JSOP_NOP, PN_CODE, pos)
+ {
+ MOZ_ASSERT(kind == PNK_FUNCTION || kind == PNK_MODULE);
+ MOZ_ASSERT(!pn_body);
+ MOZ_ASSERT(!pn_objbox);
+ }
+
+ public:
+#ifdef DEBUG
+ void dump(int indent);
+#endif
+};
+
+struct NameNode : public ParseNode
+{
+ NameNode(ParseNodeKind kind, JSOp op, JSAtom* atom, const TokenPos& pos)
+ : ParseNode(kind, op, PN_NAME, pos)
+ {
+ pn_atom = atom;
+ pn_expr = nullptr;
+ }
+
+#ifdef DEBUG
+ void dump(int indent);
+#endif
+};
+
+struct LexicalScopeNode : public ParseNode
+{
+ LexicalScopeNode(LexicalScope::Data* bindings, ParseNode* body)
+ : ParseNode(PNK_LEXICALSCOPE, JSOP_NOP, PN_SCOPE, body->pn_pos)
+ {
+ pn_u.scope.bindings = bindings;
+ pn_u.scope.body = body;
+ }
+
+ static bool test(const ParseNode& node) {
+ return node.isKind(PNK_LEXICALSCOPE);
+ }
+
+#ifdef DEBUG
+ void dump(int indent);
+#endif
+};
+
+class LabeledStatement : public ParseNode
+{
+ public:
+ LabeledStatement(PropertyName* label, ParseNode* stmt, uint32_t begin)
+ : ParseNode(PNK_LABEL, JSOP_NOP, PN_NAME, TokenPos(begin, stmt->pn_pos.end))
+ {
+ pn_atom = label;
+ pn_expr = stmt;
+ }
+
+ PropertyName* label() const {
+ return pn_atom->asPropertyName();
+ }
+
+ ParseNode* statement() const {
+ return pn_expr;
+ }
+
+ static bool test(const ParseNode& node) {
+ bool match = node.isKind(PNK_LABEL);
+ MOZ_ASSERT_IF(match, node.isArity(PN_NAME));
+ MOZ_ASSERT_IF(match, node.isOp(JSOP_NOP));
+ return match;
+ }
+};
+
+// 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(PNK_CASE, JSOP_NOP, TokenPos(begin, stmts->pn_pos.end), expr, stmts) {}
+
+ ParseNode* caseExpression() const { return pn_left; }
+ bool isDefault() const { return !caseExpression(); }
+ ParseNode* statementList() const { return pn_right; }
+
+ // The next CaseClause in the same switch statement.
+ CaseClause* next() const { return pn_next ? &pn_next->as<CaseClause>() : nullptr; }
+
+ // Scratch space used by the emitter.
+ uint32_t offset() const { return pn_u.binary.offset; }
+ void setOffset(uint32_t u) { pn_u.binary.offset = u; }
+
+ static bool test(const ParseNode& node) {
+ bool match = node.isKind(PNK_CASE);
+ MOZ_ASSERT_IF(match, node.isArity(PN_BINARY));
+ MOZ_ASSERT_IF(match, node.isOp(JSOP_NOP));
+ return match;
+ }
+};
+
+class LoopControlStatement : public ParseNode
+{
+ protected:
+ LoopControlStatement(ParseNodeKind kind, PropertyName* label, const TokenPos& pos)
+ : ParseNode(kind, JSOP_NOP, PN_NULLARY, pos)
+ {
+ MOZ_ASSERT(kind == PNK_BREAK || kind == PNK_CONTINUE);
+ pn_u.loopControl.label = label;
+ }
+
+ public:
+ /* Label associated with this break/continue statement, if any. */
+ PropertyName* label() const {
+ return pn_u.loopControl.label;
+ }
+
+ static bool test(const ParseNode& node) {
+ bool match = node.isKind(PNK_BREAK) || node.isKind(PNK_CONTINUE);
+ MOZ_ASSERT_IF(match, node.isArity(PN_NULLARY));
+ MOZ_ASSERT_IF(match, node.isOp(JSOP_NOP));
+ return match;
+ }
+};
+
+class BreakStatement : public LoopControlStatement
+{
+ public:
+ BreakStatement(PropertyName* label, const TokenPos& pos)
+ : LoopControlStatement(PNK_BREAK, label, pos)
+ { }
+
+ static bool test(const ParseNode& node) {
+ bool match = node.isKind(PNK_BREAK);
+ MOZ_ASSERT_IF(match, node.isArity(PN_NULLARY));
+ MOZ_ASSERT_IF(match, node.isOp(JSOP_NOP));
+ return match;
+ }
+};
+
+class ContinueStatement : public LoopControlStatement
+{
+ public:
+ ContinueStatement(PropertyName* label, const TokenPos& pos)
+ : LoopControlStatement(PNK_CONTINUE, label, pos)
+ { }
+
+ static bool test(const ParseNode& node) {
+ bool match = node.isKind(PNK_CONTINUE);
+ MOZ_ASSERT_IF(match, node.isArity(PN_NULLARY));
+ MOZ_ASSERT_IF(match, node.isOp(JSOP_NOP));
+ return match;
+ }
+};
+
+class DebuggerStatement : public ParseNode
+{
+ public:
+ explicit DebuggerStatement(const TokenPos& pos)
+ : ParseNode(PNK_DEBUGGER, JSOP_NOP, PN_NULLARY, pos)
+ { }
+};
+
+class ConditionalExpression : public ParseNode
+{
+ public:
+ ConditionalExpression(ParseNode* condition, ParseNode* thenExpr, ParseNode* elseExpr)
+ : ParseNode(PNK_CONDITIONAL, JSOP_NOP, PN_TERNARY,
+ TokenPos(condition->pn_pos.begin, elseExpr->pn_pos.end))
+ {
+ MOZ_ASSERT(condition);
+ MOZ_ASSERT(thenExpr);
+ MOZ_ASSERT(elseExpr);
+ pn_u.ternary.kid1 = condition;
+ pn_u.ternary.kid2 = thenExpr;
+ pn_u.ternary.kid3 = elseExpr;
+ }
+
+ ParseNode& condition() const {
+ return *pn_u.ternary.kid1;
+ }
+
+ ParseNode& thenExpression() const {
+ return *pn_u.ternary.kid2;
+ }
+
+ ParseNode& elseExpression() const {
+ return *pn_u.ternary.kid3;
+ }
+
+ static bool test(const ParseNode& node) {
+ bool match = node.isKind(PNK_CONDITIONAL);
+ MOZ_ASSERT_IF(match, node.isArity(PN_TERNARY));
+ MOZ_ASSERT_IF(match, node.isOp(JSOP_NOP));
+ return match;
+ }
+};
+
+class ThisLiteral : public UnaryNode
+{
+ public:
+ ThisLiteral(const TokenPos& pos, ParseNode* thisName)
+ : UnaryNode(PNK_THIS, JSOP_NOP, pos, thisName)
+ { }
+};
+
+class NullLiteral : public ParseNode
+{
+ public:
+ explicit NullLiteral(const TokenPos& pos) : ParseNode(PNK_NULL, JSOP_NULL, PN_NULLARY, pos) { }
+};
+
+class BooleanLiteral : public ParseNode
+{
+ public:
+ BooleanLiteral(bool b, const TokenPos& pos)
+ : ParseNode(b ? PNK_TRUE : PNK_FALSE, b ? JSOP_TRUE : JSOP_FALSE, PN_NULLARY, pos)
+ { }
+};
+
+class RegExpLiteral : public NullaryNode
+{
+ public:
+ RegExpLiteral(ObjectBox* reobj, const TokenPos& pos)
+ : NullaryNode(PNK_REGEXP, JSOP_REGEXP, pos)
+ {
+ pn_objbox = reobj;
+ }
+
+ ObjectBox* objbox() const { return pn_objbox; }
+
+ static bool test(const ParseNode& node) {
+ bool match = node.isKind(PNK_REGEXP);
+ MOZ_ASSERT_IF(match, node.isArity(PN_NULLARY));
+ MOZ_ASSERT_IF(match, node.isOp(JSOP_REGEXP));
+ return match;
+ }
+};
+
+class PropertyAccess : public ParseNode
+{
+ public:
+ PropertyAccess(ParseNode* lhs, PropertyName* name, uint32_t begin, uint32_t end)
+ : ParseNode(PNK_DOT, JSOP_NOP, PN_NAME, TokenPos(begin, end))
+ {
+ MOZ_ASSERT(lhs != nullptr);
+ MOZ_ASSERT(name != nullptr);
+ pn_u.name.expr = lhs;
+ pn_u.name.atom = name;
+ }
+
+ static bool test(const ParseNode& node) {
+ bool match = node.isKind(PNK_DOT);
+ MOZ_ASSERT_IF(match, node.isArity(PN_NAME));
+ return match;
+ }
+
+ ParseNode& expression() const {
+ return *pn_u.name.expr;
+ }
+
+ PropertyName& name() const {
+ return *pn_u.name.atom->asPropertyName();
+ }
+
+ bool isSuper() const {
+ // PNK_SUPERBASE cannot result from any expression syntax.
+ return expression().isKind(PNK_SUPERBASE);
+ }
+};
+
+class PropertyByValue : public ParseNode
+{
+ public:
+ PropertyByValue(ParseNode* lhs, ParseNode* propExpr, uint32_t begin, uint32_t end)
+ : ParseNode(PNK_ELEM, JSOP_NOP, PN_BINARY, TokenPos(begin, end))
+ {
+ pn_u.binary.left = lhs;
+ pn_u.binary.right = propExpr;
+ }
+
+ static bool test(const ParseNode& node) {
+ bool match = node.isKind(PNK_ELEM);
+ MOZ_ASSERT_IF(match, node.isArity(PN_BINARY));
+ return match;
+ }
+
+ bool isSuper() const {
+ return pn_left->isKind(PNK_SUPERBASE);
+ }
+};
+
+/*
+ * A CallSiteNode represents the implicit call site object argument in a TaggedTemplate.
+ */
+struct CallSiteNode : public ListNode {
+ explicit CallSiteNode(uint32_t begin): ListNode(PNK_CALLSITEOBJ, TokenPos(begin, begin + 1)) {}
+
+ static bool test(const ParseNode& node) {
+ return node.isKind(PNK_CALLSITEOBJ);
+ }
+
+ MOZ_MUST_USE bool getRawArrayValue(ExclusiveContext* cx, MutableHandleValue vp) {
+ return pn_head->getConstantValue(cx, AllowObjects, vp);
+ }
+};
+
+struct ClassMethod : public BinaryNode {
+ /*
+ * Method defintions often keep a name and function body that overlap,
+ * so explicitly define the beginning and end here.
+ */
+ ClassMethod(ParseNode* name, ParseNode* body, JSOp op, bool isStatic)
+ : BinaryNode(PNK_CLASSMETHOD, op, TokenPos(name->pn_pos.begin, body->pn_pos.end), name, body)
+ {
+ pn_u.binary.isStatic = isStatic;
+ }
+
+ static bool test(const ParseNode& node) {
+ bool match = node.isKind(PNK_CLASSMETHOD);
+ MOZ_ASSERT_IF(match, node.isArity(PN_BINARY));
+ return match;
+ }
+
+ ParseNode& name() const {
+ return *pn_u.binary.left;
+ }
+ ParseNode& method() const {
+ return *pn_u.binary.right;
+ }
+ bool isStatic() const {
+ return pn_u.binary.isStatic;
+ }
+};
+
+struct ClassNames : public BinaryNode {
+ ClassNames(ParseNode* outerBinding, ParseNode* innerBinding, const TokenPos& pos)
+ : BinaryNode(PNK_CLASSNAMES, JSOP_NOP, pos, outerBinding, innerBinding)
+ {
+ MOZ_ASSERT_IF(outerBinding, outerBinding->isKind(PNK_NAME));
+ MOZ_ASSERT(innerBinding->isKind(PNK_NAME));
+ MOZ_ASSERT_IF(outerBinding, innerBinding->pn_atom == outerBinding->pn_atom);
+ }
+
+ static bool test(const ParseNode& node) {
+ bool match = node.isKind(PNK_CLASSNAMES);
+ MOZ_ASSERT_IF(match, node.isArity(PN_BINARY));
+ 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.
+ */
+ ParseNode* outerBinding() const {
+ return pn_u.binary.left;
+ }
+ ParseNode* innerBinding() const {
+ return pn_u.binary.right;
+ }
+};
+
+struct ClassNode : public TernaryNode {
+ ClassNode(ParseNode* names, ParseNode* heritage, ParseNode* methodsOrBlock)
+ : TernaryNode(PNK_CLASS, JSOP_NOP, names, heritage, methodsOrBlock)
+ {
+ MOZ_ASSERT_IF(names, names->is<ClassNames>());
+ MOZ_ASSERT(methodsOrBlock->is<LexicalScopeNode>() ||
+ methodsOrBlock->isKind(PNK_CLASSMETHODLIST));
+ }
+
+ static bool test(const ParseNode& node) {
+ bool match = node.isKind(PNK_CLASS);
+ MOZ_ASSERT_IF(match, node.isArity(PN_TERNARY));
+ return match;
+ }
+
+ ClassNames* names() const {
+ return pn_kid1 ? &pn_kid1->as<ClassNames>() : nullptr;
+ }
+ ParseNode* heritage() const {
+ return pn_kid2;
+ }
+ ParseNode* methodList() const {
+ if (pn_kid3->isKind(PNK_CLASSMETHODLIST))
+ return pn_kid3;
+
+ MOZ_ASSERT(pn_kid3->is<LexicalScopeNode>());
+ ParseNode* list = pn_kid3->scopeBody();
+ MOZ_ASSERT(list->isKind(PNK_CLASSMETHODLIST));
+ return list;
+ }
+ Handle<LexicalScope::Data*> scopeBindings() const {
+ MOZ_ASSERT(pn_kid3->is<LexicalScopeNode>());
+ return pn_kid3->scopeBindings();
+ }
+};
+
+#ifdef DEBUG
+void DumpParseTree(ParseNode* pn, int indent = 0);
+#endif
+
+class ParseNodeAllocator
+{
+ public:
+ explicit ParseNodeAllocator(ExclusiveContext* cx, LifoAlloc& alloc)
+ : cx(cx), alloc(alloc), freelist(nullptr)
+ {}
+
+ void* allocNode();
+ void freeNode(ParseNode* pn);
+ ParseNode* freeTree(ParseNode* pn);
+ void prepareNodeForMutation(ParseNode* pn);
+
+ private:
+ ExclusiveContext* cx;
+ LifoAlloc& alloc;
+ ParseNode* freelist;
+};
+
+inline bool
+ParseNode::isConstant()
+{
+ switch (pn_type) {
+ case PNK_NUMBER:
+ case PNK_STRING:
+ case PNK_TEMPLATE_STRING:
+ case PNK_NULL:
+ case PNK_FALSE:
+ case PNK_TRUE:
+ return true;
+ case PNK_ARRAY:
+ case PNK_OBJECT:
+ MOZ_ASSERT(isOp(JSOP_NEWINIT));
+ return !(pn_xflags & PNX_NONCONST);
+ default:
+ return false;
+ }
+}
+
+class ObjectBox
+{
+ public:
+ JSObject* object;
+
+ ObjectBox(JSObject* object, ObjectBox* traceLink);
+ bool isFunctionBox() { return object->is<JSFunction>(); }
+ FunctionBox* asFunctionBox();
+ virtual void trace(JSTracer* trc);
+
+ static void TraceList(JSTracer* trc, ObjectBox* listHead);
+
+ protected:
+ friend struct CGObjectList;
+
+ ObjectBox* traceLink;
+ ObjectBox* emitLink;
+
+ ObjectBox(JSFunction* function, ObjectBox* traceLink);
+};
+
+enum ParseReportKind
+{
+ ParseError,
+ ParseWarning,
+ ParseExtraWarning,
+ ParseStrictError
+};
+
+enum FunctionSyntaxKind
+{
+ Expression,
+ Statement,
+ Arrow,
+ Method,
+ ClassConstructor,
+ DerivedClassConstructor,
+ Getter,
+ GetterNoExpressionClosure,
+ Setter,
+ SetterNoExpressionClosure
+};
+
+static inline bool
+IsConstructorKind(FunctionSyntaxKind kind)
+{
+ return kind == ClassConstructor || kind == DerivedClassConstructor;
+}
+
+static inline bool
+IsGetterKind(FunctionSyntaxKind kind)
+{
+ return kind == Getter || kind == GetterNoExpressionClosure;
+}
+
+static inline bool
+IsSetterKind(FunctionSyntaxKind kind)
+{
+ return kind == Setter || kind == SetterNoExpressionClosure;
+}
+
+static inline bool
+IsMethodDefinitionKind(FunctionSyntaxKind kind)
+{
+ return kind == Method || IsConstructorKind(kind) ||
+ IsGetterKind(kind) || IsSetterKind(kind);
+}
+
+static inline ParseNode*
+FunctionFormalParametersList(ParseNode* fn, unsigned* numFormals)
+{
+ MOZ_ASSERT(fn->isKind(PNK_FUNCTION));
+ ParseNode* argsBody = fn->pn_body;
+ MOZ_ASSERT(argsBody->isKind(PNK_PARAMSBODY));
+ *numFormals = argsBody->pn_count;
+ if (*numFormals > 0 &&
+ argsBody->last()->isKind(PNK_LEXICALSCOPE) &&
+ argsBody->last()->scopeBody()->isKind(PNK_STATEMENTLIST))
+ {
+ (*numFormals)--;
+ }
+ MOZ_ASSERT(argsBody->isArity(PN_LIST));
+ return argsBody->pn_head;
+}
+
+} /* namespace frontend */
+} /* namespace js */
+
+#endif /* frontend_ParseNode_h */
diff --git a/js/src/frontend/Parser.cpp b/js/src/frontend/Parser.cpp
new file mode 100644
index 0000000000..49fef2bf95
--- /dev/null
+++ b/js/src/frontend/Parser.cpp
@@ -0,0 +1,9627 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sts=4 et sw=4 tw=99:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "jsapi.h"
+#include "jsatom.h"
+#include "jscntxt.h"
+#include "jsfun.h"
+#include "jsopcode.h"
+#include "jsscript.h"
+#include "jstypes.h"
+
+#include "builtin/ModuleObject.h"
+#include "builtin/SelfHostingDefines.h"
+#include "frontend/BytecodeCompiler.h"
+#include "frontend/FoldConstants.h"
+#include "frontend/TokenStream.h"
+#include "wasm/AsmJS.h"
+
+#include "jsatominlines.h"
+#include "jsscriptinlines.h"
+
+#include "frontend/ParseNode-inl.h"
+#include "vm/EnvironmentObject-inl.h"
+
+using namespace js;
+using namespace js::gc;
+
+using mozilla::Maybe;
+using mozilla::Move;
+using mozilla::Nothing;
+using mozilla::PodCopy;
+using mozilla::PodZero;
+using mozilla::Some;
+
+using JS::AutoGCRooter;
+
+namespace js {
+namespace frontend {
+
+using DeclaredNamePtr = ParseContext::Scope::DeclaredNamePtr;
+using AddDeclaredNamePtr = ParseContext::Scope::AddDeclaredNamePtr;
+using BindingIter = ParseContext::Scope::BindingIter;
+using UsedNamePtr = UsedNameTracker::UsedNameMap::Ptr;
+
+/* Read a token. Report an error and return null() if that token isn't of type tt. */
+#define MUST_MATCH_TOKEN_MOD(tt, modifier, errno) \
+ JS_BEGIN_MACRO \
+ TokenKind token; \
+ if (!tokenStream.getToken(&token, modifier)) \
+ return null(); \
+ if (token != tt) { \
+ report(ParseError, false, null(), errno); \
+ return null(); \
+ } \
+ JS_END_MACRO
+
+#define MUST_MATCH_TOKEN(tt, errno) MUST_MATCH_TOKEN_MOD(tt, TokenStream::None, errno)
+
+template <class T, class U>
+static inline void
+PropagateTransitiveParseFlags(const T* inner, U* outer)
+{
+ if (inner->bindingsAccessedDynamically())
+ outer->setBindingsAccessedDynamically();
+ if (inner->hasDebuggerStatement())
+ outer->setHasDebuggerStatement();
+ if (inner->hasDirectEval())
+ outer->setHasDirectEval();
+}
+
+static 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::Import:
+ return "import";
+ case DeclarationKind::BodyLevelFunction:
+ case DeclarationKind::LexicalFunction:
+ return "function";
+ case DeclarationKind::VarForAnnexBLexicalFunction:
+ return "annex b var";
+ case DeclarationKind::ForOfVar:
+ return "var in for-of";
+ case DeclarationKind::SimpleCatchParameter:
+ case DeclarationKind::CatchParameter:
+ return "catch parameter";
+ }
+
+ MOZ_CRASH("Bad DeclarationKind");
+}
+
+static bool
+StatementKindIsBraced(StatementKind kind)
+{
+ return kind == StatementKind::Block ||
+ kind == StatementKind::Switch ||
+ kind == StatementKind::Try ||
+ kind == StatementKind::Catch ||
+ kind == StatementKind::Finally;
+}
+
+void
+ParseContext::Scope::dump(ParseContext* pc)
+{
+ ExclusiveContext* cx = pc->sc()->context;
+
+ fprintf(stdout, "ParseScope %p", this);
+
+ fprintf(stdout, "\n decls:\n");
+ for (DeclaredNameMap::Range r = declared_->all(); !r.empty(); r.popFront()) {
+ JSAutoByteString bytes;
+ if (!AtomToPrintableString(cx, r.front().key(), &bytes))
+ return;
+ DeclaredNameInfo& info = r.front().value().wrapped;
+ fprintf(stdout, " %s %s%s\n",
+ DeclarationKindString(info.kind()),
+ bytes.ptr(),
+ info.closedOver() ? " (closed over)" : "");
+ }
+
+ fprintf(stdout, "\n");
+}
+
+/* static */ void
+ParseContext::Scope::removeVarForAnnexBLexicalFunction(ParseContext* pc, JSAtom* name)
+{
+ // Local strict mode is allowed, e.g., a class binding removing a
+ // synthesized Annex B binding.
+ MOZ_ASSERT(!pc->sc()->strictScript);
+
+ for (ParseContext::Scope* scope = pc->innermostScope();
+ scope != pc->varScope().enclosing();
+ scope = scope->enclosing())
+ {
+ if (DeclaredNamePtr p = scope->declared_->lookup(name)) {
+ if (p->value()->kind() == DeclarationKind::VarForAnnexBLexicalFunction)
+ scope->declared_->remove(p);
+ }
+ }
+
+ // Annex B semantics no longer applies to any functions with this name, as
+ // an early error would have occurred.
+ pc->removeInnerFunctionBoxesForAnnexB(name);
+}
+
+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();
+ MOZ_ASSERT(DeclarationKindIsCatchParameter(kind));
+ JSAtom* name = r.front().key();
+ AddDeclaredNamePtr p = lookupDeclaredNameForAdd(name);
+ MOZ_ASSERT(!p);
+ if (!addDeclaredName(pc, p, name, kind))
+ return false;
+ }
+
+ return true;
+}
+
+void
+ParseContext::Scope::removeCatchParameters(ParseContext* pc, Scope& catchParamScope)
+{
+ if (pc->useAsmOrInsideUseAsm())
+ return;
+
+ for (DeclaredNameMap::Range r = catchParamScope.declared_->all(); !r.empty(); r.popFront()) {
+ DeclaredNamePtr p = declared_->lookup(r.front().key());
+ MOZ_ASSERT(p);
+
+ // This check is needed because the catch body could have declared
+ // vars, which would have been added to catchParamScope.
+ if (DeclarationKindIsCatchParameter(r.front().value()->kind()))
+ declared_->remove(p);
+ }
+}
+
+void
+SharedContext::computeAllowSyntax(Scope* scope)
+{
+ for (ScopeIter si(scope); si; si++) {
+ if (si.kind() == ScopeKind::Function) {
+ JSFunction* fun = si.scope()->as<FunctionScope>().canonicalFunction();
+ if (fun->isArrow())
+ continue;
+ allowNewTarget_ = true;
+ allowSuperProperty_ = fun->allowSuperProperty();
+ allowSuperCall_ = fun->isDerivedClassConstructor();
+ return;
+ }
+ }
+}
+
+void
+SharedContext::computeThisBinding(Scope* scope)
+{
+ for (ScopeIter si(scope); si; si++) {
+ if (si.kind() == ScopeKind::Module) {
+ thisBinding_ = ThisBinding::Module;
+ return;
+ }
+
+ if (si.kind() == ScopeKind::Function) {
+ JSFunction* fun = si.scope()->as<FunctionScope>().canonicalFunction();
+
+ // Arrow functions and generator expression lambdas don't have
+ // their own `this` binding.
+ if (fun->isArrow() || fun->nonLazyScript()->isGeneratorExp())
+ continue;
+
+ // Derived class constructors (including nested arrow functions and
+ // eval) need TDZ checks when accessing |this|.
+ if (fun->isDerivedClassConstructor())
+ needsThisTDZChecks_ = true;
+
+ thisBinding_ = ThisBinding::Function;
+ return;
+ }
+ }
+
+ thisBinding_ = ThisBinding::Global;
+}
+
+void
+SharedContext::computeInWith(Scope* scope)
+{
+ for (ScopeIter si(scope); si; si++) {
+ if (si.kind() == ScopeKind::With) {
+ inWith_ = true;
+ break;
+ }
+ }
+}
+
+EvalSharedContext::EvalSharedContext(ExclusiveContext* cx, JSObject* enclosingEnv,
+ Scope* enclosingScope, Directives directives,
+ bool extraWarnings)
+ : SharedContext(cx, Kind::Eval, directives, extraWarnings),
+ enclosingScope_(cx, enclosingScope),
+ bindings(cx)
+{
+ computeAllowSyntax(enclosingScope);
+ computeInWith(enclosingScope);
+ computeThisBinding(enclosingScope);
+
+ // Like all things Debugger, Debugger.Frame.eval needs special
+ // handling. Since the environment chain of such evals are non-syntactic
+ // (DebuggerEnvironmentProxy is not an EnvironmentObject), computing the
+ // this binding with respect to enclosingScope is incorrect if the
+ // Debugger.Frame is a function frame. Recompute the this binding if we
+ // are such an eval.
+ if (enclosingEnv && enclosingScope->hasOnChain(ScopeKind::NonSyntactic)) {
+ // For Debugger.Frame.eval with bindings, the environment chain may
+ // have more than the DebugEnvironmentProxy.
+ JSObject* env = enclosingEnv;
+ while (env) {
+ if (env->is<DebugEnvironmentProxy>())
+ env = &env->as<DebugEnvironmentProxy>().environment();
+
+ if (env->is<CallObject>()) {
+ computeThisBinding(env->as<CallObject>().callee().nonLazyScript()->bodyScope());
+ break;
+ }
+
+ env = env->enclosingEnvironment();
+ }
+ }
+}
+
+bool
+ParseContext::init()
+{
+ if (scriptId_ == UINT32_MAX) {
+ tokenStream_.reportError(JSMSG_NEED_DIET, js_script_str);
+ return false;
+ }
+
+ ExclusiveContext* cx = sc()->context;
+
+ if (isFunctionBox()) {
+ // Named lambdas always need a binding for their own name. If this
+ // binding is closed over when we finish parsing the function in
+ // finishExtraFunctionScopes, the function box needs to be marked as
+ // needing a dynamic DeclEnv object.
+ RootedFunction fun(cx, functionBox()->function());
+ if (fun->isNamedLambda()) {
+ if (!namedLambdaScope_->init(this))
+ return false;
+ AddDeclaredNamePtr p = namedLambdaScope_->lookupDeclaredNameForAdd(fun->name());
+ MOZ_ASSERT(!p);
+ if (!namedLambdaScope_->addDeclaredName(this, p, fun->name(), DeclarationKind::Const))
+ return false;
+ }
+
+ if (!functionScope_->init(this))
+ return false;
+
+ if (!positionalFormalParameterNames_.acquire(cx))
+ return false;
+ }
+
+ if (!closedOverBindingsForLazy_.acquire(cx))
+ return false;
+
+ if (!sc()->strict()) {
+ if (!innerFunctionBoxesForAnnexB_.acquire(cx))
+ return false;
+ }
+
+ return true;
+}
+
+bool
+ParseContext::addInnerFunctionBoxForAnnexB(FunctionBox* funbox)
+{
+ for (uint32_t i = 0; i < innerFunctionBoxesForAnnexB_->length(); i++) {
+ if (!innerFunctionBoxesForAnnexB_[i]) {
+ innerFunctionBoxesForAnnexB_[i] = funbox;
+ return true;
+ }
+ }
+ return innerFunctionBoxesForAnnexB_->append(funbox);
+}
+
+void
+ParseContext::removeInnerFunctionBoxesForAnnexB(JSAtom* name)
+{
+ for (uint32_t i = 0; i < innerFunctionBoxesForAnnexB_->length(); i++) {
+ if (FunctionBox* funbox = innerFunctionBoxesForAnnexB_[i]) {
+ if (funbox->function()->name() == name)
+ innerFunctionBoxesForAnnexB_[i] = nullptr;
+ }
+ }
+}
+
+void
+ParseContext::finishInnerFunctionBoxesForAnnexB()
+{
+ // Strict mode doesn't have wack Annex B function semantics. Or we
+ // could've failed to initialize ParseContext.
+ if (sc()->strict() || !innerFunctionBoxesForAnnexB_)
+ return;
+
+ for (uint32_t i = 0; i < innerFunctionBoxesForAnnexB_->length(); i++) {
+ if (FunctionBox* funbox = innerFunctionBoxesForAnnexB_[i])
+ funbox->isAnnexB = true;
+ }
+}
+
+ParseContext::~ParseContext()
+{
+ // Any funboxes still in the list at the end of parsing means no early
+ // error would have occurred for declaring a binding in the nearest var
+ // scope. Mark them as needing extra assignments to this var binding.
+ finishInnerFunctionBoxesForAnnexB();
+}
+
+bool
+UsedNameTracker::noteUse(ExclusiveContext* cx, JSAtom* name, uint32_t scriptId, uint32_t scopeId)
+{
+ if (UsedNameMap::AddPtr p = map_.lookupForAdd(name)) {
+ if (!p->value().noteUsedInScope(scriptId, scopeId))
+ return false;
+ } else {
+ UsedNameInfo info(cx);
+ if (!info.noteUsedInScope(scriptId, scopeId))
+ return false;
+ if (!map_.add(p, name, Move(info)))
+ return false;
+ }
+
+ 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);
+}
+
+FunctionBox::FunctionBox(ExclusiveContext* cx, LifoAlloc& alloc, ObjectBox* traceListHead,
+ JSFunction* fun, Directives directives, bool extraWarnings,
+ GeneratorKind generatorKind, FunctionAsyncKind asyncKind)
+ : ObjectBox(fun, traceListHead),
+ SharedContext(cx, Kind::ObjectBox, directives, extraWarnings),
+ enclosingScope_(nullptr),
+ namedLambdaBindings_(nullptr),
+ functionScopeBindings_(nullptr),
+ extraVarScopeBindings_(nullptr),
+ functionNode(nullptr),
+ bufStart(0),
+ bufEnd(0),
+ startLine(1),
+ startColumn(0),
+ length(0),
+ generatorKindBits_(GeneratorKindAsBits(generatorKind)),
+ asyncKindBits_(AsyncKindAsBits(asyncKind)),
+ isGenexpLambda(false),
+ hasDestructuringArgs(false),
+ hasParameterExprs(false),
+ hasDirectEvalInParameterExpr(false),
+ hasDuplicateParameters(false),
+ useAsm(false),
+ insideUseAsm(false),
+ isAnnexB(false),
+ wasEmitted(false),
+ declaredArguments(false),
+ usesArguments(false),
+ usesApply(false),
+ usesThis(false),
+ usesReturn(false),
+ funCxFlags()
+{
+ // Functions created at parse time may be set singleton after parsing and
+ // baked into JIT code, so they must be allocated tenured. They are held by
+ // the JSScript so cannot be collected during a minor GC anyway.
+ MOZ_ASSERT(fun->isTenured());
+}
+
+void
+FunctionBox::initFromLazyFunction()
+{
+ JSFunction* fun = function();
+ length = fun->nargs() - fun->hasRest();
+ if (fun->lazyScript()->isDerivedClassConstructor())
+ setDerivedClassConstructor();
+ if (fun->lazyScript()->needsHomeObject())
+ setNeedsHomeObject();
+ enclosingScope_ = fun->lazyScript()->enclosingScope();
+ initWithEnclosingScope(enclosingScope_);
+}
+
+void
+FunctionBox::initStandaloneFunction(Scope* enclosingScope)
+{
+ // Standalone functions are Function or Generator constructors and are
+ // always scoped to the global.
+ MOZ_ASSERT(enclosingScope->is<GlobalScope>());
+ JSFunction* fun = function();
+ length = fun->nargs() - fun->hasRest();
+ enclosingScope_ = enclosingScope;
+ allowNewTarget_ = true;
+ thisBinding_ = ThisBinding::Function;
+}
+
+void
+FunctionBox::initWithEnclosingParseContext(ParseContext* enclosing, FunctionSyntaxKind kind)
+{
+ SharedContext* sc = enclosing->sc();
+ useAsm = sc->isFunctionBox() && sc->asFunctionBox()->useAsmOrInsideUseAsm();
+
+ JSFunction* fun = function();
+
+ // Arrow functions and generator expression lambdas don't have
+ // their own `this` binding.
+ if (fun->isArrow()) {
+ allowNewTarget_ = sc->allowNewTarget();
+ allowSuperProperty_ = sc->allowSuperProperty();
+ allowSuperCall_ = sc->allowSuperCall();
+ needsThisTDZChecks_ = sc->needsThisTDZChecks();
+ thisBinding_ = sc->thisBinding();
+ } else {
+ allowNewTarget_ = true;
+ allowSuperProperty_ = fun->allowSuperProperty();
+
+ if (kind == DerivedClassConstructor) {
+ setDerivedClassConstructor();
+ allowSuperCall_ = true;
+ needsThisTDZChecks_ = true;
+ }
+
+ if (isGenexpLambda)
+ thisBinding_ = sc->thisBinding();
+ else
+ thisBinding_ = ThisBinding::Function;
+ }
+
+ if (sc->inWith()) {
+ inWith_ = true;
+ } else {
+ auto isWith = [](ParseContext::Statement* stmt) {
+ return stmt->kind() == StatementKind::With;
+ };
+
+ inWith_ = enclosing->findInnermostStatement(isWith);
+ }
+}
+
+void
+FunctionBox::initWithEnclosingScope(Scope* enclosingScope)
+{
+ if (!function()->isArrow()) {
+ allowNewTarget_ = true;
+ allowSuperProperty_ = function()->allowSuperProperty();
+
+ if (isDerivedClassConstructor()) {
+ setDerivedClassConstructor();
+ allowSuperCall_ = true;
+ needsThisTDZChecks_ = true;
+ }
+
+ thisBinding_ = ThisBinding::Function;
+ } else {
+ computeAllowSyntax(enclosingScope);
+ computeThisBinding(enclosingScope);
+ }
+
+ computeInWith(enclosingScope);
+}
+
+template <typename ParseHandler>
+bool
+Parser<ParseHandler>::reportHelper(ParseReportKind kind, bool strict, uint32_t offset,
+ unsigned errorNumber, va_list args)
+{
+ bool result = false;
+ switch (kind) {
+ case ParseError:
+ result = tokenStream.reportCompileErrorNumberVA(offset, JSREPORT_ERROR, errorNumber, args);
+ break;
+ case ParseWarning:
+ result =
+ tokenStream.reportCompileErrorNumberVA(offset, JSREPORT_WARNING, errorNumber, args);
+ break;
+ case ParseExtraWarning:
+ result = tokenStream.reportStrictWarningErrorNumberVA(offset, errorNumber, args);
+ break;
+ case ParseStrictError:
+ result = tokenStream.reportStrictModeErrorNumberVA(offset, strict, errorNumber, args);
+ break;
+ }
+ return result;
+}
+
+template <typename ParseHandler>
+bool
+Parser<ParseHandler>::report(ParseReportKind kind, bool strict, Node pn, unsigned errorNumber, ...)
+{
+ uint32_t offset = (pn ? handler.getPosition(pn) : pos()).begin;
+
+ va_list args;
+ va_start(args, errorNumber);
+ bool result = reportHelper(kind, strict, offset, errorNumber, args);
+ va_end(args);
+ return result;
+}
+
+template <typename ParseHandler>
+bool
+Parser<ParseHandler>::reportNoOffset(ParseReportKind kind, bool strict, unsigned errorNumber, ...)
+{
+ va_list args;
+ va_start(args, errorNumber);
+ bool result = reportHelper(kind, strict, TokenStream::NoOffset, errorNumber, args);
+ va_end(args);
+ return result;
+}
+
+template <typename ParseHandler>
+bool
+Parser<ParseHandler>::reportWithOffset(ParseReportKind kind, bool strict, uint32_t offset,
+ unsigned errorNumber, ...)
+{
+ va_list args;
+ va_start(args, errorNumber);
+ bool result = reportHelper(kind, strict, offset, errorNumber, args);
+ va_end(args);
+ return result;
+}
+
+template <>
+bool
+Parser<FullParseHandler>::abortIfSyntaxParser()
+{
+ handler.disableSyntaxParser();
+ return true;
+}
+
+template <>
+bool
+Parser<SyntaxParseHandler>::abortIfSyntaxParser()
+{
+ abortedSyntaxParse = true;
+ return false;
+}
+
+template <typename ParseHandler>
+Parser<ParseHandler>::Parser(ExclusiveContext* cx, LifoAlloc& alloc,
+ const ReadOnlyCompileOptions& options,
+ const char16_t* chars, size_t length,
+ bool foldConstants,
+ UsedNameTracker& usedNames,
+ Parser<SyntaxParseHandler>* syntaxParser,
+ LazyScript* lazyOuterFunction)
+ : AutoGCRooter(cx, PARSER),
+ context(cx),
+ alloc(alloc),
+ tokenStream(cx, options, chars, length, thisForCtor()),
+ traceListHead(nullptr),
+ pc(nullptr),
+ usedNames(usedNames),
+ sct(nullptr),
+ ss(nullptr),
+ keepAtoms(cx->perThreadData),
+ foldConstants(foldConstants),
+#ifdef DEBUG
+ checkOptionsCalled(false),
+#endif
+ abortedSyntaxParse(false),
+ isUnexpectedEOF_(false),
+ handler(cx, alloc, tokenStream, syntaxParser, lazyOuterFunction)
+{
+ cx->perThreadData->frontendCollectionPool.addActiveCompilation();
+
+ // The Mozilla specific JSOPTION_EXTRA_WARNINGS option adds extra warnings
+ // which are not generated if functions are parsed lazily. Note that the
+ // standard "use strict" does not inhibit lazy parsing.
+ if (options.extraWarningsOption)
+ handler.disableSyntaxParser();
+
+ tempPoolMark = alloc.mark();
+}
+
+template<typename ParseHandler>
+bool
+Parser<ParseHandler>::checkOptions()
+{
+#ifdef DEBUG
+ checkOptionsCalled = true;
+#endif
+
+ if (!tokenStream.checkOptions())
+ return false;
+
+ return true;
+}
+
+template <typename ParseHandler>
+Parser<ParseHandler>::~Parser()
+{
+ MOZ_ASSERT(checkOptionsCalled);
+ alloc.release(tempPoolMark);
+
+ /*
+ * The parser can allocate enormous amounts of memory for large functions.
+ * Eagerly free the memory now (which otherwise won't be freed until the
+ * next GC) to avoid unnecessary OOMs.
+ */
+ alloc.freeAllIfHugeAndUnused();
+
+ context->perThreadData->frontendCollectionPool.removeActiveCompilation();
+}
+
+template <typename ParseHandler>
+ObjectBox*
+Parser<ParseHandler>::newObjectBox(JSObject* obj)
+{
+ MOZ_ASSERT(obj);
+
+ /*
+ * 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.
+ */
+
+ ObjectBox* objbox = alloc.new_<ObjectBox>(obj, traceListHead);
+ if (!objbox) {
+ ReportOutOfMemory(context);
+ return nullptr;
+ }
+
+ traceListHead = objbox;
+
+ return objbox;
+}
+
+template <typename ParseHandler>
+FunctionBox*
+Parser<ParseHandler>::newFunctionBox(Node fn, JSFunction* fun, Directives inheritedDirectives,
+ GeneratorKind generatorKind, FunctionAsyncKind asyncKind,
+ bool tryAnnexB)
+{
+ MOZ_ASSERT(fun);
+ MOZ_ASSERT_IF(tryAnnexB, !pc->sc()->strict());
+
+ /*
+ * 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>(context, alloc, traceListHead, fun, inheritedDirectives,
+ options().extraWarningsOption, generatorKind, asyncKind);
+ if (!funbox) {
+ ReportOutOfMemory(context);
+ return nullptr;
+ }
+
+ traceListHead = funbox;
+ if (fn)
+ handler.setFunctionBox(fn, funbox);
+
+ if (tryAnnexB && !pc->addInnerFunctionBoxForAnnexB(funbox))
+ return nullptr;
+
+ return funbox;
+}
+
+ModuleSharedContext::ModuleSharedContext(ExclusiveContext* cx, ModuleObject* module,
+ Scope* enclosingScope, ModuleBuilder& builder)
+ : SharedContext(cx, Kind::Module, Directives(true), false),
+ module_(cx, module),
+ enclosingScope_(cx, enclosingScope),
+ bindings(cx),
+ builder(builder)
+{
+ thisBinding_ = ThisBinding::Module;
+}
+
+template <typename ParseHandler>
+void
+Parser<ParseHandler>::trace(JSTracer* trc)
+{
+ ObjectBox::TraceList(trc, traceListHead);
+}
+
+void
+MarkParser(JSTracer* trc, AutoGCRooter* parser)
+{
+ static_cast<Parser<FullParseHandler>*>(parser)->trace(trc);
+}
+
+/*
+ * Parse a top-level JS script.
+ */
+template <typename ParseHandler>
+typename ParseHandler::Node
+Parser<ParseHandler>::parse()
+{
+ MOZ_ASSERT(checkOptionsCalled);
+
+ Directives directives(options().strictOption);
+ GlobalSharedContext globalsc(context, ScopeKind::Global,
+ directives, options().extraWarningsOption);
+ ParseContext globalpc(this, &globalsc, /* newDirectives = */ nullptr);
+ if (!globalpc.init())
+ return null();
+
+ ParseContext::VarScope varScope(this);
+ if (!varScope.init(pc))
+ return null();
+
+ Node pn = statementList(YieldIsName);
+ if (!pn)
+ return null();
+
+ TokenKind tt;
+ if (!tokenStream.getToken(&tt, TokenStream::Operand))
+ return null();
+ if (tt != TOK_EOF) {
+ report(ParseError, false, null(), JSMSG_GARBAGE_AFTER_INPUT,
+ "script", TokenKindToDesc(tt));
+ return null();
+ }
+ if (foldConstants) {
+ if (!FoldConstants(context, &pn, this))
+ return null();
+ }
+
+ return pn;
+}
+
+template <typename ParseHandler>
+bool
+Parser<ParseHandler>::reportBadReturn(Node pn, ParseReportKind kind,
+ unsigned errnum, unsigned anonerrnum)
+{
+ JSAutoByteString name;
+ if (JSAtom* atom = pc->functionBox()->function()->name()) {
+ if (!AtomToPrintableString(context, atom, &name))
+ return false;
+ } else {
+ errnum = anonerrnum;
+ }
+ return report(kind, pc->sc()->strict(), pn, errnum, name.ptr());
+}
+
+/*
+ * Strict mode forbids introducing new definitions for 'eval', 'arguments', or
+ * for any strict mode reserved keyword.
+ */
+template <typename ParseHandler>
+bool
+Parser<ParseHandler>::isValidStrictBinding(PropertyName* name)
+{
+ return name != context->names().eval &&
+ name != context->names().arguments &&
+ name != context->names().let &&
+ name != context->names().static_ &&
+ !(IsKeyword(name) && name != context->names().await);
+}
+
+/*
+ * Check that it is permitted to introduce a binding for |name|. Use |pos| for
+ * reporting error locations.
+ */
+template <typename ParseHandler>
+bool
+Parser<ParseHandler>::checkStrictBinding(PropertyName* name, TokenPos pos)
+{
+ if (!pc->sc()->needStrictChecks())
+ return true;
+
+ if (!isValidStrictBinding(name)) {
+ JSAutoByteString bytes;
+ if (!AtomToPrintableString(context, name, &bytes))
+ return false;
+ return reportWithOffset(ParseStrictError, pc->sc()->strict(), pos.begin,
+ JSMSG_BAD_BINDING, bytes.ptr());
+ }
+
+ return true;
+}
+
+/*
+ * Returns true if all parameter names are valid strict mode binding names and
+ * no duplicate parameter names are present.
+ */
+template <typename ParseHandler>
+bool
+Parser<ParseHandler>::hasValidSimpleStrictParameterNames()
+{
+ MOZ_ASSERT(pc->isFunctionBox() && pc->functionBox()->hasSimpleParameterList());
+
+ if (pc->functionBox()->hasDuplicateParameters)
+ return false;
+
+ for (size_t i = 0; i < pc->positionalFormalParameterNames().length(); i++) {
+ JSAtom* name = pc->positionalFormalParameterNames()[i];
+ MOZ_ASSERT(name);
+ if (!isValidStrictBinding(name->asPropertyName()))
+ return false;
+ }
+ return true;
+}
+
+template <typename ParseHandler>
+void
+Parser<ParseHandler>::reportRedeclaration(HandlePropertyName name, DeclarationKind kind,
+ TokenPos pos)
+{
+ JSAutoByteString bytes;
+ if (!AtomToPrintableString(context, name, &bytes))
+ return;
+ reportWithOffset(ParseError, false, pos.begin, JSMSG_REDECLARED_VAR,
+ DeclarationKindString(kind), bytes.ptr());
+}
+
+// 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 <typename ParseHandler>
+bool
+Parser<ParseHandler>::notePositionalFormalParameter(Node fn, HandlePropertyName name,
+ bool disallowDuplicateParams,
+ bool* duplicatedParam)
+{
+ if (AddDeclaredNamePtr p = pc->functionScope().lookupDeclaredNameForAdd(name)) {
+ if (disallowDuplicateParams) {
+ report(ParseError, false, null(), 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()->needStrictChecks()) {
+ JSAutoByteString bytes;
+ if (!AtomToPrintableString(context, name, &bytes))
+ return false;
+ if (!report(ParseStrictError, pc->sc()->strict(), null(),
+ JSMSG_DUPLICATE_FORMAL, bytes.ptr()))
+ {
+ return false;
+ }
+ }
+
+ *duplicatedParam = true;
+ } else {
+ DeclarationKind kind = DeclarationKind::PositionalFormalParameter;
+ if (!pc->functionScope().addDeclaredName(pc, p, name, kind))
+ return false;
+ }
+
+ if (!pc->positionalFormalParameterNames().append(name)) {
+ ReportOutOfMemory(context);
+ return false;
+ }
+
+ Node paramNode = newName(name);
+ if (!paramNode)
+ return false;
+
+ if (!checkStrictBinding(name, pos()))
+ return false;
+
+ handler.addFunctionFormalParameter(fn, paramNode);
+ return true;
+}
+
+template <typename ParseHandler>
+bool
+Parser<ParseHandler>::noteDestructuredPositionalFormalParameter(Node fn, Node destruct)
+{
+ // Append an empty name to the positional formals vector to keep track of
+ // argument slots when making FunctionScope::Data.
+ if (!pc->positionalFormalParameterNames().append(nullptr)) {
+ ReportOutOfMemory(context);
+ return false;
+ }
+
+ handler.addFunctionFormalParameter(fn, destruct);
+ return true;
+}
+
+static bool
+DeclarationKindIsVar(DeclarationKind kind)
+{
+ return kind == DeclarationKind::Var ||
+ kind == DeclarationKind::BodyLevelFunction ||
+ kind == DeclarationKind::VarForAnnexBLexicalFunction ||
+ kind == DeclarationKind::ForOfVar;
+}
+
+template <typename ParseHandler>
+Maybe<DeclarationKind>
+Parser<ParseHandler>::isVarRedeclaredInEval(HandlePropertyName name, DeclarationKind kind)
+{
+ MOZ_ASSERT(DeclarationKindIsVar(kind));
+ MOZ_ASSERT(pc->sc()->isEvalContext());
+
+ // In the case of eval, we also need to check enclosing VM scopes to see
+ // if the var declaration is allowed in the context.
+ //
+ // This check is necessary in addition to
+ // js::CheckEvalDeclarationConflicts because we only know during parsing
+ // if a var is bound by for-of.
+ Scope* enclosingScope = pc->sc()->compilationEnclosingScope();
+ Scope* varScope = EvalScope::nearestVarScopeForDirectEval(enclosingScope);
+ MOZ_ASSERT(varScope);
+ for (ScopeIter si(enclosingScope); si; si++) {
+ for (js::BindingIter bi(si.scope()); bi; bi++) {
+ if (bi.name() != name)
+ continue;
+
+ switch (bi.kind()) {
+ case BindingKind::Let: {
+ // Annex B.3.5 allows redeclaring simple (non-destructured)
+ // catch parameters with var declarations, except when it
+ // appears in a for-of.
+ bool annexB35Allowance = si.kind() == ScopeKind::SimpleCatch &&
+ kind != DeclarationKind::ForOfVar;
+ if (!annexB35Allowance) {
+ return Some(ScopeKindIsCatch(si.kind())
+ ? DeclarationKind::CatchParameter
+ : DeclarationKind::Let);
+ }
+ break;
+ }
+
+ case BindingKind::Const:
+ return Some(DeclarationKind::Const);
+
+ case BindingKind::Import:
+ case BindingKind::FormalParameter:
+ case BindingKind::Var:
+ case BindingKind::NamedLambdaCallee:
+ break;
+ }
+ }
+
+ if (si.scope() == varScope)
+ break;
+ }
+
+ return Nothing();
+}
+
+static bool
+DeclarationKindIsParameter(DeclarationKind kind)
+{
+ return kind == DeclarationKind::PositionalFormalParameter ||
+ kind == DeclarationKind::FormalParameter;
+}
+
+template <typename ParseHandler>
+bool
+Parser<ParseHandler>::tryDeclareVar(HandlePropertyName name, DeclarationKind kind,
+ Maybe<DeclarationKind>* redeclaredKind)
+{
+ 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 = pc->innermostScope();
+ scope != pc->varScope().enclosing();
+ scope = scope->enclosing())
+ {
+ if (AddDeclaredNamePtr p = scope->lookupDeclaredNameForAdd(name)) {
+ DeclarationKind declaredKind = p->value()->kind();
+ if (DeclarationKindIsVar(declaredKind)) {
+ // 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.
+ //
+ // For a var previously declared as ForOfVar, this previous
+ // DeclarationKind is used only to check for if the
+ // 'arguments' binding should be declared. Since body-level
+ // functions shadow 'arguments' [5], it is correct to alter
+ // the kind to BodyLevelFunction. See
+ // declareFunctionArgumentsObject.
+ //
+ // For a var previously declared as
+ // VarForAnnexBLexicalFunction, this previous DeclarationKind
+ // is used so that vars synthesized solely for Annex B.3.3 may
+ // be removed if an early error would occur. If a synthesized
+ // Annex B.3.3 var has the same name as a body-level function,
+ // this is not a redeclaration, and indeed, because the
+ // body-level function binds the name, this name should not be
+ // removed should a redeclaration occur in the future. Thus it
+ // is also correct to alter the kind to BodyLevelFunction.
+ //
+ // [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
+ // [5] ES 9.2.12
+ if (kind == DeclarationKind::BodyLevelFunction)
+ p->value()->alterKind(kind);
+ } else if (!DeclarationKindIsParameter(declaredKind)) {
+ // Annex B.3.5 allows redeclaring simple (non-destructured)
+ // catch parameters with var declarations, except when it
+ // appears in a for-of.
+ bool annexB35Allowance = declaredKind == DeclarationKind::SimpleCatchParameter &&
+ kind != DeclarationKind::ForOfVar;
+
+ // Annex B.3.3 allows redeclaring functions in the same block.
+ bool annexB33Allowance = declaredKind == DeclarationKind::LexicalFunction &&
+ kind == DeclarationKind::VarForAnnexBLexicalFunction &&
+ scope == pc->innermostScope();
+
+ if (!annexB35Allowance && !annexB33Allowance) {
+ *redeclaredKind = Some(declaredKind);
+ return true;
+ }
+ }
+ } else {
+ if (!scope->addDeclaredName(pc, p, name, kind))
+ return false;
+ }
+ }
+
+ if (!pc->sc()->strict() && pc->sc()->isEvalContext())
+ *redeclaredKind = isVarRedeclaredInEval(name, kind);
+
+ return true;
+}
+
+template <typename ParseHandler>
+bool
+Parser<ParseHandler>::tryDeclareVarForAnnexBLexicalFunction(HandlePropertyName name,
+ bool* tryAnnexB)
+{
+ Maybe<DeclarationKind> redeclaredKind;
+ if (!tryDeclareVar(name, DeclarationKind::VarForAnnexBLexicalFunction, &redeclaredKind))
+ return false;
+
+ if (redeclaredKind) {
+ // If an early error would have occurred, undo all the
+ // VarForAnnexBLexicalFunction declarations.
+ *tryAnnexB = false;
+ ParseContext::Scope::removeVarForAnnexBLexicalFunction(pc, name);
+ } else {
+ *tryAnnexB = true;
+ }
+
+ return true;
+}
+
+template <typename ParseHandler>
+bool
+Parser<ParseHandler>::checkLexicalDeclarationDirectlyWithinBlock(ParseContext::Statement& stmt,
+ DeclarationKind kind,
+ TokenPos pos)
+{
+ MOZ_ASSERT(DeclarationKindIsLexical(kind));
+
+ // It is an early error to declare a lexical binding not directly
+ // within a block.
+ if (!StatementKindIsBraced(stmt.kind()) &&
+ stmt.kind() != StatementKind::ForLoopLexicalHead)
+ {
+ reportWithOffset(ParseError, false, pos.begin,
+ stmt.kind() == StatementKind::Label
+ ? JSMSG_LEXICAL_DECL_LABEL
+ : JSMSG_LEXICAL_DECL_NOT_IN_BLOCK,
+ DeclarationKindString(kind));
+ return false;
+ }
+
+ return true;
+}
+
+template <typename ParseHandler>
+bool
+Parser<ParseHandler>::noteDeclaredName(HandlePropertyName name, DeclarationKind kind,
+ TokenPos pos)
+{
+ // The asm.js validator does all its own symbol-table management so, as an
+ // optimization, avoid doing any work here.
+ if (pc->useAsmOrInsideUseAsm())
+ return true;
+
+ if (!checkStrictBinding(name, pos))
+ return false;
+
+ switch (kind) {
+ case DeclarationKind::Var:
+ case DeclarationKind::BodyLevelFunction:
+ case DeclarationKind::ForOfVar: {
+ Maybe<DeclarationKind> redeclaredKind;
+ if (!tryDeclareVar(name, kind, &redeclaredKind))
+ return false;
+
+ if (redeclaredKind) {
+ reportRedeclaration(name, *redeclaredKind, pos);
+ return false;
+ }
+
+ 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) {
+ report(ParseError, false, null(), JSMSG_BAD_DUP_ARGS);
+ return false;
+ }
+
+ if (!pc->functionScope().addDeclaredName(pc, p, name, kind))
+ return false;
+
+ break;
+ }
+
+ case DeclarationKind::LexicalFunction: {
+ // 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.
+ //
+ // In sloppy mode, lexical functions may redeclare other lexical
+ // functions for web compatibility reasons.
+ if (pc->sc()->strict() ||
+ (p->value()->kind() != DeclarationKind::LexicalFunction &&
+ p->value()->kind() != DeclarationKind::VarForAnnexBLexicalFunction))
+ {
+ reportRedeclaration(name, p->value()->kind(), pos);
+ return false;
+ }
+
+ // Update the DeclarationKind to make a LexicalFunction
+ // declaration that shadows the VarForAnnexBLexicalFunction.
+ p->value()->alterKind(kind);
+ } else {
+ if (!scope->addDeclaredName(pc, p, name, kind))
+ return false;
+ }
+
+ break;
+ }
+
+ case DeclarationKind::Let:
+ case DeclarationKind::Const:
+ // The BoundNames of LexicalDeclaration and ForDeclaration must not
+ // contain 'let'. (CatchParameter is the only lexical binding form
+ // without this restriction.)
+ if (name == context->names().let) {
+ reportWithOffset(ParseError, false, pos.begin, JSMSG_LEXICAL_DECL_DEFINES_LET);
+ return false;
+ }
+
+ MOZ_FALLTHROUGH;
+
+ case DeclarationKind::Import:
+ // Module code is always strict, so 'let' is always a keyword and never a name.
+ MOZ_ASSERT(name != context->names().let);
+ MOZ_FALLTHROUGH;
+
+ case DeclarationKind::SimpleCatchParameter:
+ case DeclarationKind::CatchParameter: {
+ if (ParseContext::Statement* stmt = pc->innermostStatement()) {
+ if (!checkLexicalDeclarationDirectlyWithinBlock(*stmt, kind, pos))
+ return false;
+ }
+
+ ParseContext::Scope* scope = pc->innermostScope();
+
+ // 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);
+ return false;
+ }
+ }
+
+ // 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) {
+ // If the early error would have occurred due to Annex B.3.3
+ // semantics, remove the synthesized Annex B var declaration, do
+ // not report the redeclaration, and declare the lexical name.
+ if (p->value()->kind() == DeclarationKind::VarForAnnexBLexicalFunction) {
+ ParseContext::Scope::removeVarForAnnexBLexicalFunction(pc, name);
+ p = scope->lookupDeclaredNameForAdd(name);
+ MOZ_ASSERT(!p);
+ } else {
+ reportRedeclaration(name, p->value()->kind(), pos);
+ return false;
+ }
+ }
+
+ if (!p && !scope->addDeclaredName(pc, p, name, kind))
+ 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 <typename ParseHandler>
+bool
+Parser<ParseHandler>::noteUsedName(HandlePropertyName name)
+{
+ // If the we are delazifying, the LazyScript already has all the
+ // closed-over info for bindings and there's no need to track used names.
+ if (handler.canSkipLazyClosedOverBindings())
+ return true;
+
+ // 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.
+ ParseContext::Scope* scope = pc->innermostScope();
+ if (pc->sc()->isGlobalContext() && scope == &pc->varScope())
+ return true;
+
+ return usedNames.noteUse(context, name, pc->scriptId(), scope->id());
+}
+
+template <typename ParseHandler>
+bool
+Parser<ParseHandler>::hasUsedName(HandlePropertyName name)
+{
+ if (UsedNamePtr p = usedNames.lookup(name))
+ return p->value().isUsedInScript(pc->scriptId());
+ return false;
+}
+
+template <typename ParseHandler>
+bool
+Parser<ParseHandler>::propagateFreeNamesAndMarkClosedOverBindings(ParseContext::Scope& scope)
+{
+ if (handler.canSkipLazyClosedOverBindings()) {
+ // Scopes are nullptr-delimited in the LazyScript closed over bindings
+ // array.
+ while (JSAtom* name = handler.nextLazyClosedOverBinding())
+ scope.lookupDeclaredName(name)->value()->setClosedOver();
+ return true;
+ }
+
+ bool isSyntaxParser = mozilla::IsSame<ParseHandler, SyntaxParseHandler>::value;
+ uint32_t scriptId = pc->scriptId();
+ uint32_t scopeId = scope.id();
+ for (BindingIter bi = scope.bindings(pc); bi; bi++) {
+ if (UsedNamePtr p = usedNames.lookup(bi.name())) {
+ bool closedOver;
+ p->value().noteBoundInScope(scriptId, scopeId, &closedOver);
+ if (closedOver) {
+ bi.setClosedOver();
+
+ if (isSyntaxParser && !pc->closedOverBindingsForLazy().append(bi.name())) {
+ ReportOutOfMemory(context);
+ return false;
+ }
+ }
+ }
+ }
+
+ // Append a nullptr to denote end-of-scope.
+ if (isSyntaxParser && !pc->closedOverBindingsForLazy().append(nullptr)) {
+ ReportOutOfMemory(context);
+ return false;
+ }
+
+ return true;
+}
+
+template <>
+bool
+Parser<FullParseHandler>::checkStatementsEOF()
+{
+ // This is designed to be paired with parsing a statement list at the top
+ // level.
+ //
+ // The statementList() call breaks on TOK_RC, so make sure we've
+ // reached EOF here.
+ TokenKind tt;
+ if (!tokenStream.peekToken(&tt, TokenStream::Operand))
+ return false;
+ if (tt != TOK_EOF) {
+ report(ParseError, false, null(), JSMSG_UNEXPECTED_TOKEN,
+ "expression", TokenKindToDesc(tt));
+ return false;
+ }
+ return true;
+}
+
+template <typename Scope>
+static typename Scope::Data*
+NewEmptyBindingData(ExclusiveContext* cx, LifoAlloc& alloc, uint32_t numBindings)
+{
+ size_t allocSize = Scope::sizeOfData(numBindings);
+ typename Scope::Data* bindings = static_cast<typename Scope::Data*>(alloc.alloc(allocSize));
+ if (!bindings) {
+ ReportOutOfMemory(cx);
+ return nullptr;
+ }
+ PodZero(bindings);
+ return bindings;
+}
+
+template <>
+Maybe<GlobalScope::Data*>
+Parser<FullParseHandler>::newGlobalScopeData(ParseContext::Scope& scope)
+{
+ Vector<BindingName> funs(context);
+ Vector<BindingName> vars(context);
+ Vector<BindingName> lets(context);
+ Vector<BindingName> consts(context);
+
+ bool allBindingsClosedOver = pc->sc()->allBindingsClosedOver();
+ for (BindingIter bi = scope.bindings(pc); bi; bi++) {
+ BindingName binding(bi.name(), allBindingsClosedOver || bi.closedOver());
+ switch (bi.kind()) {
+ case BindingKind::Var:
+ if (bi.declarationKind() == DeclarationKind::BodyLevelFunction) {
+ if (!funs.append(binding))
+ return Nothing();
+ } else {
+ 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 global scope BindingKind");
+ }
+ }
+
+ GlobalScope::Data* bindings = nullptr;
+ uint32_t numBindings = funs.length() + vars.length() + lets.length() + consts.length();
+
+ if (numBindings > 0) {
+ bindings = NewEmptyBindingData<GlobalScope>(context, alloc, numBindings);
+ if (!bindings)
+ return Nothing();
+
+ // The ordering here is important. See comments in GlobalScope.
+ BindingName* start = bindings->names;
+ BindingName* cursor = start;
+
+ PodCopy(cursor, funs.begin(), funs.length());
+ cursor += funs.length();
+
+ bindings->varStart = cursor - start;
+ PodCopy(cursor, vars.begin(), vars.length());
+ cursor += vars.length();
+
+ bindings->letStart = cursor - start;
+ PodCopy(cursor, lets.begin(), lets.length());
+ cursor += lets.length();
+
+ bindings->constStart = cursor - start;
+ PodCopy(cursor, consts.begin(), consts.length());
+ bindings->length = numBindings;
+ }
+
+ return Some(bindings);
+}
+
+template <>
+Maybe<ModuleScope::Data*>
+Parser<FullParseHandler>::newModuleScopeData(ParseContext::Scope& scope)
+{
+ Vector<BindingName> imports(context);
+ Vector<BindingName> vars(context);
+ Vector<BindingName> lets(context);
+ Vector<BindingName> consts(context);
+
+ bool allBindingsClosedOver = pc->sc()->allBindingsClosedOver();
+ for (BindingIter bi = scope.bindings(pc); bi; bi++) {
+ // Imports are indirect bindings and must not be given known slots.
+ BindingName 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::Data* bindings = nullptr;
+ uint32_t numBindings = imports.length() + vars.length() + lets.length() + consts.length();
+
+ if (numBindings > 0) {
+ bindings = NewEmptyBindingData<ModuleScope>(context, alloc, numBindings);
+ if (!bindings)
+ return Nothing();
+
+ // The ordering here is important. See comments in ModuleScope.
+ BindingName* start = bindings->names;
+ BindingName* cursor = start;
+
+ PodCopy(cursor, imports.begin(), imports.length());
+ cursor += imports.length();
+
+ bindings->varStart = cursor - start;
+ PodCopy(cursor, vars.begin(), vars.length());
+ cursor += vars.length();
+
+ bindings->letStart = cursor - start;
+ PodCopy(cursor, lets.begin(), lets.length());
+ cursor += lets.length();
+
+ bindings->constStart = cursor - start;
+ PodCopy(cursor, consts.begin(), consts.length());
+ bindings->length = numBindings;
+ }
+
+ return Some(bindings);
+}
+
+template <>
+Maybe<EvalScope::Data*>
+Parser<FullParseHandler>::newEvalScopeData(ParseContext::Scope& scope)
+{
+ Vector<BindingName> funs(context);
+ Vector<BindingName> vars(context);
+
+ for (BindingIter bi = scope.bindings(pc); bi; bi++) {
+ // Eval scopes only contain 'var' bindings. Make all bindings aliased
+ // for now.
+ MOZ_ASSERT(bi.kind() == BindingKind::Var);
+ BindingName binding(bi.name(), true);
+ if (bi.declarationKind() == DeclarationKind::BodyLevelFunction) {
+ if (!funs.append(binding))
+ return Nothing();
+ } else {
+ if (!vars.append(binding))
+ return Nothing();
+ }
+ }
+
+ EvalScope::Data* bindings = nullptr;
+ uint32_t numBindings = funs.length() + vars.length();
+
+ if (numBindings > 0) {
+ bindings = NewEmptyBindingData<EvalScope>(context, alloc, numBindings);
+ if (!bindings)
+ return Nothing();
+
+ BindingName* start = bindings->names;
+ BindingName* cursor = start;
+
+ // Keep track of what vars are functions. This is only used in BCE to omit
+ // superfluous DEFVARs.
+ PodCopy(cursor, funs.begin(), funs.length());
+ cursor += funs.length();
+
+ bindings->varStart = cursor - start;
+ PodCopy(cursor, vars.begin(), vars.length());
+ bindings->length = numBindings;
+ }
+
+ return Some(bindings);
+}
+
+template <>
+Maybe<FunctionScope::Data*>
+Parser<FullParseHandler>::newFunctionScopeData(ParseContext::Scope& scope, bool hasParameterExprs)
+{
+ Vector<BindingName> positionalFormals(context);
+ Vector<BindingName> formals(context);
+ Vector<BindingName> vars(context);
+
+ bool allBindingsClosedOver = pc->sc()->allBindingsClosedOver();
+ 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++) {
+ JSAtom* name = pc->positionalFormalParameterNames()[i];
+
+ BindingName 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 = allBindingsClosedOver ||
+ (p && p->value()->closedOver());
+
+ // If the parameter name has duplicates, only the final parameter
+ // name should be on the environment, as otherwise the environment
+ // object would have multiple, same-named properties.
+ if (hasDuplicateParams) {
+ for (size_t j = pc->positionalFormalParameterNames().length() - 1; j > i; j--) {
+ if (pc->positionalFormalParameterNames()[j] == name) {
+ closedOver = false;
+ break;
+ }
+ }
+ }
+
+ bindName = BindingName(name, closedOver);
+ }
+
+ if (!positionalFormals.append(bindName))
+ return Nothing();
+ }
+
+ for (BindingIter bi = scope.bindings(pc); bi; bi++) {
+ BindingName 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 internal bindings.
+ MOZ_ASSERT_IF(hasParameterExprs,
+ bi.name() == context->names().arguments ||
+ bi.name() == context->names().dotThis ||
+ bi.name() == context->names().dotGenerator);
+ if (!vars.append(binding))
+ return Nothing();
+ break;
+ default:
+ break;
+ }
+ }
+
+ FunctionScope::Data* bindings = nullptr;
+ uint32_t numBindings = positionalFormals.length() + formals.length() + vars.length();
+
+ if (numBindings > 0) {
+ bindings = NewEmptyBindingData<FunctionScope>(context, alloc, numBindings);
+ if (!bindings)
+ return Nothing();
+
+ // The ordering here is important. See comments in FunctionScope.
+ BindingName* start = bindings->names;
+ BindingName* cursor = start;
+
+ PodCopy(cursor, positionalFormals.begin(), positionalFormals.length());
+ cursor += positionalFormals.length();
+
+ bindings->nonPositionalFormalStart = cursor - start;
+ PodCopy(cursor, formals.begin(), formals.length());
+ cursor += formals.length();
+
+ bindings->varStart = cursor - start;
+ PodCopy(cursor, vars.begin(), vars.length());
+ bindings->length = numBindings;
+ }
+
+ return Some(bindings);
+}
+
+template <>
+Maybe<VarScope::Data*>
+Parser<FullParseHandler>::newVarScopeData(ParseContext::Scope& scope)
+{
+ Vector<BindingName> vars(context);
+
+ bool allBindingsClosedOver = pc->sc()->allBindingsClosedOver();
+
+ for (BindingIter bi = scope.bindings(pc); bi; bi++) {
+ BindingName binding(bi.name(), allBindingsClosedOver || bi.closedOver());
+ if (!vars.append(binding))
+ return Nothing();
+ }
+
+ VarScope::Data* bindings = nullptr;
+ uint32_t numBindings = vars.length();
+
+ if (numBindings > 0) {
+ bindings = NewEmptyBindingData<VarScope>(context, alloc, numBindings);
+ if (!bindings)
+ return Nothing();
+
+ // The ordering here is important. See comments in FunctionScope.
+ BindingName* start = bindings->names;
+ BindingName* cursor = start;
+
+ PodCopy(cursor, vars.begin(), vars.length());
+ bindings->length = numBindings;
+ }
+
+ return Some(bindings);
+}
+
+template <>
+Maybe<LexicalScope::Data*>
+Parser<FullParseHandler>::newLexicalScopeData(ParseContext::Scope& scope)
+{
+ Vector<BindingName> lets(context);
+ Vector<BindingName> consts(context);
+
+ // Unlike other scopes with bindings which are body-level, it is unknown
+ // if pc->sc()->allBindingsClosedOver() is correct at the time of
+ // finishing parsing a lexical scope.
+ //
+ // Instead, pc->sc()->allBindingsClosedOver() is checked in
+ // EmitterScope::enterLexical. Also see comment there.
+ for (BindingIter bi = scope.bindings(pc); bi; bi++) {
+ BindingName binding(bi.name(), bi.closedOver());
+ switch (bi.kind()) {
+ case BindingKind::Let:
+ if (!lets.append(binding))
+ return Nothing();
+ break;
+ case BindingKind::Const:
+ if (!consts.append(binding))
+ return Nothing();
+ break;
+ default:
+ break;
+ }
+ }
+
+ LexicalScope::Data* bindings = nullptr;
+ uint32_t numBindings = lets.length() + consts.length();
+
+ if (numBindings > 0) {
+ bindings = NewEmptyBindingData<LexicalScope>(context, alloc, numBindings);
+ if (!bindings)
+ return Nothing();
+
+ // The ordering here is important. See comments in LexicalScope.
+ BindingName* cursor = bindings->names;
+ BindingName* start = cursor;
+
+ PodCopy(cursor, lets.begin(), lets.length());
+ cursor += lets.length();
+
+ bindings->constStart = cursor - start;
+ PodCopy(cursor, consts.begin(), consts.length());
+ bindings->length = numBindings;
+ }
+
+ return Some(bindings);
+}
+
+template <>
+SyntaxParseHandler::Node
+Parser<SyntaxParseHandler>::finishLexicalScope(ParseContext::Scope& scope, Node body)
+{
+ if (!propagateFreeNamesAndMarkClosedOverBindings(scope))
+ return null();
+ return body;
+}
+
+template <>
+ParseNode*
+Parser<FullParseHandler>::finishLexicalScope(ParseContext::Scope& scope, ParseNode* body)
+{
+ if (!propagateFreeNamesAndMarkClosedOverBindings(scope))
+ return nullptr;
+ Maybe<LexicalScope::Data*> bindings = newLexicalScopeData(scope);
+ if (!bindings)
+ return nullptr;
+ return handler.newLexicalScope(*bindings, body);
+}
+
+static bool
+IsArgumentsUsedInLegacyGenerator(ExclusiveContext* cx, Scope* scope)
+{
+ JSAtom* argumentsName = cx->names().arguments;
+ for (ScopeIter si(scope); si; si++) {
+ if (si.scope()->is<LexicalScope>()) {
+ // Using a shadowed lexical 'arguments' is okay.
+ for (::BindingIter bi(si.scope()); bi; bi++) {
+ if (bi.name() == argumentsName)
+ return false;
+ }
+ } else if (si.scope()->is<FunctionScope>()) {
+ // It's an error to use 'arguments' in a legacy generator expression.
+ JSScript* script = si.scope()->as<FunctionScope>().script();
+ return script->isGeneratorExp() && script->isLegacyGenerator();
+ }
+ }
+
+ return false;
+}
+
+template <>
+ParseNode*
+Parser<FullParseHandler>::evalBody(EvalSharedContext* evalsc)
+{
+ ParseContext evalpc(this, evalsc, /* newDirectives = */ nullptr);
+ if (!evalpc.init())
+ return nullptr;
+
+ ParseContext::VarScope varScope(this);
+ if (!varScope.init(pc))
+ return nullptr;
+
+ // All evals have an implicit non-extensible lexical scope.
+ ParseContext::Scope lexicalScope(this);
+ if (!lexicalScope.init(pc))
+ return nullptr;
+
+ ParseNode* body = statementList(YieldIsName);
+ if (!body)
+ return nullptr;
+
+ if (!checkStatementsEOF())
+ return nullptr;
+
+ body = finishLexicalScope(lexicalScope, body);
+ if (!body)
+ return nullptr;
+
+ // It's an error to use 'arguments' in a legacy generator expression.
+ //
+ // If 'arguments' appears free (i.e. not a declared name) or if the
+ // declaration does not shadow the enclosing script's 'arguments'
+ // binding (i.e. not a lexical declaration), check the enclosing
+ // script.
+ if (hasUsedName(context->names().arguments)) {
+ if (IsArgumentsUsedInLegacyGenerator(context, pc->sc()->compilationEnclosingScope())) {
+ report(ParseError, false, nullptr, JSMSG_BAD_GENEXP_BODY, js_arguments_str);
+ return nullptr;
+ }
+ }
+
+#ifdef DEBUG
+ if (evalpc.superScopeNeedsHomeObject() && evalsc->compilationEnclosingScope()) {
+ // If superScopeNeedsHomeObject_ is set and we are an entry-point
+ // ParseContext, then we must be emitting an eval script, and the
+ // outer function must already be marked as needing a home object
+ // since it contains an eval.
+ ScopeIter si(evalsc->compilationEnclosingScope());
+ for (; si; si++) {
+ if (si.kind() == ScopeKind::Function) {
+ JSFunction* fun = si.scope()->as<FunctionScope>().canonicalFunction();
+ if (fun->isArrow())
+ continue;
+ MOZ_ASSERT(fun->allowSuperProperty());
+ MOZ_ASSERT(fun->nonLazyScript()->needsHomeObject());
+ break;
+ }
+ }
+ MOZ_ASSERT(!si.done(),
+ "Eval must have found an enclosing function box scope that allows super.property");
+ }
+#endif
+
+ if (!FoldConstants(context, &body, this))
+ return nullptr;
+
+ Maybe<EvalScope::Data*> bindings = newEvalScopeData(pc->varScope());
+ if (!bindings)
+ return nullptr;
+ evalsc->bindings = *bindings;
+
+ return body;
+}
+
+template <>
+ParseNode*
+Parser<FullParseHandler>::globalBody(GlobalSharedContext* globalsc)
+{
+ ParseContext globalpc(this, globalsc, /* newDirectives = */ nullptr);
+ if (!globalpc.init())
+ return nullptr;
+
+ ParseContext::VarScope varScope(this);
+ if (!varScope.init(pc))
+ return nullptr;
+
+ ParseNode* body = statementList(YieldIsName);
+ if (!body)
+ return nullptr;
+
+ if (!checkStatementsEOF())
+ return nullptr;
+
+ if (!FoldConstants(context, &body, this))
+ return nullptr;
+
+ Maybe<GlobalScope::Data*> bindings = newGlobalScopeData(pc->varScope());
+ if (!bindings)
+ return nullptr;
+ globalsc->bindings = *bindings;
+
+ return body;
+}
+
+template <>
+ParseNode*
+Parser<FullParseHandler>::moduleBody(ModuleSharedContext* modulesc)
+{
+ MOZ_ASSERT(checkOptionsCalled);
+
+ ParseContext modulepc(this, modulesc, nullptr);
+ if (!modulepc.init())
+ return null();
+
+ ParseContext::VarScope varScope(this);
+ if (!varScope.init(pc))
+ return nullptr;
+
+ Node mn = handler.newModule();
+ if (!mn)
+ return null();
+
+ AutoAwaitIsKeyword awaitIsKeyword(&tokenStream, true);
+ ParseNode* pn = statementList(YieldIsKeyword);
+ if (!pn)
+ return null();
+
+ MOZ_ASSERT(pn->isKind(PNK_STATEMENTLIST));
+ mn->pn_body = pn;
+
+ TokenKind tt;
+ if (!tokenStream.getToken(&tt, TokenStream::Operand))
+ return null();
+ if (tt != TOK_EOF) {
+ report(ParseError, false, null(), JSMSG_GARBAGE_AFTER_INPUT, "module", TokenKindToDesc(tt));
+ return null();
+ }
+
+ if (!modulesc->builder.buildTables())
+ return null();
+
+ // Check exported local bindings exist and mark them as closed over.
+ for (auto entry : modulesc->builder.localExportEntries()) {
+ JSAtom* name = entry->localName();
+ MOZ_ASSERT(name);
+
+ DeclaredNamePtr p = modulepc.varScope().lookupDeclaredName(name);
+ if (!p) {
+ JSAutoByteString str;
+ if (!str.encodeLatin1(context, name))
+ return null();
+
+ JS_ReportErrorNumberLatin1(context->asJSContext(), GetErrorMessage, nullptr,
+ JSMSG_MISSING_EXPORT, str.ptr());
+ return null();
+ }
+
+ p->value()->setClosedOver();
+ }
+
+ if (!FoldConstants(context, &pn, this))
+ return null();
+
+ if (!propagateFreeNamesAndMarkClosedOverBindings(modulepc.varScope()))
+ return null();
+
+ Maybe<ModuleScope::Data*> bindings = newModuleScopeData(modulepc.varScope());
+ if (!bindings)
+ return nullptr;
+
+ modulesc->bindings = *bindings;
+ return mn;
+}
+
+template <>
+SyntaxParseHandler::Node
+Parser<SyntaxParseHandler>::moduleBody(ModuleSharedContext* modulesc)
+{
+ MOZ_ALWAYS_FALSE(abortIfSyntaxParser());
+ return SyntaxParseHandler::NodeFailure;
+}
+
+template <typename ParseHandler>
+bool
+Parser<ParseHandler>::hasUsedFunctionSpecialName(HandlePropertyName name)
+{
+ MOZ_ASSERT(name == context->names().arguments || name == context->names().dotThis);
+ return hasUsedName(name) || pc->functionBox()->bindingsAccessedDynamically();
+}
+
+template <typename ParseHandler>
+bool
+Parser<ParseHandler>::declareFunctionThis()
+{
+ // 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;
+
+ // Derived class constructors emit JSOP_CHECKRETURN, which requires
+ // '.this' to be bound.
+ FunctionBox* funbox = pc->functionBox();
+ HandlePropertyName dotThis = context->names().dotThis;
+
+ bool declareThis;
+ if (handler.canSkipLazyClosedOverBindings())
+ declareThis = funbox->function()->lazyScript()->hasThisBinding();
+ else
+ declareThis = hasUsedFunctionSpecialName(dotThis) || funbox->isDerivedClassConstructor();
+
+ if (declareThis) {
+ ParseContext::Scope& funScope = pc->functionScope();
+ AddDeclaredNamePtr p = funScope.lookupDeclaredNameForAdd(dotThis);
+ MOZ_ASSERT(!p);
+ if (!funScope.addDeclaredName(pc, p, dotThis, DeclarationKind::Var))
+ return false;
+ funbox->setHasThisBinding();
+ }
+
+ return true;
+}
+
+template <typename ParseHandler>
+typename ParseHandler::Node
+Parser<ParseHandler>::newInternalDotName(HandlePropertyName name)
+{
+ Node nameNode = newName(name);
+ if (!nameNode)
+ return null();
+ if (!noteUsedName(name))
+ return null();
+ return nameNode;
+}
+
+template <typename ParseHandler>
+typename ParseHandler::Node
+Parser<ParseHandler>::newThisName()
+{
+ return newInternalDotName(context->names().dotThis);
+}
+
+template <typename ParseHandler>
+typename ParseHandler::Node
+Parser<ParseHandler>::newDotGeneratorName()
+{
+ return newInternalDotName(context->names().dotGenerator);
+}
+
+template <typename ParseHandler>
+bool
+Parser<ParseHandler>::declareDotGeneratorName()
+{
+ // The special '.generator' binding must be on the function scope, as
+ // generators expect to find it on the CallObject.
+ ParseContext::Scope& funScope = pc->functionScope();
+ HandlePropertyName dotGenerator = context->names().dotGenerator;
+ AddDeclaredNamePtr p = funScope.lookupDeclaredNameForAdd(dotGenerator);
+ if (!p && !funScope.addDeclaredName(pc, p, dotGenerator, DeclarationKind::Var))
+ return false;
+ return true;
+}
+
+template <typename ParseHandler>
+bool
+Parser<ParseHandler>::finishFunctionScopes()
+{
+ FunctionBox* funbox = pc->functionBox();
+
+ if (funbox->hasParameterExprs) {
+ if (!propagateFreeNamesAndMarkClosedOverBindings(pc->functionScope()))
+ return false;
+ }
+
+ if (funbox->function()->isNamedLambda()) {
+ if (!propagateFreeNamesAndMarkClosedOverBindings(pc->namedLambdaScope()))
+ return false;
+ }
+
+ return true;
+}
+
+template <>
+bool
+Parser<FullParseHandler>::finishFunction()
+{
+ if (!finishFunctionScopes())
+ return false;
+
+ FunctionBox* funbox = pc->functionBox();
+ bool hasParameterExprs = funbox->hasParameterExprs;
+
+ if (hasParameterExprs) {
+ Maybe<VarScope::Data*> bindings = newVarScopeData(pc->varScope());
+ if (!bindings)
+ return false;
+ funbox->extraVarScopeBindings().set(*bindings);
+ }
+
+ {
+ Maybe<FunctionScope::Data*> bindings = newFunctionScopeData(pc->functionScope(),
+ hasParameterExprs);
+ if (!bindings)
+ return false;
+ funbox->functionScopeBindings().set(*bindings);
+ }
+
+ if (funbox->function()->isNamedLambda()) {
+ Maybe<LexicalScope::Data*> bindings = newLexicalScopeData(pc->namedLambdaScope());
+ if (!bindings)
+ return false;
+ funbox->namedLambdaBindings().set(*bindings);
+ }
+
+ return true;
+}
+
+template <>
+bool
+Parser<SyntaxParseHandler>::finishFunction()
+{
+ // The LazyScript 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())
+ return false;
+
+ // There are too many bindings or inner functions to be saved into the
+ // LazyScript. Do a full parse.
+ if (pc->closedOverBindingsForLazy().length() >= LazyScript::NumClosedOverBindingsLimit ||
+ pc->innerFunctionsForLazy.length() >= LazyScript::NumInnerFunctionsLimit)
+ {
+ MOZ_ALWAYS_FALSE(abortIfSyntaxParser());
+ return false;
+ }
+
+ FunctionBox* funbox = pc->functionBox();
+ RootedFunction fun(context, funbox->function());
+ LazyScript* lazy = LazyScript::Create(context, fun, pc->closedOverBindingsForLazy(),
+ pc->innerFunctionsForLazy, versionNumber(),
+ funbox->bufStart, funbox->bufEnd,
+ funbox->startLine, funbox->startColumn);
+ if (!lazy)
+ return false;
+
+ // Flags that need to be copied into the JSScript when we do the full
+ // parse.
+ if (pc->sc()->strict())
+ lazy->setStrict();
+ lazy->setGeneratorKind(funbox->generatorKind());
+ lazy->setAsyncKind(funbox->asyncKind());
+ if (funbox->isLikelyConstructorWrapper())
+ lazy->setLikelyConstructorWrapper();
+ if (funbox->isDerivedClassConstructor())
+ lazy->setIsDerivedClassConstructor();
+ if (funbox->needsHomeObject())
+ lazy->setNeedsHomeObject();
+ if (funbox->declaredArguments)
+ lazy->setShouldDeclareArguments();
+ if (funbox->hasThisBinding())
+ lazy->setHasThisBinding();
+
+ // Flags that need to copied back into the parser when we do the full
+ // parse.
+ PropagateTransitiveParseFlags(funbox, lazy);
+
+ fun->initLazyScript(lazy);
+ return true;
+}
+
+static YieldHandling
+GetYieldHandling(GeneratorKind generatorKind, FunctionAsyncKind asyncKind)
+{
+ if (asyncKind == AsyncFunction)
+ return YieldIsName;
+ if (generatorKind == NotGenerator)
+ return YieldIsName;
+ return YieldIsKeyword;
+}
+
+template <>
+ParseNode*
+Parser<FullParseHandler>::standaloneFunctionBody(HandleFunction fun,
+ HandleScope enclosingScope,
+ Handle<PropertyNameVector> formals,
+ GeneratorKind generatorKind,
+ FunctionAsyncKind asyncKind,
+ Directives inheritedDirectives,
+ Directives* newDirectives)
+{
+ MOZ_ASSERT(checkOptionsCalled);
+
+ Node fn = handler.newFunctionDefinition();
+ if (!fn)
+ return null();
+
+ ParseNode* argsbody = handler.newList(PNK_PARAMSBODY);
+ if (!argsbody)
+ return null();
+ fn->pn_body = argsbody;
+
+ FunctionBox* funbox = newFunctionBox(fn, fun, inheritedDirectives, generatorKind,
+ asyncKind, /* tryAnnexB = */ false);
+ if (!funbox)
+ return null();
+ funbox->initStandaloneFunction(enclosingScope);
+
+ ParseContext funpc(this, funbox, newDirectives);
+ if (!funpc.init())
+ return null();
+ funpc.setIsStandaloneFunctionBody();
+ funpc.functionScope().useAsVarScope(&funpc);
+
+ if (formals.length() >= ARGNO_LIMIT) {
+ report(ParseError, false, null(), JSMSG_TOO_MANY_FUN_ARGS);
+ return null();
+ }
+
+ bool duplicatedParam = false;
+ for (uint32_t i = 0; i < formals.length(); i++) {
+ if (!notePositionalFormalParameter(fn, formals[i], false, &duplicatedParam))
+ return null();
+ }
+ funbox->hasDuplicateParameters = duplicatedParam;
+
+ YieldHandling yieldHandling = GetYieldHandling(generatorKind, asyncKind);
+ AutoAwaitIsKeyword awaitIsKeyword(&tokenStream, asyncKind == AsyncFunction);
+ ParseNode* pn = functionBody(InAllowed, yieldHandling, Statement, StatementListBody);
+ if (!pn)
+ return null();
+
+ TokenKind tt;
+ if (!tokenStream.getToken(&tt, TokenStream::Operand))
+ return null();
+ if (tt != TOK_EOF) {
+ report(ParseError, false, null(), JSMSG_GARBAGE_AFTER_INPUT,
+ "function body", TokenKindToDesc(tt));
+ return null();
+ }
+
+ if (!FoldConstants(context, &pn, this))
+ return null();
+
+ fn->pn_pos.end = pos().end;
+
+ MOZ_ASSERT(fn->pn_body->isKind(PNK_PARAMSBODY));
+ fn->pn_body->append(pn);
+
+ if (!finishFunction())
+ return null();
+
+ return fn;
+}
+
+template <typename ParseHandler>
+bool
+Parser<ParseHandler>::declareFunctionArgumentsObject()
+{
+ FunctionBox* funbox = pc->functionBox();
+ ParseContext::Scope& funScope = pc->functionScope();
+ ParseContext::Scope& varScope = pc->varScope();
+
+ bool hasExtraBodyVarScope = &funScope != &varScope;
+
+ // Time to implement the odd semantics of 'arguments'.
+ HandlePropertyName argumentsName = context->names().arguments;
+
+ bool tryDeclareArguments;
+ if (handler.canSkipLazyClosedOverBindings())
+ tryDeclareArguments = funbox->function()->lazyScript()->shouldDeclareArguments();
+ else
+ tryDeclareArguments = hasUsedFunctionSpecialName(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 ||
+ p->value()->kind() == DeclarationKind::ForOfVar))
+ {
+ if (hasExtraBodyVarScope)
+ tryDeclareArguments = true;
+ else
+ funbox->usesArguments = true;
+ }
+
+ if (tryDeclareArguments) {
+ AddDeclaredNamePtr p = funScope.lookupDeclaredNameForAdd(argumentsName);
+ if (!p) {
+ if (!funScope.addDeclaredName(pc, p, argumentsName, DeclarationKind::Var))
+ return false;
+ funbox->declaredArguments = true;
+ funbox->usesArguments = true;
+ } else if (hasExtraBodyVarScope) {
+ // Formal parameters shadow the arguments object.
+ return true;
+ }
+ }
+
+ // Compute if we need an arguments object.
+ if (funbox->usesArguments) {
+ // There is an 'arguments' binding. Is the arguments object definitely
+ // needed?
+ //
+ // Also see the flags' comments in ContextFlags.
+ funbox->setArgumentsHasLocalBinding();
+
+ // Dynamic scope access destroys all hope of optimization.
+ if (pc->sc()->bindingsAccessedDynamically())
+ funbox->setDefinitelyNeedsArgsObj();
+
+ // If a script contains the debugger statement either directly or
+ // within an inner function, the arguments object should be created
+ // eagerly so the Debugger API may observe bindings.
+ if (pc->sc()->hasDebuggerStatement())
+ funbox->setDefinitelyNeedsArgsObj();
+ }
+
+ return true;
+}
+
+template <typename ParseHandler>
+typename ParseHandler::Node
+Parser<ParseHandler>::functionBody(InHandling inHandling, YieldHandling yieldHandling,
+ FunctionSyntaxKind kind, FunctionBodyType type)
+{
+ MOZ_ASSERT(pc->isFunctionBox());
+ MOZ_ASSERT(!pc->funHasReturnExpr && !pc->funHasReturnVoid);
+
+#ifdef DEBUG
+ uint32_t startYieldOffset = pc->lastYieldOffset;
+#endif
+
+ Node pn;
+ if (type == StatementListBody) {
+ bool inheritedStrict = pc->sc()->strict();
+ pn = statementList(yieldHandling);
+ if (!pn)
+ 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 star generators, and star
+ // generators are assumed to be statement lists, to prepend initial
+ // `yield`.
+ Node stmtList = null();
+ if (pc->isAsync()) {
+ stmtList = handler.newStatementList(pos());
+ if (!stmtList)
+ return null();
+ }
+
+ Node kid = assignExpr(inHandling, yieldHandling, TripledotProhibited);
+ if (!kid)
+ return null();
+
+ pn = handler.newReturnStatement(kid, handler.getPosition(kid));
+ if (!pn)
+ return null();
+
+ if (pc->isAsync()) {
+ handler.addStatementToList(stmtList, pn);
+ pn = stmtList;
+ }
+ }
+
+ switch (pc->generatorKind()) {
+ case NotGenerator:
+ MOZ_ASSERT(pc->lastYieldOffset == startYieldOffset);
+ break;
+
+ case LegacyGenerator:
+ MOZ_ASSERT(pc->lastYieldOffset != startYieldOffset);
+
+ // These should throw while parsing the yield expression.
+ MOZ_ASSERT(kind != Arrow);
+ MOZ_ASSERT(!IsGetterKind(kind));
+ MOZ_ASSERT(!IsSetterKind(kind));
+ MOZ_ASSERT(!IsConstructorKind(kind));
+ MOZ_ASSERT(kind != Method);
+ MOZ_ASSERT(type != ExpressionBody);
+ break;
+
+ case StarGenerator:
+ MOZ_ASSERT_IF(!pc->isAsync(), kind != Arrow);
+ MOZ_ASSERT_IF(!pc->isAsync(), type == StatementListBody);
+ break;
+ }
+
+ if (pc->isGenerator()) {
+ MOZ_ASSERT_IF(!pc->isAsync(), type == StatementListBody);
+ if (!declareDotGeneratorName())
+ return null();
+ Node generator = newDotGeneratorName();
+ if (!generator)
+ return null();
+ if (!handler.prependInitialYield(pn, generator))
+ return null();
+ }
+
+ // Declare the 'arguments' and 'this' bindings if necessary before
+ // finishing up the scope so these special bindings get marked as closed
+ // over if necessary. Arrow functions don't have these bindings.
+ if (kind != Arrow) {
+ if (!declareFunctionArgumentsObject())
+ return null();
+ if (!declareFunctionThis())
+ return null();
+ }
+
+ return finishLexicalScope(pc->varScope(), pn);
+}
+
+template <typename ParseHandler>
+JSFunction*
+Parser<ParseHandler>::newFunction(HandleAtom atom, FunctionSyntaxKind kind,
+ GeneratorKind generatorKind, FunctionAsyncKind asyncKind,
+ HandleObject proto)
+{
+ MOZ_ASSERT_IF(kind == Statement, atom != nullptr);
+
+ RootedFunction fun(context);
+
+ gc::AllocKind allocKind = gc::AllocKind::FUNCTION;
+ JSFunction::Flags flags;
+#ifdef DEBUG
+ bool isGlobalSelfHostedBuiltin = false;
+#endif
+ switch (kind) {
+ case Expression:
+ flags = (generatorKind == NotGenerator
+ ? JSFunction::INTERPRETED_LAMBDA
+ : JSFunction::INTERPRETED_LAMBDA_GENERATOR);
+ break;
+ case Arrow:
+ flags = JSFunction::INTERPRETED_LAMBDA_ARROW;
+ allocKind = gc::AllocKind::FUNCTION_EXTENDED;
+ break;
+ case Method:
+ MOZ_ASSERT(generatorKind == NotGenerator || generatorKind == StarGenerator);
+ flags = (generatorKind == NotGenerator
+ ? JSFunction::INTERPRETED_METHOD
+ : JSFunction::INTERPRETED_METHOD_GENERATOR);
+ allocKind = gc::AllocKind::FUNCTION_EXTENDED;
+ break;
+ case ClassConstructor:
+ case DerivedClassConstructor:
+ flags = JSFunction::INTERPRETED_CLASS_CONSTRUCTOR;
+ allocKind = gc::AllocKind::FUNCTION_EXTENDED;
+ break;
+ case Getter:
+ case GetterNoExpressionClosure:
+ flags = JSFunction::INTERPRETED_GETTER;
+ allocKind = gc::AllocKind::FUNCTION_EXTENDED;
+ break;
+ case Setter:
+ case SetterNoExpressionClosure:
+ flags = JSFunction::INTERPRETED_SETTER;
+ allocKind = gc::AllocKind::FUNCTION_EXTENDED;
+ break;
+ default:
+ MOZ_ASSERT(kind == Statement);
+#ifdef DEBUG
+ if (options().selfHostingMode && !pc->isFunctionBox()) {
+ isGlobalSelfHostedBuiltin = true;
+ allocKind = gc::AllocKind::FUNCTION_EXTENDED;
+ }
+#endif
+ flags = (generatorKind == NotGenerator
+ ? JSFunction::INTERPRETED_NORMAL
+ : JSFunction::INTERPRETED_GENERATOR);
+ }
+
+ // We store the async wrapper in a slot for later access.
+ if (asyncKind == AsyncFunction)
+ allocKind = gc::AllocKind::FUNCTION_EXTENDED;
+
+ fun = NewFunctionWithProto(context, nullptr, 0, flags, nullptr, atom, proto,
+ allocKind, TenuredObject);
+ if (!fun)
+ return nullptr;
+ if (options().selfHostingMode) {
+ fun->setIsSelfHostedBuiltin();
+#ifdef DEBUG
+ if (isGlobalSelfHostedBuiltin)
+ fun->setExtendedSlot(HAS_SELFHOSTED_CANONICAL_NAME_SLOT, BooleanValue(false));
+#endif
+ }
+ return fun;
+}
+
+/*
+ * WARNING: Do not call this function directly.
+ * Call either MatchOrInsertSemicolonAfterExpression or
+ * MatchOrInsertSemicolonAfterNonExpression instead, depending on context.
+ */
+static bool
+MatchOrInsertSemicolonHelper(TokenStream& ts, TokenStream::Modifier modifier)
+{
+ TokenKind tt = TOK_EOF;
+ if (!ts.peekTokenSameLine(&tt, modifier))
+ return false;
+ if (tt != TOK_EOF && tt != TOK_EOL && tt != TOK_SEMI && tt != TOK_RC) {
+ /* Advance the scanner for proper error location reporting. */
+ ts.consumeKnownToken(tt, modifier);
+ ts.reportError(JSMSG_SEMI_BEFORE_STMNT);
+ return false;
+ }
+ bool matched;
+ if (!ts.matchToken(&matched, TOK_SEMI, modifier))
+ return false;
+ if (!matched && modifier == TokenStream::None)
+ ts.addModifierException(TokenStream::OperandIsNone);
+ return true;
+}
+
+static bool
+MatchOrInsertSemicolonAfterExpression(TokenStream& ts)
+{
+ return MatchOrInsertSemicolonHelper(ts, TokenStream::None);
+}
+
+static bool
+MatchOrInsertSemicolonAfterNonExpression(TokenStream& ts)
+{
+ return MatchOrInsertSemicolonHelper(ts, TokenStream::Operand);
+}
+
+template <typename ParseHandler>
+bool
+Parser<ParseHandler>::leaveInnerFunction(ParseContext* outerpc)
+{
+ MOZ_ASSERT(pc != outerpc);
+
+ // 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 here unconditionally; the vector is only used
+ // if the Parser using outerpc is a syntax parsing. See
+ // Parser<SyntaxParseHandler>::finishFunction.
+ if (!outerpc->innerFunctionsForLazy.append(pc->functionBox()->function()))
+ return false;
+
+ PropagateTransitiveParseFlags(pc->functionBox(), outerpc->sc());
+
+ return true;
+}
+
+template <typename ParseHandler>
+JSAtom*
+Parser<ParseHandler>::prefixAccessorName(PropertyType propType, HandleAtom propAtom)
+{
+ RootedAtom prefix(context);
+ if (propType == PropertyType::Setter || propType == PropertyType::SetterNoExpressionClosure) {
+ prefix = context->names().setPrefix;
+ } else {
+ MOZ_ASSERT(propType == PropertyType::Getter || propType == PropertyType::GetterNoExpressionClosure);
+ prefix = context->names().getPrefix;
+ }
+
+ RootedString str(context, ConcatStrings<CanGC>(context, prefix, propAtom));
+ if (!str)
+ return nullptr;
+
+ return AtomizeString(context, str);
+}
+
+template <typename ParseHandler>
+bool
+Parser<ParseHandler>::functionArguments(YieldHandling yieldHandling, FunctionSyntaxKind kind,
+ Node funcpn)
+{
+ FunctionBox* funbox = pc->functionBox();
+
+ bool parenFreeArrow = false;
+ // Modifier for the following tokens.
+ // TokenStream::None for the following cases:
+ // async a => 1
+ // ^
+ //
+ // (a) => 1
+ // ^
+ //
+ // async (a) => 1
+ // ^
+ //
+ // function f(a) {}
+ // ^
+ //
+ // TokenStream::Operand for the following case:
+ // a => 1
+ // ^
+ TokenStream::Modifier firstTokenModifier = TokenStream::None;
+
+ // Modifier for the the first token in each argument.
+ // can be changed to TokenStream::None for the following case:
+ // async a => 1
+ // ^
+ TokenStream::Modifier argModifier = TokenStream::Operand;
+ if (kind == Arrow) {
+ TokenKind tt;
+ // In async function, the first token after `async` is already gotten
+ // with TokenStream::None.
+ // In sync function, the first token is already gotten with
+ // TokenStream::Operand.
+ firstTokenModifier = funbox->isAsync() ? TokenStream::None : TokenStream::Operand;
+ if (!tokenStream.peekToken(&tt, firstTokenModifier))
+ return false;
+ if (tt == TOK_NAME || tt == TOK_YIELD) {
+ parenFreeArrow = true;
+ argModifier = firstTokenModifier;
+ }
+ }
+ if (!parenFreeArrow) {
+ TokenKind tt;
+ if (!tokenStream.getToken(&tt, firstTokenModifier))
+ return false;
+ if (tt != TOK_LP) {
+ report(ParseError, false, null(),
+ kind == Arrow ? JSMSG_BAD_ARROW_ARGS : JSMSG_PAREN_BEFORE_FORMAL);
+ return false;
+ }
+
+ // Record the start of function source (for FunctionToString). If we
+ // are parenFreeArrow, we will set this below, after consuming the NAME.
+ funbox->setStart(tokenStream);
+ }
+
+ Node argsbody = handler.newList(PNK_PARAMSBODY);
+ if (!argsbody)
+ return false;
+ handler.setFunctionFormalParametersAndBody(funcpn, argsbody);
+
+ bool hasArguments = false;
+ if (parenFreeArrow) {
+ hasArguments = true;
+ } else {
+ bool matched;
+ if (!tokenStream.matchToken(&matched, TOK_RP, TokenStream::Operand))
+ return false;
+ if (!matched)
+ hasArguments = true;
+ }
+ if (hasArguments) {
+ bool hasRest = false;
+ bool hasDefault = false;
+ bool duplicatedParam = false;
+ bool disallowDuplicateParams = kind == Arrow || kind == Method || kind == ClassConstructor;
+ AtomVector& positionalFormals = pc->positionalFormalParameterNames();
+
+ if (IsGetterKind(kind)) {
+ report(ParseError, false, null(), JSMSG_ACCESSOR_WRONG_ARGS, "getter", "no", "s");
+ return false;
+ }
+
+ while (true) {
+ if (hasRest) {
+ report(ParseError, false, null(), JSMSG_PARAMETER_AFTER_REST);
+ return false;
+ }
+
+ TokenKind tt;
+ if (!tokenStream.getToken(&tt, argModifier))
+ return false;
+ argModifier = TokenStream::Operand;
+ MOZ_ASSERT_IF(parenFreeArrow, tt == TOK_NAME || tt == TOK_YIELD);
+
+ if (tt == TOK_TRIPLEDOT) {
+ if (IsSetterKind(kind)) {
+ report(ParseError, false, null(),
+ JSMSG_ACCESSOR_WRONG_ARGS, "setter", "one", "");
+ return false;
+ }
+
+ disallowDuplicateParams = true;
+ if (duplicatedParam) {
+ // Has duplicated args before the rest parameter.
+ report(ParseError, false, null(), JSMSG_BAD_DUP_ARGS);
+ return false;
+ }
+
+ hasRest = true;
+ funbox->function()->setHasRest();
+
+ if (!tokenStream.getToken(&tt))
+ return false;
+
+ if (tt != TOK_NAME && tt != TOK_YIELD && tt != TOK_LB && tt != TOK_LC) {
+ report(ParseError, false, null(), JSMSG_NO_REST_NAME);
+ return false;
+ }
+ }
+
+ switch (tt) {
+ case TOK_LB:
+ case TOK_LC: {
+ disallowDuplicateParams = true;
+ if (duplicatedParam) {
+ // Has duplicated args before the destructuring parameter.
+ report(ParseError, false, null(), JSMSG_BAD_DUP_ARGS);
+ return false;
+ }
+
+ funbox->hasDestructuringArgs = true;
+
+ Node destruct = destructuringDeclarationWithoutYieldOrAwait(
+ DeclarationKind::FormalParameter,
+ yieldHandling, tt);
+ if (!destruct)
+ return false;
+
+ if (!noteDestructuredPositionalFormalParameter(funcpn, destruct))
+ return false;
+
+ break;
+ }
+
+ case TOK_NAME:
+ case TOK_YIELD: {
+ if (parenFreeArrow)
+ funbox->setStart(tokenStream);
+
+ if (funbox->isAsync() && tokenStream.currentName() == context->names().await) {
+ // `await` is already gotten as TOK_NAME for the following
+ // case:
+ //
+ // async await => 1
+ report(ParseError, false, null(), JSMSG_RESERVED_ID, "await");
+ return false;
+ }
+
+ RootedPropertyName name(context, bindingIdentifier(yieldHandling));
+ if (!name)
+ return false;
+
+ if (!notePositionalFormalParameter(funcpn, name, disallowDuplicateParams,
+ &duplicatedParam))
+ {
+ return false;
+ }
+ if (duplicatedParam)
+ funbox->hasDuplicateParameters = true;
+
+ break;
+ }
+
+ default:
+ report(ParseError, false, null(), JSMSG_MISSING_FORMAL);
+ return false;
+ }
+
+ if (positionalFormals.length() >= ARGNO_LIMIT) {
+ report(ParseError, false, null(), JSMSG_TOO_MANY_FUN_ARGS);
+ return false;
+ }
+
+ bool matched;
+ if (!tokenStream.matchToken(&matched, TOK_ASSIGN))
+ return false;
+ if (matched) {
+ // A default argument without parentheses would look like:
+ // a = expr => body, but both operators are right-associative, so
+ // that would have been parsed as a = (expr => body) instead.
+ // Therefore it's impossible to get here with parenFreeArrow.
+ MOZ_ASSERT(!parenFreeArrow);
+
+ if (hasRest) {
+ report(ParseError, false, null(), JSMSG_REST_WITH_DEFAULT);
+ return false;
+ }
+ disallowDuplicateParams = true;
+ if (duplicatedParam) {
+ report(ParseError, false, null(), 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->length = positionalFormals.length() - 1;
+ }
+ funbox->hasParameterExprs = true;
+
+ Node def_expr = assignExprWithoutYieldOrAwait(yieldHandling);
+ if (!def_expr)
+ return false;
+ if (!handler.setLastFunctionFormalParameterDefault(funcpn, def_expr))
+ return false;
+ }
+
+ if (parenFreeArrow || IsSetterKind(kind))
+ break;
+
+ if (!tokenStream.matchToken(&matched, TOK_COMMA))
+ return false;
+ if (!matched)
+ break;
+
+ if (!hasRest) {
+ if (!tokenStream.peekToken(&tt, TokenStream::Operand))
+ return null();
+ if (tt == TOK_RP) {
+ tokenStream.addModifierException(TokenStream::NoneIsOperand);
+ break;
+ }
+ }
+ }
+
+ if (!parenFreeArrow) {
+ TokenKind tt;
+ if (!tokenStream.getToken(&tt))
+ return false;
+ if (tt != TOK_RP) {
+ if (IsSetterKind(kind)) {
+ report(ParseError, false, null(),
+ JSMSG_ACCESSOR_WRONG_ARGS, "setter", "one", "");
+ return false;
+ }
+
+ report(ParseError, false, null(), JSMSG_PAREN_AFTER_FORMAL);
+ return false;
+ }
+ }
+
+ if (!hasDefault)
+ funbox->length = positionalFormals.length() - hasRest;
+
+ if (funbox->hasParameterExprs && funbox->hasDirectEval())
+ funbox->hasDirectEvalInParameterExpr = true;
+
+ funbox->function()->setArgCount(positionalFormals.length());
+ } else if (IsSetterKind(kind)) {
+ report(ParseError, false, null(), JSMSG_ACCESSOR_WRONG_ARGS, "setter", "one", "");
+ return false;
+ }
+
+ return true;
+}
+
+template <typename ParseHandler>
+bool
+Parser<ParseHandler>::checkFunctionDefinition(HandleAtom funAtom, Node pn, FunctionSyntaxKind kind,
+ GeneratorKind generatorKind, bool* tryAnnexB)
+{
+ if (kind == Statement) {
+ TokenPos pos = handler.getPosition(pn);
+ RootedPropertyName funName(context, funAtom->asPropertyName());
+
+ // In sloppy mode, Annex B.3.2 allows labelled function
+ // declarations. Otherwise it is 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())) {
+ reportWithOffset(ParseError, false, pos.begin, JSMSG_SLOPPY_FUNCTION_LABEL);
+ return false;
+ }
+ }
+
+ if (declaredInStmt) {
+ MOZ_ASSERT(declaredInStmt->kind() != StatementKind::Label);
+ MOZ_ASSERT(StatementKindIsBraced(declaredInStmt->kind()));
+
+ if (!pc->sc()->strict() && generatorKind == NotGenerator) {
+ // 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.
+
+ if (!tryDeclareVarForAnnexBLexicalFunction(funName, tryAnnexB))
+ return false;
+ }
+
+ if (!noteDeclaredName(funName, DeclarationKind::LexicalFunction, pos))
+ return false;
+ } else {
+ if (!noteDeclaredName(funName, DeclarationKind::BodyLevelFunction, pos))
+ return false;
+
+ // Body-level functions in modules are always closed over.
+ if (pc->atModuleLevel())
+ pc->varScope().lookupDeclaredName(funName)->value()->setClosedOver();
+ }
+ } else {
+ // A function expression does not introduce any binding.
+ handler.setOp(pn, kind == Arrow ? JSOP_LAMBDA_ARROW : JSOP_LAMBDA);
+ }
+
+ return true;
+}
+
+template <>
+bool
+Parser<FullParseHandler>::skipLazyInnerFunction(ParseNode* pn, FunctionSyntaxKind kind,
+ bool tryAnnexB)
+{
+ // When a lazily-parsed function is called, we only fully parse (and emit)
+ // that function, not any of its nested children. The initial syntax-only
+ // parse recorded the free variables of nested functions and their extents,
+ // so we can skip over them after accounting for their free variables.
+
+ RootedFunction fun(context, handler.nextLazyInnerFunction());
+ MOZ_ASSERT(!fun->isLegacyGenerator());
+ FunctionBox* funbox = newFunctionBox(pn, fun, Directives(/* strict = */ false),
+ fun->generatorKind(), fun->asyncKind(), tryAnnexB);
+ if (!funbox)
+ return false;
+
+ LazyScript* lazy = fun->lazyScript();
+ if (lazy->needsHomeObject())
+ funbox->setNeedsHomeObject();
+
+ PropagateTransitiveParseFlags(lazy, pc->sc());
+
+ // The position passed to tokenStream.advance() is an offset of the sort
+ // returned by userbuf.offset() and expected by userbuf.rawCharPtrAt(),
+ // while LazyScript::{begin,end} offsets are relative to the outermost
+ // script source.
+ Rooted<LazyScript*> lazyOuter(context, handler.lazyOuterFunction());
+ uint32_t userbufBase = lazyOuter->begin() - lazyOuter->column();
+ if (!tokenStream.advance(fun->lazyScript()->end() - userbufBase))
+ return false;
+
+ if (kind == Statement && fun->isExprBody()) {
+ if (!MatchOrInsertSemicolonAfterExpression(tokenStream))
+ return false;
+ }
+
+ return true;
+}
+
+template <>
+bool
+Parser<SyntaxParseHandler>::skipLazyInnerFunction(Node pn, FunctionSyntaxKind kind,
+ bool tryAnnexB)
+{
+ MOZ_CRASH("Cannot skip lazy inner functions when syntax parsing");
+}
+
+template <typename ParseHandler>
+bool
+Parser<ParseHandler>::addExprAndGetNextTemplStrToken(YieldHandling yieldHandling, Node nodeList,
+ TokenKind* ttp)
+{
+ Node pn = expr(InAllowed, yieldHandling, TripledotProhibited);
+ if (!pn)
+ return false;
+ handler.addList(nodeList, pn);
+
+ TokenKind tt;
+ if (!tokenStream.getToken(&tt))
+ return false;
+ if (tt != TOK_RC) {
+ report(ParseError, false, null(), JSMSG_TEMPLSTR_UNTERM_EXPR);
+ return false;
+ }
+
+ return tokenStream.getToken(ttp, TokenStream::TemplateTail);
+}
+
+template <typename ParseHandler>
+bool
+Parser<ParseHandler>::taggedTemplate(YieldHandling yieldHandling, Node nodeList, TokenKind tt)
+{
+ Node callSiteObjNode = handler.newCallSiteObject(pos().begin);
+ if (!callSiteObjNode)
+ return false;
+ handler.addList(nodeList, callSiteObjNode);
+
+ while (true) {
+ if (!appendToCallSiteObj(callSiteObjNode))
+ return false;
+ if (tt != TOK_TEMPLATE_HEAD)
+ break;
+
+ if (!addExprAndGetNextTemplStrToken(yieldHandling, nodeList, &tt))
+ return false;
+ }
+ handler.setEndPosition(nodeList, callSiteObjNode);
+ return true;
+}
+
+template <typename ParseHandler>
+typename ParseHandler::Node
+Parser<ParseHandler>::templateLiteral(YieldHandling yieldHandling)
+{
+ Node pn = noSubstitutionTemplate();
+ if (!pn)
+ return null();
+
+ Node nodeList = handler.newList(PNK_TEMPLATE_STRING_LIST, pn);
+ if (!nodeList)
+ return null();
+
+ TokenKind tt;
+ do {
+ if (!addExprAndGetNextTemplStrToken(yieldHandling, nodeList, &tt))
+ return null();
+
+ pn = noSubstitutionTemplate();
+ if (!pn)
+ return null();
+
+ handler.addList(nodeList, pn);
+ } while (tt == TOK_TEMPLATE_HEAD);
+ return nodeList;
+}
+
+template <typename ParseHandler>
+typename ParseHandler::Node
+Parser<ParseHandler>::functionDefinition(InHandling inHandling, YieldHandling yieldHandling,
+ HandleAtom funName, FunctionSyntaxKind kind,
+ GeneratorKind generatorKind, FunctionAsyncKind asyncKind,
+ InvokedPrediction invoked)
+{
+ MOZ_ASSERT_IF(kind == Statement, funName);
+ MOZ_ASSERT_IF(asyncKind == AsyncFunction, generatorKind == StarGenerator);
+
+ Node pn = handler.newFunctionDefinition();
+ if (!pn)
+ return null();
+
+ if (invoked)
+ pn = handler.setLikelyIIFE(pn);
+
+ // Note the declared name and check for early errors.
+ bool tryAnnexB = false;
+ if (!checkFunctionDefinition(funName, pn, kind, generatorKind, &tryAnnexB))
+ return null();
+
+ // When fully parsing a LazyScript, we do not fully reparse its inner
+ // functions, which are also lazy. Instead, their free variables and
+ // source extents are recorded and may be skipped.
+ if (handler.canSkipLazyInnerFunctions()) {
+ if (!skipLazyInnerFunction(pn, kind, tryAnnexB))
+ return null();
+ return pn;
+ }
+
+ RootedObject proto(context);
+ if (generatorKind == StarGenerator) {
+ // If we are off the main thread, the generator meta-objects have
+ // already been created by js::StartOffThreadParseScript, so cx will not
+ // be necessary.
+ JSContext* cx = context->maybeJSContext();
+ proto = GlobalObject::getOrCreateStarGeneratorFunctionPrototype(cx, context->global());
+ if (!proto)
+ return null();
+ }
+ RootedFunction fun(context, newFunction(funName, kind, generatorKind, asyncKind, proto));
+ if (!fun)
+ return null();
+
+ // 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;
+
+ TokenStream::Position start(keepAtoms);
+ tokenStream.tell(&start);
+
+ // 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(pn, fun, inHandling, yieldHandling, kind, generatorKind,
+ asyncKind, tryAnnexB, directives, &newDirectives))
+ {
+ break;
+ }
+
+ // Return on error.
+ if (tokenStream.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;
+
+ tokenStream.seek(start);
+
+ // functionFormalParametersAndBody may have already set pn->pn_body before failing.
+ handler.setFunctionFormalParametersAndBody(pn, null());
+ }
+
+ return pn;
+}
+
+template <>
+bool
+Parser<FullParseHandler>::trySyntaxParseInnerFunction(ParseNode* pn, HandleFunction fun,
+ 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 (pn->isLikelyIIFE() && generatorKind == NotGenerator)
+ break;
+
+ Parser<SyntaxParseHandler>* parser = handler.syntaxParser;
+ if (!parser)
+ break;
+
+ UsedNameTracker::RewindToken token = usedNames.getRewindToken();
+
+ // Move the syntax parser to the current position in the stream.
+ TokenStream::Position position(keepAtoms);
+ tokenStream.tell(&position);
+ if (!parser->tokenStream.seek(position, tokenStream))
+ 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(pn, fun, inheritedDirectives, generatorKind,
+ asyncKind, tryAnnexB);
+ if (!funbox)
+ return false;
+ funbox->initWithEnclosingParseContext(pc, kind);
+
+ if (!parser->innerFunction(SyntaxParseHandler::NodeGeneric, pc, funbox, inHandling,
+ yieldHandling, kind, inheritedDirectives, newDirectives))
+ {
+ if (parser->hadAbortedSyntaxParse()) {
+ // Try again with a full parse. UsedNameTracker needs to be
+ // rewound to just before we tried the syntax parse for
+ // correctness.
+ parser->clearAbortedSyntaxParse();
+ usedNames.rewind(token);
+ MOZ_ASSERT_IF(parser->context->isJSContext(),
+ !parser->context->asJSContext()->isExceptionPending());
+ break;
+ }
+ return false;
+ }
+
+ // Advance this parser over tokens processed by the syntax parser.
+ parser->tokenStream.tell(&position);
+ if (!tokenStream.seek(position, parser->tokenStream))
+ return false;
+
+ // Update the end position of the parse node.
+ pn->pn_pos.end = tokenStream.currentToken().pos.end;
+ return true;
+ } while (false);
+
+ // We failed to do a syntax parse above, so do the full parse.
+ return innerFunction(pn, pc, fun, inHandling, yieldHandling, kind, generatorKind, asyncKind,
+ tryAnnexB, inheritedDirectives, newDirectives);
+}
+
+template <>
+bool
+Parser<SyntaxParseHandler>::trySyntaxParseInnerFunction(Node pn, HandleFunction fun,
+ 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.
+ return innerFunction(pn, pc, fun, inHandling, yieldHandling, kind, generatorKind, asyncKind,
+ tryAnnexB, inheritedDirectives, newDirectives);
+}
+
+template <typename ParseHandler>
+bool
+Parser<ParseHandler>::innerFunction(Node pn, ParseContext* outerpc, FunctionBox* funbox,
+ InHandling inHandling, YieldHandling yieldHandling,
+ FunctionSyntaxKind kind, 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 ParseContext from the full parser
+ // instead of the current top of the stack of the syntax parser.
+
+ // Push a new ParseContext.
+ ParseContext funpc(this, funbox, newDirectives);
+ if (!funpc.init())
+ return false;
+
+ if (!functionFormalParametersAndBody(inHandling, yieldHandling, pn, kind))
+ return false;
+
+ return leaveInnerFunction(outerpc);
+}
+
+template <typename ParseHandler>
+bool
+Parser<ParseHandler>::innerFunction(Node pn, ParseContext* outerpc, HandleFunction fun,
+ 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 ParseContext from the full parser
+ // instead of the current top of the stack of the syntax parser.
+
+ FunctionBox* funbox = newFunctionBox(pn, fun, inheritedDirectives, generatorKind,
+ asyncKind, tryAnnexB);
+ if (!funbox)
+ return false;
+ funbox->initWithEnclosingParseContext(outerpc, kind);
+
+ return innerFunction(pn, outerpc, funbox, inHandling, yieldHandling, kind, inheritedDirectives,
+ newDirectives);
+}
+
+template <typename ParseHandler>
+bool
+Parser<ParseHandler>::appendToCallSiteObj(Node callSiteObj)
+{
+ Node cookedNode = noSubstitutionTemplate();
+ if (!cookedNode)
+ return false;
+
+ JSAtom* atom = tokenStream.getRawTemplateStringAtom();
+ if (!atom)
+ return false;
+ Node rawNode = handler.newTemplateStringLiteral(atom, pos());
+ if (!rawNode)
+ return false;
+
+ handler.addToCallSiteObject(callSiteObj, rawNode, cookedNode);
+ return true;
+}
+
+template <>
+ParseNode*
+Parser<FullParseHandler>::standaloneLazyFunction(HandleFunction fun, bool strict,
+ GeneratorKind generatorKind,
+ FunctionAsyncKind asyncKind)
+{
+ MOZ_ASSERT(checkOptionsCalled);
+
+ Node pn = handler.newFunctionDefinition();
+ if (!pn)
+ return null();
+
+ Directives directives(strict);
+ FunctionBox* funbox = newFunctionBox(pn, fun, directives, generatorKind, asyncKind,
+ /* tryAnnexB = */ false);
+ if (!funbox)
+ return null();
+ funbox->initFromLazyFunction();
+
+ Directives newDirectives = directives;
+ ParseContext funpc(this, funbox, &newDirectives);
+ if (!funpc.init())
+ return null();
+
+ // Our tokenStream has no current token, so pn's position is garbage.
+ // Substitute the position of the first token in our source. If the function
+ // is a not-async arrow, use TokenStream::Operand to keep
+ // verifyConsistentModifier from complaining (we will use
+ // TokenStream::Operand in functionArguments).
+ TokenStream::Modifier modifier = (fun->isArrow() && asyncKind == SyncFunction)
+ ? TokenStream::Operand : TokenStream::None;
+ if (!tokenStream.peekTokenPos(&pn->pn_pos, modifier))
+ return null();
+
+ YieldHandling yieldHandling = GetYieldHandling(generatorKind, asyncKind);
+ FunctionSyntaxKind syntaxKind = Statement;
+ if (fun->isClassConstructor())
+ syntaxKind = ClassConstructor;
+ else if (fun->isMethod())
+ syntaxKind = Method;
+ else if (fun->isGetter())
+ syntaxKind = Getter;
+ else if (fun->isSetter())
+ syntaxKind = Setter;
+ else if (fun->isArrow())
+ syntaxKind = Arrow;
+
+ if (!functionFormalParametersAndBody(InAllowed, yieldHandling, pn, syntaxKind)) {
+ MOZ_ASSERT(directives == newDirectives);
+ return null();
+ }
+
+ if (!FoldConstants(context, &pn, this))
+ return null();
+
+ return pn;
+}
+
+template <typename ParseHandler>
+bool
+Parser<ParseHandler>::functionFormalParametersAndBody(InHandling inHandling,
+ YieldHandling yieldHandling,
+ Node pn, FunctionSyntaxKind kind)
+{
+ // 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();
+ RootedFunction fun(context, funbox->function());
+
+ AutoAwaitIsKeyword awaitIsKeyword(&tokenStream, funbox->isAsync());
+ if (!functionArguments(yieldHandling, kind, pn))
+ 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 == Arrow) {
+ bool matched;
+ if (!tokenStream.matchToken(&matched, TOK_ARROW))
+ return false;
+ if (!matched) {
+ report(ParseError, false, null(), JSMSG_BAD_ARROW_ARGS);
+ return false;
+ }
+ }
+
+ // Parse the function body.
+ FunctionBodyType bodyType = StatementListBody;
+ TokenKind tt;
+ if (!tokenStream.getToken(&tt, TokenStream::Operand))
+ return false;
+ if (tt != TOK_LC) {
+ if ((funbox->isStarGenerator() && !funbox->isAsync()) || kind == Method ||
+ kind == GetterNoExpressionClosure || kind == SetterNoExpressionClosure ||
+ IsConstructorKind(kind)) {
+ report(ParseError, false, null(), JSMSG_CURLY_BEFORE_BODY);
+ return false;
+ }
+
+ if (kind != Arrow) {
+#if JS_HAS_EXPR_CLOSURES
+ addTelemetry(JSCompartment::DeprecatedExpressionClosure);
+ if (!warnOnceAboutExprClosure())
+ return false;
+#else
+ report(ParseError, false, null(), JSMSG_CURLY_BEFORE_BODY);
+ return false;
+#endif
+ }
+
+ tokenStream.ungetToken();
+ bodyType = ExpressionBody;
+#if JS_HAS_EXPR_CLOSURES
+ fun->setIsExprBody();
+#endif
+ }
+
+ // 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.
+ YieldHandling bodyYieldHandling = GetYieldHandling(pc->generatorKind(), pc->asyncKind());
+ Node body = functionBody(inHandling, bodyYieldHandling, kind, bodyType);
+ if (!body)
+ return false;
+
+ if ((kind != Method && !IsConstructorKind(kind)) && fun->name()) {
+ RootedPropertyName propertyName(context, fun->name()->asPropertyName());
+ if (!checkStrictBinding(propertyName, handler.getPosition(pn)))
+ return false;
+ }
+
+ if (bodyType == StatementListBody) {
+ bool matched;
+ if (!tokenStream.matchToken(&matched, TOK_RC, TokenStream::Operand))
+ return false;
+ if (!matched) {
+ report(ParseError, false, null(), JSMSG_CURLY_AFTER_BODY);
+ return false;
+ }
+ funbox->bufEnd = pos().begin + 1;
+ } else {
+#if !JS_HAS_EXPR_CLOSURES
+ MOZ_ASSERT(kind == Arrow);
+#endif
+ if (tokenStream.hadError())
+ return false;
+ funbox->bufEnd = pos().end;
+ if (kind == Statement && !MatchOrInsertSemicolonAfterExpression(tokenStream))
+ return false;
+ }
+
+ if (IsMethodDefinitionKind(kind) && pc->superScopeNeedsHomeObject())
+ funbox->setNeedsHomeObject();
+
+ if (!finishFunction())
+ return false;
+
+ handler.setEndPosition(body, pos().begin);
+ handler.setEndPosition(pn, pos().end);
+ handler.setFunctionBody(pn, body);
+
+ return true;
+}
+
+template <typename ParseHandler>
+typename ParseHandler::Node
+Parser<ParseHandler>::functionStmt(YieldHandling yieldHandling, DefaultHandling defaultHandling,
+ FunctionAsyncKind asyncKind)
+{
+ MOZ_ASSERT(tokenStream.isCurrentTokenType(TOK_FUNCTION));
+
+ // Annex B.3.4 says we can parse function declarations unbraced under if
+ // or else as if it were braced. That is, |if (x) function f() {}| is
+ // parsed as |if (x) { function f() {} }|.
+ Maybe<ParseContext::Statement> synthesizedStmtForAnnexB;
+ Maybe<ParseContext::Scope> synthesizedScopeForAnnexB;
+ if (!pc->sc()->strict()) {
+ ParseContext::Statement* stmt = pc->innermostStatement();
+ if (stmt && stmt->kind() == StatementKind::If) {
+ synthesizedStmtForAnnexB.emplace(pc, StatementKind::Block);
+ synthesizedScopeForAnnexB.emplace(this);
+ if (!synthesizedScopeForAnnexB->init(pc))
+ return null();
+ }
+ }
+
+ RootedPropertyName name(context);
+ GeneratorKind generatorKind = asyncKind == AsyncFunction ? StarGenerator : NotGenerator;
+ TokenKind tt;
+ if (!tokenStream.getToken(&tt))
+ return null();
+
+ if (tt == TOK_MUL) {
+ if (asyncKind != SyncFunction) {
+ report(ParseError, false, null(), JSMSG_ASYNC_GENERATOR);
+ return null();
+ }
+ generatorKind = StarGenerator;
+ if (!tokenStream.getToken(&tt))
+ return null();
+ }
+
+ if (tt == TOK_NAME || tt == TOK_YIELD) {
+ name = bindingIdentifier(yieldHandling);
+ if (!name)
+ return null();
+ } else if (defaultHandling == AllowDefaultName) {
+ name = context->names().starDefaultStar;
+ tokenStream.ungetToken();
+ } else {
+ /* Unnamed function expressions are forbidden in statement context. */
+ report(ParseError, false, null(), JSMSG_UNNAMED_FUNCTION_STMT);
+ return null();
+ }
+
+ YieldHandling newYieldHandling = GetYieldHandling(generatorKind, asyncKind);
+ Node fun = functionDefinition(InAllowed, newYieldHandling, name, Statement, generatorKind,
+ asyncKind, PredictUninvoked);
+ if (!fun)
+ return null();
+
+ if (synthesizedStmtForAnnexB) {
+ Node synthesizedStmtList = handler.newStatementList(handler.getPosition(fun));
+ if (!synthesizedStmtList)
+ return null();
+ handler.addStatementToList(synthesizedStmtList, fun);
+ return finishLexicalScope(*synthesizedScopeForAnnexB, synthesizedStmtList);
+ }
+
+ return fun;
+}
+
+template <typename ParseHandler>
+typename ParseHandler::Node
+Parser<ParseHandler>::functionExpr(InvokedPrediction invoked, FunctionAsyncKind asyncKind)
+{
+ MOZ_ASSERT(tokenStream.isCurrentTokenType(TOK_FUNCTION));
+
+ AutoAwaitIsKeyword awaitIsKeyword(&tokenStream, asyncKind == AsyncFunction);
+ GeneratorKind generatorKind = asyncKind == AsyncFunction ? StarGenerator : NotGenerator;
+ TokenKind tt;
+ if (!tokenStream.getToken(&tt))
+ return null();
+
+ if (tt == TOK_MUL) {
+ if (asyncKind != SyncFunction) {
+ report(ParseError, false, null(), JSMSG_ASYNC_GENERATOR);
+ return null();
+ }
+ generatorKind = StarGenerator;
+ if (!tokenStream.getToken(&tt))
+ return null();
+ }
+
+ YieldHandling yieldHandling = GetYieldHandling(generatorKind, asyncKind);
+
+ RootedPropertyName name(context);
+ if (tt == TOK_NAME || tt == TOK_YIELD) {
+ name = bindingIdentifier(yieldHandling);
+ if (!name)
+ return null();
+ } else {
+ tokenStream.ungetToken();
+ }
+
+ return functionDefinition(InAllowed, yieldHandling, name, Expression, generatorKind,
+ asyncKind, invoked);
+}
+
+/*
+ * Return true if this node, known to be an unparenthesized string literal,
+ * could be the string of a directive in a Directive Prologue. Directive
+ * strings never contain escape sequences or line continuations.
+ * isEscapeFreeStringLiteral, below, checks whether the node itself could be
+ * a directive.
+ */
+static inline bool
+IsEscapeFreeStringLiteral(const TokenPos& pos, JSAtom* str)
+{
+ /*
+ * If the string's length in the source code is its length as a value,
+ * accounting for the quotes, then it must not contain any escape
+ * sequences or line continuations.
+ */
+ return pos.begin + str->length() + 2 == pos.end;
+}
+
+template <typename ParseHandler>
+bool
+Parser<ParseHandler>::checkUnescapedName()
+{
+ if (!tokenStream.currentToken().nameContainsEscape())
+ return true;
+
+ report(ParseError, false, null(), JSMSG_ESCAPED_KEYWORD);
+ return false;
+}
+
+template <>
+bool
+Parser<SyntaxParseHandler>::asmJS(Node 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.
+ JS_ALWAYS_FALSE(abortIfSyntaxParser());
+ return false;
+}
+
+template <>
+bool
+Parser<FullParseHandler>::asmJS(Node list)
+{
+ // Disable syntax parsing in anything nested inside the asm.js module.
+ handler.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(context, *this, list, &validated))
+ return false;
+ if (!validated) {
+ pc->newDirectives->setAsmJS();
+ return false;
+ }
+
+ return true;
+}
+
+/*
+ * 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
+ * |pn_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 <typename ParseHandler>
+bool
+Parser<ParseHandler>::maybeParseDirective(Node list, Node pn, bool* cont)
+{
+ TokenPos directivePos;
+ JSAtom* directive = handler.isStringExprStatement(pn, &directivePos);
+
+ *cont = !!directive;
+ if (!*cont)
+ return true;
+
+ if (IsEscapeFreeStringLiteral(directivePos, directive)) {
+ // Mark this statement as being a possibly legitimate part of a
+ // directive prologue, so the bytecode emitter won't warn about it being
+ // useless code. (We mustn't just omit the statement entirely yet, as it
+ // could be producing the value of an eval or JSScript execution.)
+ //
+ // Note that even if the string isn't one we recognize as a directive,
+ // the emitter still shouldn't flag it as useless, as it could become a
+ // directive in the future. We don't want to interfere with people
+ // taking advantage of directive-prologue-enabled features that appear
+ // in other browsers first.
+ handler.setPrologue(pn);
+
+ if (directive == context->names().useStrict) {
+ // Functions with non-simple parameter lists (destructuring,
+ // default or rest parameters) must not contain a "use strict"
+ // directive.
+ if (pc->isFunctionBox()) {
+ FunctionBox* funbox = pc->functionBox();
+ if (!funbox->hasSimpleParameterList()) {
+ const char* parameterKind = funbox->hasDestructuringArgs
+ ? "destructuring"
+ : funbox->hasParameterExprs
+ ? "default"
+ : "rest";
+ reportWithOffset(ParseError, false, directivePos.begin,
+ JSMSG_STRICT_NON_SIMPLE_PARAMS, parameterKind);
+ return false;
+ }
+ }
+
+ // We're going to be in strict mode. Note that this scope explicitly
+ // had "use strict";
+ pc->sc()->setExplicitUseStrict();
+ if (!pc->sc()->strict()) {
+ // We keep track of the one possible strict violation that could
+ // occur in the directive prologue -- octal escapes -- and
+ // complain now.
+ if (tokenStream.sawOctalEscape()) {
+ report(ParseError, false, null(), JSMSG_DEPRECATED_OCTAL);
+ return false;
+ }
+ pc->sc()->strictScript = true;
+ }
+ } else if (directive == context->names().useAsm) {
+ if (pc->isFunctionBox())
+ return asmJS(list);
+ return report(ParseWarning, false, pn, JSMSG_USE_ASM_DIRECTIVE_FAIL);
+ }
+ }
+ return true;
+}
+
+template <typename ParseHandler>
+typename ParseHandler::Node
+Parser<ParseHandler>::statementList(YieldHandling yieldHandling)
+{
+ JS_CHECK_RECURSION(context, return null());
+
+ Node pn = handler.newStatementList(pos());
+ if (!pn)
+ return null();
+
+ bool canHaveDirectives = pc->atBodyLevel();
+ if (canHaveDirectives)
+ tokenStream.clearSawOctalEscape();
+ bool afterReturn = false;
+ bool warnedAboutStatementsAfterReturn = false;
+ uint32_t statementBegin = 0;
+ for (;;) {
+ TokenKind tt = TOK_EOF;
+ if (!tokenStream.peekToken(&tt, TokenStream::Operand)) {
+ if (tokenStream.isEOF())
+ isUnexpectedEOF_ = true;
+ return null();
+ }
+ if (tt == TOK_EOF || tt == TOK_RC)
+ break;
+ if (afterReturn) {
+ TokenPos pos(0, 0);
+ if (!tokenStream.peekTokenPos(&pos, TokenStream::Operand))
+ return null();
+ statementBegin = pos.begin;
+ }
+ Node next = statementListItem(yieldHandling, canHaveDirectives);
+ if (!next) {
+ if (tokenStream.isEOF())
+ isUnexpectedEOF_ = true;
+ return null();
+ }
+ if (!warnedAboutStatementsAfterReturn) {
+ if (afterReturn) {
+ if (!handler.isStatementPermittedAfterReturnStatement(next)) {
+ if (!reportWithOffset(ParseWarning, false, statementBegin,
+ JSMSG_STMT_AFTER_RETURN))
+ {
+ return null();
+ }
+ warnedAboutStatementsAfterReturn = true;
+ }
+ } else if (handler.isReturnStatement(next)) {
+ afterReturn = true;
+ }
+ }
+
+ if (canHaveDirectives) {
+ if (!maybeParseDirective(pn, next, &canHaveDirectives))
+ return null();
+ }
+
+ handler.addStatementToList(pn, next);
+ }
+
+ return pn;
+}
+
+template <typename ParseHandler>
+typename ParseHandler::Node
+Parser<ParseHandler>::condition(InHandling inHandling, YieldHandling yieldHandling)
+{
+ MUST_MATCH_TOKEN(TOK_LP, JSMSG_PAREN_BEFORE_COND);
+ Node pn = exprInParens(inHandling, yieldHandling, TripledotProhibited);
+ if (!pn)
+ return null();
+ MUST_MATCH_TOKEN(TOK_RP, JSMSG_PAREN_AFTER_COND);
+
+ /* Check for (a = b) and warn about possible (a == b) mistype. */
+ if (handler.isUnparenthesizedAssignment(pn)) {
+ if (!report(ParseExtraWarning, false, null(), JSMSG_EQUAL_AS_ASSIGN))
+ return null();
+ }
+ return pn;
+}
+
+template <typename ParseHandler>
+bool
+Parser<ParseHandler>::matchLabel(YieldHandling yieldHandling, MutableHandle<PropertyName*> label)
+{
+ TokenKind tt = TOK_EOF;
+ if (!tokenStream.peekTokenSameLine(&tt, TokenStream::Operand))
+ return false;
+
+ if (tt == TOK_NAME || tt == TOK_YIELD) {
+ tokenStream.consumeKnownToken(tt, TokenStream::Operand);
+
+ label.set(labelIdentifier(yieldHandling));
+ if (!label)
+ return false;
+ } else {
+ label.set(nullptr);
+ }
+ return true;
+}
+
+template <typename ParseHandler>
+Parser<ParseHandler>::PossibleError::PossibleError(Parser<ParseHandler>& parser)
+ : parser_(parser)
+{}
+
+template <typename ParseHandler>
+typename Parser<ParseHandler>::PossibleError::Error&
+Parser<ParseHandler>::PossibleError::error(ErrorKind kind)
+{
+ if (kind == ErrorKind::Expression)
+ return exprError_;
+ MOZ_ASSERT(kind == ErrorKind::Destructuring);
+ return destructuringError_;
+}
+
+template <typename ParseHandler>
+void
+Parser<ParseHandler>::PossibleError::setResolved(ErrorKind kind)
+{
+ error(kind).state_ = ErrorState::None;
+}
+
+template <typename ParseHandler>
+bool
+Parser<ParseHandler>::PossibleError::hasError(ErrorKind kind)
+{
+ return error(kind).state_ == ErrorState::Pending;
+}
+
+template <typename ParseHandler>
+void
+Parser<ParseHandler>::PossibleError::setPending(ErrorKind kind, Node pn, 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_ = (pn ? parser_.handler.getPosition(pn) : parser_.pos()).begin;
+ err.errorNumber_ = errorNumber;
+ err.state_ = ErrorState::Pending;
+}
+
+template <typename ParseHandler>
+void
+Parser<ParseHandler>::PossibleError::setPendingDestructuringError(Node pn, unsigned errorNumber)
+{
+ setPending(ErrorKind::Destructuring, pn, errorNumber);
+}
+
+template <typename ParseHandler>
+void
+Parser<ParseHandler>::PossibleError::setPendingExpressionError(Node pn, unsigned errorNumber)
+{
+ setPending(ErrorKind::Expression, pn, errorNumber);
+}
+
+template <typename ParseHandler>
+bool
+Parser<ParseHandler>::PossibleError::checkForError(ErrorKind kind)
+{
+ if (!hasError(kind))
+ return true;
+
+ Error& err = error(kind);
+ parser_.reportWithOffset(ParseError, false, err.offset_, err.errorNumber_);
+ return false;
+}
+
+template <typename ParseHandler>
+bool
+Parser<ParseHandler>::PossibleError::checkForDestructuringError()
+{
+ // 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 <typename ParseHandler>
+bool
+Parser<ParseHandler>::PossibleError::checkForExpressionError()
+{
+ // Clear pending destructuring error, because we're definitely not in a
+ // destructuring context.
+ setResolved(ErrorKind::Destructuring);
+
+ // Report any pending expression error.
+ return checkForError(ErrorKind::Expression);
+}
+
+template <typename ParseHandler>
+void
+Parser<ParseHandler>::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 <typename ParseHandler>
+void
+Parser<ParseHandler>::PossibleError::transferErrorsTo(PossibleError* other)
+{
+ MOZ_ASSERT(other);
+ MOZ_ASSERT(this != other);
+ MOZ_ASSERT(&parser_ == &other->parser_,
+ "Can't transfer fields to an instance which belongs to a different parser");
+
+ transferErrorTo(ErrorKind::Destructuring, other);
+ transferErrorTo(ErrorKind::Expression, other);
+}
+
+template <typename ParseHandler>
+bool
+Parser<ParseHandler>::checkAssignmentToCall(Node target, unsigned msg)
+{
+ MOZ_ASSERT(handler.isFunctionCall(target));
+
+ // 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 (or if the werror option has been set), and otherwise
+ // warn.
+ return report(ParseStrictError, pc->sc()->strict(), target, msg);
+}
+
+template <>
+bool
+Parser<FullParseHandler>::checkDestructuringName(ParseNode* expr, Maybe<DeclarationKind> maybeDecl)
+{
+ MOZ_ASSERT(!handler.isUnparenthesizedDestructuringPattern(expr));
+
+ // Parentheses are forbidden around destructuring *patterns* (but allowed
+ // around names). Use our nicer error message for parenthesized, nested
+ // patterns.
+ if (handler.isParenthesizedDestructuringPattern(expr)) {
+ report(ParseError, false, expr, JSMSG_BAD_DESTRUCT_PARENS);
+ return false;
+ }
+
+ // This expression might be in a variable-binding pattern where only plain,
+ // unparenthesized names are permitted.
+ if (maybeDecl) {
+ // Destructuring patterns in declarations must only contain
+ // unparenthesized names.
+ if (!handler.isUnparenthesizedName(expr)) {
+ report(ParseError, false, expr, JSMSG_NO_VARIABLE_NAME);
+ return false;
+ }
+
+ RootedPropertyName name(context, expr->name());
+ return noteDeclaredName(name, *maybeDecl, handler.getPosition(expr));
+ }
+
+ // Otherwise this is an expression in destructuring outside a declaration.
+ if (!reportIfNotValidSimpleAssignmentTarget(expr, KeyedDestructuringAssignment))
+ return false;
+
+ MOZ_ASSERT(!handler.isFunctionCall(expr),
+ "function calls shouldn't be considered valid targets in "
+ "destructuring patterns");
+
+ if (handler.isNameAnyParentheses(expr)) {
+ // The arguments/eval identifiers are simple in non-strict mode code.
+ // Warn to discourage their use nonetheless.
+ return reportIfArgumentsEvalTarget(expr);
+ }
+
+ // Nothing further to do for property accesses.
+ MOZ_ASSERT(handler.isPropertyAccess(expr));
+ return true;
+}
+
+template <>
+bool
+Parser<FullParseHandler>::checkDestructuringPattern(ParseNode* pattern,
+ Maybe<DeclarationKind> maybeDecl,
+ PossibleError* possibleError /* = nullptr */);
+
+template <>
+bool
+Parser<FullParseHandler>::checkDestructuringObject(ParseNode* objectPattern,
+ Maybe<DeclarationKind> maybeDecl)
+{
+ MOZ_ASSERT(objectPattern->isKind(PNK_OBJECT));
+
+ for (ParseNode* member = objectPattern->pn_head; member; member = member->pn_next) {
+ ParseNode* target;
+ if (member->isKind(PNK_MUTATEPROTO)) {
+ target = member->pn_kid;
+ } else {
+ MOZ_ASSERT(member->isKind(PNK_COLON) || member->isKind(PNK_SHORTHAND));
+ MOZ_ASSERT_IF(member->isKind(PNK_SHORTHAND),
+ member->pn_left->isKind(PNK_OBJECT_PROPERTY_NAME) &&
+ member->pn_right->isKind(PNK_NAME) &&
+ member->pn_left->pn_atom == member->pn_right->pn_atom);
+
+ target = member->pn_right;
+ }
+ if (handler.isUnparenthesizedAssignment(target))
+ target = target->pn_left;
+
+ if (handler.isUnparenthesizedDestructuringPattern(target)) {
+ if (!checkDestructuringPattern(target, maybeDecl))
+ return false;
+ } else {
+ if (!checkDestructuringName(target, maybeDecl))
+ return false;
+ }
+ }
+
+ return true;
+}
+
+template <>
+bool
+Parser<FullParseHandler>::checkDestructuringArray(ParseNode* arrayPattern,
+ Maybe<DeclarationKind> maybeDecl)
+{
+ MOZ_ASSERT(arrayPattern->isKind(PNK_ARRAY));
+
+ for (ParseNode* element = arrayPattern->pn_head; element; element = element->pn_next) {
+ if (element->isKind(PNK_ELISION))
+ continue;
+
+ ParseNode* target;
+ if (element->isKind(PNK_SPREAD)) {
+ if (element->pn_next) {
+ report(ParseError, false, element->pn_next, JSMSG_PARAMETER_AFTER_REST);
+ return false;
+ }
+ target = element->pn_kid;
+ } else if (handler.isUnparenthesizedAssignment(element)) {
+ target = element->pn_left;
+ } else {
+ target = element;
+ }
+
+ if (handler.isUnparenthesizedDestructuringPattern(target)) {
+ if (!checkDestructuringPattern(target, maybeDecl))
+ return false;
+ } else {
+ if (!checkDestructuringName(target, maybeDecl))
+ return false;
+ }
+ }
+
+ return true;
+}
+
+/*
+ * Destructuring patterns can appear in two kinds of contexts:
+ *
+ * - assignment-like: assignment expressions and |for| loop heads. In
+ * these cases, the patterns' property value positions can be
+ * arbitrary lvalue expressions; the destructuring is just a fancy
+ * assignment.
+ *
+ * - binding-like: |var| and |let| declarations, functions' formal
+ * parameter lists, |catch| clauses, and comprehension tails. In
+ * these cases, the patterns' property value positions must be
+ * simple names; the destructuring defines them as new variables.
+ *
+ * In both cases, other code parses the pattern as an arbitrary
+ * primaryExpr, and then, here in checkDestructuringPattern, verify
+ * that the tree is a valid AssignmentPattern or BindingPattern.
+ *
+ * In assignment-like contexts, we parse the pattern with
+ * pc->inDestructuringDecl clear, so the lvalue expressions in the
+ * pattern are parsed normally. primaryExpr links variable references
+ * into the appropriate use chains; creates placeholder definitions;
+ * and so on. checkDestructuringPattern won't bind any new names and
+ * we specialize lvalues as appropriate.
+ *
+ * In declaration-like contexts, the normal variable reference
+ * processing would just be an obstruction, because we're going to
+ * define the names that appear in the property value positions as new
+ * variables anyway. In this case, we parse the pattern with
+ * pc->inDestructuringDecl set, which directs primaryExpr to leave
+ * whatever name nodes it creates unconnected. Then, here in
+ * checkDestructuringPattern, we require the pattern's property value
+ * positions to be simple names, and define them as appropriate to the
+ * context.
+ */
+template <>
+bool
+Parser<FullParseHandler>::checkDestructuringPattern(ParseNode* pattern,
+ Maybe<DeclarationKind> maybeDecl,
+ PossibleError* possibleError /* = nullptr */)
+{
+ if (pattern->isKind(PNK_ARRAYCOMP)) {
+ report(ParseError, false, pattern, JSMSG_ARRAY_COMP_LEFTSIDE);
+ return false;
+ }
+
+ bool isDestructuring = pattern->isKind(PNK_ARRAY)
+ ? checkDestructuringArray(pattern, maybeDecl)
+ : checkDestructuringObject(pattern, maybeDecl);
+
+ // Report any pending destructuring error.
+ if (isDestructuring && possibleError && !possibleError->checkForDestructuringError())
+ return false;
+
+ return isDestructuring;
+}
+
+template <>
+bool
+Parser<SyntaxParseHandler>::checkDestructuringPattern(Node pattern,
+ Maybe<DeclarationKind> maybeDecl,
+ PossibleError* possibleError /* = nullptr */)
+{
+ return abortIfSyntaxParser();
+}
+
+template <typename ParseHandler>
+typename ParseHandler::Node
+Parser<ParseHandler>::destructuringDeclaration(DeclarationKind kind, YieldHandling yieldHandling,
+ TokenKind tt)
+{
+ MOZ_ASSERT(tokenStream.isCurrentTokenType(tt));
+ MOZ_ASSERT(tt == TOK_LB || tt == TOK_LC);
+
+ PossibleError possibleError(*this);
+ Node pattern;
+ {
+ pc->inDestructuringDecl = Some(kind);
+ pattern = primaryExpr(yieldHandling, TripledotProhibited, tt, &possibleError);
+ pc->inDestructuringDecl = Nothing();
+ }
+
+ if (!pattern || !checkDestructuringPattern(pattern, Some(kind), &possibleError))
+ return null();
+
+ return pattern;
+}
+
+template <typename ParseHandler>
+typename ParseHandler::Node
+Parser<ParseHandler>::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) {
+ reportWithOffset(ParseError, false, pc->lastYieldOffset, JSMSG_YIELD_IN_DEFAULT);
+ return null();
+ }
+ if (pc->lastAwaitOffset != startAwaitOffset) {
+ reportWithOffset(ParseError, false, pc->lastAwaitOffset, JSMSG_AWAIT_IN_DEFAULT);
+ return null();
+ }
+ }
+ return res;
+}
+
+template <typename ParseHandler>
+typename ParseHandler::Node
+Parser<ParseHandler>::blockStatement(YieldHandling yieldHandling, unsigned errorNumber)
+{
+ MOZ_ASSERT(tokenStream.isCurrentTokenType(TOK_LC));
+
+ ParseContext::Statement stmt(pc, StatementKind::Block);
+ ParseContext::Scope scope(this);
+ if (!scope.init(pc))
+ return null();
+
+ Node list = statementList(yieldHandling);
+ if (!list)
+ return null();
+
+ MUST_MATCH_TOKEN_MOD(TOK_RC, TokenStream::Operand, errorNumber);
+
+ return finishLexicalScope(scope, list);
+}
+
+template <typename ParseHandler>
+typename ParseHandler::Node
+Parser<ParseHandler>::expressionAfterForInOrOf(ParseNodeKind forHeadKind,
+ YieldHandling yieldHandling)
+{
+ MOZ_ASSERT(forHeadKind == PNK_FORIN || forHeadKind == PNK_FOROF);
+ Node pn = forHeadKind == PNK_FOROF
+ ? assignExpr(InAllowed, yieldHandling, TripledotProhibited)
+ : expr(InAllowed, yieldHandling, TripledotProhibited);
+ return pn;
+}
+
+template <typename ParseHandler>
+typename ParseHandler::Node
+Parser<ParseHandler>::declarationPattern(Node decl, DeclarationKind declKind, TokenKind tt,
+ bool initialDeclaration, YieldHandling yieldHandling,
+ ParseNodeKind* forHeadKind, Node* forInOrOfExpression)
+{
+ MOZ_ASSERT(tokenStream.isCurrentTokenType(TOK_LB) ||
+ tokenStream.isCurrentTokenType(TOK_LC));
+
+ 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 = PNK_FORIN;
+ } else if (isForOf) {
+ *forHeadKind = PNK_FOROF;
+
+ // Annex B.3.5 has different early errors for vars in for-of loops.
+ if (declKind == DeclarationKind::Var)
+ declKind = DeclarationKind::ForOfVar;
+ } else {
+ *forHeadKind = PNK_FORHEAD;
+ }
+
+ if (*forHeadKind != PNK_FORHEAD) {
+ *forInOrOfExpression = expressionAfterForInOrOf(*forHeadKind, yieldHandling);
+ if (!*forInOrOfExpression)
+ return null();
+
+ return pattern;
+ }
+ }
+
+ TokenKind token;
+ if (!tokenStream.getToken(&token, TokenStream::None))
+ return null();
+
+ if (token != TOK_ASSIGN) {
+ report(ParseError, false, null(), JSMSG_BAD_DESTRUCT_DECL);
+ return null();
+ }
+
+ Node init = assignExpr(forHeadKind ? InProhibited : InAllowed,
+ yieldHandling, TripledotProhibited);
+ if (!init)
+ return null();
+
+ if (forHeadKind) {
+ // For for(;;) declarations, consistency with |for (;| parsing requires
+ // that the ';' first be examined as Operand, even though absence of a
+ // binary operator (examined with modifier None) terminated |init|.
+ // For all other declarations, through ASI's infinite majesty, a next
+ // token on a new line would begin an expression.
+ tokenStream.addModifierException(TokenStream::OperandIsNone);
+ }
+
+ return handler.newBinary(PNK_ASSIGN, pattern, init);
+}
+
+template <typename ParseHandler>
+bool
+Parser<ParseHandler>::initializerInNameDeclaration(Node decl, Node binding,
+ Handle<PropertyName*> name,
+ DeclarationKind declKind,
+ bool initialDeclaration,
+ YieldHandling yieldHandling,
+ ParseNodeKind* forHeadKind,
+ Node* forInOrOfExpression)
+{
+ MOZ_ASSERT(tokenStream.isCurrentTokenType(TOK_ASSIGN));
+
+ Node initializer = assignExpr(forHeadKind ? InProhibited : InAllowed,
+ yieldHandling, TripledotProhibited);
+ if (!initializer)
+ return false;
+
+ if (forHeadKind) {
+ if (initialDeclaration) {
+ bool isForIn, isForOf;
+ if (!matchInOrOf(&isForIn, &isForOf))
+ return false;
+
+ // An initialized declaration can't appear in a for-of:
+ //
+ // for (var/let/const x = ... of ...); // BAD
+ if (isForOf) {
+ report(ParseError, false, binding, JSMSG_BAD_FOR_LEFTSIDE);
+ return false;
+ }
+
+ if (isForIn) {
+ // Lexical declarations in for-in loops can't be initialized:
+ //
+ // for (let/const x = ... in ...); // BAD
+ if (DeclarationKindIsLexical(declKind)) {
+ report(ParseError, false, binding, JSMSG_BAD_FOR_LEFTSIDE);
+ return false;
+ }
+
+ // This leaves only initialized for-in |var| declarations. ES6
+ // forbids these; later ES un-forbids in non-strict mode code.
+ *forHeadKind = PNK_FORIN;
+ if (!report(ParseStrictError, pc->sc()->strict(), initializer,
+ JSMSG_INVALID_FOR_IN_DECL_WITH_INIT))
+ {
+ return false;
+ }
+
+ *forInOrOfExpression = expressionAfterForInOrOf(PNK_FORIN, yieldHandling);
+ if (!*forInOrOfExpression)
+ return false;
+ } else {
+ *forHeadKind = PNK_FORHEAD;
+ }
+ } else {
+ MOZ_ASSERT(*forHeadKind == PNK_FORHEAD);
+
+ // In the very rare case of Parser::assignExpr consuming an
+ // ArrowFunction with block body, when full-parsing with the arrow
+ // function being a skipped lazy inner function, we don't have
+ // lookahead for the next token. Do a one-off peek here to be
+ // consistent with what Parser::matchForInOrOf does in the other
+ // arm of this |if|.
+ //
+ // If you think this all sounds pretty code-smelly, you're almost
+ // certainly correct.
+ TokenKind ignored;
+ if (!tokenStream.peekToken(&ignored))
+ return false;
+ }
+
+ if (*forHeadKind == PNK_FORHEAD) {
+ // Per Parser::forHeadStart, the semicolon in |for (;| is
+ // ultimately gotten as Operand. But initializer expressions
+ // terminate with the absence of an operator gotten as None,
+ // so we need an exception.
+ tokenStream.addModifierException(TokenStream::OperandIsNone);
+ }
+ }
+
+ return handler.finishInitializerAssignment(binding, initializer);
+}
+
+template <typename ParseHandler>
+typename ParseHandler::Node
+Parser<ParseHandler>::declarationName(Node decl, DeclarationKind declKind, TokenKind tt,
+ bool initialDeclaration, YieldHandling yieldHandling,
+ ParseNodeKind* forHeadKind, Node* forInOrOfExpression)
+{
+ // Anything other than TOK_YIELD or TOK_NAME is an error.
+ if (tt != TOK_NAME && tt != TOK_YIELD) {
+ report(ParseError, false, null(), JSMSG_NO_VARIABLE_NAME);
+ return null();
+ }
+
+ RootedPropertyName name(context, bindingIdentifier(yieldHandling));
+ if (!name)
+ return null();
+
+ Node 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 as Operand.
+ bool matched;
+ if (!tokenStream.matchToken(&matched, TOK_ASSIGN, TokenStream::Operand))
+ return null();
+
+ if (matched) {
+ if (!initializerInNameDeclaration(decl, binding, name, declKind, initialDeclaration,
+ yieldHandling, forHeadKind, forInOrOfExpression))
+ {
+ return null();
+ }
+ } else {
+ tokenStream.addModifierException(TokenStream::NoneIsOperand);
+
+ if (initialDeclaration && forHeadKind) {
+ bool isForIn, isForOf;
+ if (!matchInOrOf(&isForIn, &isForOf))
+ return null();
+
+ if (isForIn) {
+ *forHeadKind = PNK_FORIN;
+ } else if (isForOf) {
+ *forHeadKind = PNK_FOROF;
+
+ // Annex B.3.5 has different early errors for vars in for-of loops.
+ if (declKind == DeclarationKind::Var)
+ declKind = DeclarationKind::ForOfVar;
+ } else {
+ *forHeadKind = PNK_FORHEAD;
+ }
+ }
+
+ if (forHeadKind && *forHeadKind != PNK_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) {
+ report(ParseError, false, binding, 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 binding;
+}
+
+template <typename ParseHandler>
+typename ParseHandler::Node
+Parser<ParseHandler>::declarationList(YieldHandling yieldHandling,
+ ParseNodeKind kind,
+ ParseNodeKind* forHeadKind /* = nullptr */,
+ Node* forInOrOfExpression /* = nullptr */)
+{
+ MOZ_ASSERT(kind == PNK_VAR || kind == PNK_LET || kind == PNK_CONST);
+
+ JSOp op;
+ DeclarationKind declKind;
+ switch (kind) {
+ case PNK_VAR:
+ op = JSOP_DEFVAR;
+ declKind = DeclarationKind::Var;
+ break;
+ case PNK_CONST:
+ op = JSOP_DEFCONST;
+ declKind = DeclarationKind::Const;
+ break;
+ case PNK_LET:
+ op = JSOP_DEFLET;
+ declKind = DeclarationKind::Let;
+ break;
+ default:
+ MOZ_CRASH("Unknown declaration kind");
+ }
+
+ Node decl = handler.newDeclarationList(kind, op);
+ if (!decl)
+ return null();
+
+ bool matched;
+ bool initialDeclaration = true;
+ do {
+ MOZ_ASSERT_IF(!initialDeclaration && forHeadKind,
+ *forHeadKind == PNK_FORHEAD);
+
+ TokenKind tt;
+ if (!tokenStream.getToken(&tt))
+ return null();
+
+ Node binding = (tt == TOK_LB || tt == TOK_LC)
+ ? declarationPattern(decl, declKind, tt, initialDeclaration, yieldHandling,
+ forHeadKind, forInOrOfExpression)
+ : declarationName(decl, declKind, tt, initialDeclaration, yieldHandling,
+ forHeadKind, forInOrOfExpression);
+ if (!binding)
+ return null();
+
+ handler.addList(decl, binding);
+
+ if (forHeadKind && *forHeadKind != PNK_FORHEAD)
+ break;
+
+ initialDeclaration = false;
+
+ if (!tokenStream.matchToken(&matched, TOK_COMMA))
+ return null();
+ } while (matched);
+
+ return decl;
+}
+
+template <typename ParseHandler>
+typename ParseHandler::Node
+Parser<ParseHandler>::lexicalDeclaration(YieldHandling yieldHandling, bool isConst)
+{
+ /*
+ * 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.
+ */
+ Node decl = declarationList(yieldHandling, isConst ? PNK_CONST : PNK_LET);
+ if (!decl || !MatchOrInsertSemicolonAfterExpression(tokenStream))
+ return null();
+
+ return decl;
+}
+
+template <>
+bool
+Parser<FullParseHandler>::namedImportsOrNamespaceImport(TokenKind tt, Node importSpecSet)
+{
+ if (tt == TOK_LC) {
+ TokenStream::Modifier modifier = TokenStream::KeywordIsName;
+ while (true) {
+ // Handle the forms |import {} from 'a'| and
+ // |import { ..., } from 'a'| (where ... is non empty), by
+ // escaping the loop early if the next token is }.
+ if (!tokenStream.peekToken(&tt, TokenStream::KeywordIsName))
+ return false;
+
+ if (tt == TOK_RC)
+ break;
+
+ // If the next token is a keyword, the previous call to
+ // peekToken matched it as a TOK_NAME, and put it in the
+ // lookahead buffer, so this call will match keywords as well.
+ MUST_MATCH_TOKEN_MOD(TOK_NAME, TokenStream::KeywordIsName, JSMSG_NO_IMPORT_NAME);
+ Rooted<PropertyName*> importName(context, tokenStream.currentName());
+ TokenPos importNamePos = pos();
+
+ TokenKind maybeAs;
+ if (!tokenStream.peekToken(&maybeAs))
+ return null();
+
+ if (maybeAs == TOK_NAME &&
+ tokenStream.nextName() == context->names().as)
+ {
+ tokenStream.consumeKnownToken(TOK_NAME);
+
+ if (!checkUnescapedName())
+ return false;
+
+ TokenKind afterAs;
+ if (!tokenStream.getToken(&afterAs))
+ return false;
+
+ if (afterAs != TOK_NAME && afterAs != TOK_YIELD) {
+ report(ParseError, false, null(), JSMSG_NO_BINDING_NAME);
+ return false;
+ }
+ } else {
+ // Keywords cannot be bound to themselves, so an import name
+ // that is a keyword is a syntax error if it is not followed
+ // by the keyword 'as'.
+ // See the ImportSpecifier production in ES6 section 15.2.2.
+ if (IsKeyword(importName)) {
+ JSAutoByteString bytes;
+ if (!AtomToPrintableString(context, importName, &bytes))
+ return false;
+ report(ParseError, false, null(), JSMSG_AS_AFTER_RESERVED_WORD, bytes.ptr());
+ return false;
+ }
+ }
+
+ RootedPropertyName bindingAtom(context, importedBinding());
+ if (!bindingAtom)
+ return false;
+
+ Node bindingName = newName(bindingAtom);
+ if (!bindingName)
+ return false;
+ if (!noteDeclaredName(bindingAtom, DeclarationKind::Import, pos()))
+ return false;
+
+ Node importNameNode = newName(importName, importNamePos);
+ if (!importNameNode)
+ return false;
+
+ Node importSpec = handler.newBinary(PNK_IMPORT_SPEC, importNameNode, bindingName);
+ if (!importSpec)
+ return false;
+
+ handler.addList(importSpecSet, importSpec);
+
+ bool matched;
+ if (!tokenStream.matchToken(&matched, TOK_COMMA))
+ return false;
+
+ if (!matched) {
+ modifier = TokenStream::None;
+ break;
+ }
+ }
+
+ MUST_MATCH_TOKEN_MOD(TOK_RC, modifier, JSMSG_RC_AFTER_IMPORT_SPEC_LIST);
+ } else {
+ MOZ_ASSERT(tt == TOK_MUL);
+ if (!tokenStream.getToken(&tt))
+ return false;
+
+ if (tt != TOK_NAME || tokenStream.currentName() != context->names().as) {
+ report(ParseError, false, null(), JSMSG_AS_AFTER_IMPORT_STAR);
+ return false;
+ }
+
+ if (!checkUnescapedName())
+ return false;
+
+ MUST_MATCH_TOKEN(TOK_NAME, JSMSG_NO_BINDING_NAME);
+
+ Node importName = newName(context->names().star);
+ if (!importName)
+ return false;
+
+ // Namespace imports are are not indirect bindings but lexical
+ // definitions that hold a module namespace object. They are treated
+ // as const variables which are initialized during the
+ // ModuleDeclarationInstantiation step.
+ RootedPropertyName bindingName(context, importedBinding());
+ if (!bindingName)
+ return false;
+ Node 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();
+
+ Node importSpec = handler.newBinary(PNK_IMPORT_SPEC, importName, bindingNameNode);
+ if (!importSpec)
+ return false;
+
+ handler.addList(importSpecSet, importSpec);
+ }
+
+ return true;
+}
+
+template<>
+bool
+Parser<SyntaxParseHandler>::namedImportsOrNamespaceImport(TokenKind tt, Node importSpecSet)
+{
+ MOZ_ALWAYS_FALSE(abortIfSyntaxParser());
+ return false;
+}
+
+template<>
+ParseNode*
+Parser<FullParseHandler>::importDeclaration()
+{
+ MOZ_ASSERT(tokenStream.currentToken().type == TOK_IMPORT);
+
+ if (!pc->atModuleLevel()) {
+ report(ParseError, false, null(), JSMSG_IMPORT_DECL_AT_TOP_LEVEL);
+ return null();
+ }
+
+ uint32_t begin = pos().begin;
+ TokenKind tt;
+ if (!tokenStream.getToken(&tt))
+ return null();
+
+ Node importSpecSet = handler.newList(PNK_IMPORT_SPEC_LIST);
+ if (!importSpecSet)
+ return null();
+
+ if (tt == TOK_NAME || tt == TOK_LC || tt == TOK_MUL) {
+ if (tt == TOK_NAME) {
+ // 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'|.
+ Node importName = newName(context->names().default_);
+ if (!importName)
+ return null();
+
+ RootedPropertyName bindingAtom(context, importedBinding());
+ if (!bindingAtom)
+ return null();
+
+ Node bindingName = newName(bindingAtom);
+ if (!bindingName)
+ return null();
+
+ if (!noteDeclaredName(bindingAtom, DeclarationKind::Import, pos()))
+ return null();
+
+ Node importSpec = handler.newBinary(PNK_IMPORT_SPEC, importName, bindingName);
+ if (!importSpec)
+ return null();
+
+ handler.addList(importSpecSet, importSpec);
+
+ if (!tokenStream.peekToken(&tt))
+ return null();
+
+ if (tt == TOK_COMMA) {
+ tokenStream.consumeKnownToken(tt);
+ if (!tokenStream.getToken(&tt))
+ return null();
+
+ if (tt != TOK_LC && tt != TOK_MUL) {
+ report(ParseError, false, null(), JSMSG_NAMED_IMPORTS_OR_NAMESPACE_IMPORT);
+ return null();
+ }
+
+ if (!namedImportsOrNamespaceImport(tt, importSpecSet))
+ return null();
+ }
+ } else {
+ if (!namedImportsOrNamespaceImport(tt, importSpecSet))
+ return null();
+ }
+
+ if (!tokenStream.getToken(&tt))
+ return null();
+
+ if (tt != TOK_NAME || tokenStream.currentName() != context->names().from) {
+ report(ParseError, false, null(), JSMSG_FROM_AFTER_IMPORT_CLAUSE);
+ return null();
+ }
+
+ if (!checkUnescapedName())
+ return null();
+
+ MUST_MATCH_TOKEN(TOK_STRING, JSMSG_MODULE_SPEC_AFTER_FROM);
+ } else if (tt == TOK_STRING) {
+ // Handle the form |import 'a'| by leaving the list empty. This is
+ // equivalent to |import {} from 'a'|.
+ importSpecSet->pn_pos.end = importSpecSet->pn_pos.begin;
+ } else {
+ report(ParseError, false, null(), JSMSG_DECLARATION_AFTER_IMPORT);
+ return null();
+ }
+
+ Node moduleSpec = stringLiteral();
+ if (!moduleSpec)
+ return null();
+
+ if (!MatchOrInsertSemicolonAfterNonExpression(tokenStream))
+ return null();
+
+ ParseNode* node =
+ handler.newImportDeclaration(importSpecSet, moduleSpec, TokenPos(begin, pos().end));
+ if (!node || !pc->sc()->asModuleContext()->builder.processImport(node))
+ return null();
+
+ return node;
+}
+
+template<>
+SyntaxParseHandler::Node
+Parser<SyntaxParseHandler>::importDeclaration()
+{
+ JS_ALWAYS_FALSE(abortIfSyntaxParser());
+ return SyntaxParseHandler::NodeFailure;
+}
+
+template<>
+bool
+Parser<FullParseHandler>::checkExportedName(JSAtom* exportName)
+{
+ if (!pc->sc()->asModuleContext()->builder.hasExportedName(exportName))
+ return true;
+
+ JSAutoByteString str;
+ if (!AtomToPrintableString(context, exportName, &str))
+ return false;
+
+ report(ParseError, false, null(), JSMSG_DUPLICATE_EXPORT_NAME, str.ptr());
+ return false;
+}
+
+template<>
+bool
+Parser<SyntaxParseHandler>::checkExportedName(JSAtom* exportName)
+{
+ MOZ_ALWAYS_FALSE(abortIfSyntaxParser());
+ return false;
+}
+
+template<>
+bool
+Parser<FullParseHandler>::checkExportedNamesForDeclaration(ParseNode* node)
+{
+ MOZ_ASSERT(node->isArity(PN_LIST));
+ for (ParseNode* binding = node->pn_head; binding; binding = binding->pn_next) {
+ if (binding->isKind(PNK_ASSIGN))
+ binding = binding->pn_left;
+ MOZ_ASSERT(binding->isKind(PNK_NAME));
+ if (!checkExportedName(binding->pn_atom))
+ return false;
+ }
+
+ return true;
+}
+
+template<>
+bool
+Parser<SyntaxParseHandler>::checkExportedNamesForDeclaration(Node node)
+{
+ MOZ_ALWAYS_FALSE(abortIfSyntaxParser());
+ return false;
+}
+
+template<>
+ParseNode*
+Parser<FullParseHandler>::exportDeclaration()
+{
+ MOZ_ASSERT(tokenStream.currentToken().type == TOK_EXPORT);
+
+ if (!pc->atModuleLevel()) {
+ report(ParseError, false, null(), JSMSG_EXPORT_DECL_AT_TOP_LEVEL);
+ return null();
+ }
+
+ uint32_t begin = pos().begin;
+
+ Node kid;
+ TokenKind tt;
+ if (!tokenStream.getToken(&tt))
+ return null();
+ switch (tt) {
+ case TOK_LC: {
+ kid = handler.newList(PNK_EXPORT_SPEC_LIST);
+ if (!kid)
+ return null();
+
+ while (true) {
+ // Handle the forms |export {}| and |export { ..., }| (where ...
+ // is non empty), by escaping the loop early if the next token
+ // is }.
+ if (!tokenStream.peekToken(&tt))
+ return null();
+ if (tt == TOK_RC)
+ break;
+
+ MUST_MATCH_TOKEN(TOK_NAME, JSMSG_NO_BINDING_NAME);
+ Node bindingName = newName(tokenStream.currentName());
+ if (!bindingName)
+ return null();
+
+ bool foundAs;
+ if (!tokenStream.matchContextualKeyword(&foundAs, context->names().as))
+ return null();
+ if (foundAs) {
+ if (!tokenStream.getToken(&tt, TokenStream::KeywordIsName))
+ return null();
+ if (tt != TOK_NAME) {
+ report(ParseError, false, null(), JSMSG_NO_EXPORT_NAME);
+ return null();
+ }
+ }
+
+ Node exportName = newName(tokenStream.currentName());
+ if (!exportName)
+ return null();
+
+ if (!checkExportedName(exportName->pn_atom))
+ return null();
+
+ Node exportSpec = handler.newBinary(PNK_EXPORT_SPEC, bindingName, exportName);
+ if (!exportSpec)
+ return null();
+
+ handler.addList(kid, exportSpec);
+
+ bool matched;
+ if (!tokenStream.matchToken(&matched, TOK_COMMA))
+ return null();
+ if (!matched)
+ break;
+ }
+
+ MUST_MATCH_TOKEN(TOK_RC, JSMSG_RC_AFTER_EXPORT_SPEC_LIST);
+
+ // 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 Operand
+ // context, so simply matching a contextual keyword won't work:
+ //
+ // export { x } // ExportDeclaration, terminated by ASI
+ // fro\u006D // ExpressionStatement, the name "from"
+ //
+ // In that case let MatchOrInsertSemicolonAfterNonExpression sort out
+ // ASI or any necessary error.
+ TokenKind tt;
+ if (!tokenStream.getToken(&tt, TokenStream::Operand))
+ return null();
+
+ if (tt == TOK_NAME &&
+ tokenStream.currentToken().name() == context->names().from &&
+ !tokenStream.currentToken().nameContainsEscape())
+ {
+ MUST_MATCH_TOKEN(TOK_STRING, JSMSG_MODULE_SPEC_AFTER_FROM);
+
+ Node moduleSpec = stringLiteral();
+ if (!moduleSpec)
+ return null();
+
+ if (!MatchOrInsertSemicolonAfterNonExpression(tokenStream))
+ return null();
+
+ ParseNode* node = handler.newExportFromDeclaration(begin, kid, moduleSpec);
+ if (!node || !pc->sc()->asModuleContext()->builder.processExportFrom(node))
+ return null();
+
+ return node;
+ }
+
+ tokenStream.ungetToken();
+
+ if (!MatchOrInsertSemicolonAfterNonExpression(tokenStream))
+ return null();
+ break;
+ }
+
+ case TOK_MUL: {
+ kid = handler.newList(PNK_EXPORT_SPEC_LIST);
+ if (!kid)
+ return null();
+
+ // Handle the form |export *| by adding a special export batch
+ // specifier to the list.
+ Node exportSpec = handler.newNullary(PNK_EXPORT_BATCH_SPEC, JSOP_NOP, pos());
+ if (!exportSpec)
+ return null();
+
+ handler.addList(kid, exportSpec);
+
+ if (!tokenStream.getToken(&tt))
+ return null();
+ if (tt != TOK_NAME || tokenStream.currentName() != context->names().from) {
+ report(ParseError, false, null(), JSMSG_FROM_AFTER_EXPORT_STAR);
+ return null();
+ }
+
+ if (!checkUnescapedName())
+ return null();
+
+ MUST_MATCH_TOKEN(TOK_STRING, JSMSG_MODULE_SPEC_AFTER_FROM);
+
+ Node moduleSpec = stringLiteral();
+ if (!moduleSpec)
+ return null();
+
+ if (!MatchOrInsertSemicolonAfterNonExpression(tokenStream))
+ return null();
+
+ ParseNode* node = handler.newExportFromDeclaration(begin, kid, moduleSpec);
+ if (!node || !pc->sc()->asModuleContext()->builder.processExportFrom(node))
+ return null();
+
+ return node;
+
+ }
+
+ case TOK_FUNCTION:
+ kid = functionStmt(YieldIsKeyword, NameRequired);
+ if (!kid)
+ return null();
+
+ if (!checkExportedName(kid->pn_funbox->function()->name()))
+ return null();
+ break;
+
+ case TOK_CLASS: {
+ kid = classDefinition(YieldIsKeyword, ClassStatement, NameRequired);
+ if (!kid)
+ return null();
+
+ const ClassNode& cls = kid->as<ClassNode>();
+ MOZ_ASSERT(cls.names());
+ if (!checkExportedName(cls.names()->innerBinding()->pn_atom))
+ return null();
+ break;
+ }
+
+ case TOK_VAR:
+ kid = declarationList(YieldIsName, PNK_VAR);
+ if (!kid)
+ return null();
+ if (!MatchOrInsertSemicolonAfterExpression(tokenStream))
+ return null();
+ if (!checkExportedNamesForDeclaration(kid))
+ return null();
+ break;
+
+ case TOK_DEFAULT: {
+ if (!tokenStream.getToken(&tt, TokenStream::Operand))
+ return null();
+
+ if (!checkExportedName(context->names().default_))
+ return null();
+
+ ParseNode* nameNode = nullptr;
+ switch (tt) {
+ case TOK_FUNCTION:
+ kid = functionStmt(YieldIsKeyword, AllowDefaultName);
+ if (!kid)
+ return null();
+ break;
+ case TOK_CLASS:
+ kid = classDefinition(YieldIsKeyword, ClassStatement, AllowDefaultName);
+ if (!kid)
+ return null();
+ break;
+ default: {
+ if (tt == TOK_NAME && tokenStream.currentName() == context->names().async) {
+ TokenKind nextSameLine = TOK_EOF;
+ if (!tokenStream.peekTokenSameLine(&nextSameLine))
+ return null();
+
+ if (nextSameLine == TOK_FUNCTION) {
+ tokenStream.consumeKnownToken(nextSameLine);
+ kid = functionStmt(YieldIsName, AllowDefaultName, AsyncFunction);
+ if (!kid)
+ return null();
+ break;
+ }
+ }
+
+ tokenStream.ungetToken();
+ RootedPropertyName name(context, context->names().starDefaultStar);
+ nameNode = newName(name);
+ if (!nameNode)
+ return null();
+ if (!noteDeclaredName(name, DeclarationKind::Const, pos()))
+ return null();
+ kid = assignExpr(InAllowed, YieldIsKeyword, TripledotProhibited);
+ if (!kid)
+ return null();
+ if (!MatchOrInsertSemicolonAfterExpression(tokenStream))
+ return null();
+ break;
+ }
+ }
+
+ ParseNode* node = handler.newExportDefaultDeclaration(kid, nameNode,
+ TokenPos(begin, pos().end));
+ if (!node || !pc->sc()->asModuleContext()->builder.processExport(node))
+ return null();
+
+ return node;
+ }
+
+ case TOK_CONST:
+ kid = lexicalDeclaration(YieldIsName, /* isConst = */ true);
+ if (!kid)
+ return null();
+ if (!checkExportedNamesForDeclaration(kid))
+ return null();
+ break;
+
+ case TOK_NAME:
+ if (tokenStream.currentName() == context->names().let) {
+ if (!checkUnescapedName())
+ return null();
+
+ kid = lexicalDeclaration(YieldIsName, /* isConst = */ false);
+ if (!kid)
+ return null();
+ if (!checkExportedNamesForDeclaration(kid))
+ return null();
+ break;
+ }
+ MOZ_FALLTHROUGH;
+
+ default:
+ report(ParseError, false, null(), JSMSG_DECLARATION_AFTER_EXPORT);
+ return null();
+ }
+
+ ParseNode* node = handler.newExportDeclaration(kid, TokenPos(begin, pos().end));
+ if (!node || !pc->sc()->asModuleContext()->builder.processExport(node))
+ return null();
+
+ return node;
+}
+
+template<>
+SyntaxParseHandler::Node
+Parser<SyntaxParseHandler>::exportDeclaration()
+{
+ JS_ALWAYS_FALSE(abortIfSyntaxParser());
+ return SyntaxParseHandler::NodeFailure;
+}
+
+template <typename ParseHandler>
+typename ParseHandler::Node
+Parser<ParseHandler>::expressionStatement(YieldHandling yieldHandling, InvokedPrediction invoked)
+{
+ tokenStream.ungetToken();
+ Node pnexpr = expr(InAllowed, yieldHandling, TripledotProhibited,
+ /* possibleError = */ nullptr, invoked);
+ if (!pnexpr)
+ return null();
+ if (!MatchOrInsertSemicolonAfterExpression(tokenStream))
+ return null();
+ return handler.newExprStatement(pnexpr, pos().end);
+}
+
+template <class ParseHandler>
+typename ParseHandler::Node
+Parser<ParseHandler>::consequentOrAlternative(YieldHandling yieldHandling)
+{
+ TokenKind next;
+ if (!tokenStream.peekToken(&next, TokenStream::Operand))
+ return null();
+
+ if (next == TOK_FUNCTION) {
+ // Apply Annex B.3.4 in non-strict code to allow FunctionDeclaration as
+ // the consequent/alternative of an |if| or |else|. Parser::statement
+ // will report the strict mode error.
+ if (!pc->sc()->strict()) {
+ tokenStream.consumeKnownToken(next, TokenStream::Operand);
+ return functionStmt(yieldHandling, NameRequired);
+ }
+ }
+
+ return statement(yieldHandling);
+}
+
+template <typename ParseHandler>
+typename ParseHandler::Node
+Parser<ParseHandler>::ifStatement(YieldHandling yieldHandling)
+{
+ Vector<Node, 4> condList(context), thenList(context);
+ Vector<uint32_t, 4> posList(context);
+ 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::Operand))
+ return null();
+ if (tt == TOK_SEMI) {
+ if (!report(ParseExtraWarning, false, null(), JSMSG_EMPTY_CONSEQUENT))
+ 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, TOK_ELSE, TokenStream::Operand))
+ return null();
+ if (matched) {
+ if (!tokenStream.matchToken(&matched, TOK_IF, TokenStream::Operand))
+ return null();
+ if (matched)
+ continue;
+ elseBranch = consequentOrAlternative(yieldHandling);
+ if (!elseBranch)
+ return null();
+ } else {
+ elseBranch = null();
+ }
+ break;
+ }
+
+ for (int i = condList.length() - 1; i >= 0; i--) {
+ elseBranch = handler.newIfStatement(posList[i], condList[i], thenList[i], elseBranch);
+ if (!elseBranch)
+ return null();
+ }
+
+ return elseBranch;
+}
+
+template <typename ParseHandler>
+typename ParseHandler::Node
+Parser<ParseHandler>::doWhileStatement(YieldHandling yieldHandling)
+{
+ uint32_t begin = pos().begin;
+ ParseContext::Statement stmt(pc, StatementKind::DoLoop);
+ Node body = statement(yieldHandling);
+ if (!body)
+ return null();
+ MUST_MATCH_TOKEN_MOD(TOK_WHILE, TokenStream::Operand, JSMSG_WHILE_AFTER_DO);
+ 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 Operand.
+ bool ignored;
+ if (!tokenStream.matchToken(&ignored, TOK_SEMI, TokenStream::Operand))
+ return null();
+ return handler.newDoWhileStatement(body, cond, TokenPos(begin, pos().end));
+}
+
+template <typename ParseHandler>
+typename ParseHandler::Node
+Parser<ParseHandler>::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 <typename ParseHandler>
+bool
+Parser<ParseHandler>::matchInOrOf(bool* isForInp, bool* isForOfp)
+{
+ TokenKind tt;
+ if (!tokenStream.getToken(&tt))
+ return false;
+
+ *isForInp = tt == TOK_IN;
+ *isForOfp = tt == TOK_NAME && tokenStream.currentToken().name() == context->names().of;
+ if (!*isForInp && !*isForOfp) {
+ tokenStream.ungetToken();
+ } else {
+ if (tt == TOK_NAME && !checkUnescapedName())
+ return false;
+ }
+
+ MOZ_ASSERT_IF(*isForInp || *isForOfp, *isForInp != *isForOfp);
+ return true;
+}
+
+template <class ParseHandler>
+bool
+Parser<ParseHandler>::validateForInOrOfLHSExpression(Node target, PossibleError* possibleError)
+{
+ if (handler.isUnparenthesizedDestructuringPattern(target))
+ return checkDestructuringPattern(target, Nothing(), possibleError);
+
+ // All other permitted targets are simple.
+ if (!reportIfNotValidSimpleAssignmentTarget(target, ForInOrOfTarget))
+ return false;
+
+ if (handler.isPropertyAccess(target))
+ return true;
+
+ if (handler.isNameAnyParentheses(target)) {
+ // The arguments/eval identifiers are simple in non-strict mode code,
+ // but warn to discourage use nonetheless.
+ if (!reportIfArgumentsEvalTarget(target))
+ return false;
+
+ handler.adjustGetToSet(target);
+ return true;
+ }
+
+ if (handler.isFunctionCall(target))
+ return checkAssignmentToCall(target, JSMSG_BAD_FOR_LEFTSIDE);
+
+ report(ParseError, false, target, JSMSG_BAD_FOR_LEFTSIDE);
+ return false;
+}
+
+template <class ParseHandler>
+bool
+Parser<ParseHandler>::forHeadStart(YieldHandling yieldHandling,
+ ParseNodeKind* forHeadKind,
+ Node* forInitialPart,
+ Maybe<ParseContext::Scope>& forLoopLexicalScope,
+ Node* forInOrOfExpression)
+{
+ MOZ_ASSERT(tokenStream.isCurrentTokenType(TOK_LP));
+
+ TokenKind tt;
+ if (!tokenStream.peekToken(&tt, TokenStream::Operand))
+ return null();
+
+ // Super-duper easy case: |for (;| is a C-style for-loop with no init
+ // component.
+ if (tt == TOK_SEMI) {
+ *forInitialPart = null();
+ *forHeadKind = PNK_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 == TOK_VAR) {
+ tokenStream.consumeKnownToken(tt, TokenStream::Operand);
+
+ // Pass null for block object because |var| declarations don't use one.
+ *forInitialPart = declarationList(yieldHandling, PNK_VAR, 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.)
+ bool parsingLexicalDeclaration = false;
+ bool letIsIdentifier = false;
+ if (tt == TOK_CONST) {
+ parsingLexicalDeclaration = true;
+ tokenStream.consumeKnownToken(tt, TokenStream::Operand);
+ } else if (tt == TOK_NAME &&
+ tokenStream.nextName() == context->names().let &&
+ !tokenStream.nextNameContainsEscape())
+ {
+ // 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(TOK_NAME, TokenStream::Operand);
+
+ TokenKind next;
+ if (!tokenStream.peekToken(&next))
+ return false;
+
+ parsingLexicalDeclaration = nextTokenContinuesLetDeclaration(next, yieldHandling);
+ if (!parsingLexicalDeclaration) {
+ tokenStream.ungetToken();
+ letIsIdentifier = true;
+ }
+ }
+
+ if (parsingLexicalDeclaration) {
+ forLoopLexicalScope.emplace(this);
+ if (!forLoopLexicalScope->init(pc))
+ return null();
+
+ // 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 == TOK_CONST ? PNK_CONST : PNK_LET,
+ forHeadKind, forInOrOfExpression);
+ return *forInitialPart != null();
+ }
+
+ // 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. Allow the Operand
+ // modifier when regetting: Operand must be used to examine the ';' in
+ // |for (;|, and our caller handles this case and that.
+ if (!isForIn && !isForOf) {
+ if (!possibleError.checkForExpressionError())
+ return false;
+ *forHeadKind = PNK_FORHEAD;
+ tokenStream.addModifierException(TokenStream::OperandIsNone);
+ 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) {
+ report(ParseError, false, *forInitialPart, JSMSG_LET_STARTING_FOROF_LHS);
+ return false;
+ }
+
+ *forHeadKind = isForIn ? PNK_FORIN : PNK_FOROF;
+
+ if (!validateForInOrOfLHSExpression(*forInitialPart, &possibleError))
+ 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 ParseHandler::Node
+Parser<ParseHandler>::forStatement(YieldHandling yieldHandling)
+{
+ MOZ_ASSERT(tokenStream.isCurrentTokenType(TOK_FOR));
+ uint32_t begin = pos().begin;
+
+ ParseContext::Statement stmt(pc, StatementKind::ForLoop);
+
+ bool isForEach = false;
+ unsigned iflags = 0;
+
+ if (allowsForEachIn()) {
+ bool matched;
+ if (!tokenStream.matchContextualKeyword(&matched, context->names().each))
+ return null();
+ if (matched) {
+ iflags = JSITER_FOREACH;
+ isForEach = true;
+ addTelemetry(JSCompartment::DeprecatedForEach);
+ if (!warnOnceAboutForEach())
+ return null();
+ }
+ }
+
+ MUST_MATCH_TOKEN(TOK_LP, JSMSG_PAREN_AFTER_FOR);
+
+ // PNK_FORHEAD, PNK_FORIN, or PNK_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::None semantics.
+ if (!forHeadStart(yieldHandling, &headKind, &startNode, forLoopLexicalScope,
+ &iteratedExpr))
+ {
+ return null();
+ }
+
+ MOZ_ASSERT(headKind == PNK_FORIN || headKind == PNK_FOROF || headKind == PNK_FORHEAD);
+
+ Node forHead;
+ if (headKind == PNK_FORHEAD) {
+ Node init = startNode;
+
+ if (isForEach) {
+ reportWithOffset(ParseError, false, begin, JSMSG_BAD_FOR_EACH_LOOP);
+ return null();
+ }
+
+ // Look for an operand: |for (;| means we might have already examined
+ // this semicolon with that modifier.
+ MUST_MATCH_TOKEN_MOD(TOK_SEMI, TokenStream::Operand, JSMSG_SEMI_AFTER_FOR_INIT);
+
+ TokenKind tt;
+ if (!tokenStream.peekToken(&tt, TokenStream::Operand))
+ return null();
+
+ Node test;
+ TokenStream::Modifier mod;
+ if (tt == TOK_SEMI) {
+ test = null();
+ mod = TokenStream::Operand;
+ } else {
+ test = expr(InAllowed, yieldHandling, TripledotProhibited);
+ if (!test)
+ return null();
+ mod = TokenStream::None;
+ }
+
+ MUST_MATCH_TOKEN_MOD(TOK_SEMI, mod, JSMSG_SEMI_AFTER_FOR_COND);
+
+ if (!tokenStream.peekToken(&tt, TokenStream::Operand))
+ return null();
+
+ Node update;
+ if (tt == TOK_RP) {
+ update = null();
+ mod = TokenStream::Operand;
+ } else {
+ update = expr(InAllowed, yieldHandling, TripledotProhibited);
+ if (!update)
+ return null();
+ mod = TokenStream::None;
+ }
+
+ MUST_MATCH_TOKEN_MOD(TOK_RP, mod, JSMSG_PAREN_AFTER_FOR_CTRL);
+
+ TokenPos headPos(begin, pos().end);
+ forHead = handler.newForHead(init, test, update, headPos);
+ if (!forHead)
+ return null();
+ } else {
+ MOZ_ASSERT(headKind == PNK_FORIN || headKind == PNK_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 == PNK_FORIN) {
+ stmt.refineForKind(StatementKind::ForInLoop);
+ iflags |= JSITER_ENUMERATE;
+ } else {
+ if (isForEach) {
+ report(ParseError, false, startNode, JSMSG_BAD_FOR_EACH_LOOP);
+ return null();
+ }
+
+ stmt.refineForKind(StatementKind::ForOfLoop);
+ }
+
+ if (!handler.isDeclarationList(target)) {
+ MOZ_ASSERT(!forLoopLexicalScope);
+ if (!checkAndMarkAsAssignmentLhs(target, PlainAssignment))
+ return null();
+ }
+
+ // Parser::declaration consumed everything up to the closing ')'. That
+ // token follows an {Assignment,}Expression, so the next token must be
+ // consumed as if an operator continued the expression, i.e. as None.
+ MUST_MATCH_TOKEN_MOD(TOK_RP, TokenStream::None, JSMSG_PAREN_AFTER_FOR_CTRL);
+
+ TokenPos headPos(begin, pos().end);
+ forHead = handler.newForInOrOfHead(headKind, target, iteratedExpr, headPos);
+ if (!forHead)
+ return null();
+ }
+
+ Node body = statement(yieldHandling);
+ if (!body)
+ return null();
+
+ Node forLoop = handler.newForStatement(begin, forHead, body, iflags);
+ if (!forLoop)
+ return null();
+
+ if (forLoopLexicalScope)
+ return finishLexicalScope(*forLoopLexicalScope, forLoop);
+
+ return forLoop;
+}
+
+template <typename ParseHandler>
+typename ParseHandler::Node
+Parser<ParseHandler>::switchStatement(YieldHandling yieldHandling)
+{
+ MOZ_ASSERT(tokenStream.isCurrentTokenType(TOK_SWITCH));
+ uint32_t begin = pos().begin;
+
+ MUST_MATCH_TOKEN(TOK_LP, JSMSG_PAREN_BEFORE_SWITCH);
+
+ Node discriminant = exprInParens(InAllowed, yieldHandling, TripledotProhibited);
+ if (!discriminant)
+ return null();
+
+ MUST_MATCH_TOKEN(TOK_RP, JSMSG_PAREN_AFTER_SWITCH);
+ MUST_MATCH_TOKEN(TOK_LC, JSMSG_CURLY_BEFORE_SWITCH);
+
+ ParseContext::Statement stmt(pc, StatementKind::Switch);
+ ParseContext::Scope scope(this);
+ if (!scope.init(pc))
+ return null();
+
+ Node caseList = handler.newStatementList(pos());
+ if (!caseList)
+ return null();
+
+ bool seenDefault = false;
+ TokenKind tt;
+ while (true) {
+ if (!tokenStream.getToken(&tt, TokenStream::Operand))
+ return null();
+ if (tt == TOK_RC)
+ break;
+ uint32_t caseBegin = pos().begin;
+
+ Node caseExpr;
+ switch (tt) {
+ case TOK_DEFAULT:
+ if (seenDefault) {
+ report(ParseError, false, null(), JSMSG_TOO_MANY_DEFAULTS);
+ return null();
+ }
+ seenDefault = true;
+ caseExpr = null(); // The default case has pn_left == nullptr.
+ break;
+
+ case TOK_CASE:
+ caseExpr = expr(InAllowed, yieldHandling, TripledotProhibited);
+ if (!caseExpr)
+ return null();
+ break;
+
+ default:
+ report(ParseError, false, null(), JSMSG_BAD_SWITCH);
+ return null();
+ }
+
+ MUST_MATCH_TOKEN(TOK_COLON, JSMSG_COLON_AFTER_CASE);
+
+ Node 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::Operand))
+ return null();
+ if (tt == TOK_RC || tt == TOK_CASE || tt == TOK_DEFAULT)
+ break;
+ if (afterReturn) {
+ TokenPos pos(0, 0);
+ if (!tokenStream.peekTokenPos(&pos, TokenStream::Operand))
+ return null();
+ statementBegin = pos.begin;
+ }
+ Node stmt = statementListItem(yieldHandling);
+ if (!stmt)
+ return null();
+ if (!warnedAboutStatementsAfterReturn) {
+ if (afterReturn) {
+ if (!handler.isStatementPermittedAfterReturnStatement(stmt)) {
+ if (!reportWithOffset(ParseWarning, false, statementBegin,
+ JSMSG_STMT_AFTER_RETURN))
+ {
+ return null();
+ }
+ warnedAboutStatementsAfterReturn = true;
+ }
+ } else if (handler.isReturnStatement(stmt)) {
+ afterReturn = true;
+ }
+ }
+ handler.addStatementToList(body, stmt);
+ }
+
+ Node casepn = handler.newCaseOrDefault(caseBegin, caseExpr, body);
+ if (!casepn)
+ return null();
+ handler.addCaseStatementToList(caseList, casepn);
+ }
+
+ caseList = finishLexicalScope(scope, caseList);
+ if (!caseList)
+ return null();
+
+ handler.setEndPosition(caseList, pos().end);
+
+ return handler.newSwitchStatement(begin, discriminant, caseList);
+}
+
+template <typename ParseHandler>
+typename ParseHandler::Node
+Parser<ParseHandler>::continueStatement(YieldHandling yieldHandling)
+{
+ MOZ_ASSERT(tokenStream.isCurrentTokenType(TOK_CONTINUE));
+ uint32_t begin = pos().begin;
+
+ RootedPropertyName label(context);
+ if (!matchLabel(yieldHandling, &label))
+ return null();
+
+ // 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) {
+ return StatementKindIsLoop(stmt->kind());
+ };
+
+ if (label) {
+ ParseContext::Statement* stmt = pc->innermostStatement();
+ bool foundLoop = false;
+
+ for (;;) {
+ stmt = ParseContext::Statement::findNearest(stmt, isLoop);
+ if (!stmt) {
+ report(ParseError, false, null(),
+ foundLoop ? JSMSG_LABEL_NOT_FOUND : JSMSG_BAD_CONTINUE);
+ return null();
+ }
+
+ foundLoop = true;
+
+ // Is it labeled by our label?
+ bool foundTarget = false;
+ stmt = stmt->enclosing();
+ while (stmt && stmt->is<ParseContext::LabelStatement>()) {
+ if (stmt->as<ParseContext::LabelStatement>().label() == label) {
+ foundTarget = true;
+ break;
+ }
+ stmt = stmt->enclosing();
+ }
+ if (foundTarget)
+ break;
+ }
+ } else if (!pc->findInnermostStatement(isLoop)) {
+ report(ParseError, false, null(), JSMSG_BAD_CONTINUE);
+ return null();
+ }
+
+ if (!MatchOrInsertSemicolonAfterNonExpression(tokenStream))
+ return null();
+
+ return handler.newContinueStatement(label, TokenPos(begin, pos().end));
+}
+
+template <typename ParseHandler>
+typename ParseHandler::Node
+Parser<ParseHandler>::breakStatement(YieldHandling yieldHandling)
+{
+ MOZ_ASSERT(tokenStream.isCurrentTokenType(TOK_BREAK));
+ uint32_t begin = pos().begin;
+
+ RootedPropertyName label(context);
+ if (!matchLabel(yieldHandling, &label))
+ return null();
+
+ // 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) {
+ return stmt->label() == label;
+ };
+
+ if (!pc->findInnermostStatement<ParseContext::LabelStatement>(hasSameLabel)) {
+ report(ParseError, false, null(), JSMSG_LABEL_NOT_FOUND);
+ return null();
+ }
+ } else {
+ auto isBreakTarget = [](ParseContext::Statement* stmt) {
+ return StatementKindIsUnlabeledBreakTarget(stmt->kind());
+ };
+
+ if (!pc->findInnermostStatement(isBreakTarget)) {
+ report(ParseError, false, null(), JSMSG_TOUGH_BREAK);
+ return null();
+ }
+ }
+
+ if (!MatchOrInsertSemicolonAfterNonExpression(tokenStream))
+ return null();
+
+ return handler.newBreakStatement(label, TokenPos(begin, pos().end));
+}
+
+template <typename ParseHandler>
+typename ParseHandler::Node
+Parser<ParseHandler>::returnStatement(YieldHandling yieldHandling)
+{
+ MOZ_ASSERT(tokenStream.isCurrentTokenType(TOK_RETURN));
+ uint32_t begin = pos().begin;
+
+ MOZ_ASSERT(pc->isFunctionBox());
+ pc->functionBox()->usesReturn = true;
+
+ // Parse an optional operand.
+ //
+ // This is ugly, but we don't want to require a semicolon.
+ Node exprNode;
+ TokenKind tt = TOK_EOF;
+ if (!tokenStream.peekTokenSameLine(&tt, TokenStream::Operand))
+ return null();
+ switch (tt) {
+ case TOK_EOL:
+ case TOK_EOF:
+ case TOK_SEMI:
+ case TOK_RC:
+ exprNode = null();
+ pc->funHasReturnVoid = true;
+ break;
+ default: {
+ exprNode = expr(InAllowed, yieldHandling, TripledotProhibited);
+ if (!exprNode)
+ return null();
+ pc->funHasReturnExpr = true;
+ }
+ }
+
+ if (exprNode) {
+ if (!MatchOrInsertSemicolonAfterExpression(tokenStream))
+ return null();
+ } else {
+ if (!MatchOrInsertSemicolonAfterNonExpression(tokenStream))
+ return null();
+ }
+
+ Node pn = handler.newReturnStatement(exprNode, TokenPos(begin, pos().end));
+ if (!pn)
+ return null();
+
+ if (pc->isLegacyGenerator() && exprNode) {
+ /* Disallow "return v;" in legacy generators. */
+ reportBadReturn(pn, ParseError, JSMSG_BAD_GENERATOR_RETURN,
+ JSMSG_BAD_ANON_GENERATOR_RETURN);
+ return null();
+ }
+
+ return pn;
+}
+
+template <typename ParseHandler>
+typename ParseHandler::Node
+Parser<ParseHandler>::newYieldExpression(uint32_t begin, typename ParseHandler::Node expr,
+ bool isYieldStar)
+{
+ Node generator = newDotGeneratorName();
+ if (!generator)
+ return null();
+ if (isYieldStar)
+ return handler.newYieldStarExpression(begin, expr, generator);
+ return handler.newYieldExpression(begin, expr, generator);
+}
+
+template <typename ParseHandler>
+typename ParseHandler::Node
+Parser<ParseHandler>::newAwaitExpression(uint32_t begin, typename ParseHandler::Node expr)
+{
+ Node generator = newDotGeneratorName();
+ if (!generator)
+ return null();
+ return handler.newAwaitExpression(begin, expr, generator);
+}
+
+template <typename ParseHandler>
+typename ParseHandler::Node
+Parser<ParseHandler>::yieldExpression(InHandling inHandling)
+{
+ MOZ_ASSERT(tokenStream.isCurrentTokenType(TOK_YIELD));
+ uint32_t begin = pos().begin;
+
+ switch (pc->generatorKind()) {
+ case StarGenerator:
+ {
+ MOZ_ASSERT(pc->isFunctionBox());
+
+ pc->lastYieldOffset = begin;
+
+ Node exprNode;
+ ParseNodeKind kind = PNK_YIELD;
+ TokenKind tt = TOK_EOF;
+ if (!tokenStream.peekTokenSameLine(&tt, TokenStream::Operand))
+ return null();
+ switch (tt) {
+ // TOK_EOL is special; it implements the [no LineTerminator here]
+ // quirk in the grammar.
+ case TOK_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 TOK_EOF:
+ case TOK_SEMI:
+ case TOK_RC:
+ case TOK_RB:
+ case TOK_RP:
+ case TOK_COLON:
+ case TOK_COMMA:
+ case TOK_IN:
+ // No value.
+ exprNode = null();
+ tokenStream.addModifierException(TokenStream::NoneIsOperand);
+ break;
+ case TOK_MUL:
+ kind = PNK_YIELD_STAR;
+ tokenStream.consumeKnownToken(TOK_MUL, TokenStream::Operand);
+ MOZ_FALLTHROUGH;
+ default:
+ exprNode = assignExpr(inHandling, YieldIsKeyword, TripledotProhibited);
+ if (!exprNode)
+ return null();
+ }
+ return newYieldExpression(begin, exprNode, kind == PNK_YIELD_STAR);
+ }
+
+ case NotGenerator:
+ // We are in code that has not seen a yield, but we are in JS 1.7 or
+ // later. Try to transition to being a legacy generator.
+ MOZ_ASSERT(tokenStream.versionNumber() >= JSVERSION_1_7);
+ MOZ_ASSERT(pc->lastYieldOffset == ParseContext::NoYieldOffset);
+
+ if (!abortIfSyntaxParser())
+ return null();
+
+ if (!pc->isFunctionBox()) {
+ report(ParseError, false, null(), JSMSG_BAD_RETURN_OR_YIELD, js_yield_str);
+ return null();
+ }
+
+ if (pc->functionBox()->isArrow()) {
+ reportWithOffset(ParseError, false, begin,
+ JSMSG_YIELD_IN_ARROW, js_yield_str);
+ return null();
+ }
+
+ if (pc->functionBox()->function()->isMethod() ||
+ pc->functionBox()->function()->isGetter() ||
+ pc->functionBox()->function()->isSetter())
+ {
+ reportWithOffset(ParseError, false, begin,
+ JSMSG_YIELD_IN_METHOD, js_yield_str);
+ return null();
+ }
+
+ if (pc->funHasReturnExpr
+#if JS_HAS_EXPR_CLOSURES
+ || pc->functionBox()->function()->isExprBody()
+#endif
+ )
+ {
+ /* As in Python (see PEP-255), disallow return v; in generators. */
+ reportBadReturn(null(), ParseError, JSMSG_BAD_GENERATOR_RETURN,
+ JSMSG_BAD_ANON_GENERATOR_RETURN);
+ return null();
+ }
+
+ pc->functionBox()->setGeneratorKind(LegacyGenerator);
+ addTelemetry(JSCompartment::DeprecatedLegacyGenerator);
+
+ MOZ_FALLTHROUGH;
+
+ case LegacyGenerator:
+ {
+ // We are in a legacy generator: a function that has already seen a
+ // yield.
+ MOZ_ASSERT(pc->isFunctionBox());
+
+ pc->lastYieldOffset = begin;
+
+ // Legacy generators do not require a value.
+ Node exprNode;
+ TokenKind tt = TOK_EOF;
+ if (!tokenStream.peekTokenSameLine(&tt, TokenStream::Operand))
+ return null();
+ switch (tt) {
+ case TOK_EOF:
+ case TOK_EOL:
+ case TOK_SEMI:
+ case TOK_RC:
+ case TOK_RB:
+ case TOK_RP:
+ case TOK_COLON:
+ case TOK_COMMA:
+ // No value.
+ exprNode = null();
+ tokenStream.addModifierException(TokenStream::NoneIsOperand);
+ break;
+ default:
+ exprNode = assignExpr(inHandling, YieldIsKeyword, TripledotProhibited);
+ if (!exprNode)
+ return null();
+ }
+
+ return newYieldExpression(begin, exprNode);
+ }
+ }
+
+ MOZ_CRASH("yieldExpr");
+}
+
+template <typename ParseHandler>
+typename ParseHandler::Node
+Parser<ParseHandler>::withStatement(YieldHandling yieldHandling)
+{
+ MOZ_ASSERT(tokenStream.isCurrentTokenType(TOK_WITH));
+ uint32_t begin = pos().begin;
+
+ // In most cases, we want the constructs forbidden in strict mode code to be
+ // a subset of those that JSOPTION_EXTRA_WARNINGS warns about, and we should
+ // use reportStrictModeError. However, 'with' is the sole instance of a
+ // construct that is forbidden in strict mode code, but doesn't even merit a
+ // warning under JSOPTION_EXTRA_WARNINGS. See
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=514576#c1.
+ if (pc->sc()->strict()) {
+ if (!report(ParseStrictError, true, null(), JSMSG_STRICT_CODE_WITH))
+ return null();
+ }
+
+ MUST_MATCH_TOKEN(TOK_LP, JSMSG_PAREN_BEFORE_WITH);
+ Node objectExpr = exprInParens(InAllowed, yieldHandling, TripledotProhibited);
+ if (!objectExpr)
+ return null();
+ MUST_MATCH_TOKEN(TOK_RP, JSMSG_PAREN_AFTER_WITH);
+
+ Node innerBlock;
+ {
+ ParseContext::Statement stmt(pc, StatementKind::With);
+ innerBlock = statement(yieldHandling);
+ if (!innerBlock)
+ return null();
+ }
+
+ pc->sc()->setBindingsAccessedDynamically();
+
+ return handler.newWithStatement(begin, objectExpr, innerBlock);
+}
+
+template <typename ParseHandler>
+typename ParseHandler::Node
+Parser<ParseHandler>::labeledItem(YieldHandling yieldHandling)
+{
+ TokenKind tt;
+ if (!tokenStream.getToken(&tt, TokenStream::Operand))
+ return null();
+
+ if (tt == TOK_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 == TOK_MUL) {
+ report(ParseError, false, null(), 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()) {
+ report(ParseError, false, null(), JSMSG_FUNCTION_LABEL);
+ return null();
+ }
+
+ return functionStmt(yieldHandling, NameRequired);
+ }
+
+ tokenStream.ungetToken();
+ return statement(yieldHandling);
+}
+
+template <typename ParseHandler>
+typename ParseHandler::Node
+Parser<ParseHandler>::labeledStatement(YieldHandling yieldHandling)
+{
+ RootedPropertyName label(context, labelIdentifier(yieldHandling));
+ if (!label)
+ return null();
+
+ auto hasSameLabel = [&label](ParseContext::LabelStatement* stmt) {
+ return stmt->label() == label;
+ };
+
+ if (pc->findInnermostStatement<ParseContext::LabelStatement>(hasSameLabel)) {
+ report(ParseError, false, null(), JSMSG_DUPLICATE_LABEL);
+ return null();
+ }
+
+ uint32_t begin = pos().begin;
+
+ tokenStream.consumeKnownToken(TOK_COLON);
+
+ /* Push a label struct and parse the statement. */
+ ParseContext::LabelStatement stmt(pc, label);
+ Node pn = labeledItem(yieldHandling);
+ if (!pn)
+ return null();
+
+ return handler.newLabeledStatement(label, pn, begin);
+}
+
+template <typename ParseHandler>
+typename ParseHandler::Node
+Parser<ParseHandler>::throwStatement(YieldHandling yieldHandling)
+{
+ MOZ_ASSERT(tokenStream.isCurrentTokenType(TOK_THROW));
+ uint32_t begin = pos().begin;
+
+ /* ECMA-262 Edition 3 says 'throw [no LineTerminator here] Expr'. */
+ TokenKind tt = TOK_EOF;
+ if (!tokenStream.peekTokenSameLine(&tt, TokenStream::Operand))
+ return null();
+ if (tt == TOK_EOF || tt == TOK_SEMI || tt == TOK_RC) {
+ report(ParseError, false, null(), JSMSG_MISSING_EXPR_AFTER_THROW);
+ return null();
+ }
+ if (tt == TOK_EOL) {
+ report(ParseError, false, null(), JSMSG_LINE_BREAK_AFTER_THROW);
+ return null();
+ }
+
+ Node throwExpr = expr(InAllowed, yieldHandling, TripledotProhibited);
+ if (!throwExpr)
+ return null();
+
+ if (!MatchOrInsertSemicolonAfterExpression(tokenStream))
+ return null();
+
+ return handler.newThrowStatement(throwExpr, TokenPos(begin, pos().end));
+}
+
+template <typename ParseHandler>
+typename ParseHandler::Node
+Parser<ParseHandler>::tryStatement(YieldHandling yieldHandling)
+{
+ MOZ_ASSERT(tokenStream.isCurrentTokenType(TOK_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 ternary.
+ * kid1 is the lvalue (TOK_NAME, TOK_LB, or TOK_LC)
+ * kid2 is the catch guard or null if no guard
+ * kid3 is the catch block
+ *
+ * catch lvalue nodes are either:
+ * TOK_NAME for a single identifier
+ * TOK_RB or TOK_RC for a destructuring left-hand side
+ *
+ * finally nodes are TOK_LC statement lists.
+ */
+
+ Node innerBlock;
+ {
+ MUST_MATCH_TOKEN(TOK_LC, JSMSG_CURLY_BEFORE_TRY);
+
+ 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();
+
+ MUST_MATCH_TOKEN_MOD(TOK_RC, TokenStream::Operand, JSMSG_CURLY_AFTER_TRY);
+ }
+
+ bool hasUnconditionalCatch = false;
+ Node catchList = null();
+ TokenKind tt;
+ if (!tokenStream.getToken(&tt))
+ return null();
+ if (tt == TOK_CATCH) {
+ catchList = handler.newCatchList();
+ if (!catchList)
+ return null();
+
+ do {
+ Node pnblock;
+
+ /* Check for another catch after unconditional catch. */
+ if (hasUnconditionalCatch) {
+ report(ParseError, false, null(), JSMSG_CATCH_AFTER_GENERAL);
+ return null();
+ }
+
+ /*
+ * 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 (lhs if <boolean_expression>)
+ * where lhs is a name or a destructuring left-hand side.
+ * (the latter is legal only #ifdef JS_HAS_CATCH_GUARD)
+ */
+ MUST_MATCH_TOKEN(TOK_LP, JSMSG_PAREN_BEFORE_CATCH);
+
+ if (!tokenStream.getToken(&tt))
+ return null();
+ Node catchName;
+ switch (tt) {
+ case TOK_LB:
+ case TOK_LC:
+ catchName = destructuringDeclaration(DeclarationKind::CatchParameter,
+ yieldHandling, tt);
+ if (!catchName)
+ return null();
+ break;
+
+ case TOK_NAME:
+ case TOK_YIELD: {
+ RootedPropertyName param(context, bindingIdentifier(yieldHandling));
+ if (!param)
+ return null();
+ catchName = newName(param);
+ if (!catchName)
+ return null();
+ if (!noteDeclaredName(param, DeclarationKind::SimpleCatchParameter, pos()))
+ return null();
+ break;
+ }
+
+ default:
+ report(ParseError, false, null(), JSMSG_CATCH_IDENTIFIER);
+ return null();
+ }
+
+ Node catchGuard = null();
+#if JS_HAS_CATCH_GUARD
+ /*
+ * We use 'catch (x if x === 5)' (not 'catch (x : x === 5)')
+ * to avoid conflicting with the JS2/ECMAv4 type annotation
+ * catchguard syntax.
+ */
+ bool matched;
+ if (!tokenStream.matchToken(&matched, TOK_IF))
+ return null();
+ if (matched) {
+ catchGuard = expr(InAllowed, yieldHandling, TripledotProhibited);
+ if (!catchGuard)
+ return null();
+ }
+#endif
+ MUST_MATCH_TOKEN(TOK_RP, JSMSG_PAREN_AFTER_CATCH);
+
+ MUST_MATCH_TOKEN(TOK_LC, JSMSG_CURLY_BEFORE_CATCH);
+
+ Node catchBody = catchBlockStatement(yieldHandling, scope);
+ if (!catchBody)
+ return null();
+
+ if (!catchGuard)
+ hasUnconditionalCatch = true;
+
+ pnblock = finishLexicalScope(scope, catchBody);
+ if (!pnblock)
+ return null();
+
+ if (!handler.addCatchBlock(catchList, pnblock, catchName, catchGuard, catchBody))
+ return null();
+ handler.setEndPosition(catchList, pos().end);
+ handler.setEndPosition(pnblock, pos().end);
+
+ if (!tokenStream.getToken(&tt, TokenStream::Operand))
+ return null();
+ } while (tt == TOK_CATCH);
+ }
+
+ Node finallyBlock = null();
+
+ if (tt == TOK_FINALLY) {
+ MUST_MATCH_TOKEN(TOK_LC, JSMSG_CURLY_BEFORE_FINALLY);
+
+ 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();
+
+ MUST_MATCH_TOKEN_MOD(TOK_RC, TokenStream::Operand, JSMSG_CURLY_AFTER_FINALLY);
+ } else {
+ tokenStream.ungetToken();
+ }
+ if (!catchList && !finallyBlock) {
+ report(ParseError, false, null(), JSMSG_CATCH_OR_FINALLY);
+ return null();
+ }
+
+ return handler.newTryStatement(begin, innerBlock, catchList, finallyBlock);
+}
+
+template <typename ParseHandler>
+typename ParseHandler::Node
+Parser<ParseHandler>::catchBlockStatement(YieldHandling yieldHandling,
+ ParseContext::Scope& catchParamScope)
+{
+ 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();
+
+ Node list = statementList(yieldHandling);
+ if (!list)
+ return null();
+
+ MUST_MATCH_TOKEN_MOD(TOK_RC, TokenStream::Operand, JSMSG_CURLY_AFTER_CATCH);
+
+ // The catch parameter names are not bound in the body scope, so remove
+ // them before generating bindings.
+ scope.removeCatchParameters(pc, catchParamScope);
+ return finishLexicalScope(scope, list);
+}
+
+template <typename ParseHandler>
+typename ParseHandler::Node
+Parser<ParseHandler>::debuggerStatement()
+{
+ TokenPos p;
+ p.begin = pos().begin;
+ if (!MatchOrInsertSemicolonAfterNonExpression(tokenStream))
+ return null();
+ p.end = pos().end;
+
+ pc->sc()->setBindingsAccessedDynamically();
+ pc->sc()->setHasDebuggerStatement();
+
+ return handler.newDebuggerStatement(p);
+}
+
+static JSOp
+JSOpFromPropertyType(PropertyType propType)
+{
+ switch (propType) {
+ case PropertyType::Getter:
+ case PropertyType::GetterNoExpressionClosure:
+ return JSOP_INITPROP_GETTER;
+ case PropertyType::Setter:
+ case PropertyType::SetterNoExpressionClosure:
+ return JSOP_INITPROP_SETTER;
+ case PropertyType::Normal:
+ case PropertyType::Method:
+ case PropertyType::GeneratorMethod:
+ case PropertyType::AsyncMethod:
+ case PropertyType::Constructor:
+ case PropertyType::DerivedConstructor:
+ return JSOP_INITPROP;
+ default:
+ MOZ_CRASH("unexpected property type");
+ }
+}
+
+static FunctionSyntaxKind
+FunctionSyntaxKindFromPropertyType(PropertyType propType)
+{
+ switch (propType) {
+ case PropertyType::Getter:
+ return Getter;
+ case PropertyType::GetterNoExpressionClosure:
+ return GetterNoExpressionClosure;
+ case PropertyType::Setter:
+ return Setter;
+ case PropertyType::SetterNoExpressionClosure:
+ return SetterNoExpressionClosure;
+ case PropertyType::Method:
+ case PropertyType::GeneratorMethod:
+ case PropertyType::AsyncMethod:
+ return Method;
+ case PropertyType::Constructor:
+ return ClassConstructor;
+ case PropertyType::DerivedConstructor:
+ return DerivedClassConstructor;
+ default:
+ MOZ_CRASH("unexpected property type");
+ }
+}
+
+static GeneratorKind
+GeneratorKindFromPropertyType(PropertyType propType)
+{
+ if (propType == PropertyType::GeneratorMethod)
+ return StarGenerator;
+ if (propType == PropertyType::AsyncMethod)
+ return StarGenerator;
+ return NotGenerator;
+}
+
+static FunctionAsyncKind
+AsyncKindFromPropertyType(PropertyType propType)
+{
+ if (propType == PropertyType::AsyncMethod)
+ return AsyncFunction;
+ return SyncFunction;
+}
+
+template <typename ParseHandler>
+typename ParseHandler::Node
+Parser<ParseHandler>::classDefinition(YieldHandling yieldHandling,
+ ClassContext classContext,
+ DefaultHandling defaultHandling)
+{
+ MOZ_ASSERT(tokenStream.isCurrentTokenType(TOK_CLASS));
+
+ bool savedStrictness = setLocalStrictMode(true);
+
+ TokenKind tt;
+ if (!tokenStream.getToken(&tt))
+ return null();
+
+ RootedPropertyName name(context);
+ if (tt == TOK_NAME || tt == TOK_YIELD) {
+ name = bindingIdentifier(yieldHandling);
+ if (!name)
+ return null();
+ } else if (classContext == ClassStatement) {
+ if (defaultHandling == AllowDefaultName) {
+ name = context->names().starDefaultStar;
+ tokenStream.ungetToken();
+ } else {
+ // Class statements must have a bound name
+ report(ParseError, false, null(), JSMSG_UNNAMED_CLASS_STMT);
+ return null();
+ }
+ } else {
+ // Make sure to put it back, whatever it was
+ tokenStream.ungetToken();
+ }
+
+ RootedAtom propAtom(context);
+
+ // A named class creates a new lexical scope with a const binding of the
+ // class name.
+ Maybe<ParseContext::Statement> classStmt;
+ Maybe<ParseContext::Scope> classScope;
+ if (name) {
+ classStmt.emplace(pc, StatementKind::Block);
+ classScope.emplace(this);
+ if (!classScope->init(pc))
+ return null();
+ }
+
+ // 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();
+
+ Node classHeritage = null();
+ bool hasHeritage;
+ if (!tokenStream.matchToken(&hasHeritage, TOK_EXTENDS))
+ return null();
+ if (hasHeritage) {
+ if (!tokenStream.getToken(&tt))
+ return null();
+ classHeritage = memberExpr(yieldHandling, TripledotProhibited, tt);
+ if (!classHeritage)
+ return null();
+ }
+
+ MUST_MATCH_TOKEN(TOK_LC, JSMSG_CURLY_BEFORE_CLASS);
+
+ Node classMethods = handler.newClassMethodList(pos().begin);
+ if (!classMethods)
+ return null();
+
+ bool seenConstructor = false;
+ for (;;) {
+ TokenKind tt;
+ if (!tokenStream.getToken(&tt, TokenStream::KeywordIsName))
+ return null();
+ if (tt == TOK_RC)
+ break;
+
+ if (tt == TOK_SEMI)
+ continue;
+
+ bool isStatic = false;
+ if (tt == TOK_NAME && tokenStream.currentName() == context->names().static_) {
+ if (!tokenStream.peekToken(&tt, TokenStream::KeywordIsName))
+ return null();
+ if (tt == TOK_RC) {
+ tokenStream.consumeKnownToken(tt, TokenStream::KeywordIsName);
+ report(ParseError, false, null(), JSMSG_UNEXPECTED_TOKEN,
+ "property name", TokenKindToDesc(tt));
+ return null();
+ }
+
+ if (tt != TOK_LP) {
+ if (!checkUnescapedName())
+ return null();
+
+ isStatic = true;
+ } else {
+ tokenStream.addModifierException(TokenStream::NoneIsKeywordIsName);
+ tokenStream.ungetToken();
+ }
+ } else {
+ tokenStream.ungetToken();
+ }
+
+ PropertyType propType;
+ Node propName = propertyName(yieldHandling, classMethods, &propType, &propAtom);
+ if (!propName)
+ return null();
+
+ if (propType != PropertyType::Getter && propType != PropertyType::Setter &&
+ propType != PropertyType::Method && propType != PropertyType::GeneratorMethod &&
+ propType != PropertyType::AsyncMethod &&
+ propType != PropertyType::Constructor && propType != PropertyType::DerivedConstructor)
+ {
+ report(ParseError, false, null(), JSMSG_BAD_METHOD_DEF);
+ return null();
+ }
+
+ if (propType == PropertyType::Getter)
+ propType = PropertyType::GetterNoExpressionClosure;
+ if (propType == PropertyType::Setter)
+ propType = PropertyType::SetterNoExpressionClosure;
+ if (!isStatic && propAtom == context->names().constructor) {
+ if (propType != PropertyType::Method) {
+ report(ParseError, false, propName, JSMSG_BAD_METHOD_DEF);
+ return null();
+ }
+ if (seenConstructor) {
+ report(ParseError, false, propName, JSMSG_DUPLICATE_PROPERTY, "constructor");
+ return null();
+ }
+ seenConstructor = true;
+ propType = hasHeritage ? PropertyType::DerivedConstructor : PropertyType::Constructor;
+ } else if (isStatic && propAtom == context->names().prototype) {
+ report(ParseError, false, propName, JSMSG_BAD_METHOD_DEF);
+ return null();
+ }
+
+ // FIXME: Implement ES6 function "name" property semantics
+ // (bug 883377).
+ RootedAtom funName(context);
+ switch (propType) {
+ case PropertyType::GetterNoExpressionClosure:
+ case PropertyType::SetterNoExpressionClosure:
+ if (!tokenStream.isCurrentTokenType(TOK_RB)) {
+ funName = prefixAccessorName(propType, propAtom);
+ if (!funName)
+ return null();
+ }
+ break;
+ case PropertyType::Constructor:
+ case PropertyType::DerivedConstructor:
+ funName = name;
+ break;
+ default:
+ if (!tokenStream.isCurrentTokenType(TOK_RB))
+ funName = propAtom;
+ }
+ Node fn = methodDefinition(propType, funName);
+ if (!fn)
+ return null();
+
+ JSOp op = JSOpFromPropertyType(propType);
+ if (!handler.addClassMethodDefinition(classMethods, propName, fn, op, isStatic))
+ return null();
+ }
+
+ Node nameNode = null();
+ Node methodsOrBlock = classMethods;
+ if (name) {
+ // The inner name is immutable.
+ if (!noteDeclaredName(name, DeclarationKind::Const, namePos))
+ return null();
+
+ Node innerName = newName(name, namePos);
+ if (!innerName)
+ return null();
+
+ Node classBlock = finishLexicalScope(*classScope, classMethods);
+ if (!classBlock)
+ return null();
+
+ methodsOrBlock = classBlock;
+
+ // Pop the inner scope.
+ classScope.reset();
+ classStmt.reset();
+
+ Node outerName = null();
+ if (classContext == ClassStatement) {
+ // The outer name is mutable.
+ if (!noteDeclaredName(name, DeclarationKind::Let, namePos))
+ return null();
+
+ outerName = newName(name, namePos);
+ if (!outerName)
+ return null();
+ }
+
+ nameNode = handler.newClassNames(outerName, innerName, namePos);
+ if (!nameNode)
+ return null();
+ }
+
+ MOZ_ALWAYS_TRUE(setLocalStrictMode(savedStrictness));
+
+ return handler.newClass(nameNode, classHeritage, methodsOrBlock);
+}
+
+template <class ParseHandler>
+bool
+Parser<ParseHandler>::nextTokenContinuesLetDeclaration(TokenKind next, YieldHandling yieldHandling)
+{
+ MOZ_ASSERT(tokenStream.isCurrentTokenType(TOK_NAME));
+ MOZ_ASSERT(tokenStream.currentName() == context->names().let);
+ MOZ_ASSERT(!tokenStream.currentToken().nameContainsEscape());
+
+#ifdef DEBUG
+ TokenKind verify;
+ MOZ_ALWAYS_TRUE(tokenStream.peekToken(&verify));
+ MOZ_ASSERT(next == verify);
+#endif
+
+ // Destructuring is (for once) the easy case.
+ if (next == TOK_LB || next == TOK_LC)
+ return true;
+
+ // Otherwise a let declaration must have a name.
+ if (next == TOK_NAME) {
+ if (tokenStream.nextName() == context->names().yield) {
+ MOZ_ASSERT(tokenStream.nextNameContainsEscape(),
+ "token stream should interpret unescaped 'yield' as TOK_YIELD");
+
+ // Same as |next == TOK_YIELD|.
+ return yieldHandling == YieldIsName;
+ }
+
+ // One non-"yield" TOK_NAME 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!
+ return true;
+ }
+
+ // If we have the name "yield", the grammar parameter exactly states
+ // whether this is okay. (This wasn't true for SpiderMonkey's ancient
+ // legacy generator syntax, but that's dead now.) If YieldIsName,
+ // declaration-parsing code will (if necessary) enforce a strict mode
+ // restriction on defining "yield". If YieldIsKeyword, consider this the
+ // end of the declaration, in case ASI induces a semicolon that makes the
+ // "yield" valid.
+ if (next == TOK_YIELD)
+ return yieldHandling == YieldIsName;
+
+ // Otherwise not a let declaration.
+ return false;
+}
+
+template <typename ParseHandler>
+typename ParseHandler::Node
+Parser<ParseHandler>::variableStatement(YieldHandling yieldHandling)
+{
+ Node vars = declarationList(yieldHandling, PNK_VAR);
+ if (!vars)
+ return null();
+ if (!MatchOrInsertSemicolonAfterExpression(tokenStream))
+ return null();
+ return vars;
+}
+
+template <typename ParseHandler>
+typename ParseHandler::Node
+Parser<ParseHandler>::statement(YieldHandling yieldHandling)
+{
+ MOZ_ASSERT(checkOptionsCalled);
+
+ JS_CHECK_RECURSION(context, return null());
+
+ TokenKind tt;
+ if (!tokenStream.getToken(&tt, TokenStream::Operand))
+ return null();
+
+ switch (tt) {
+ // BlockStatement[?Yield, ?Return]
+ case TOK_LC:
+ return blockStatement(yieldHandling);
+
+ // VariableStatement[?Yield]
+ case TOK_VAR:
+ return variableStatement(yieldHandling);
+
+ // EmptyStatement
+ case TOK_SEMI:
+ return handler.newEmptyStatement(pos());
+
+ // ExpressionStatement[?Yield].
+
+ case TOK_YIELD: {
+ // Don't use a ternary operator here due to obscure linker issues
+ // around using static consts in the arms of a ternary.
+ TokenStream::Modifier modifier;
+ if (yieldExpressionsSupported())
+ modifier = TokenStream::Operand;
+ else
+ modifier = TokenStream::None;
+
+ TokenKind next;
+ if (!tokenStream.peekToken(&next, modifier))
+ return null();
+
+ if (next == TOK_COLON)
+ return labeledStatement(yieldHandling);
+
+ return expressionStatement(yieldHandling);
+ }
+
+ case TOK_NAME: {
+ 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 (!tokenStream.currentToken().nameContainsEscape() &&
+ tokenStream.currentName() == context->names().let)
+ {
+ bool forbiddenLetDeclaration = false;
+
+ if (pc->sc()->strict() || versionNumber() >= JSVERSION_1_7) {
+ // |let| can't be an Identifier in strict mode code. Ditto for
+ // non-standard JavaScript 1.7+.
+ forbiddenLetDeclaration = true;
+ } else if (next == TOK_LB) {
+ // Enforce ExpressionStatement's 'let [' lookahead restriction.
+ forbiddenLetDeclaration = true;
+ } else if (next == TOK_LC || next == TOK_NAME) {
+ // '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 TOK_YIELD: we'll handle it
+ // correctly but with a worse error message.
+ TokenKind nextSameLine;
+ if (!tokenStream.peekTokenSameLine(&nextSameLine))
+ return null();
+
+ MOZ_ASSERT(nextSameLine == TOK_NAME ||
+ nextSameLine == TOK_LC ||
+ nextSameLine == TOK_EOL);
+
+ forbiddenLetDeclaration = nextSameLine != TOK_EOL;
+ }
+
+ if (forbiddenLetDeclaration) {
+ report(ParseError, false, null(), JSMSG_FORBIDDEN_AS_STATEMENT,
+ "lexical declarations");
+ return null();
+ }
+ }
+
+ // NOTE: It's unfortunately allowed to have a label named 'let' in
+ // non-strict code. 💯
+ if (next == TOK_COLON)
+ return labeledStatement(yieldHandling);
+
+ return expressionStatement(yieldHandling);
+ }
+
+ case TOK_NEW:
+ return expressionStatement(yieldHandling, PredictInvoked);
+
+ default:
+ return expressionStatement(yieldHandling);
+
+ // IfStatement[?Yield, ?Return]
+ case TOK_IF:
+ return ifStatement(yieldHandling);
+
+ // BreakableStatement[?Yield, ?Return]
+ //
+ // BreakableStatement[Yield, Return]:
+ // IterationStatement[?Yield, ?Return]
+ // SwitchStatement[?Yield, ?Return]
+ case TOK_DO:
+ return doWhileStatement(yieldHandling);
+
+ case TOK_WHILE:
+ return whileStatement(yieldHandling);
+
+ case TOK_FOR:
+ return forStatement(yieldHandling);
+
+ case TOK_SWITCH:
+ return switchStatement(yieldHandling);
+
+ // ContinueStatement[?Yield]
+ case TOK_CONTINUE:
+ return continueStatement(yieldHandling);
+
+ // BreakStatement[?Yield]
+ case TOK_BREAK:
+ return breakStatement(yieldHandling);
+
+ // [+Return] ReturnStatement[?Yield]
+ case TOK_RETURN:
+ // The Return parameter is only used here, and the effect is easily
+ // detected this way, so don't bother passing around an extra parameter
+ // everywhere.
+ if (!pc->isFunctionBox()) {
+ report(ParseError, false, null(), JSMSG_BAD_RETURN_OR_YIELD, js_return_str);
+ return null();
+ }
+ return returnStatement(yieldHandling);
+
+ // WithStatement[?Yield, ?Return]
+ case TOK_WITH:
+ return withStatement(yieldHandling);
+
+ // LabelledStatement[?Yield, ?Return]
+ // This is really handled by TOK_NAME and TOK_YIELD cases above.
+
+ // ThrowStatement[?Yield]
+ case TOK_THROW:
+ return throwStatement(yieldHandling);
+
+ // TryStatement[?Yield, ?Return]
+ case TOK_TRY:
+ return tryStatement(yieldHandling);
+
+ // DebuggerStatement
+ case TOK_DEBUGGER:
+ return debuggerStatement();
+
+ // |function| is forbidden by lookahead restriction (unless as child
+ // statement of |if| or |else|, but Parser::consequentOrAlternative
+ // handles that).
+ case TOK_FUNCTION:
+ report(ParseError, false, null(), JSMSG_FORBIDDEN_AS_STATEMENT, "function declarations");
+ return null();
+
+ // |class| is also forbidden by lookahead restriction.
+ case TOK_CLASS:
+ report(ParseError, false, null(), JSMSG_FORBIDDEN_AS_STATEMENT, "classes");
+ return null();
+
+ // ImportDeclaration (only inside modules)
+ case TOK_IMPORT:
+ return importDeclaration();
+
+ // ExportDeclaration (only inside modules)
+ case TOK_EXPORT:
+ return exportDeclaration();
+
+ // Miscellaneous error cases arguably better caught here than elsewhere.
+
+ case TOK_CATCH:
+ report(ParseError, false, null(), JSMSG_CATCH_WITHOUT_TRY);
+ return null();
+
+ case TOK_FINALLY:
+ report(ParseError, false, null(), JSMSG_FINALLY_WITHOUT_TRY);
+ return null();
+
+ // NOTE: default case handled in the ExpressionStatement section.
+ }
+}
+
+template <typename ParseHandler>
+typename ParseHandler::Node
+Parser<ParseHandler>::statementListItem(YieldHandling yieldHandling,
+ bool canHaveDirectives /* = false */)
+{
+ MOZ_ASSERT(checkOptionsCalled);
+
+ JS_CHECK_RECURSION(context, return null());
+
+ TokenKind tt;
+ if (!tokenStream.getToken(&tt, TokenStream::Operand))
+ return null();
+
+ switch (tt) {
+ // BlockStatement[?Yield, ?Return]
+ case TOK_LC:
+ return blockStatement(yieldHandling);
+
+ // VariableStatement[?Yield]
+ case TOK_VAR:
+ return variableStatement(yieldHandling);
+
+ // EmptyStatement
+ case TOK_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 TOK_STRING:
+ if (!canHaveDirectives && tokenStream.currentToken().atom() == context->names().useAsm) {
+ if (!abortIfSyntaxParser())
+ return null();
+ if (!report(ParseWarning, false, null(), JSMSG_USE_ASM_DIRECTIVE_FAIL))
+ return null();
+ }
+ return expressionStatement(yieldHandling);
+
+ case TOK_YIELD: {
+ // Don't use a ternary operator here due to obscure linker issues
+ // around using static consts in the arms of a ternary.
+ TokenStream::Modifier modifier;
+ if (yieldExpressionsSupported())
+ modifier = TokenStream::Operand;
+ else
+ modifier = TokenStream::None;
+
+ TokenKind next;
+ if (!tokenStream.peekToken(&next, modifier))
+ return null();
+
+ if (next == TOK_COLON)
+ return labeledStatement(yieldHandling);
+
+ return expressionStatement(yieldHandling);
+ }
+
+ case TOK_NAME: {
+ TokenKind next;
+ if (!tokenStream.peekToken(&next))
+ return null();
+
+ if (!tokenStream.currentToken().nameContainsEscape() &&
+ tokenStream.currentName() == context->names().let &&
+ nextTokenContinuesLetDeclaration(next, yieldHandling))
+ {
+ return lexicalDeclaration(yieldHandling, /* isConst = */ false);
+ }
+
+ if (tokenStream.currentName() == context->names().async) {
+ TokenKind nextSameLine = TOK_EOF;
+ if (!tokenStream.peekTokenSameLine(&nextSameLine))
+ return null();
+ if (nextSameLine == TOK_FUNCTION) {
+ tokenStream.consumeKnownToken(TOK_FUNCTION);
+ return functionStmt(yieldHandling, NameRequired, AsyncFunction);
+ }
+ }
+
+ if (next == TOK_COLON)
+ return labeledStatement(yieldHandling);
+
+ return expressionStatement(yieldHandling);
+ }
+
+ case TOK_NEW:
+ return expressionStatement(yieldHandling, PredictInvoked);
+
+ default:
+ return expressionStatement(yieldHandling);
+
+ // IfStatement[?Yield, ?Return]
+ case TOK_IF:
+ return ifStatement(yieldHandling);
+
+ // BreakableStatement[?Yield, ?Return]
+ //
+ // BreakableStatement[Yield, Return]:
+ // IterationStatement[?Yield, ?Return]
+ // SwitchStatement[?Yield, ?Return]
+ case TOK_DO:
+ return doWhileStatement(yieldHandling);
+
+ case TOK_WHILE:
+ return whileStatement(yieldHandling);
+
+ case TOK_FOR:
+ return forStatement(yieldHandling);
+
+ case TOK_SWITCH:
+ return switchStatement(yieldHandling);
+
+ // ContinueStatement[?Yield]
+ case TOK_CONTINUE:
+ return continueStatement(yieldHandling);
+
+ // BreakStatement[?Yield]
+ case TOK_BREAK:
+ return breakStatement(yieldHandling);
+
+ // [+Return] ReturnStatement[?Yield]
+ case TOK_RETURN:
+ // The Return parameter is only used here, and the effect is easily
+ // detected this way, so don't bother passing around an extra parameter
+ // everywhere.
+ if (!pc->isFunctionBox()) {
+ report(ParseError, false, null(), JSMSG_BAD_RETURN_OR_YIELD, js_return_str);
+ return null();
+ }
+ return returnStatement(yieldHandling);
+
+ // WithStatement[?Yield, ?Return]
+ case TOK_WITH:
+ return withStatement(yieldHandling);
+
+ // LabelledStatement[?Yield, ?Return]
+ // This is really handled by TOK_NAME and TOK_YIELD cases above.
+
+ // ThrowStatement[?Yield]
+ case TOK_THROW:
+ return throwStatement(yieldHandling);
+
+ // TryStatement[?Yield, ?Return]
+ case TOK_TRY:
+ return tryStatement(yieldHandling);
+
+ // DebuggerStatement
+ case TOK_DEBUGGER:
+ return debuggerStatement();
+
+ // Declaration[Yield]:
+
+ // HoistableDeclaration[?Yield, ~Default]
+ case TOK_FUNCTION:
+ return functionStmt(yieldHandling, NameRequired);
+
+ // ClassDeclaration[?Yield, ~Default]
+ case TOK_CLASS:
+ return classDefinition(yieldHandling, ClassStatement, NameRequired);
+
+ // LexicalDeclaration[In, ?Yield]
+ // LetOrConst BindingList[?In, ?Yield]
+ case TOK_CONST:
+ // [In] is the default behavior, because for-loops specially parse
+ // their heads to handle |in| in this situation.
+ return lexicalDeclaration(yieldHandling, /* isConst = */ true);
+
+ // ImportDeclaration (only inside modules)
+ case TOK_IMPORT:
+ return importDeclaration();
+
+ // ExportDeclaration (only inside modules)
+ case TOK_EXPORT:
+ return exportDeclaration();
+
+ // Miscellaneous error cases arguably better caught here than elsewhere.
+
+ case TOK_CATCH:
+ report(ParseError, false, null(), JSMSG_CATCH_WITHOUT_TRY);
+ return null();
+
+ case TOK_FINALLY:
+ report(ParseError, false, null(), JSMSG_FINALLY_WITHOUT_TRY);
+ return null();
+
+ // NOTE: default case handled in the ExpressionStatement section.
+ }
+}
+
+template <typename ParseHandler>
+typename ParseHandler::Node
+Parser<ParseHandler>::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, TOK_COMMA))
+ return null();
+ if (!matched)
+ return pn;
+
+ Node 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::Operand))
+ return null();
+
+ if (tt == TOK_RP) {
+ tokenStream.consumeKnownToken(TOK_RP, TokenStream::Operand);
+
+ if (!tokenStream.peekToken(&tt))
+ return null();
+ if (tt != TOK_ARROW) {
+ report(ParseError, false, null(), JSMSG_UNEXPECTED_TOKEN,
+ "expression", TokenKindToDesc(TOK_RP));
+ return null();
+ }
+
+ tokenStream.ungetToken(); // put back right paren
+ tokenStream.addModifierException(TokenStream::NoneIsOperand);
+ 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, TOK_COMMA))
+ return null();
+ if (!matched)
+ break;
+ }
+ return seq;
+}
+
+static const JSOp ParseNodeKindToJSOp[] = {
+ JSOP_OR,
+ JSOP_AND,
+ JSOP_BITOR,
+ JSOP_BITXOR,
+ JSOP_BITAND,
+ JSOP_STRICTEQ,
+ JSOP_EQ,
+ JSOP_STRICTNE,
+ JSOP_NE,
+ JSOP_LT,
+ JSOP_LE,
+ JSOP_GT,
+ JSOP_GE,
+ JSOP_INSTANCEOF,
+ JSOP_IN,
+ JSOP_LSH,
+ JSOP_RSH,
+ JSOP_URSH,
+ JSOP_ADD,
+ JSOP_SUB,
+ JSOP_MUL,
+ JSOP_DIV,
+ JSOP_MOD,
+ JSOP_POW
+};
+
+static inline JSOp
+BinaryOpParseNodeKindToJSOp(ParseNodeKind pnk)
+{
+ MOZ_ASSERT(pnk >= PNK_BINOP_FIRST);
+ MOZ_ASSERT(pnk <= PNK_BINOP_LAST);
+ return ParseNodeKindToJSOp[pnk - PNK_BINOP_FIRST];
+}
+
+static ParseNodeKind
+BinaryOpTokenKindToParseNodeKind(TokenKind tok)
+{
+ MOZ_ASSERT(TokenKindIsBinaryOp(tok));
+ return ParseNodeKind(PNK_BINOP_FIRST + (tok - TOK_BINOP_FIRST));
+}
+
+static const int PrecedenceTable[] = {
+ 1, /* PNK_OR */
+ 2, /* PNK_AND */
+ 3, /* PNK_BITOR */
+ 4, /* PNK_BITXOR */
+ 5, /* PNK_BITAND */
+ 6, /* PNK_STRICTEQ */
+ 6, /* PNK_EQ */
+ 6, /* PNK_STRICTNE */
+ 6, /* PNK_NE */
+ 7, /* PNK_LT */
+ 7, /* PNK_LE */
+ 7, /* PNK_GT */
+ 7, /* PNK_GE */
+ 7, /* PNK_INSTANCEOF */
+ 7, /* PNK_IN */
+ 8, /* PNK_LSH */
+ 8, /* PNK_RSH */
+ 8, /* PNK_URSH */
+ 9, /* PNK_ADD */
+ 9, /* PNK_SUB */
+ 10, /* PNK_STAR */
+ 10, /* PNK_DIV */
+ 10, /* PNK_MOD */
+ 11 /* PNK_POW */
+};
+
+static const int PRECEDENCE_CLASSES = 11;
+
+static int
+Precedence(ParseNodeKind pnk) {
+ // Everything binds tighter than PNK_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 == PNK_LIMIT)
+ return 0;
+
+ MOZ_ASSERT(pnk >= PNK_BINOP_FIRST);
+ MOZ_ASSERT(pnk <= PNK_BINOP_LAST);
+ return PrecedenceTable[pnk - PNK_BINOP_FIRST];
+}
+
+template <typename ParseHandler>
+MOZ_ALWAYS_INLINE typename ParseHandler::Node
+Parser<ParseHandler>::orExpr1(InHandling inHandling, YieldHandling yieldHandling,
+ TripledotHandling tripledotHandling,
+ PossibleError* possibleError,
+ InvokedPrediction invoked /* = PredictUninvoked */)
+{
+ // 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;
+ for (;;) {
+ pn = unaryExpr(yieldHandling, tripledotHandling, possibleError, invoked);
+ if (!pn)
+ return pn;
+
+ // If a binary operator follows, consume it and compute the
+ // corresponding operator.
+ TokenKind tok;
+ if (!tokenStream.getToken(&tok))
+ return null();
+
+ ParseNodeKind pnk;
+ if (tok == TOK_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();
+ // Report an error for unary expressions on the LHS of **.
+ if (tok == TOK_POW && handler.isUnparenthesizedUnaryExpression(pn)) {
+ report(ParseError, false, null(), JSMSG_BAD_POW_LEFTSIDE);
+ return null();
+ }
+ pnk = BinaryOpTokenKindToParseNodeKind(tok);
+ } else {
+ tok = TOK_EOF;
+ pnk = PNK_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];
+ JSOp combiningOp = BinaryOpParseNodeKindToJSOp(combiningPnk);
+ pn = handler.appendOrCreateList(combiningPnk, nodeStack[depth], pn, pc, combiningOp);
+ if (!pn)
+ return pn;
+ }
+
+ if (pnk == PNK_LIMIT)
+ break;
+
+ nodeStack[depth] = pn;
+ kindStack[depth] = pnk;
+ depth++;
+ MOZ_ASSERT(depth <= PRECEDENCE_CLASSES);
+ }
+
+ MOZ_ASSERT(depth == 0);
+ return pn;
+}
+
+template <typename ParseHandler>
+MOZ_ALWAYS_INLINE typename ParseHandler::Node
+Parser<ParseHandler>::condExpr1(InHandling inHandling, YieldHandling yieldHandling,
+ TripledotHandling tripledotHandling,
+ PossibleError* possibleError,
+ InvokedPrediction invoked /* = PredictUninvoked */)
+{
+ Node condition = orExpr1(inHandling, yieldHandling, tripledotHandling, possibleError, invoked);
+
+ if (!condition || !tokenStream.isCurrentTokenType(TOK_HOOK))
+ return condition;
+
+ Node thenExpr = assignExpr(InAllowed, yieldHandling, TripledotProhibited);
+ if (!thenExpr)
+ return null();
+
+ MUST_MATCH_TOKEN(TOK_COLON, JSMSG_COLON_IN_COND);
+
+ Node elseExpr = assignExpr(inHandling, yieldHandling, TripledotProhibited);
+ if (!elseExpr)
+ return null();
+
+ // Advance to the next token; the caller is responsible for interpreting it.
+ TokenKind ignored;
+ if (!tokenStream.getToken(&ignored))
+ return null();
+ return handler.newConditional(condition, thenExpr, elseExpr);
+}
+
+template <typename ParseHandler>
+bool
+Parser<ParseHandler>::checkAndMarkAsAssignmentLhs(Node target, AssignmentFlavor flavor,
+ PossibleError* possibleError)
+{
+ MOZ_ASSERT(flavor != KeyedDestructuringAssignment,
+ "destructuring must use special checking/marking code, not "
+ "this method");
+
+ if (handler.isUnparenthesizedDestructuringPattern(target)) {
+ if (flavor == CompoundAssignment) {
+ report(ParseError, false, null(), JSMSG_BAD_DESTRUCT_ASS);
+ return false;
+ }
+
+ return checkDestructuringPattern(target, Nothing(), possibleError);
+ }
+
+ // All other permitted targets are simple.
+ if (!reportIfNotValidSimpleAssignmentTarget(target, flavor))
+ return false;
+
+ if (handler.isPropertyAccess(target))
+ return true;
+
+ if (handler.isNameAnyParentheses(target)) {
+ // The arguments/eval identifiers are simple in non-strict mode code,
+ // but warn to discourage use nonetheless.
+ if (!reportIfArgumentsEvalTarget(target))
+ return false;
+
+ handler.adjustGetToSet(target);
+ return true;
+ }
+
+ MOZ_ASSERT(handler.isFunctionCall(target));
+ return checkAssignmentToCall(target, JSMSG_BAD_LEFTSIDE_OF_ASS);
+}
+
+class AutoClearInDestructuringDecl
+{
+ ParseContext* pc_;
+ Maybe<DeclarationKind> saved_;
+
+ public:
+ explicit AutoClearInDestructuringDecl(ParseContext* pc)
+ : pc_(pc),
+ saved_(pc->inDestructuringDecl)
+ {
+ pc->inDestructuringDecl = Nothing();
+ if (saved_ && *saved_ == DeclarationKind::FormalParameter)
+ pc->functionBox()->hasParameterExprs = true;
+ }
+
+ ~AutoClearInDestructuringDecl() {
+ pc_->inDestructuringDecl = saved_;
+ }
+};
+
+template <typename ParseHandler>
+typename ParseHandler::Node
+Parser<ParseHandler>::assignExpr(InHandling inHandling, YieldHandling yieldHandling,
+ TripledotHandling tripledotHandling,
+ PossibleError* possibleError /* = nullptr */,
+ InvokedPrediction invoked /* = PredictUninvoked */)
+{
+ JS_CHECK_RECURSION(context, 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(), condExpr1(), orExpr1(), unaryExpr(), memberExpr(), and
+ // primaryExpr().
+
+ TokenKind tt;
+ if (!tokenStream.getToken(&tt, TokenStream::Operand))
+ return null();
+
+ bool endsExpr;
+
+ if (tt == TOK_NAME) {
+ if (!tokenStream.nextTokenEndsExpr(&endsExpr))
+ return null();
+ if (endsExpr) {
+ Rooted<PropertyName*> name(context, identifierReference(yieldHandling));
+ if (!name)
+ return null();
+
+ return identifierReference(name);
+ }
+ }
+
+ if (tt == TOK_NUMBER) {
+ if (!tokenStream.nextTokenEndsExpr(&endsExpr))
+ return null();
+ if (endsExpr)
+ return newNumber(tokenStream.currentToken());
+ }
+
+ if (tt == TOK_STRING) {
+ if (!tokenStream.nextTokenEndsExpr(&endsExpr))
+ return null();
+ if (endsExpr)
+ return stringLiteral();
+ }
+
+ if (tt == TOK_YIELD && yieldExpressionsSupported())
+ return yieldExpression(inHandling);
+
+ bool maybeAsyncArrow = false;
+ if (tt == TOK_NAME && tokenStream.currentName() == context->names().async) {
+ TokenKind nextSameLine = TOK_EOF;
+ if (!tokenStream.peekTokenSameLine(&nextSameLine))
+ return null();
+
+ if (nextSameLine == TOK_NAME || nextSameLine == TOK_YIELD)
+ maybeAsyncArrow = true;
+ }
+
+ tokenStream.ungetToken();
+
+ // Save the tokenizer state in case we find an arrow function and have to
+ // rewind.
+ TokenStream::Position start(keepAtoms);
+ tokenStream.tell(&start);
+
+ PossibleError possibleErrorInner(*this);
+ Node lhs;
+ if (maybeAsyncArrow) {
+ tokenStream.consumeKnownToken(TOK_NAME, TokenStream::Operand);
+ MOZ_ASSERT(tokenStream.currentName() == context->names().async);
+
+ TokenKind tt;
+ if (!tokenStream.getToken(&tt))
+ return null();
+ MOZ_ASSERT(tt == TOK_NAME || tt == TOK_YIELD);
+
+ // Check yield validity here.
+ RootedPropertyName name(context, bindingIdentifier(yieldHandling));
+ if (!name)
+ return null();
+
+ if (!tokenStream.getToken(&tt))
+ return null();
+ if (tt != TOK_ARROW) {
+ report(ParseError, false, null(), JSMSG_UNEXPECTED_TOKEN,
+ "'=>' after argument list", TokenKindToDesc(tt));
+
+ return null();
+ }
+ } else {
+ lhs = condExpr1(inHandling, yieldHandling, tripledotHandling, &possibleErrorInner, invoked);
+ if (!lhs) {
+ return null();
+ }
+ }
+
+ ParseNodeKind kind;
+ JSOp op;
+ switch (tokenStream.currentToken().type) {
+ case TOK_ASSIGN: kind = PNK_ASSIGN; op = JSOP_NOP; break;
+ case TOK_ADDASSIGN: kind = PNK_ADDASSIGN; op = JSOP_ADD; break;
+ case TOK_SUBASSIGN: kind = PNK_SUBASSIGN; op = JSOP_SUB; break;
+ case TOK_BITORASSIGN: kind = PNK_BITORASSIGN; op = JSOP_BITOR; break;
+ case TOK_BITXORASSIGN: kind = PNK_BITXORASSIGN; op = JSOP_BITXOR; break;
+ case TOK_BITANDASSIGN: kind = PNK_BITANDASSIGN; op = JSOP_BITAND; break;
+ case TOK_LSHASSIGN: kind = PNK_LSHASSIGN; op = JSOP_LSH; break;
+ case TOK_RSHASSIGN: kind = PNK_RSHASSIGN; op = JSOP_RSH; break;
+ case TOK_URSHASSIGN: kind = PNK_URSHASSIGN; op = JSOP_URSH; break;
+ case TOK_MULASSIGN: kind = PNK_MULASSIGN; op = JSOP_MUL; break;
+ case TOK_DIVASSIGN: kind = PNK_DIVASSIGN; op = JSOP_DIV; break;
+ case TOK_MODASSIGN: kind = PNK_MODASSIGN; op = JSOP_MOD; break;
+ case TOK_POWASSIGN: kind = PNK_POWASSIGN; op = JSOP_POW; break;
+
+ case TOK_ARROW: {
+
+ // A line terminator between ArrowParameters and the => should trigger a SyntaxError.
+ tokenStream.ungetToken();
+ TokenKind next;
+ if (!tokenStream.peekTokenSameLine(&next))
+ return null();
+ MOZ_ASSERT(next == TOK_ARROW || next == TOK_EOL);
+
+ if (next != TOK_ARROW) {
+ report(ParseError, false, null(), JSMSG_LINE_BREAK_BEFORE_ARROW);
+ return null();
+ }
+ tokenStream.consumeKnownToken(TOK_ARROW);
+
+ bool isBlock = false;
+ if (!tokenStream.peekToken(&next, TokenStream::Operand))
+ return null();
+ if (next == TOK_LC)
+ isBlock = true;
+
+ tokenStream.seek(start);
+
+ if (!tokenStream.peekToken(&next, TokenStream::Operand))
+ return null();
+
+ GeneratorKind generatorKind = NotGenerator;
+ FunctionAsyncKind asyncKind = SyncFunction;
+
+ if (next == TOK_NAME) {
+ tokenStream.consumeKnownToken(next, TokenStream::Operand);
+
+ if (tokenStream.currentName() == context->names().async) {
+ TokenKind nextSameLine = TOK_EOF;
+ if (!tokenStream.peekTokenSameLine(&nextSameLine))
+ return null();
+
+ if (nextSameLine == TOK_ARROW) {
+ tokenStream.ungetToken();
+ } else {
+ generatorKind = StarGenerator;
+ asyncKind = AsyncFunction;
+ }
+ } else {
+ tokenStream.ungetToken();
+ }
+ }
+
+ Node arrowFunc = functionDefinition(inHandling, yieldHandling, nullptr,
+ Arrow, generatorKind, asyncKind);
+ if (!arrowFunc)
+ return null();
+
+ if (isBlock) {
+ // This arrow function could be a non-trailing member of a comma
+ // expression or a semicolon terminating a full expression. If so,
+ // the next token is that comma/semicolon, gotten with None:
+ //
+ // a => {}, b; // as if (a => {}), b;
+ // a => {};
+ //
+ // But if this arrow function ends a statement, ASI permits the
+ // next token to start an expression statement. In that case the
+ // next token must be gotten as Operand:
+ //
+ // a => {} // complete expression statement
+ // /x/g; // regular expression as a statement, *not* division
+ //
+ // Getting the second case right requires the first token-peek
+ // after the arrow function use Operand, and that peek must occur
+ // before Parser::expr() looks for a comma. Do so here, then
+ // immediately add the modifier exception needed for the first
+ // case.
+ //
+ // Note that the second case occurs *only* if the arrow function
+ // has block body. An arrow function not ending in such, ends in
+ // another AssignmentExpression that we can inductively assume was
+ // peeked consistently.
+ TokenKind ignored;
+ if (!tokenStream.peekToken(&ignored, TokenStream::Operand))
+ return null();
+ tokenStream.addModifierException(TokenStream::NoneIsOperand);
+ }
+ return arrowFunc;
+ }
+
+ default:
+ MOZ_ASSERT(!tokenStream.isCurrentTokenAssignment());
+ if (!possibleError) {
+ if (!possibleErrorInner.checkForExpressionError())
+ return null();
+ } else {
+ possibleErrorInner.transferErrorsTo(possibleError);
+ }
+ tokenStream.ungetToken();
+ return lhs;
+ }
+
+ AssignmentFlavor flavor = kind == PNK_ASSIGN ? PlainAssignment : CompoundAssignment;
+ if (!checkAndMarkAsAssignmentLhs(lhs, flavor, &possibleErrorInner))
+ return null();
+ if (!possibleErrorInner.checkForExpressionError())
+ return null();
+
+ Node rhs;
+ {
+ AutoClearInDestructuringDecl autoClear(pc);
+ rhs = assignExpr(inHandling, yieldHandling, TripledotProhibited);
+ if (!rhs)
+ return null();
+ }
+
+ return handler.newAssignment(kind, lhs, rhs, op);
+}
+
+template <typename ParseHandler>
+bool
+Parser<ParseHandler>::isValidSimpleAssignmentTarget(Node node,
+ FunctionCallBehavior behavior /* = ForbidAssignmentToFunctionCalls */)
+{
+ // Note that this method implements *only* a boolean test. Reporting an
+ // error for the various syntaxes that fail this, and warning for the
+ // various syntaxes that "pass" this but should not, occurs elsewhere.
+
+ if (handler.isNameAnyParentheses(node)) {
+ if (!pc->sc()->strict())
+ return true;
+
+ return !handler.nameIsArgumentsEvalAnyParentheses(node, context);
+ }
+
+ if (handler.isPropertyAccess(node))
+ return true;
+
+ if (behavior == PermitAssignmentToFunctionCalls) {
+ if (handler.isFunctionCall(node))
+ return true;
+ }
+
+ return false;
+}
+
+template <typename ParseHandler>
+bool
+Parser<ParseHandler>::reportIfArgumentsEvalTarget(Node nameNode)
+{
+ const char* chars = handler.nameIsArgumentsEvalAnyParentheses(nameNode, context);
+ if (!chars)
+ return true;
+
+ if (!report(ParseStrictError, pc->sc()->strict(), nameNode, JSMSG_BAD_STRICT_ASSIGN, chars))
+ return false;
+
+ MOZ_ASSERT(!pc->sc()->strict(),
+ "an error should have been reported if this was strict mode "
+ "code");
+ return true;
+}
+
+template <typename ParseHandler>
+bool
+Parser<ParseHandler>::reportIfNotValidSimpleAssignmentTarget(Node target, AssignmentFlavor flavor)
+{
+ FunctionCallBehavior behavior = flavor == KeyedDestructuringAssignment
+ ? ForbidAssignmentToFunctionCalls
+ : PermitAssignmentToFunctionCalls;
+ if (isValidSimpleAssignmentTarget(target, behavior))
+ return true;
+
+ if (handler.isNameAnyParentheses(target)) {
+ // Use a special error if the target is arguments/eval. This ensures
+ // targeting these names is consistently a SyntaxError (which error numbers
+ // below don't guarantee) while giving us a nicer error message.
+ if (!reportIfArgumentsEvalTarget(target))
+ return false;
+ }
+
+ unsigned errnum = 0;
+ const char* extra = nullptr;
+
+ switch (flavor) {
+ case IncrementAssignment:
+ errnum = JSMSG_BAD_OPERAND;
+ extra = "increment";
+ break;
+
+ case DecrementAssignment:
+ errnum = JSMSG_BAD_OPERAND;
+ extra = "decrement";
+ break;
+
+ case KeyedDestructuringAssignment:
+ errnum = JSMSG_BAD_DESTRUCT_TARGET;
+ break;
+
+ case PlainAssignment:
+ case CompoundAssignment:
+ errnum = JSMSG_BAD_LEFTSIDE_OF_ASS;
+ break;
+
+ case ForInOrOfTarget:
+ errnum = JSMSG_BAD_FOR_LEFTSIDE;
+ break;
+ }
+
+ report(ParseError, pc->sc()->strict(), target, errnum, extra);
+ return false;
+}
+
+template <typename ParseHandler>
+bool
+Parser<ParseHandler>::checkAndMarkAsIncOperand(Node target, AssignmentFlavor flavor)
+{
+ MOZ_ASSERT(flavor == IncrementAssignment || flavor == DecrementAssignment);
+
+ // Check.
+ if (!reportIfNotValidSimpleAssignmentTarget(target, flavor))
+ return false;
+
+ // Mark.
+ if (handler.isNameAnyParentheses(target)) {
+ // Assignment to arguments/eval is allowed outside strict mode code,
+ // but it's dodgy. Report a strict warning (error, if werror was set).
+ if (!reportIfArgumentsEvalTarget(target))
+ return false;
+ } else if (handler.isFunctionCall(target)) {
+ if (!checkAssignmentToCall(target, JSMSG_BAD_INCOP_OPERAND))
+ return false;
+ }
+ return true;
+}
+
+template <typename ParseHandler>
+typename ParseHandler::Node
+Parser<ParseHandler>::unaryOpExpr(YieldHandling yieldHandling, ParseNodeKind kind, JSOp op,
+ uint32_t begin)
+{
+ Node kid = unaryExpr(yieldHandling, TripledotProhibited);
+ if (!kid)
+ return null();
+ return handler.newUnary(kind, op, begin, kid);
+}
+
+template <typename ParseHandler>
+typename ParseHandler::Node
+Parser<ParseHandler>::unaryExpr(YieldHandling yieldHandling, TripledotHandling tripledotHandling,
+ PossibleError* possibleError /* = nullptr */,
+ InvokedPrediction invoked /* = PredictUninvoked */)
+{
+ JS_CHECK_RECURSION(context, return null());
+
+ TokenKind tt;
+ if (!tokenStream.getToken(&tt, TokenStream::Operand))
+ return null();
+ uint32_t begin = pos().begin;
+ switch (tt) {
+ case TOK_VOID:
+ return unaryOpExpr(yieldHandling, PNK_VOID, JSOP_VOID, begin);
+ case TOK_NOT:
+ return unaryOpExpr(yieldHandling, PNK_NOT, JSOP_NOT, begin);
+ case TOK_BITNOT:
+ return unaryOpExpr(yieldHandling, PNK_BITNOT, JSOP_BITNOT, begin);
+ case TOK_ADD:
+ return unaryOpExpr(yieldHandling, PNK_POS, JSOP_POS, begin);
+ case TOK_SUB:
+ return unaryOpExpr(yieldHandling, PNK_NEG, JSOP_NEG, begin);
+
+ case TOK_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 TOK_INC:
+ case TOK_DEC:
+ {
+ TokenKind tt2;
+ if (!tokenStream.getToken(&tt2, TokenStream::Operand))
+ return null();
+ Node pn2 = memberExpr(yieldHandling, TripledotProhibited, tt2);
+ if (!pn2)
+ return null();
+ AssignmentFlavor flavor = (tt == TOK_INC) ? IncrementAssignment : DecrementAssignment;
+ if (!checkAndMarkAsIncOperand(pn2, flavor))
+ return null();
+ return handler.newUpdate((tt == TOK_INC) ? PNK_PREINCREMENT : PNK_PREDECREMENT,
+ begin,
+ pn2);
+ }
+
+ case TOK_DELETE: {
+ Node expr = unaryExpr(yieldHandling, TripledotProhibited);
+ if (!expr)
+ return null();
+
+ // Per spec, deleting any unary expression is valid -- it simply
+ // returns true -- except for one case that is illegal in strict mode.
+ if (handler.isNameAnyParentheses(expr)) {
+ if (!report(ParseStrictError, pc->sc()->strict(), expr, JSMSG_DEPRECATED_DELETE_OPERAND))
+ return null();
+ pc->sc()->setBindingsAccessedDynamically();
+ }
+
+ return handler.newDelete(begin, expr);
+ }
+
+ case TOK_AWAIT: {
+ if (!pc->isAsync()) {
+ // TOK_AWAIT can be returned in module, even if it's not inside
+ // async function.
+ report(ParseError, false, null(), JSMSG_RESERVED_ID, "await");
+ return null();
+ }
+
+ Node kid = unaryExpr(yieldHandling, tripledotHandling, possibleError, invoked);
+ if (!kid)
+ return null();
+ pc->lastAwaitOffset = begin;
+ return newAwaitExpression(begin, kid);
+ }
+
+ default: {
+ Node pn = memberExpr(yieldHandling, tripledotHandling, tt, /* allowCallSyntax = */ true,
+ possibleError, invoked);
+ if (!pn)
+ return null();
+
+ /* Don't look across a newline boundary for a postfix incop. */
+ if (!tokenStream.peekTokenSameLine(&tt))
+ return null();
+ if (tt == TOK_INC || tt == TOK_DEC) {
+ tokenStream.consumeKnownToken(tt);
+ AssignmentFlavor flavor = (tt == TOK_INC) ? IncrementAssignment : DecrementAssignment;
+ if (!checkAndMarkAsIncOperand(pn, flavor))
+ return null();
+ return handler.newUpdate((tt == TOK_INC) ? PNK_POSTINCREMENT : PNK_POSTDECREMENT,
+ begin,
+ pn);
+ }
+ return pn;
+ }
+ }
+}
+
+
+/*** Comprehensions *******************************************************************************
+ *
+ * We currently support two flavors of comprehensions, all deprecated:
+ *
+ * [for (V of OBJ) if (COND) EXPR] // ES6-era array comprehension
+ * (for (V of OBJ) if (COND) EXPR) // ES6-era generator expression
+ *
+ * (These flavors are called "ES6-era" because they were in ES6 draft
+ * specifications for a while. Shortly after this syntax was implemented in SM,
+ * TC39 decided to drop it.)
+ */
+
+template <typename ParseHandler>
+typename ParseHandler::Node
+Parser<ParseHandler>::generatorComprehensionLambda(unsigned begin)
+{
+ Node genfn = handler.newFunctionDefinition();
+ if (!genfn)
+ return null();
+ handler.setOp(genfn, JSOP_LAMBDA);
+
+ ParseContext* outerpc = pc;
+
+ // If we are off the main thread, the generator meta-objects have
+ // already been created by js::StartOffThreadParseScript, so cx will not
+ // be necessary.
+ RootedObject proto(context);
+ JSContext* cx = context->maybeJSContext();
+ proto = GlobalObject::getOrCreateStarGeneratorFunctionPrototype(cx, context->global());
+ if (!proto)
+ return null();
+
+ RootedFunction fun(context, newFunction(/* atom = */ nullptr, Expression,
+ StarGenerator, SyncFunction, proto));
+ if (!fun)
+ return null();
+
+ // Create box for fun->object early to root it.
+ Directives directives(/* strict = */ outerpc->sc()->strict());
+ FunctionBox* genFunbox = newFunctionBox(genfn, fun, directives, StarGenerator, SyncFunction,
+ /* tryAnnexB = */ false);
+ if (!genFunbox)
+ return null();
+ genFunbox->isGenexpLambda = true;
+ genFunbox->initWithEnclosingParseContext(outerpc, Expression);
+
+ ParseContext genpc(this, genFunbox, /* newDirectives = */ nullptr);
+ if (!genpc.init())
+ return null();
+ genpc.functionScope().useAsVarScope(&genpc);
+
+ /*
+ * We assume conservatively that any deoptimization flags in pc->sc()
+ * come from the kid. So we propagate these flags into genfn. For code
+ * simplicity we also do not detect if the flags were only set in the
+ * kid and could be removed from pc->sc().
+ */
+ genFunbox->anyCxFlags = outerpc->sc()->anyCxFlags;
+
+ if (!declareDotGeneratorName())
+ return null();
+
+ Node body = handler.newStatementList(TokenPos(begin, pos().end));
+ if (!body)
+ return null();
+
+ Node comp = comprehension(StarGenerator);
+ if (!comp)
+ return null();
+
+ MUST_MATCH_TOKEN(TOK_RP, JSMSG_PAREN_IN_PAREN);
+
+ handler.setBeginPosition(comp, begin);
+ handler.setEndPosition(comp, pos().end);
+ handler.addStatementToList(body, comp);
+ handler.setEndPosition(body, pos().end);
+ handler.setBeginPosition(genfn, begin);
+ handler.setEndPosition(genfn, pos().end);
+
+ Node generator = newDotGeneratorName();
+ if (!generator)
+ return null();
+ if (!handler.prependInitialYield(body, generator))
+ return null();
+
+ if (!propagateFreeNamesAndMarkClosedOverBindings(pc->varScope()))
+ return null();
+ if (!finishFunction())
+ return null();
+ if (!leaveInnerFunction(outerpc))
+ return null();
+
+ // Note that if we ever start syntax-parsing generators, we will also
+ // need to propagate the closed-over variable set to the inner
+ // lazyscript.
+ if (!handler.setComprehensionLambdaBody(genfn, body))
+ return null();
+
+ return genfn;
+}
+
+template <typename ParseHandler>
+typename ParseHandler::Node
+Parser<ParseHandler>::comprehensionFor(GeneratorKind comprehensionKind)
+{
+ MOZ_ASSERT(tokenStream.isCurrentTokenType(TOK_FOR));
+
+ uint32_t begin = pos().begin;
+
+ MUST_MATCH_TOKEN(TOK_LP, JSMSG_PAREN_AFTER_FOR);
+
+ // FIXME: Destructuring binding (bug 980828).
+
+ MUST_MATCH_TOKEN(TOK_NAME, JSMSG_NO_VARIABLE_NAME);
+ RootedPropertyName name(context, tokenStream.currentName());
+ if (name == context->names().let) {
+ report(ParseError, false, null(), JSMSG_LET_COMP_BINDING);
+ return null();
+ }
+ TokenPos namePos = pos();
+ Node lhs = newName(name);
+ if (!lhs)
+ return null();
+ bool matched;
+ if (!tokenStream.matchContextualKeyword(&matched, context->names().of))
+ return null();
+ if (!matched) {
+ report(ParseError, false, null(), JSMSG_OF_AFTER_FOR_NAME);
+ return null();
+ }
+
+ Node rhs = assignExpr(InAllowed, YieldIsKeyword, TripledotProhibited);
+ if (!rhs)
+ return null();
+
+ MUST_MATCH_TOKEN(TOK_RP, JSMSG_PAREN_AFTER_FOR_OF_ITERABLE);
+
+ TokenPos headPos(begin, pos().end);
+
+ ParseContext::Scope scope(this);
+ if (!scope.init(pc))
+ return null();
+
+ {
+ // 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);
+ if (!noteDeclaredName(name, DeclarationKind::Let, namePos))
+ return null();
+ }
+
+ Node decls = handler.newComprehensionBinding(lhs);
+ if (!decls)
+ return null();
+
+ Node tail = comprehensionTail(comprehensionKind);
+ if (!tail)
+ return null();
+
+ // Finish the lexical scope after parsing the tail.
+ Node lexicalScope = finishLexicalScope(scope, decls);
+ if (!lexicalScope)
+ return null();
+
+ Node head = handler.newForInOrOfHead(PNK_FOROF, lexicalScope, rhs, headPos);
+ if (!head)
+ return null();
+
+ return handler.newComprehensionFor(begin, head, tail);
+}
+
+template <typename ParseHandler>
+typename ParseHandler::Node
+Parser<ParseHandler>::comprehensionIf(GeneratorKind comprehensionKind)
+{
+ MOZ_ASSERT(tokenStream.isCurrentTokenType(TOK_IF));
+
+ uint32_t begin = pos().begin;
+
+ MUST_MATCH_TOKEN(TOK_LP, JSMSG_PAREN_BEFORE_COND);
+ Node cond = assignExpr(InAllowed, YieldIsKeyword, TripledotProhibited);
+ if (!cond)
+ return null();
+ MUST_MATCH_TOKEN(TOK_RP, JSMSG_PAREN_AFTER_COND);
+
+ /* Check for (a = b) and warn about possible (a == b) mistype. */
+ if (handler.isUnparenthesizedAssignment(cond)) {
+ if (!report(ParseExtraWarning, false, null(), JSMSG_EQUAL_AS_ASSIGN))
+ return null();
+ }
+
+ Node then = comprehensionTail(comprehensionKind);
+ if (!then)
+ return null();
+
+ return handler.newIfStatement(begin, cond, then, null());
+}
+
+template <typename ParseHandler>
+typename ParseHandler::Node
+Parser<ParseHandler>::comprehensionTail(GeneratorKind comprehensionKind)
+{
+ JS_CHECK_RECURSION(context, return null());
+
+ bool matched;
+ if (!tokenStream.matchToken(&matched, TOK_FOR, TokenStream::Operand))
+ return null();
+ if (matched)
+ return comprehensionFor(comprehensionKind);
+
+ if (!tokenStream.matchToken(&matched, TOK_IF, TokenStream::Operand))
+ return null();
+ if (matched)
+ return comprehensionIf(comprehensionKind);
+
+ uint32_t begin = pos().begin;
+
+ Node bodyExpr = assignExpr(InAllowed, YieldIsKeyword, TripledotProhibited);
+ if (!bodyExpr)
+ return null();
+
+ if (comprehensionKind == NotGenerator)
+ return handler.newArrayPush(begin, bodyExpr);
+
+ MOZ_ASSERT(comprehensionKind == StarGenerator);
+ Node yieldExpr = newYieldExpression(begin, bodyExpr);
+ if (!yieldExpr)
+ return null();
+ yieldExpr = handler.parenthesize(yieldExpr);
+
+ return handler.newExprStatement(yieldExpr, pos().end);
+}
+
+// Parse an ES6-era generator or array comprehension, starting at the first
+// `for`. The caller is responsible for matching the ending TOK_RP or TOK_RB.
+template <typename ParseHandler>
+typename ParseHandler::Node
+Parser<ParseHandler>::comprehension(GeneratorKind comprehensionKind)
+{
+ MOZ_ASSERT(tokenStream.isCurrentTokenType(TOK_FOR));
+
+ uint32_t startYieldOffset = pc->lastYieldOffset;
+
+ Node body = comprehensionFor(comprehensionKind);
+ if (!body)
+ return null();
+
+ if (comprehensionKind != NotGenerator && pc->lastYieldOffset != startYieldOffset) {
+ reportWithOffset(ParseError, false, pc->lastYieldOffset,
+ JSMSG_BAD_GENEXP_BODY, js_yield_str);
+ return null();
+ }
+
+ return body;
+}
+
+template <typename ParseHandler>
+typename ParseHandler::Node
+Parser<ParseHandler>::arrayComprehension(uint32_t begin)
+{
+ Node inner = comprehension(NotGenerator);
+ if (!inner)
+ return null();
+
+ MUST_MATCH_TOKEN(TOK_RB, JSMSG_BRACKET_AFTER_ARRAY_COMPREHENSION);
+
+ Node comp = handler.newList(PNK_ARRAYCOMP, inner);
+ if (!comp)
+ return null();
+
+ handler.setBeginPosition(comp, begin);
+ handler.setEndPosition(comp, pos().end);
+
+ return comp;
+}
+
+template <typename ParseHandler>
+typename ParseHandler::Node
+Parser<ParseHandler>::generatorComprehension(uint32_t begin)
+{
+ MOZ_ASSERT(tokenStream.isCurrentTokenType(TOK_FOR));
+
+ // We have no problem parsing generator comprehensions inside lazy
+ // functions, but the bytecode emitter currently can't handle them that way,
+ // because when it goes to emit the code for the inner generator function,
+ // it expects outer functions to have non-lazy scripts.
+ if (!abortIfSyntaxParser())
+ return null();
+
+ Node genfn = generatorComprehensionLambda(begin);
+ if (!genfn)
+ return null();
+
+ Node result = handler.newList(PNK_GENEXP, genfn, JSOP_CALL);
+ if (!result)
+ return null();
+ handler.setBeginPosition(result, begin);
+ handler.setEndPosition(result, pos().end);
+
+ return result;
+}
+
+template <typename ParseHandler>
+typename ParseHandler::Node
+Parser<ParseHandler>::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) {
+ reportWithOffset(ParseError, false, pc->lastYieldOffset, JSMSG_YIELD_IN_DEFAULT);
+ return null();
+ }
+ if (pc->lastAwaitOffset != startAwaitOffset) {
+ reportWithOffset(ParseError, false, pc->lastAwaitOffset, JSMSG_AWAIT_IN_DEFAULT);
+ return null();
+ }
+ }
+ return res;
+}
+
+template <typename ParseHandler>
+bool
+Parser<ParseHandler>::argumentList(YieldHandling yieldHandling, Node listNode, bool* isSpread,
+ PossibleError* possibleError /* = nullptr */)
+{
+ bool matched;
+ if (!tokenStream.matchToken(&matched, TOK_RP, TokenStream::Operand))
+ return false;
+ if (matched) {
+ handler.setEndPosition(listNode, pos().end);
+ return true;
+ }
+
+ while (true) {
+ bool spread = false;
+ uint32_t begin = 0;
+ if (!tokenStream.matchToken(&matched, TOK_TRIPLEDOT, TokenStream::Operand))
+ return false;
+ if (matched) {
+ spread = true;
+ begin = pos().begin;
+ *isSpread = true;
+ }
+
+ Node argNode = assignExpr(InAllowed, yieldHandling, TripledotProhibited, possibleError);
+ if (!argNode)
+ return false;
+ if (spread) {
+ argNode = handler.newSpread(begin, argNode);
+ if (!argNode)
+ return false;
+ }
+
+ handler.addList(listNode, argNode);
+
+ bool matched;
+ if (!tokenStream.matchToken(&matched, TOK_COMMA))
+ return false;
+ if (!matched)
+ break;
+
+ TokenKind tt;
+ if (!tokenStream.peekToken(&tt, TokenStream::Operand))
+ return null();
+ if (tt == TOK_RP) {
+ tokenStream.addModifierException(TokenStream::NoneIsOperand);
+ break;
+ }
+ }
+
+ TokenKind tt;
+ if (!tokenStream.getToken(&tt))
+ return false;
+ if (tt != TOK_RP) {
+ report(ParseError, false, null(), JSMSG_PAREN_AFTER_ARGS);
+ return false;
+ }
+ handler.setEndPosition(listNode, pos().end);
+ return true;
+}
+
+template <typename ParseHandler>
+bool
+Parser<ParseHandler>::checkAndMarkSuperScope()
+{
+ if (!pc->sc()->allowSuperProperty())
+ return false;
+ pc->setSuperScopeNeedsHomeObject();
+ return true;
+}
+
+template <typename ParseHandler>
+typename ParseHandler::Node
+Parser<ParseHandler>::memberExpr(YieldHandling yieldHandling, TripledotHandling tripledotHandling,
+ TokenKind tt, bool allowCallSyntax /* = true */,
+ PossibleError* possibleError /* = nullptr */,
+ InvokedPrediction invoked /* = PredictUninvoked */)
+{
+ MOZ_ASSERT(tokenStream.isCurrentTokenType(tt));
+
+ Node lhs;
+
+ JS_CHECK_RECURSION(context, return null());
+
+ /* Check for new expression first. */
+ if (tt == TOK_NEW) {
+ uint32_t newBegin = pos().begin;
+ // Make sure this wasn't a |new.target| in disguise.
+ Node newTarget;
+ if (!tryNewTarget(newTarget))
+ return null();
+ if (newTarget) {
+ lhs = newTarget;
+ } else {
+ lhs = handler.newList(PNK_NEW, newBegin, JSOP_NEW);
+ if (!lhs)
+ return null();
+
+ // Gotten by tryNewTarget
+ tt = tokenStream.currentToken().type;
+ Node ctorExpr = memberExpr(yieldHandling, TripledotProhibited, tt,
+ /* allowCallSyntax = */ false,
+ /* possibleError = */ nullptr, PredictInvoked);
+ if (!ctorExpr)
+ return null();
+
+ handler.addList(lhs, ctorExpr);
+
+ bool matched;
+ if (!tokenStream.matchToken(&matched, TOK_LP))
+ return null();
+ if (matched) {
+ bool isSpread = false;
+ if (!argumentList(yieldHandling, lhs, &isSpread))
+ return null();
+ if (isSpread)
+ handler.setOp(lhs, JSOP_SPREADNEW);
+ }
+ }
+ } else if (tt == TOK_SUPER) {
+ Node thisName = newThisName();
+ if (!thisName)
+ return null();
+ lhs = handler.newSuperBase(thisName, pos());
+ if (!lhs)
+ return null();
+ } else {
+ lhs = primaryExpr(yieldHandling, tripledotHandling, tt, possibleError, invoked);
+ if (!lhs)
+ return null();
+ }
+
+ MOZ_ASSERT_IF(handler.isSuperBase(lhs), tokenStream.isCurrentTokenType(TOK_SUPER));
+
+ while (true) {
+ if (!tokenStream.getToken(&tt))
+ return null();
+ if (tt == TOK_EOF)
+ break;
+
+ Node nextMember;
+ if (tt == TOK_DOT) {
+ if (!tokenStream.getToken(&tt, TokenStream::KeywordIsName))
+ return null();
+ if (tt == TOK_NAME) {
+ PropertyName* field = tokenStream.currentName();
+ if (handler.isSuperBase(lhs) && !checkAndMarkSuperScope()) {
+ report(ParseError, false, null(), JSMSG_BAD_SUPERPROP, "property");
+ return null();
+ }
+ nextMember = handler.newPropertyAccess(lhs, field, pos().end);
+ if (!nextMember)
+ return null();
+ } else {
+ report(ParseError, false, null(), JSMSG_NAME_AFTER_DOT);
+ return null();
+ }
+ } else if (tt == TOK_LB) {
+ Node propExpr = expr(InAllowed, yieldHandling, TripledotProhibited);
+ if (!propExpr)
+ return null();
+
+ MUST_MATCH_TOKEN(TOK_RB, JSMSG_BRACKET_IN_INDEX);
+
+ if (handler.isSuperBase(lhs) && !checkAndMarkSuperScope()) {
+ report(ParseError, false, null(), JSMSG_BAD_SUPERPROP, "member");
+ return null();
+ }
+ nextMember = handler.newPropertyByValue(lhs, propExpr, pos().end);
+ if (!nextMember)
+ return null();
+ } else if ((allowCallSyntax && tt == TOK_LP) ||
+ tt == TOK_TEMPLATE_HEAD ||
+ tt == TOK_NO_SUBS_TEMPLATE)
+ {
+ if (handler.isSuperBase(lhs)) {
+ if (!pc->sc()->allowSuperCall()) {
+ report(ParseError, false, null(), JSMSG_BAD_SUPERCALL);
+ return null();
+ }
+
+ if (tt != TOK_LP) {
+ report(ParseError, false, null(), JSMSG_BAD_SUPER);
+ return null();
+ }
+
+ nextMember = handler.newList(PNK_SUPERCALL, lhs, JSOP_SUPERCALL);
+ if (!nextMember)
+ return null();
+
+ // 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;
+ if (!argumentList(yieldHandling, nextMember, &isSpread))
+ return null();
+
+ if (isSpread)
+ handler.setOp(nextMember, JSOP_SPREADSUPERCALL);
+
+ Node thisName = newThisName();
+ if (!thisName)
+ return null();
+
+ nextMember = handler.newSetThis(thisName, nextMember);
+ if (!nextMember)
+ return null();
+ } else {
+ if (options().selfHostingMode && handler.isPropertyAccess(lhs)) {
+ report(ParseError, false, null(), JSMSG_SELFHOSTED_METHOD_CALL);
+ return null();
+ }
+
+ nextMember = tt == TOK_LP ? handler.newCall() : handler.newTaggedTemplate();
+ if (!nextMember)
+ return null();
+
+ JSOp op = JSOP_CALL;
+ bool maybeAsyncArrow = false;
+ if (tt == TOK_LP && handler.isNameAnyParentheses(lhs)) {
+ if (handler.nameIsEvalAnyParentheses(lhs, context)) {
+ // 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()->setHasExtensibleScope();
+
+ // 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();
+ } else if (handler.nameIsUnparenthesizedAsync(lhs, context)) {
+ // |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 (PropertyName* prop = handler.maybeDottedProperty(lhs)) {
+ // Use the JSOP_FUN{APPLY,CALL} optimizations given the
+ // right syntax.
+ if (prop == context->names().apply) {
+ op = JSOP_FUNAPPLY;
+ if (pc->isFunctionBox())
+ pc->functionBox()->usesApply = true;
+ } else if (prop == context->names().call) {
+ op = JSOP_FUNCALL;
+ }
+ }
+
+ handler.setBeginPosition(nextMember, lhs);
+ handler.addList(nextMember, lhs);
+
+ if (tt == TOK_LP) {
+ bool isSpread = false;
+ PossibleError* asyncPossibleError = maybeAsyncArrow ? possibleError : nullptr;
+ if (!argumentList(yieldHandling, nextMember, &isSpread, asyncPossibleError))
+ return null();
+ if (isSpread) {
+ if (op == JSOP_EVAL)
+ op = JSOP_SPREADEVAL;
+ else if (op == JSOP_STRICTEVAL)
+ op = JSOP_STRICTSPREADEVAL;
+ else
+ op = JSOP_SPREADCALL;
+ }
+ } else {
+ if (!taggedTemplate(yieldHandling, nextMember, tt))
+ return null();
+ }
+ handler.setOp(nextMember, op);
+ }
+ } else {
+ tokenStream.ungetToken();
+ if (handler.isSuperBase(lhs))
+ break;
+ return lhs;
+ }
+
+ lhs = nextMember;
+ }
+
+ if (handler.isSuperBase(lhs)) {
+ report(ParseError, false, null(), JSMSG_BAD_SUPER);
+ return null();
+ }
+
+ return lhs;
+}
+
+template <typename ParseHandler>
+typename ParseHandler::Node
+Parser<ParseHandler>::newName(PropertyName* name)
+{
+ return newName(name, pos());
+}
+
+template <typename ParseHandler>
+typename ParseHandler::Node
+Parser<ParseHandler>::newName(PropertyName* name, TokenPos pos)
+{
+ return handler.newName(name, pos, context);
+}
+
+template <typename ParseHandler>
+PropertyName*
+Parser<ParseHandler>::labelOrIdentifierReference(YieldHandling yieldHandling,
+ bool yieldTokenizedAsName)
+{
+ PropertyName* ident;
+ bool isYield;
+ const Token& tok = tokenStream.currentToken();
+ if (tok.type == TOK_NAME) {
+ MOZ_ASSERT(tok.name() != context->names().yield ||
+ tok.nameContainsEscape() ||
+ yieldTokenizedAsName,
+ "tokenizer should have treated unescaped 'yield' as TOK_YIELD");
+ MOZ_ASSERT_IF(yieldTokenizedAsName, tok.name() == context->names().yield);
+
+ ident = tok.name();
+ isYield = ident == context->names().yield;
+ } else {
+ MOZ_ASSERT(tok.type == TOK_YIELD && !yieldTokenizedAsName);
+
+ ident = context->names().yield;
+ isYield = true;
+ }
+
+ if (!isYield) {
+ if (pc->sc()->strict()) {
+ const char* badName = ident == context->names().let
+ ? "let"
+ : ident == context->names().static_
+ ? "static"
+ : nullptr;
+ if (badName) {
+ report(ParseError, false, null(), JSMSG_RESERVED_ID, badName);
+ return nullptr;
+ }
+ }
+ } else {
+ if (yieldHandling == YieldIsKeyword ||
+ pc->sc()->strict() ||
+ versionNumber() >= JSVERSION_1_7)
+ {
+ report(ParseError, false, null(), JSMSG_RESERVED_ID, "yield");
+ return nullptr;
+ }
+ }
+
+ return ident;
+}
+
+template <typename ParseHandler>
+PropertyName*
+Parser<ParseHandler>::bindingIdentifier(YieldHandling yieldHandling)
+{
+ PropertyName* ident;
+ bool isYield;
+ const Token& tok = tokenStream.currentToken();
+ if (tok.type == TOK_NAME) {
+ MOZ_ASSERT(tok.name() != context->names().yield || tok.nameContainsEscape(),
+ "tokenizer should have treated unescaped 'yield' as TOK_YIELD");
+
+ ident = tok.name();
+ isYield = ident == context->names().yield;
+ } else {
+ MOZ_ASSERT(tok.type == TOK_YIELD);
+
+ ident = context->names().yield;
+ isYield = true;
+ }
+
+ if (!isYield) {
+ if (pc->sc()->strict()) {
+ const char* badName = ident == context->names().arguments
+ ? "arguments"
+ : ident == context->names().eval
+ ? "eval"
+ : nullptr;
+ if (badName) {
+ report(ParseError, false, null(), JSMSG_BAD_STRICT_ASSIGN, badName);
+ return nullptr;
+ }
+
+ badName = ident == context->names().let
+ ? "let"
+ : ident == context->names().static_
+ ? "static"
+ : nullptr;
+ if (badName) {
+ report(ParseError, false, null(), JSMSG_RESERVED_ID, badName);
+ return nullptr;
+ }
+ }
+ } else {
+ if (yieldHandling == YieldIsKeyword ||
+ pc->sc()->strict() ||
+ versionNumber() >= JSVERSION_1_7)
+ {
+ report(ParseError, false, null(), JSMSG_RESERVED_ID, "yield");
+ return nullptr;
+ }
+ }
+
+ return ident;
+}
+
+template <typename ParseHandler>
+typename ParseHandler::Node
+Parser<ParseHandler>::identifierReference(Handle<PropertyName*> name)
+{
+ Node pn = newName(name);
+ if (!pn)
+ return null();
+
+ if (!pc->inDestructuringDecl && !noteUsedName(name))
+ return null();
+
+ return pn;
+}
+
+template <typename ParseHandler>
+typename ParseHandler::Node
+Parser<ParseHandler>::stringLiteral()
+{
+ return handler.newStringLiteral(stopStringCompression(), pos());
+}
+
+template <typename ParseHandler>
+typename ParseHandler::Node
+Parser<ParseHandler>::noSubstitutionTemplate()
+{
+ return handler.newTemplateStringLiteral(stopStringCompression(), pos());
+}
+
+template <typename ParseHandler>
+JSAtom * Parser<ParseHandler>::stopStringCompression() {
+ JSAtom* atom = tokenStream.currentToken().atom();
+
+ // Large strings are fast to parse but slow to compress. Stop compression on
+ // them, so we don't wait for a long time for compression to finish at the
+ // end of compilation.
+ const size_t HUGE_STRING = 50000;
+ if (sct && sct->active() && atom->length() >= HUGE_STRING)
+ sct->abort();
+ return atom;
+}
+
+template <typename ParseHandler>
+typename ParseHandler::Node
+Parser<ParseHandler>::newRegExp()
+{
+ MOZ_ASSERT(!options().selfHostingMode);
+ // Create the regexp even when doing a syntax parse, to check the regexp's syntax.
+ const char16_t* chars = tokenStream.getTokenbuf().begin();
+ size_t length = tokenStream.getTokenbuf().length();
+ RegExpFlag flags = tokenStream.currentToken().regExpFlags();
+
+ Rooted<RegExpObject*> reobj(context);
+ reobj = RegExpObject::create(context, chars, length, flags, &tokenStream, alloc);
+ if (!reobj)
+ return null();
+
+ return handler.newRegExp(reobj, pos(), *this);
+}
+
+template <typename ParseHandler>
+typename ParseHandler::Node
+Parser<ParseHandler>::arrayInitializer(YieldHandling yieldHandling, PossibleError* possibleError)
+{
+ MOZ_ASSERT(tokenStream.isCurrentTokenType(TOK_LB));
+
+ uint32_t begin = pos().begin;
+ Node literal = handler.newArrayLiteral(begin);
+ if (!literal)
+ return null();
+
+ TokenKind tt;
+ if (!tokenStream.getToken(&tt, TokenStream::Operand))
+ return null();
+
+ // Handle an ES6-era array comprehension first.
+ if (tt == TOK_FOR)
+ return arrayComprehension(begin);
+
+ if (tt == TOK_RB) {
+ /*
+ * Mark empty arrays as non-constant, since we cannot easily
+ * determine their type.
+ */
+ handler.setListFlag(literal, PNX_NONCONST);
+ } else {
+ tokenStream.ungetToken();
+
+ uint32_t index = 0;
+ TokenStream::Modifier modifier = TokenStream::Operand;
+ for (; ; index++) {
+ if (index >= NativeObject::MAX_DENSE_ELEMENTS_COUNT) {
+ report(ParseError, false, null(), JSMSG_ARRAY_INIT_TOO_BIG);
+ return null();
+ }
+
+ TokenKind tt;
+ if (!tokenStream.peekToken(&tt, TokenStream::Operand))
+ return null();
+ if (tt == TOK_RB)
+ break;
+
+ if (tt == TOK_COMMA) {
+ tokenStream.consumeKnownToken(TOK_COMMA, TokenStream::Operand);
+ if (!handler.addElision(literal, pos()))
+ return null();
+ } else if (tt == TOK_TRIPLEDOT) {
+ tokenStream.consumeKnownToken(TOK_TRIPLEDOT, TokenStream::Operand);
+ uint32_t begin = pos().begin;
+ Node inner = assignExpr(InAllowed, yieldHandling, TripledotProhibited,
+ possibleError);
+ if (!inner)
+ return null();
+ if (!handler.addSpreadElement(literal, begin, inner))
+ return null();
+ } else {
+ Node element = assignExpr(InAllowed, yieldHandling, TripledotProhibited,
+ possibleError);
+ if (!element)
+ return null();
+ if (foldConstants && !FoldConstants(context, &element, this))
+ return null();
+ handler.addArrayElement(literal, element);
+ }
+
+ if (tt != TOK_COMMA) {
+ /* If we didn't already match TOK_COMMA in above case. */
+ bool matched;
+ if (!tokenStream.matchToken(&matched, TOK_COMMA))
+ return null();
+ if (!matched) {
+ modifier = TokenStream::None;
+ break;
+ }
+ if (tt == TOK_TRIPLEDOT && possibleError)
+ possibleError->setPendingDestructuringError(null(), JSMSG_REST_WITH_COMMA);
+ }
+ }
+
+ MUST_MATCH_TOKEN_MOD(TOK_RB, modifier, JSMSG_BRACKET_AFTER_LIST);
+ }
+ handler.setEndPosition(literal, pos().end);
+ return literal;
+}
+
+static JSAtom*
+DoubleToAtom(ExclusiveContext* cx, double value)
+{
+ // This is safe because doubles can not be moved.
+ Value tmp = DoubleValue(value);
+ return ToAtom<CanGC>(cx, HandleValue::fromMarkedLocation(&tmp));
+}
+
+template <typename ParseHandler>
+typename ParseHandler::Node
+Parser<ParseHandler>::propertyName(YieldHandling yieldHandling, Node propList,
+ PropertyType* propType, MutableHandleAtom propAtom)
+{
+ TokenKind ltok;
+ if (!tokenStream.getToken(&ltok, TokenStream::KeywordIsName))
+ return null();
+
+ MOZ_ASSERT(ltok != TOK_RC, "caller should have handled TOK_RC");
+
+ bool isGenerator = false;
+ bool isAsync = false;
+ if (ltok == TOK_MUL) {
+ isGenerator = true;
+ if (!tokenStream.getToken(&ltok, TokenStream::KeywordIsName))
+ return null();
+ }
+
+ if (ltok == TOK_NAME && tokenStream.currentName() == context->names().async) {
+ // AsyncMethod[Yield, Await]:
+ // async [no LineTerminator here] PropertyName[?Yield, ?Await] ...
+ //
+ // PropertyName:
+ // LiteralPropertyName
+ // ComputedPropertyName[?Yield, ?Await]
+ //
+ // LiteralPropertyName:
+ // IdentifierName
+ // StringLiteral
+ // NumericLiteral
+ //
+ // ComputedPropertyName[Yield, Await]:
+ // [ ...
+ TokenKind tt = TOK_EOF;
+ if (!tokenStream.peekTokenSameLine(&tt, TokenStream::KeywordIsName))
+ return null();
+ if (tt == TOK_STRING || tt == TOK_NUMBER || tt == TOK_LB ||
+ tt == TOK_NAME || tt == TOK_YIELD)
+ {
+ isAsync = true;
+ tokenStream.consumeKnownToken(tt, TokenStream::KeywordIsName);
+ ltok = tt;
+ } else {
+ tokenStream.addModifierException(TokenStream::NoneIsKeywordIsName);
+ }
+ }
+
+ if (isAsync && isGenerator) {
+ report(ParseError, false, null(), JSMSG_ASYNC_GENERATOR);
+ return null();
+ }
+
+ propAtom.set(nullptr);
+ Node propName;
+ switch (ltok) {
+ case TOK_NUMBER:
+ propAtom.set(DoubleToAtom(context, tokenStream.currentToken().number()));
+ if (!propAtom.get())
+ return null();
+ propName = newNumber(tokenStream.currentToken());
+ if (!propName)
+ return null();
+ break;
+
+ case TOK_LB:
+ propName = computedPropertyName(yieldHandling, propList);
+ if (!propName)
+ return null();
+ break;
+
+ case TOK_NAME: {
+ propAtom.set(tokenStream.currentName());
+ // Do not look for accessor syntax on generators
+ if (isGenerator || isAsync ||
+ !(propAtom.get() == context->names().get ||
+ propAtom.get() == context->names().set))
+ {
+ propName = handler.newObjectLiteralPropertyName(propAtom, pos());
+ if (!propName)
+ return null();
+ break;
+ }
+
+ *propType = propAtom.get() == context->names().get ? PropertyType::Getter
+ : PropertyType::Setter;
+
+ // We have parsed |get| or |set|. Look for an accessor property
+ // name next.
+ TokenKind tt;
+ if (!tokenStream.peekToken(&tt, TokenStream::KeywordIsName))
+ return null();
+ if (tt == TOK_NAME) {
+ if (!checkUnescapedName())
+ return null();
+
+ tokenStream.consumeKnownToken(TOK_NAME, TokenStream::KeywordIsName);
+
+ propAtom.set(tokenStream.currentName());
+ return handler.newObjectLiteralPropertyName(propAtom, pos());
+ }
+ if (tt == TOK_STRING) {
+ if (!checkUnescapedName())
+ return null();
+
+ tokenStream.consumeKnownToken(TOK_STRING, TokenStream::KeywordIsName);
+
+ propAtom.set(tokenStream.currentToken().atom());
+
+ uint32_t index;
+ if (propAtom->isIndex(&index)) {
+ propAtom.set(DoubleToAtom(context, index));
+ if (!propAtom.get())
+ return null();
+ return handler.newNumber(index, NoDecimal, pos());
+ }
+ return stringLiteral();
+ }
+ if (tt == TOK_NUMBER) {
+ if (!checkUnescapedName())
+ return null();
+
+ tokenStream.consumeKnownToken(TOK_NUMBER, TokenStream::KeywordIsName);
+
+ propAtom.set(DoubleToAtom(context, tokenStream.currentToken().number()));
+ if (!propAtom.get())
+ return null();
+ return newNumber(tokenStream.currentToken());
+ }
+ if (tt == TOK_LB) {
+ if (!checkUnescapedName())
+ return null();
+
+ tokenStream.consumeKnownToken(TOK_LB, TokenStream::KeywordIsName);
+
+ return computedPropertyName(yieldHandling, propList);
+ }
+
+ // Not an accessor property after all.
+ propName = handler.newObjectLiteralPropertyName(propAtom.get(), pos());
+ if (!propName)
+ return null();
+ tokenStream.addModifierException(TokenStream::NoneIsKeywordIsName);
+ break;
+ }
+
+ case TOK_STRING: {
+ propAtom.set(tokenStream.currentToken().atom());
+ uint32_t index;
+ if (propAtom->isIndex(&index)) {
+ propName = handler.newNumber(index, NoDecimal, pos());
+ if (!propName)
+ return null();
+ break;
+ }
+ propName = stringLiteral();
+ if (!propName)
+ return null();
+ break;
+ }
+
+ default:
+ report(ParseError, false, null(), JSMSG_BAD_PROP_ID);
+ return null();
+ }
+
+ TokenKind tt;
+ if (!tokenStream.getToken(&tt))
+ return null();
+
+ if (tt == TOK_COLON) {
+ if (isGenerator) {
+ report(ParseError, false, null(), JSMSG_BAD_PROP_ID);
+ return null();
+ }
+ *propType = PropertyType::Normal;
+ return propName;
+ }
+
+ if (ltok == TOK_NAME && (tt == TOK_COMMA || tt == TOK_RC || tt == TOK_ASSIGN)) {
+ if (isGenerator) {
+ report(ParseError, false, null(), JSMSG_BAD_PROP_ID);
+ return null();
+ }
+ tokenStream.ungetToken();
+ *propType = tt == TOK_ASSIGN ?
+ PropertyType::CoverInitializedName :
+ PropertyType::Shorthand;
+ return propName;
+ }
+
+ if (tt == TOK_LP) {
+ tokenStream.ungetToken();
+ if (isGenerator)
+ *propType = PropertyType::GeneratorMethod;
+ else if (isAsync)
+ *propType = PropertyType::AsyncMethod;
+ else
+ *propType = PropertyType::Method;
+ return propName;
+ }
+
+ report(ParseError, false, null(), JSMSG_COLON_AFTER_ID);
+ return null();
+}
+
+template <typename ParseHandler>
+typename ParseHandler::Node
+Parser<ParseHandler>::computedPropertyName(YieldHandling yieldHandling, Node literal)
+{
+ uint32_t begin = pos().begin;
+
+ Node assignNode;
+ {
+ // Turn off the inDestructuringDecl flag when parsing computed property
+ // names. In short, when parsing 'let {[x + y]: z} = obj;', noteUsedName()
+ // should be called on x and y, but not on z. See the comment on
+ // Parser<>::checkDestructuringPattern() for details.
+ AutoClearInDestructuringDecl autoClear(pc);
+ assignNode = assignExpr(InAllowed, yieldHandling, TripledotProhibited);
+ if (!assignNode)
+ return null();
+ }
+
+ MUST_MATCH_TOKEN(TOK_RB, JSMSG_COMP_PROP_UNTERM_EXPR);
+ Node propname = handler.newComputedName(assignNode, begin, pos().end);
+ if (!propname)
+ return null();
+ handler.setListFlag(literal, PNX_NONCONST);
+ return propname;
+}
+
+template <typename ParseHandler>
+typename ParseHandler::Node
+Parser<ParseHandler>::objectLiteral(YieldHandling yieldHandling, PossibleError* possibleError)
+{
+ MOZ_ASSERT(tokenStream.isCurrentTokenType(TOK_LC));
+
+ Node literal = handler.newObjectLiteral(pos().begin);
+ if (!literal)
+ return null();
+
+ bool seenPrototypeMutation = false;
+ bool seenCoverInitializedName = false;
+ RootedAtom propAtom(context);
+ for (;;) {
+ TokenKind tt;
+ if (!tokenStream.getToken(&tt, TokenStream::KeywordIsName))
+ return null();
+ if (tt == TOK_RC)
+ break;
+
+ tokenStream.ungetToken();
+
+ PropertyType propType;
+ Node propName = propertyName(yieldHandling, literal, &propType, &propAtom);
+ if (!propName)
+ return null();
+
+ if (propType == PropertyType::Normal) {
+ Node propExpr = assignExpr(InAllowed, yieldHandling, TripledotProhibited,
+ possibleError);
+ if (!propExpr)
+ return null();
+
+ if (foldConstants && !FoldConstants(context, &propExpr, this))
+ return null();
+
+ if (propAtom == context->names().proto) {
+ if (seenPrototypeMutation) {
+ // Directly report the error when we're not in a
+ // destructuring context.
+ if (!possibleError) {
+ report(ParseError, false, propName, JSMSG_DUPLICATE_PROTO_PROPERTY);
+ return null();
+ }
+
+ // Otherwise delay error reporting until we've determined
+ // whether or not we're destructuring.
+ possibleError->setPendingExpressionError(propName,
+ JSMSG_DUPLICATE_PROTO_PROPERTY);
+ }
+ seenPrototypeMutation = true;
+
+ // Note: this occurs *only* if we observe TOK_COLON! Only
+ // __proto__: v mutates [[Prototype]]. Getters, setters,
+ // method/generator definitions, computed property name
+ // versions of all of these, and shorthands do not.
+ uint32_t begin = handler.getPosition(propName).begin;
+ if (!handler.addPrototypeMutation(literal, begin, propExpr))
+ return null();
+ } else {
+ if (!handler.isConstant(propExpr))
+ handler.setListFlag(literal, PNX_NONCONST);
+
+ if (!handler.addPropertyDefinition(literal, propName, propExpr))
+ return null();
+ }
+ } else if (propType == PropertyType::Shorthand) {
+ /*
+ * Support, e.g., |var {x, y} = o| as destructuring shorthand
+ * for |var {x: x, y: y} = o|, and |var o = {x, y}| as initializer
+ * shorthand for |var o = {x: x, y: y}|.
+ */
+ TokenKind propToken = TOK_NAME;
+ if (!tokenStream.checkForKeyword(propAtom, &propToken))
+ return null();
+
+ if (propToken != TOK_NAME && propToken != TOK_YIELD) {
+ report(ParseError, false, null(), JSMSG_RESERVED_ID, TokenKindToDesc(propToken));
+ return null();
+ }
+
+ Rooted<PropertyName*> name(context,
+ identifierReference(yieldHandling, propToken == TOK_YIELD));
+ if (!name)
+ return null();
+
+ Node nameExpr = identifierReference(name);
+ if (!nameExpr)
+ return null();
+
+ if (!handler.addShorthand(literal, propName, nameExpr))
+ return null();
+ } else if (propType == PropertyType::CoverInitializedName) {
+ /*
+ * Support, e.g., |var {x=1, y=2} = o| as destructuring shorthand
+ * with default values, as per ES6 12.14.5
+ */
+ TokenKind propToken = TOK_NAME;
+ if (!tokenStream.checkForKeyword(propAtom, &propToken))
+ return null();
+
+ if (propToken != TOK_NAME && propToken != TOK_YIELD) {
+ report(ParseError, false, null(), JSMSG_RESERVED_ID, TokenKindToDesc(propToken));
+ return null();
+ }
+
+ Rooted<PropertyName*> name(context,
+ identifierReference(yieldHandling, propToken == TOK_YIELD));
+ if (!name)
+ return null();
+
+ Node lhs = identifierReference(name);
+ if (!lhs)
+ return null();
+
+ tokenStream.consumeKnownToken(TOK_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}`.
+ report(ParseError, false, null(), 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->setPendingExpressionError(null(), JSMSG_COLON_AFTER_ID);
+ }
+
+ Node rhs;
+ {
+ // Clearing `inDestructuringDecl` allows name use to be noted
+ // in Parser::identifierReference. See bug 1255167.
+ AutoClearInDestructuringDecl autoClear(pc);
+ rhs = assignExpr(InAllowed, yieldHandling, TripledotProhibited);
+ if (!rhs)
+ return null();
+ }
+
+ Node propExpr = handler.newAssignment(PNK_ASSIGN, lhs, rhs, JSOP_NOP);
+ if (!propExpr)
+ return null();
+
+ if (!handler.addPropertyDefinition(literal, propName, propExpr))
+ return null();
+
+ if (!abortIfSyntaxParser())
+ return null();
+ } else {
+ // FIXME: Implement ES6 function "name" property semantics
+ // (bug 883377).
+ RootedAtom funName(context);
+ if (!tokenStream.isCurrentTokenType(TOK_RB)) {
+ funName = propAtom;
+
+ if (propType == PropertyType::Getter || propType == PropertyType::Setter) {
+ funName = prefixAccessorName(propType, propAtom);
+ if (!funName)
+ return null();
+ }
+ }
+
+ Node fn = methodDefinition(propType, funName);
+ if (!fn)
+ return null();
+
+ JSOp op = JSOpFromPropertyType(propType);
+ if (!handler.addObjectMethodDefinition(literal, propName, fn, op))
+ return null();
+ }
+
+ if (!tokenStream.getToken(&tt))
+ return null();
+ if (tt == TOK_RC)
+ break;
+ if (tt != TOK_COMMA) {
+ report(ParseError, false, null(), JSMSG_CURLY_AFTER_LIST);
+ return null();
+ }
+ }
+
+ handler.setEndPosition(literal, pos().end);
+ return literal;
+}
+
+template <typename ParseHandler>
+typename ParseHandler::Node
+Parser<ParseHandler>::methodDefinition(PropertyType propType, HandleAtom funName)
+{
+ FunctionSyntaxKind kind = FunctionSyntaxKindFromPropertyType(propType);
+ GeneratorKind generatorKind = GeneratorKindFromPropertyType(propType);
+ FunctionAsyncKind asyncKind = AsyncKindFromPropertyType(propType);
+ YieldHandling yieldHandling = GetYieldHandling(generatorKind, asyncKind);
+ return functionDefinition(InAllowed, yieldHandling, funName, kind, generatorKind, asyncKind);
+}
+
+template <typename ParseHandler>
+bool
+Parser<ParseHandler>::tryNewTarget(Node &newTarget)
+{
+ MOZ_ASSERT(tokenStream.isCurrentTokenType(TOK_NEW));
+
+ newTarget = null();
+
+ Node 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::Operand))
+ return false;
+
+ // Don't unget the token, since lookahead cannot handle someone calling
+ // getToken() with a different modifier. Callers should inspect currentToken().
+ if (next != TOK_DOT)
+ return true;
+
+ if (!tokenStream.getToken(&next))
+ return false;
+ if (next != TOK_NAME || tokenStream.currentName() != context->names().target) {
+ report(ParseError, false, null(), JSMSG_UNEXPECTED_TOKEN,
+ "target", TokenKindToDesc(next));
+ return false;
+ }
+
+ if (!checkUnescapedName())
+ return false;
+
+ if (!pc->sc()->allowNewTarget()) {
+ reportWithOffset(ParseError, false, begin, JSMSG_BAD_NEWTARGET);
+ return false;
+ }
+
+ Node targetHolder = handler.newPosHolder(pos());
+ if (!targetHolder)
+ return false;
+
+ newTarget = handler.newNewTarget(newHolder, targetHolder);
+ return !!newTarget;
+}
+
+template <typename ParseHandler>
+typename ParseHandler::Node
+Parser<ParseHandler>::primaryExpr(YieldHandling yieldHandling, TripledotHandling tripledotHandling,
+ TokenKind tt, PossibleError* possibleError,
+ InvokedPrediction invoked /* = PredictUninvoked */)
+{
+ MOZ_ASSERT(tokenStream.isCurrentTokenType(tt));
+ JS_CHECK_RECURSION(context, return null());
+
+ switch (tt) {
+ case TOK_FUNCTION:
+ return functionExpr(invoked);
+
+ case TOK_CLASS:
+ return classDefinition(yieldHandling, ClassExpression, NameRequired);
+
+ case TOK_LB:
+ return arrayInitializer(yieldHandling, possibleError);
+
+ case TOK_LC:
+ return objectLiteral(yieldHandling, possibleError);
+
+ case TOK_LP: {
+ TokenKind next;
+ if (!tokenStream.peekToken(&next, TokenStream::Operand))
+ return null();
+
+ if (next == TOK_RP) {
+ // Not valid expression syntax, but this is valid in an arrow function
+ // with no params: `() => body`.
+ tokenStream.consumeKnownToken(next, TokenStream::Operand);
+
+ if (!tokenStream.peekToken(&next))
+ return null();
+ if (next != TOK_ARROW) {
+ report(ParseError, false, null(), JSMSG_UNEXPECTED_TOKEN,
+ "expression", TokenKindToDesc(TOK_RP));
+ 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());
+ }
+
+ if (next == TOK_FOR) {
+ uint32_t begin = pos().begin;
+ tokenStream.consumeKnownToken(next, TokenStream::Operand);
+ return generatorComprehension(begin);
+ }
+
+ // Pass |possibleError| to support destructuring in arrow parameters.
+ Node expr = exprInParens(InAllowed, yieldHandling, TripledotAllowed, possibleError);
+ if (!expr)
+ return null();
+ MUST_MATCH_TOKEN(TOK_RP, JSMSG_PAREN_IN_PAREN);
+ handler.setEndPosition(expr, pos().end);
+ return handler.parenthesize(expr);
+ }
+
+ case TOK_TEMPLATE_HEAD:
+ return templateLiteral(yieldHandling);
+
+ case TOK_NO_SUBS_TEMPLATE:
+ return noSubstitutionTemplate();
+
+ case TOK_STRING:
+ return stringLiteral();
+
+ case TOK_YIELD:
+ case TOK_NAME: {
+ if (tokenStream.currentName() == context->names().async) {
+ TokenKind nextSameLine = TOK_EOF;
+ if (!tokenStream.peekTokenSameLine(&nextSameLine))
+ return null();
+
+ if (nextSameLine == TOK_FUNCTION) {
+ tokenStream.consumeKnownToken(TOK_FUNCTION);
+ return functionExpr(PredictUninvoked, AsyncFunction);
+ }
+ }
+
+ Rooted<PropertyName*> name(context, identifierReference(yieldHandling));
+ if (!name)
+ return null();
+
+ return identifierReference(name);
+ }
+
+ case TOK_REGEXP:
+ return newRegExp();
+
+ case TOK_NUMBER:
+ return newNumber(tokenStream.currentToken());
+
+ case TOK_TRUE:
+ return handler.newBooleanLiteral(true, pos());
+ case TOK_FALSE:
+ return handler.newBooleanLiteral(false, pos());
+ case TOK_THIS: {
+ if (pc->isFunctionBox())
+ pc->functionBox()->usesThis = true;
+ Node thisName = null();
+ if (pc->sc()->thisBinding() == ThisBinding::Function) {
+ thisName = newThisName();
+ if (!thisName)
+ return null();
+ }
+ return handler.newThisLiteral(pos(), thisName);
+ }
+ case TOK_NULL:
+ return handler.newNullLiteral(pos());
+
+ case TOK_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) {
+ report(ParseError, false, null(), JSMSG_UNEXPECTED_TOKEN,
+ "expression", TokenKindToDesc(tt));
+ return null();
+ }
+
+ TokenKind next;
+ if (!tokenStream.getToken(&next))
+ return null();
+
+ if (next == TOK_LB || next == TOK_LC) {
+ // 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 (next != TOK_NAME && next != TOK_YIELD) {
+ report(ParseError, false, null(), JSMSG_UNEXPECTED_TOKEN,
+ "rest argument name", TokenKindToDesc(next));
+ return null();
+ }
+ }
+
+ if (!tokenStream.getToken(&next))
+ return null();
+ if (next != TOK_RP) {
+ report(ParseError, false, null(), JSMSG_UNEXPECTED_TOKEN,
+ "closing parenthesis", TokenKindToDesc(next));
+ return null();
+ }
+
+ if (!tokenStream.peekToken(&next))
+ return null();
+ if (next != TOK_ARROW) {
+ // Advance the scanner for proper error location reporting.
+ tokenStream.consumeKnownToken(next);
+ report(ParseError, false, null(), JSMSG_UNEXPECTED_TOKEN,
+ "'=>' after argument list", TokenKindToDesc(next));
+ return null();
+ }
+
+ tokenStream.ungetToken(); // put back right paren
+
+ // Return an arbitrary expression node. See case TOK_RP above.
+ return handler.newNullLiteral(pos());
+ }
+
+ default:
+ report(ParseError, false, null(), JSMSG_UNEXPECTED_TOKEN,
+ "expression", TokenKindToDesc(tt));
+ return null();
+ }
+}
+
+template <typename ParseHandler>
+typename ParseHandler::Node
+Parser<ParseHandler>::exprInParens(InHandling inHandling, YieldHandling yieldHandling,
+ TripledotHandling tripledotHandling,
+ PossibleError* possibleError /* = nullptr */)
+{
+ MOZ_ASSERT(tokenStream.isCurrentTokenType(TOK_LP));
+ return expr(inHandling, yieldHandling, tripledotHandling, possibleError, PredictInvoked);
+}
+
+template <typename ParseHandler>
+void
+Parser<ParseHandler>::addTelemetry(JSCompartment::DeprecatedLanguageExtension e)
+{
+ JSContext* cx = context->maybeJSContext();
+ if (!cx)
+ return;
+ cx->compartment()->addTelemetry(getFilename(), e);
+}
+
+template <typename ParseHandler>
+bool
+Parser<ParseHandler>::warnOnceAboutExprClosure()
+{
+#ifndef RELEASE_OR_BETA
+ JSContext* cx = context->maybeJSContext();
+ if (!cx)
+ return true;
+
+ if (!cx->compartment()->warnedAboutExprClosure) {
+ if (!report(ParseWarning, false, null(), JSMSG_DEPRECATED_EXPR_CLOSURE))
+ return false;
+ cx->compartment()->warnedAboutExprClosure = true;
+ }
+#endif
+ return true;
+}
+
+template <typename ParseHandler>
+bool
+Parser<ParseHandler>::warnOnceAboutForEach()
+{
+ JSContext* cx = context->maybeJSContext();
+ if (!cx)
+ return true;
+
+ if (!cx->compartment()->warnedAboutForEach) {
+ if (!report(ParseWarning, false, null(), JSMSG_DEPRECATED_FOR_EACH))
+ return false;
+ cx->compartment()->warnedAboutForEach = true;
+ }
+ return true;
+}
+
+template class Parser<FullParseHandler>;
+template class Parser<SyntaxParseHandler>;
+
+} /* namespace frontend */
+} /* namespace js */
diff --git a/js/src/frontend/Parser.h b/js/src/frontend/Parser.h
new file mode 100644
index 0000000000..0ad4d56a0b
--- /dev/null
+++ b/js/src/frontend/Parser.h
@@ -0,0 +1,1430 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sts=4 et sw=4 tw=99:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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
+
+#include "mozilla/Array.h"
+#include "mozilla/Maybe.h"
+
+#include "jspubtd.h"
+
+#include "frontend/BytecodeCompiler.h"
+#include "frontend/FullParseHandler.h"
+#include "frontend/NameAnalysisTypes.h"
+#include "frontend/NameCollections.h"
+#include "frontend/SharedContext.h"
+#include "frontend/SyntaxParseHandler.h"
+
+namespace js {
+
+class ModuleObject;
+
+namespace frontend {
+
+/*
+ * 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
+ {
+ RootedAtom label_;
+
+ public:
+ LabelStatement(ParseContext* pc, JSAtom* label)
+ : Statement(pc, StatementKind::Label),
+ label_(pc->sc_->context, label)
+ { }
+
+ HandleAtom label() const {
+ return label_;
+ }
+ };
+
+ // 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_;
+
+ // Monotonically increasing id.
+ uint32_t id_;
+
+ bool maybeReportOOM(ParseContext* pc, bool result) {
+ if (!result)
+ ReportOutOfMemory(pc->sc()->context);
+ return result;
+ }
+
+ public:
+ using DeclaredNamePtr = DeclaredNameMap::Ptr;
+ using AddDeclaredNamePtr = DeclaredNameMap::AddPtr;
+
+ using Nestable<Scope>::enclosing;
+
+ template <typename ParseHandler>
+ explicit Scope(Parser<ParseHandler>* parser)
+ : Nestable<Scope>(&parser->pc->innermostScope_),
+ declared_(parser->context->frontendCollectionPool()),
+ id_(parser->usedNames.nextScopeId())
+ { }
+
+ void dump(ParseContext* pc);
+
+ uint32_t id() const {
+ return id_;
+ }
+
+ MOZ_MUST_USE bool init(ParseContext* pc) {
+ if (id_ == UINT32_MAX) {
+ pc->tokenStream_.reportError(JSMSG_NEED_DIET, js_script_str);
+ return false;
+ }
+
+ return declared_.acquire(pc->sc()->context);
+ }
+
+ DeclaredNamePtr lookupDeclaredName(JSAtom* name) {
+ return declared_->lookup(name);
+ }
+
+ AddDeclaredNamePtr lookupDeclaredNameForAdd(JSAtom* name) {
+ return declared_->lookupForAdd(name);
+ }
+
+ MOZ_MUST_USE bool addDeclaredName(ParseContext* pc, AddDeclaredNamePtr& p, JSAtom* name,
+ DeclarationKind kind)
+ {
+ return maybeReportOOM(pc, declared_->add(p, name, DeclaredNameInfo(kind)));
+ }
+
+ // Remove all VarForAnnexBLexicalFunction declarations of a certain
+ // name from all scopes in pc's scope stack.
+ static void removeVarForAnnexBLexicalFunction(ParseContext* pc, JSAtom* name);
+
+ // 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;
+ }
+
+ // An iterator for the set of names a scope binds: the set of all
+ // declared names for 'var' scopes, and the set of lexically declared
+ // names for non-'var' scopes.
+ class BindingIter
+ {
+ friend class Scope;
+
+ DeclaredNameMap::Range declaredRange_;
+ mozilla::DebugOnly<uint32_t> count_;
+ bool isVarScope_;
+
+ BindingIter(Scope& scope, bool isVarScope)
+ : declaredRange_(scope.declared_->all()),
+ count_(0),
+ isVarScope_(isVarScope)
+ {
+ settle();
+ }
+
+ void settle() {
+ // Both var and lexically declared names are binding in a var
+ // scope.
+ if (isVarScope_)
+ return;
+
+ // Otherwise, pop only lexically declared names are
+ // binding. Pop the range until we find such a name.
+ while (!declaredRange_.empty()) {
+ if (BindingKindIsLexical(kind()))
+ break;
+ declaredRange_.popFront();
+ }
+ }
+
+ public:
+ bool done() const {
+ return declaredRange_.empty();
+ }
+
+ explicit operator bool() const {
+ return !done();
+ }
+
+ JSAtom* 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:
+ template <typename ParseHandler>
+ explicit VarScope(Parser<ParseHandler>* parser)
+ : Scope(parser)
+ {
+ useAsVarScope(parser->pc);
+ }
+ };
+
+ private:
+ // Context shared between parsing and bytecode generation.
+ SharedContext* sc_;
+
+ // TokenStream used for error reporting.
+ TokenStream& tokenStream_;
+
+ // 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 parameers.
+ 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_;
+
+ // Inner function boxes in this context to try Annex B.3.3 semantics
+ // on. Only used when full parsing.
+ PooledVectorPtr<FunctionBoxVector> innerFunctionBoxesForAnnexB_;
+
+ // 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_;
+
+ // Monotonically increasing id.
+ uint32_t scriptId_;
+
+ // Set when compiling a function using Parser::standaloneFunctionBody via
+ // the Function or Generator constructor.
+ bool isStandaloneFunctionBody_;
+
+ // 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:
+ // 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;
+
+ // All inner functions in this context. Only used when syntax parsing.
+ Rooted<GCVector<JSFunction*, 8>> innerFunctionsForLazy;
+
+ // 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;
+
+ // Set when parsing a declaration-like destructuring pattern. This flag
+ // causes PrimaryExpr to create PN_NAME parse nodes for variable references
+ // which are not hooked into any definition's use chain, added to any tree
+ // context's AtomList, etc. etc. checkDestructuring will do that work
+ // later.
+ //
+ // The comments atop checkDestructuring explain the distinction between
+ // assignment-like and declaration-like destructuring patterns, and why
+ // they need to be treated differently.
+ mozilla::Maybe<DeclarationKind> inDestructuringDecl;
+
+ // Set when parsing a function and it has 'return <expr>;'
+ bool funHasReturnExpr;
+
+ // Set when parsing a function and it has 'return;'
+ bool funHasReturnVoid;
+
+ public:
+ template <typename ParseHandler>
+ ParseContext(Parser<ParseHandler>* prs, SharedContext* sc, Directives* newDirectives)
+ : Nestable<ParseContext>(&prs->pc),
+ sc_(sc),
+ tokenStream_(prs->tokenStream),
+ innermostStatement_(nullptr),
+ innermostScope_(nullptr),
+ varScope_(nullptr),
+ innerFunctionBoxesForAnnexB_(prs->context->frontendCollectionPool()),
+ positionalFormalParameterNames_(prs->context->frontendCollectionPool()),
+ closedOverBindingsForLazy_(prs->context->frontendCollectionPool()),
+ scriptId_(prs->usedNames.nextScriptId()),
+ isStandaloneFunctionBody_(false),
+ superScopeNeedsHomeObject_(false),
+ lastYieldOffset(NoYieldOffset),
+ lastAwaitOffset(NoAwaitOffset),
+ innerFunctionsForLazy(prs->context, GCVector<JSFunction*, 8>(prs->context)),
+ newDirectives(newDirectives),
+ funHasReturnExpr(false),
+ funHasReturnVoid(false)
+ {
+ if (isFunctionBox()) {
+ if (functionBox()->function()->isNamedLambda())
+ namedLambdaScope_.emplace(prs);
+ functionScope_.emplace(prs);
+ }
+ }
+
+ ~ParseContext();
+
+ MOZ_MUST_USE bool init();
+
+ SharedContext* sc() {
+ return sc_;
+ }
+
+ 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()->function()->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);
+ }
+
+ AtomVector& positionalFormalParameterNames() {
+ return *positionalFormalParameterNames_;
+ }
+
+ AtomVector& closedOverBindingsForLazy() {
+ return *closedOverBindingsForLazy_;
+ }
+
+ MOZ_MUST_USE bool addInnerFunctionBoxForAnnexB(FunctionBox* funbox);
+ void removeInnerFunctionBoxesForAnnexB(JSAtom* name);
+ void finishInnerFunctionBoxesForAnnexB();
+
+ // 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();
+ }
+
+ void setIsStandaloneFunctionBody() {
+ isStandaloneFunctionBody_ = true;
+ }
+
+ bool isStandaloneFunctionBody() const {
+ return isStandaloneFunctionBody_;
+ }
+
+ void setSuperScopeNeedsHomeObject() {
+ MOZ_ASSERT(sc_->allowSuperProperty());
+ superScopeNeedsHomeObject_ = true;
+ }
+
+ bool superScopeNeedsHomeObject() const {
+ return superScopeNeedsHomeObject_;
+ }
+
+ bool useAsmOrInsideUseAsm() const {
+ return sc_->isFunctionBox() && sc_->asFunctionBox()->useAsmOrInsideUseAsm();
+ }
+
+ // Most functions start off being parsed as non-generators.
+ // Non-generators transition to LegacyGenerator on parsing "yield" in JS 1.7.
+ // An ES6 generator is marked as a "star generator" before its body is parsed.
+ GeneratorKind generatorKind() const {
+ return sc_->isFunctionBox() ? sc_->asFunctionBox()->generatorKind() : NotGenerator;
+ }
+
+ bool isGenerator() const {
+ return generatorKind() != NotGenerator;
+ }
+
+ bool isLegacyGenerator() const {
+ return generatorKind() == LegacyGenerator;
+ }
+
+ bool isStarGenerator() const {
+ return generatorKind() == StarGenerator;
+ }
+
+ bool isAsync() const {
+ return sc_->isFunctionBox() && sc_->asFunctionBox()->isAsync();
+ }
+
+ FunctionAsyncKind asyncKind() const {
+ return isAsync() ? AsyncFunction : SyncFunction;
+ }
+
+ bool isArrowFunction() const {
+ return sc_->isFunctionBox() && sc_->asFunctionBox()->function()->isArrow();
+ }
+
+ bool isMethod() const {
+ return sc_->isFunctionBox() && sc_->asFunctionBox()->function()->isMethod();
+ }
+
+ uint32_t scriptId() const {
+ return scriptId_;
+ }
+};
+
+template <>
+inline bool
+ParseContext::Statement::is<ParseContext::LabelStatement>() const
+{
+ return kind_ == StatementKind::Label;
+}
+
+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
+Directives::Directives(ParseContext* parent)
+ : strict_(parent->sc()->strict()),
+ asmJS_(parent->useAsmOrInsideUseAsm())
+{}
+
+enum VarContext { HoistVars, DontHoistVars };
+enum PropListType { ObjectLiteral, ClassBody, DerivedClassBody };
+enum class PropertyType {
+ Normal,
+ Shorthand,
+ CoverInitializedName,
+ Getter,
+ GetterNoExpressionClosure,
+ Setter,
+ SetterNoExpressionClosure,
+ Method,
+ GeneratorMethod,
+ AsyncMethod,
+ Constructor,
+ DerivedConstructor
+};
+
+// Specify a value for an ES6 grammar parametrization. We have no enum for
+// [Return] because its behavior is exactly equivalent to checking whether
+// we're in a function box -- easier and simpler than passing an extra
+// parameter everywhere.
+enum YieldHandling { YieldIsName, YieldIsKeyword };
+enum InHandling { InAllowed, InProhibited };
+enum DefaultHandling { NameRequired, AllowDefaultName };
+enum TripledotHandling { TripledotAllowed, TripledotProhibited };
+
+// 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 name uses are tracked by lists of
+// (script id, scope id) pairs of their use sites.
+//
+// Intuitively, in a pair (P,S), P tracks the most nested function that has a
+// use of u, and S tracks the most nested scope that is still being parsed.
+//
+// P is used to answer the question "is u used by a nested function?"
+// S is used to answer the question "is u used in any scopes currently being
+// parsed?"
+//
+// The algorithm:
+//
+// Let Used by a map of names to lists.
+//
+// 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 u is used in scope numbered S in script numbered P,
+// and u is found in Used,
+// a. Append (P,S) to Used[u].
+// b. Otherwise, assign the the list [(P,S)] to Used[u].
+// 4. When we finish parsing a scope S in script P, for each declared name d in
+// Declared(S):
+// a. If d is found in Used, mark d as closed over if there is a value
+// (P_d, S_d) in Used[d] such that P_d > P and S_d > S.
+// b. Remove all values (P_d, S_d) in Used[d] such that S_d are >= S.
+//
+// Steps 1 and 2 are implemented by UsedNameTracker::next{Script,Scope}Id.
+// Step 3 is implemented by UsedNameTracker::noteUsedInScope.
+// Step 4 is implemented by UsedNameTracker::noteBoundInScope and
+// Parser::propagateFreeNamesAndMarkClosedOverBindings.
+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);
+
+ public:
+ explicit UsedNameInfo(ExclusiveContext* cx)
+ : uses_(cx)
+ { }
+
+ UsedNameInfo(UsedNameInfo&& other)
+ : uses_(mozilla::Move(other.uses_))
+ { }
+
+ 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;
+ }
+ };
+
+ using UsedNameMap = HashMap<JSAtom*,
+ UsedNameInfo,
+ DefaultHasher<JSAtom*>>;
+
+ 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_;
+
+ public:
+ explicit UsedNameTracker(ExclusiveContext* cx)
+ : map_(cx),
+ scriptCounter_(0),
+ scopeCounter_(0)
+ { }
+
+ MOZ_MUST_USE bool init() {
+ return map_.init();
+ }
+
+ 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(JSAtom* name) const {
+ return map_.lookup(name);
+ }
+
+ MOZ_MUST_USE bool noteUse(ExclusiveContext* cx, JSAtom* name,
+ uint32_t scriptId, uint32_t scopeId);
+
+ 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);
+
+ // Resets state to beginning of compilation.
+ void reset() {
+ map_.clear();
+ RewindToken token;
+ token.scriptId = 0;
+ token.scopeId = 0;
+ rewind(token);
+ }
+};
+
+template <typename ParseHandler>
+class Parser final : private JS::AutoGCRooter, public StrictModeGetter
+{
+ private:
+ using Node = typename ParseHandler::Node;
+
+ /*
+ * 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.setPendingExpressionError(pn, 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.setPendingExpressionError(pn, 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 };
+
+ enum class ErrorState { None, Pending };
+
+ struct Error {
+ ErrorState state_ = ErrorState::None;
+
+ // Error reporting fields.
+ uint32_t offset_;
+ unsigned errorNumber_;
+ };
+
+ Parser<ParseHandler>& parser_;
+ Error exprError_;
+ Error destructuringError_;
+
+ // 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, Node pn, unsigned errorNumber);
+
+ // If there is a pending error, report it and return false, otherwise
+ // return true.
+ bool checkForError(ErrorKind kind);
+
+ // Transfer an existing error to another instance.
+ void transferErrorTo(ErrorKind kind, PossibleError* other);
+
+ public:
+ explicit PossibleError(Parser<ParseHandler>& parser);
+
+ // 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 setPendingDestructuringError(Node pn, 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 setPendingExpressionError(Node pn, unsigned errorNumber);
+
+ // If there is a pending destructuring error, report it and return
+ // false, otherwise return true. Clears any pending expression error.
+ bool checkForDestructuringError();
+
+ // If there is a pending expression error, report it and return false,
+ // otherwise return true. Clears any pending destructuring error.
+ 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);
+ };
+
+ public:
+ ExclusiveContext* const context;
+
+ LifoAlloc& alloc;
+
+ TokenStream tokenStream;
+ LifoAlloc::Mark tempPoolMark;
+
+ /* list of parsed objects for GC tracing */
+ ObjectBox* traceListHead;
+
+ /* innermost parse context (stack-allocated) */
+ ParseContext* pc;
+
+ // For tracking used names in this parsing session.
+ UsedNameTracker& usedNames;
+
+ /* Compression token for aborting. */
+ SourceCompressionTask* sct;
+
+ ScriptSource* ss;
+
+ /* Root atoms and objects allocated for the parsed tree. */
+ AutoKeepAtoms keepAtoms;
+
+ /* Perform constant-folding; must be true when interfacing with the emitter. */
+ const bool foldConstants:1;
+
+ private:
+#if DEBUG
+ /* Our fallible 'checkOptions' member function has been called. */
+ bool checkOptionsCalled:1;
+#endif
+
+ /*
+ * Not all language constructs can be handled during syntax parsing. If it
+ * is not known whether the parse succeeds or fails, this bit is set and
+ * the parse will return false.
+ */
+ bool abortedSyntaxParse:1;
+
+ /* Unexpected end of input, i.e. TOK_EOF not at top-level. */
+ bool isUnexpectedEOF_:1;
+
+ public:
+ /* State specific to the kind of parse being performed. */
+ ParseHandler handler;
+
+ void prepareNodeForMutation(Node node) { handler.prepareNodeForMutation(node); }
+ void freeTree(Node node) { handler.freeTree(node); }
+
+ private:
+ bool reportHelper(ParseReportKind kind, bool strict, uint32_t offset,
+ unsigned errorNumber, va_list args);
+ public:
+ bool report(ParseReportKind kind, bool strict, Node pn, unsigned errorNumber, ...);
+ bool reportNoOffset(ParseReportKind kind, bool strict, unsigned errorNumber, ...);
+ bool reportWithOffset(ParseReportKind kind, bool strict, uint32_t offset, unsigned errorNumber,
+ ...);
+
+ Parser(ExclusiveContext* cx, LifoAlloc& alloc, const ReadOnlyCompileOptions& options,
+ const char16_t* chars, size_t length, bool foldConstants, UsedNameTracker& usedNames,
+ Parser<SyntaxParseHandler>* syntaxParser, LazyScript* lazyOuterFunction);
+ ~Parser();
+
+ bool checkOptions();
+
+ // 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 Parser;
+ LifoAlloc::Mark mark;
+ ObjectBox* traceListHead;
+ };
+ Mark mark() const {
+ Mark m;
+ m.mark = alloc.mark();
+ m.traceListHead = traceListHead;
+ return m;
+ }
+ void release(Mark m) {
+ alloc.release(m.mark);
+ traceListHead = m.traceListHead;
+ }
+
+ friend void js::frontend::MarkParser(JSTracer* trc, JS::AutoGCRooter* parser);
+
+ const char* getFilename() const { return tokenStream.getFilename(); }
+ JSVersion versionNumber() const { return tokenStream.versionNumber(); }
+
+ /*
+ * Parse a top-level JS script.
+ */
+ Node parse();
+
+ /*
+ * Allocate a new parsed object or function container from
+ * cx->tempLifoAlloc.
+ */
+ ObjectBox* newObjectBox(JSObject* obj);
+ FunctionBox* newFunctionBox(Node fn, JSFunction* fun, Directives directives,
+ GeneratorKind generatorKind, FunctionAsyncKind asyncKind,
+ bool tryAnnexB);
+
+ /*
+ * Create a new function object given a name (which is optional if this is
+ * a function expression).
+ */
+ JSFunction* newFunction(HandleAtom atom, FunctionSyntaxKind kind,
+ GeneratorKind generatorKind, FunctionAsyncKind asyncKind,
+ HandleObject proto);
+
+ void trace(JSTracer* trc);
+
+ bool hadAbortedSyntaxParse() {
+ return abortedSyntaxParse;
+ }
+ void clearAbortedSyntaxParse() {
+ abortedSyntaxParse = false;
+ }
+
+ bool isUnexpectedEOF() const { return isUnexpectedEOF_; }
+
+ bool checkUnescapedName();
+
+ private:
+ Parser* thisForCtor() { return this; }
+
+ JSAtom* stopStringCompression();
+
+ Node stringLiteral();
+ Node noSubstitutionTemplate();
+ Node templateLiteral(YieldHandling yieldHandling);
+ bool taggedTemplate(YieldHandling yieldHandling, Node nodeList, TokenKind tt);
+ bool appendToCallSiteObj(Node callSiteObj);
+ bool addExprAndGetNextTemplStrToken(YieldHandling yieldHandling, Node nodeList,
+ TokenKind* ttp);
+ bool checkStatementsEOF();
+
+ inline Node newName(PropertyName* name);
+ inline Node newName(PropertyName* name, TokenPos pos);
+ inline Node newYieldExpression(uint32_t begin, Node expr, bool isYieldStar = false);
+ inline Node newAwaitExpression(uint32_t begin, Node expr);
+
+ inline bool abortIfSyntaxParser();
+
+ public:
+ /* Public entry points for parsing. */
+ Node statement(YieldHandling yieldHandling);
+ Node statementListItem(YieldHandling yieldHandling, bool canHaveDirectives = false);
+
+ bool maybeParseDirective(Node list, Node pn, bool* cont);
+
+ // 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.
+ Node evalBody(EvalSharedContext* evalsc);
+
+ // Parse the body of a global script.
+ Node globalBody(GlobalSharedContext* globalsc);
+
+ // Parse a module.
+ Node moduleBody(ModuleSharedContext* modulesc);
+
+ // Parse a function, given only its body. Used for the Function and
+ // Generator constructors.
+ Node standaloneFunctionBody(HandleFunction fun, HandleScope enclosingScope,
+ Handle<PropertyNameVector> formals,
+ GeneratorKind generatorKind, FunctionAsyncKind asyncKind,
+ Directives inheritedDirectives, Directives* newDirectives);
+
+ // Parse a function, given only its arguments and body. Used for lazily
+ // parsed functions.
+ Node standaloneLazyFunction(HandleFunction fun, bool strict,
+ GeneratorKind generatorKind, FunctionAsyncKind asyncKind);
+
+ // Parse an inner function given an enclosing ParseContext and a
+ // FunctionBox for the inner function.
+ bool innerFunction(Node pn, ParseContext* outerpc, FunctionBox* funbox, InHandling inHandling,
+ YieldHandling yieldHandling, FunctionSyntaxKind kind,
+ Directives inheritedDirectives, 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,
+ Node pn, FunctionSyntaxKind kind);
+
+ // Determine whether |yield| is a valid name in the current context, or
+ // whether it's prohibited due to strictness, JS version, or occurrence
+ // inside a star generator.
+ bool yieldExpressionsSupported() {
+ return (versionNumber() >= JSVERSION_1_7 || pc->isGenerator()) && !pc->isAsync();
+ }
+
+ // Match the current token against the BindingIdentifier production with
+ // the given Yield parameter. If there is no match, report a syntax
+ // error.
+ PropertyName* bindingIdentifier(YieldHandling yieldHandling);
+
+ virtual bool strictMode() { return pc->sc()->strict(); }
+ bool setLocalStrictMode(bool strict) {
+ MOZ_ASSERT(tokenStream.debugHasNoLookahead());
+ return pc->sc()->setLocalStrictMode(strict);
+ }
+
+ const ReadOnlyCompileOptions& options() const {
+ return tokenStream.options();
+ }
+
+ private:
+ enum InvokedPrediction { PredictUninvoked = false, PredictInvoked = true };
+ enum ForInitLocation { InForInit, NotInForInit };
+
+ 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.
+ *
+ * Parsers whose name has a '1' suffix leave the TokenStream state
+ * pointing to the token one past the end of the parsed fragment. For a
+ * number of the parsers this is convenient and avoids a lot of
+ * unnecessary ungetting and regetting of tokens.
+ *
+ * Some parsers have two versions: an always-inlined version (with an 'i'
+ * suffix) and a never-inlined version (with an 'n' suffix).
+ */
+ Node functionStmt(YieldHandling yieldHandling, DefaultHandling defaultHandling,
+ FunctionAsyncKind asyncKind = SyncFunction);
+ Node functionExpr(InvokedPrediction invoked = PredictUninvoked,
+ FunctionAsyncKind asyncKind = SyncFunction);
+
+ Node statementList(YieldHandling yieldHandling);
+
+ Node blockStatement(YieldHandling yieldHandling,
+ unsigned errorNumber = JSMSG_CURLY_IN_COMPOUND);
+ Node doWhileStatement(YieldHandling yieldHandling);
+ Node whileStatement(YieldHandling yieldHandling);
+
+ Node forStatement(YieldHandling yieldHandling);
+ bool forHeadStart(YieldHandling yieldHandling,
+ ParseNodeKind* forHeadKind,
+ Node* forInitialPart,
+ mozilla::Maybe<ParseContext::Scope>& forLetImpliedScope,
+ Node* forInOrOfExpression);
+ bool validateForInOrOfLHSExpression(Node target, PossibleError* possibleError);
+ Node expressionAfterForInOrOf(ParseNodeKind forHeadKind, YieldHandling yieldHandling);
+
+ Node switchStatement(YieldHandling yieldHandling);
+ Node continueStatement(YieldHandling yieldHandling);
+ Node breakStatement(YieldHandling yieldHandling);
+ Node returnStatement(YieldHandling yieldHandling);
+ Node withStatement(YieldHandling yieldHandling);
+ Node throwStatement(YieldHandling yieldHandling);
+ Node tryStatement(YieldHandling yieldHandling);
+ Node catchBlockStatement(YieldHandling yieldHandling, ParseContext::Scope& catchParamScope);
+ Node debuggerStatement();
+
+ Node variableStatement(YieldHandling yieldHandling);
+
+ Node labeledStatement(YieldHandling yieldHandling);
+ Node labeledItem(YieldHandling yieldHandling);
+
+ Node ifStatement(YieldHandling yieldHandling);
+ Node consequentOrAlternative(YieldHandling yieldHandling);
+
+ // While on a |let| TOK_NAME token, examine |next|. Indicate whether
+ // |next|, the next token already gotten with modifier TokenStream::None,
+ // continues a LexicalDeclaration.
+ bool nextTokenContinuesLetDeclaration(TokenKind next, YieldHandling yieldHandling);
+
+ Node lexicalDeclaration(YieldHandling yieldHandling, bool isConst);
+
+ Node importDeclaration();
+ Node exportDeclaration();
+ Node 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|.
+ Node 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(Node decl, DeclarationKind declKind, TokenKind tt,
+ bool initialDeclaration, YieldHandling yieldHandling,
+ ParseNodeKind* forHeadKind, Node* forInOrOfExpression);
+ Node declarationName(Node decl, 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|.
+ bool initializerInNameDeclaration(Node decl, Node binding, Handle<PropertyName*> name,
+ 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);
+ Node yieldExpression(InHandling inHandling);
+ Node condExpr1(InHandling inHandling, YieldHandling yieldHandling,
+ TripledotHandling tripledotHandling,
+ PossibleError* possibleError,
+ InvokedPrediction invoked = PredictUninvoked);
+ Node orExpr1(InHandling inHandling, YieldHandling yieldHandling,
+ TripledotHandling tripledotHandling,
+ PossibleError* possibleError,
+ InvokedPrediction invoked = PredictUninvoked);
+ Node unaryExpr(YieldHandling yieldHandling, TripledotHandling tripledotHandling,
+ PossibleError* possibleError = nullptr,
+ InvokedPrediction invoked = PredictUninvoked);
+ Node memberExpr(YieldHandling yieldHandling, TripledotHandling tripledotHandling,
+ TokenKind tt, bool allowCallSyntax = true,
+ PossibleError* possibleError = nullptr,
+ InvokedPrediction invoked = PredictUninvoked);
+ Node primaryExpr(YieldHandling yieldHandling, TripledotHandling tripledotHandling,
+ TokenKind tt, PossibleError* possibleError,
+ InvokedPrediction invoked = PredictUninvoked);
+ Node exprInParens(InHandling inHandling, YieldHandling yieldHandling,
+ TripledotHandling tripledotHandling, PossibleError* possibleError = nullptr);
+
+ bool tryNewTarget(Node& newTarget);
+ bool checkAndMarkSuperScope();
+
+ Node methodDefinition(PropertyType propType, HandleAtom funName);
+
+ /*
+ * Additional JS parsers.
+ */
+ bool functionArguments(YieldHandling yieldHandling, FunctionSyntaxKind kind,
+ Node funcpn);
+
+ Node functionDefinition(InHandling inHandling, YieldHandling yieldHandling, HandleAtom name,
+ FunctionSyntaxKind kind,
+ GeneratorKind generatorKind, FunctionAsyncKind asyncKind,
+ InvokedPrediction invoked = PredictUninvoked);
+
+ // Parse a function body. Pass StatementListBody if the body is a list of
+ // statements; pass ExpressionBody if the body is a single expression.
+ enum FunctionBodyType { StatementListBody, ExpressionBody };
+ Node functionBody(InHandling inHandling, YieldHandling yieldHandling, FunctionSyntaxKind kind,
+ FunctionBodyType type);
+
+ Node unaryOpExpr(YieldHandling yieldHandling, ParseNodeKind kind, JSOp op, uint32_t begin);
+
+ Node condition(InHandling inHandling, YieldHandling yieldHandling);
+
+ /* comprehensions */
+ Node generatorComprehensionLambda(unsigned begin);
+ Node comprehensionFor(GeneratorKind comprehensionKind);
+ Node comprehensionIf(GeneratorKind comprehensionKind);
+ Node comprehensionTail(GeneratorKind comprehensionKind);
+ Node comprehension(GeneratorKind comprehensionKind);
+ Node arrayComprehension(uint32_t begin);
+ Node generatorComprehension(uint32_t begin);
+
+ bool argumentList(YieldHandling yieldHandling, Node listNode, bool* isSpread,
+ PossibleError* possibleError = nullptr);
+ Node destructuringDeclaration(DeclarationKind kind, YieldHandling yieldHandling,
+ TokenKind tt);
+ Node destructuringDeclarationWithoutYieldOrAwait(DeclarationKind kind, YieldHandling yieldHandling,
+ TokenKind tt);
+
+ bool namedImportsOrNamespaceImport(TokenKind tt, Node importSpecSet);
+ bool checkExportedName(JSAtom* exportName);
+ bool checkExportedNamesForDeclaration(Node node);
+
+ enum ClassContext { ClassStatement, ClassExpression };
+ Node classDefinition(YieldHandling yieldHandling, ClassContext classContext,
+ DefaultHandling defaultHandling);
+
+ PropertyName* labelOrIdentifierReference(YieldHandling yieldHandling,
+ bool yieldTokenizedAsName);
+
+ PropertyName* labelIdentifier(YieldHandling yieldHandling) {
+ return labelOrIdentifierReference(yieldHandling, false);
+ }
+
+ PropertyName* identifierReference(YieldHandling yieldHandling,
+ bool yieldTokenizedAsName = false)
+ {
+ return labelOrIdentifierReference(yieldHandling, yieldTokenizedAsName);
+ }
+
+ PropertyName* importedBinding() {
+ return bindingIdentifier(YieldIsName);
+ }
+
+ Node identifierReference(Handle<PropertyName*> name);
+
+ bool matchLabel(YieldHandling yieldHandling, MutableHandle<PropertyName*> label);
+
+ bool allowsForEachIn() {
+#if !JS_HAS_FOR_EACH_IN
+ return false;
+#else
+ return versionNumber() >= JSVERSION_1_6;
+#endif
+ }
+
+ enum AssignmentFlavor {
+ PlainAssignment,
+ CompoundAssignment,
+ KeyedDestructuringAssignment,
+ IncrementAssignment,
+ DecrementAssignment,
+ ForInOrOfTarget
+ };
+
+ bool checkAndMarkAsAssignmentLhs(Node pn, AssignmentFlavor flavor,
+ PossibleError* possibleError=nullptr);
+ bool matchInOrOf(bool* isForInp, bool* isForOfp);
+
+ bool hasUsedFunctionSpecialName(HandlePropertyName name);
+ bool declareFunctionArgumentsObject();
+ bool declareFunctionThis();
+ Node newInternalDotName(HandlePropertyName name);
+ Node newThisName();
+ Node newDotGeneratorName();
+ bool declareDotGeneratorName();
+
+ bool checkFunctionDefinition(HandleAtom funAtom, Node pn, FunctionSyntaxKind kind,
+ GeneratorKind generatorKind, bool* tryAnnexB);
+ bool skipLazyInnerFunction(Node pn, FunctionSyntaxKind kind, bool tryAnnexB);
+ bool innerFunction(Node pn, ParseContext* outerpc, HandleFunction fun,
+ InHandling inHandling, YieldHandling yieldHandling,
+ FunctionSyntaxKind kind,
+ GeneratorKind generatorKind, FunctionAsyncKind asyncKind, bool tryAnnexB,
+ Directives inheritedDirectives, Directives* newDirectives);
+ bool trySyntaxParseInnerFunction(Node pn, HandleFunction fun, InHandling inHandling,
+ YieldHandling yieldHandling, FunctionSyntaxKind kind,
+ GeneratorKind generatorKind, FunctionAsyncKind asyncKind,
+ bool tryAnnexB,
+ Directives inheritedDirectives, Directives* newDirectives);
+ bool finishFunctionScopes();
+ bool finishFunction();
+ bool leaveInnerFunction(ParseContext* outerpc);
+
+ public:
+ enum FunctionCallBehavior {
+ PermitAssignmentToFunctionCalls,
+ ForbidAssignmentToFunctionCalls
+ };
+
+ bool isValidSimpleAssignmentTarget(Node node,
+ FunctionCallBehavior behavior = ForbidAssignmentToFunctionCalls);
+
+ private:
+ bool reportIfArgumentsEvalTarget(Node nameNode);
+ bool reportIfNotValidSimpleAssignmentTarget(Node target, AssignmentFlavor flavor);
+
+ bool checkAndMarkAsIncOperand(Node kid, AssignmentFlavor flavor);
+ bool checkStrictAssignment(Node lhs);
+ bool checkStrictBinding(PropertyName* name, TokenPos pos);
+
+ bool hasValidSimpleStrictParameterNames();
+
+ bool isValidStrictBinding(PropertyName* name);
+
+ void reportRedeclaration(HandlePropertyName name, DeclarationKind kind, TokenPos pos);
+ bool notePositionalFormalParameter(Node fn, HandlePropertyName name,
+ bool disallowDuplicateParams, bool* duplicatedParam);
+ bool noteDestructuredPositionalFormalParameter(Node fn, Node destruct);
+ mozilla::Maybe<DeclarationKind> isVarRedeclaredInEval(HandlePropertyName name,
+ DeclarationKind kind);
+ bool tryDeclareVar(HandlePropertyName name, DeclarationKind kind,
+ mozilla::Maybe<DeclarationKind>* redeclaredKind);
+ bool tryDeclareVarForAnnexBLexicalFunction(HandlePropertyName name, bool* tryAnnexB);
+ bool checkLexicalDeclarationDirectlyWithinBlock(ParseContext::Statement& stmt,
+ DeclarationKind kind, TokenPos pos);
+ bool noteDeclaredName(HandlePropertyName name, DeclarationKind kind, TokenPos pos);
+ bool noteUsedName(HandlePropertyName name);
+ bool hasUsedName(HandlePropertyName name);
+
+ // Required on Scope exit.
+ bool propagateFreeNamesAndMarkClosedOverBindings(ParseContext::Scope& scope);
+
+ mozilla::Maybe<GlobalScope::Data*> newGlobalScopeData(ParseContext::Scope& scope);
+ mozilla::Maybe<ModuleScope::Data*> newModuleScopeData(ParseContext::Scope& scope);
+ mozilla::Maybe<EvalScope::Data*> newEvalScopeData(ParseContext::Scope& scope);
+ mozilla::Maybe<FunctionScope::Data*> newFunctionScopeData(ParseContext::Scope& scope,
+ bool hasParameterExprs);
+ mozilla::Maybe<VarScope::Data*> newVarScopeData(ParseContext::Scope& scope);
+ mozilla::Maybe<LexicalScope::Data*> newLexicalScopeData(ParseContext::Scope& scope);
+ Node finishLexicalScope(ParseContext::Scope& scope, Node body);
+
+ Node propertyName(YieldHandling yieldHandling, Node propList,
+ PropertyType* propType, MutableHandleAtom propAtom);
+ Node computedPropertyName(YieldHandling yieldHandling, Node literal);
+ Node arrayInitializer(YieldHandling yieldHandling, PossibleError* possibleError);
+ Node newRegExp();
+
+ Node objectLiteral(YieldHandling yieldHandling, PossibleError* possibleError);
+
+ // Top-level entrypoint into destructuring pattern checking/name-analyzing.
+ bool checkDestructuringPattern(Node pattern, mozilla::Maybe<DeclarationKind> maybeDecl,
+ PossibleError* possibleError = nullptr);
+
+ // Recursive methods for checking/name-analyzing subcomponents of a
+ // destructuring pattern. The array/object methods *must* be passed arrays
+ // or objects. The name method may be passed anything but will report an
+ // error if not passed a name.
+ bool checkDestructuringArray(Node arrayPattern, mozilla::Maybe<DeclarationKind> maybeDecl);
+ bool checkDestructuringObject(Node objectPattern, mozilla::Maybe<DeclarationKind> maybeDecl);
+ bool checkDestructuringName(Node expr, mozilla::Maybe<DeclarationKind> maybeDecl);
+
+ bool checkAssignmentToCall(Node node, unsigned errnum);
+
+ Node newNumber(const Token& tok) {
+ return handler.newNumber(tok.number(), tok.decimalPoint(), tok.pos);
+ }
+
+ static Node null() { return ParseHandler::null(); }
+
+ bool reportBadReturn(Node pn, ParseReportKind kind, unsigned errnum, unsigned anonerrnum);
+
+ JSAtom* prefixAccessorName(PropertyType propType, HandleAtom propAtom);
+
+ TokenPos pos() const { return tokenStream.currentToken().pos; }
+
+ bool asmJS(Node list);
+
+ void addTelemetry(JSCompartment::DeprecatedLanguageExtension e);
+
+ bool warnOnceAboutExprClosure();
+ bool warnOnceAboutForEach();
+};
+
+} /* namespace frontend */
+} /* namespace js */
+
+#endif /* frontend_Parser_h */
diff --git a/js/src/frontend/SharedContext.h b/js/src/frontend/SharedContext.h
new file mode 100644
index 0000000000..39df47c20a
--- /dev/null
+++ b/js/src/frontend/SharedContext.h
@@ -0,0 +1,639 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sts=4 et sw=4 tw=99:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "jsatom.h"
+#include "jsopcode.h"
+#include "jspubtd.h"
+#include "jsscript.h"
+#include "jstypes.h"
+
+#include "builtin/ModuleObject.h"
+#include "ds/InlineTable.h"
+#include "frontend/ParseNode.h"
+#include "frontend/TokenStream.h"
+#include "vm/EnvironmentObject.h"
+
+namespace js {
+namespace frontend {
+
+enum class StatementKind : uint8_t
+{
+ Label,
+ Block,
+ If,
+ Switch,
+ With,
+ Catch,
+ Try,
+ Finally,
+ ForLoopLexicalHead,
+ ForLoop,
+ ForInLoop,
+ ForOfLoop,
+ DoLoop,
+ WhileLoop,
+
+ // Used only by BytecodeEmitter.
+ Spread
+};
+
+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;
+}
+
+static inline bool
+StatementKindIsUnlabeledBreakTarget(StatementKind kind)
+{
+ return StatementKindIsLoop(kind) || kind == StatementKind::Switch;
+}
+
+// A base class for nestable structures in the frontend, such as statements
+// and scopes.
+template <typename Concrete>
+class MOZ_STACK_CLASS Nestable
+{
+ Concrete** stack_;
+ Concrete* enclosing_;
+
+ protected:
+ explicit Nestable(Concrete** stack)
+ : stack_(stack),
+ enclosing_(*stack)
+ {
+ *stack_ = static_cast<Concrete*>(this);
+ }
+
+ // These method are protected. Some derived classes, such as ParseContext,
+ // do not expose the ability to walk the stack.
+ Concrete* enclosing() const {
+ return enclosing_;
+ }
+
+ template <typename Predicate /* (Concrete*) -> bool */>
+ static Concrete* findNearest(Concrete* it, Predicate predicate) {
+ while (it && !predicate(it))
+ it = it->enclosing();
+ return it;
+ }
+
+ template <typename T>
+ static T* findNearest(Concrete* it) {
+ while (it && !it->template is<T>())
+ it = it->enclosing();
+ return it ? &it->template as<T>() : nullptr;
+ }
+
+ template <typename T, typename Predicate /* (T*) -> bool */>
+ static T* findNearest(Concrete* it, Predicate predicate) {
+ while (it && (!it->template is<T>() || !predicate(&it->template as<T>())))
+ it = it->enclosing();
+ return it ? &it->template as<T>() : nullptr;
+ }
+
+ public:
+ ~Nestable() {
+ MOZ_ASSERT(*stack_ == static_cast<Concrete*>(this));
+ *stack_ = enclosing_;
+ }
+};
+
+// These flags apply to both global and function contexts.
+class AnyContextFlags
+{
+ // This class's data is all private and so only visible to these friends.
+ friend class SharedContext;
+
+ // True if "use strict"; appears in the body instead of being inherited.
+ bool hasExplicitUseStrict:1;
+
+ // The (static) bindings of this script need to support dynamic name
+ // read/write access. Here, 'dynamic' means dynamic dictionary lookup on
+ // the scope chain for a dynamic set of keys. The primary examples are:
+ // - direct eval
+ // - function::
+ // - with
+ // since both effectively allow any name to be accessed. Non-examples are:
+ // - upvars of nested functions
+ // - function statement
+ // since the set of assigned name is known dynamically.
+ //
+ // Note: access through the arguments object is not considered dynamic
+ // binding access since it does not go through the normal name lookup
+ // mechanism. This is debatable and could be changed (although care must be
+ // taken not to turn off the whole 'arguments' optimization). To answer the
+ // more general "is this argument aliased" question, script->needsArgsObj
+ // should be tested (see JSScript::argIsAliased).
+ bool bindingsAccessedDynamically:1;
+
+ // Whether this script, or any of its inner scripts contains a debugger
+ // statement which could potentially read or write anywhere along the
+ // scope chain.
+ bool hasDebuggerStatement:1;
+
+ // A direct eval occurs in the body of the script.
+ bool hasDirectEval:1;
+
+ public:
+ AnyContextFlags()
+ : hasExplicitUseStrict(false),
+ bindingsAccessedDynamically(false),
+ hasDebuggerStatement(false),
+ hasDirectEval(false)
+ { }
+};
+
+class FunctionContextFlags
+{
+ // This class's data is all private and so only visible to these friends.
+ friend class FunctionBox;
+
+ // This function does something that can extend the set of bindings in its
+ // call objects --- it does a direct eval in non-strict code, or includes a
+ // function statement (as opposed to a function definition).
+ //
+ // This flag is *not* inherited by enclosed or enclosing functions; it
+ // applies only to the function in whose flags it appears.
+ //
+ bool hasExtensibleScope:1;
+
+ // Technically, every function has a binding named 'arguments'. Internally,
+ // this binding is only added when 'arguments' is mentioned by the function
+ // body. This flag indicates whether 'arguments' has been bound either
+ // through implicit use:
+ // function f() { return arguments }
+ // or explicit redeclaration:
+ // function f() { var arguments; return arguments }
+ //
+ // Note 1: overwritten arguments (function() { arguments = 3 }) will cause
+ // this flag to be set but otherwise require no special handling:
+ // 'arguments' is just a local variable and uses of 'arguments' will just
+ // read the local's current slot which may have been assigned. The only
+ // special semantics is that the initial value of 'arguments' is the
+ // arguments object (not undefined, like normal locals).
+ //
+ // Note 2: if 'arguments' is bound as a formal parameter, there will be an
+ // 'arguments' in Bindings, but, as the "LOCAL" in the name indicates, this
+ // flag will not be set. This is because, as a formal, 'arguments' will
+ // have no special semantics: the initial value is unconditionally the
+ // actual argument (or undefined if nactual < nformal).
+ //
+ bool argumentsHasLocalBinding:1;
+
+ // In many cases where 'arguments' has a local binding (as described above)
+ // we do not need to actually create an arguments object in the function
+ // prologue: instead we can analyze how 'arguments' is used (using the
+ // simple dataflow analysis in analyzeSSA) to determine that uses of
+ // 'arguments' can just read from the stack frame directly. However, the
+ // dataflow analysis only looks at how JSOP_ARGUMENTS is used, so it will
+ // be unsound in several cases. The frontend filters out such cases by
+ // setting this flag which eagerly sets script->needsArgsObj to true.
+ //
+ bool definitelyNeedsArgsObj:1;
+
+ bool needsHomeObject:1;
+ bool isDerivedClassConstructor:1;
+
+ // Whether this function has a .this binding. If true, we need to emit
+ // JSOP_FUNCTIONTHIS in the prologue to initialize it.
+ bool hasThisBinding:1;
+
+ // Whether this function has nested functions.
+ bool hasInnerFunctions:1;
+
+ public:
+ FunctionContextFlags()
+ : hasExtensibleScope(false),
+ argumentsHasLocalBinding(false),
+ definitelyNeedsArgsObj(false),
+ needsHomeObject(false),
+ isDerivedClassConstructor(false),
+ hasThisBinding(false),
+ hasInnerFunctions(false)
+ { }
+};
+
+// 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
+// (and generator expression lambdas) have a lexical this-binding so their
+// ThisBinding is the same as the ThisBinding of their enclosing scope and can
+// be any value.
+enum class ThisBinding { Global, Function, Module };
+
+class GlobalSharedContext;
+class EvalSharedContext;
+class ModuleSharedContext;
+
+/*
+ * 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:
+ ExclusiveContext* const context;
+ AnyContextFlags anyCxFlags;
+ bool strictScript;
+ bool localStrict;
+ bool extraWarnings;
+
+ protected:
+ enum class Kind {
+ ObjectBox,
+ Global,
+ Eval,
+ Module
+ };
+
+ Kind kind_;
+
+ ThisBinding thisBinding_;
+
+ bool allowNewTarget_;
+ bool allowSuperProperty_;
+ bool allowSuperCall_;
+ bool inWith_;
+ bool needsThisTDZChecks_;
+
+ void computeAllowSyntax(Scope* scope);
+ void computeInWith(Scope* scope);
+ void computeThisBinding(Scope* scope);
+
+ public:
+ SharedContext(ExclusiveContext* cx, Kind kind, Directives directives, bool extraWarnings)
+ : context(cx),
+ anyCxFlags(),
+ strictScript(directives.strict()),
+ localStrict(false),
+ extraWarnings(extraWarnings),
+ kind_(kind),
+ thisBinding_(ThisBinding::Global),
+ allowNewTarget_(false),
+ allowSuperProperty_(false),
+ allowSuperCall_(false),
+ inWith_(false),
+ needsThisTDZChecks_(false)
+ { }
+
+ // If this is the outermost SharedContext, the Scope that encloses
+ // it. Otherwise nullptr.
+ virtual Scope* compilationEnclosingScope() const = 0;
+
+ virtual ObjectBox* toObjectBox() { return nullptr; }
+ bool isObjectBox() { return toObjectBox(); }
+ bool isFunctionBox() { return isObjectBox() && toObjectBox()->isFunctionBox(); }
+ inline FunctionBox* asFunctionBox();
+ bool isModuleContext() { return kind_ == Kind::Module; }
+ inline ModuleSharedContext* asModuleContext();
+ bool isGlobalContext() { return kind_ == Kind::Global; }
+ inline GlobalSharedContext* asGlobalContext();
+ bool isEvalContext() { return kind_ == Kind::Eval; }
+ inline EvalSharedContext* asEvalContext();
+
+ ThisBinding thisBinding() const { return thisBinding_; }
+
+ bool allowNewTarget() const { return allowNewTarget_; }
+ bool allowSuperProperty() const { return allowSuperProperty_; }
+ bool allowSuperCall() const { return allowSuperCall_; }
+ bool inWith() const { return inWith_; }
+ bool needsThisTDZChecks() const { return needsThisTDZChecks_; }
+
+ bool hasExplicitUseStrict() const { return anyCxFlags.hasExplicitUseStrict; }
+ bool bindingsAccessedDynamically() const { return anyCxFlags.bindingsAccessedDynamically; }
+ bool hasDebuggerStatement() const { return anyCxFlags.hasDebuggerStatement; }
+ bool hasDirectEval() const { return anyCxFlags.hasDirectEval; }
+
+ void setExplicitUseStrict() { anyCxFlags.hasExplicitUseStrict = true; }
+ void setBindingsAccessedDynamically() { anyCxFlags.bindingsAccessedDynamically = true; }
+ void setHasDebuggerStatement() { anyCxFlags.hasDebuggerStatement = true; }
+ void setHasDirectEval() { anyCxFlags.hasDirectEval = true; }
+
+ inline bool allBindingsClosedOver();
+
+ bool strict() const {
+ return strictScript || localStrict;
+ }
+ bool setLocalStrictMode(bool strict) {
+ bool retVal = localStrict;
+ localStrict = strict;
+ return retVal;
+ }
+
+ // JSOPTION_EXTRA_WARNINGS warnings or strict mode errors.
+ bool needStrictChecks() const {
+ return strict() || extraWarnings;
+ }
+
+ bool isDotVariable(JSAtom* atom) const {
+ return atom == context->names().dotGenerator || atom == context->names().dotThis;
+ }
+};
+
+class MOZ_STACK_CLASS GlobalSharedContext : public SharedContext
+{
+ ScopeKind scopeKind_;
+
+ public:
+ Rooted<GlobalScope::Data*> bindings;
+
+ GlobalSharedContext(ExclusiveContext* cx, ScopeKind scopeKind, Directives directives,
+ bool extraWarnings)
+ : SharedContext(cx, Kind::Global, directives, extraWarnings),
+ scopeKind_(scopeKind),
+ bindings(cx)
+ {
+ MOZ_ASSERT(scopeKind == ScopeKind::Global || scopeKind == ScopeKind::NonSyntactic);
+ thisBinding_ = ThisBinding::Global;
+ }
+
+ Scope* compilationEnclosingScope() const override {
+ return nullptr;
+ }
+
+ ScopeKind scopeKind() const {
+ return scopeKind_;
+ }
+};
+
+inline GlobalSharedContext*
+SharedContext::asGlobalContext()
+{
+ MOZ_ASSERT(isGlobalContext());
+ return static_cast<GlobalSharedContext*>(this);
+}
+
+class MOZ_STACK_CLASS EvalSharedContext : public SharedContext
+{
+ RootedScope enclosingScope_;
+
+ public:
+ Rooted<EvalScope::Data*> bindings;
+
+ EvalSharedContext(ExclusiveContext* cx, JSObject* enclosingEnv, Scope* enclosingScope,
+ Directives directives, bool extraWarnings);
+
+ Scope* compilationEnclosingScope() const override {
+ return enclosingScope_;
+ }
+};
+
+inline EvalSharedContext*
+SharedContext::asEvalContext()
+{
+ MOZ_ASSERT(isEvalContext());
+ return static_cast<EvalSharedContext*>(this);
+}
+
+class FunctionBox : public ObjectBox, public SharedContext
+{
+ // The parser handles tracing the fields below via the ObjectBox linked
+ // list.
+
+ Scope* enclosingScope_;
+
+ // Names from the named lambda scope, if a named lambda.
+ LexicalScope::Data* namedLambdaBindings_;
+
+ // Names from the function scope.
+ FunctionScope::Data* functionScopeBindings_;
+
+ // Names from the extra 'var' scope of the function, if the parameter list
+ // has expressions.
+ VarScope::Data* extraVarScopeBindings_;
+
+ void initWithEnclosingScope(Scope* enclosingScope);
+
+ public:
+ ParseNode* functionNode; /* back pointer used by asm.js for error messages */
+ uint32_t bufStart;
+ uint32_t bufEnd;
+ uint32_t startLine;
+ uint32_t startColumn;
+ uint16_t length;
+
+ uint8_t generatorKindBits_; /* The GeneratorKind of this function. */
+ uint8_t asyncKindBits_; /* The FunctionAsyncKing of this function. */
+
+ bool isGenexpLambda:1; /* lambda from generator expression */
+ bool hasDestructuringArgs:1; /* parameter list contains destructuring expression */
+ bool hasParameterExprs:1; /* parameter list contains expressions */
+ bool hasDirectEvalInParameterExpr:1; /* parameter list contains direct eval */
+ bool hasDuplicateParameters:1; /* parameter list contains duplicate names */
+ bool useAsm:1; /* see useAsmOrInsideUseAsm */
+ bool insideUseAsm:1; /* see useAsmOrInsideUseAsm */
+ bool isAnnexB:1; /* need to emit a synthesized Annex B assignment */
+ bool wasEmitted:1; /* Bytecode has been emitted for this function. */
+
+ // Fields for use in heuristics.
+ bool declaredArguments:1; /* the Parser declared 'arguments' */
+ bool usesArguments:1; /* contains a free use of 'arguments' */
+ bool usesApply:1; /* contains an f.apply() call */
+ bool usesThis:1; /* contains 'this' */
+ bool usesReturn:1; /* contains a 'return' statement */
+
+ FunctionContextFlags funCxFlags;
+
+ FunctionBox(ExclusiveContext* cx, LifoAlloc& alloc, ObjectBox* traceListHead, JSFunction* fun,
+ Directives directives, bool extraWarnings, GeneratorKind generatorKind,
+ FunctionAsyncKind asyncKind);
+
+ MutableHandle<LexicalScope::Data*> namedLambdaBindings() {
+ MOZ_ASSERT(context->compartment()->runtimeFromAnyThread()->keepAtoms());
+ return MutableHandle<LexicalScope::Data*>::fromMarkedLocation(&namedLambdaBindings_);
+ }
+
+ MutableHandle<FunctionScope::Data*> functionScopeBindings() {
+ MOZ_ASSERT(context->compartment()->runtimeFromAnyThread()->keepAtoms());
+ return MutableHandle<FunctionScope::Data*>::fromMarkedLocation(&functionScopeBindings_);
+ }
+
+ MutableHandle<VarScope::Data*> extraVarScopeBindings() {
+ MOZ_ASSERT(context->compartment()->runtimeFromAnyThread()->keepAtoms());
+ return MutableHandle<VarScope::Data*>::fromMarkedLocation(&extraVarScopeBindings_);
+ }
+
+ void initFromLazyFunction();
+ void initStandaloneFunction(Scope* enclosingScope);
+ void initWithEnclosingParseContext(ParseContext* enclosing, FunctionSyntaxKind kind);
+
+ ObjectBox* toObjectBox() override { return this; }
+ JSFunction* function() const { return &object->as<JSFunction>(); }
+
+ Scope* compilationEnclosingScope() const override {
+ // This method is used to distinguish the outermost SharedContext. If
+ // a FunctionBox is the outermost SharedContext, it must be a lazy
+ // function.
+ MOZ_ASSERT_IF(function()->isInterpretedLazy(),
+ enclosingScope_ == function()->lazyScript()->enclosingScope());
+ return enclosingScope_;
+ }
+
+ bool needsCallObjectRegardlessOfBindings() const {
+ return hasExtensibleScope() ||
+ needsHomeObject() ||
+ isDerivedClassConstructor() ||
+ isGenerator();
+ }
+
+ bool hasExtraBodyVarScope() const {
+ return hasParameterExprs &&
+ (extraVarScopeBindings_ ||
+ needsExtraBodyVarEnvironmentRegardlessOfBindings());
+ }
+
+ bool needsExtraBodyVarEnvironmentRegardlessOfBindings() const {
+ MOZ_ASSERT(hasParameterExprs);
+ return hasExtensibleScope() || isGenerator();
+ }
+
+ bool isLikelyConstructorWrapper() const {
+ return usesArguments && usesApply && usesThis && !usesReturn;
+ }
+
+ GeneratorKind generatorKind() const { return GeneratorKindFromBits(generatorKindBits_); }
+ bool isGenerator() const { return generatorKind() != NotGenerator; }
+ bool isLegacyGenerator() const { return generatorKind() == LegacyGenerator; }
+ bool isStarGenerator() const { return generatorKind() == StarGenerator; }
+ FunctionAsyncKind asyncKind() const { return AsyncKindFromBits(asyncKindBits_); }
+ bool isAsync() const { return asyncKind() == AsyncFunction; }
+ bool isArrow() const { return function()->isArrow(); }
+
+ void setGeneratorKind(GeneratorKind kind) {
+ // A generator kind can be set at initialization, or when "yield" is
+ // first seen. In both cases the transition can only happen from
+ // NotGenerator.
+ MOZ_ASSERT(!isGenerator());
+ generatorKindBits_ = GeneratorKindAsBits(kind);
+ }
+
+ bool hasExtensibleScope() const { return funCxFlags.hasExtensibleScope; }
+ bool hasThisBinding() const { return funCxFlags.hasThisBinding; }
+ bool argumentsHasLocalBinding() const { return funCxFlags.argumentsHasLocalBinding; }
+ bool definitelyNeedsArgsObj() const { return funCxFlags.definitelyNeedsArgsObj; }
+ bool needsHomeObject() const { return funCxFlags.needsHomeObject; }
+ bool isDerivedClassConstructor() const { return funCxFlags.isDerivedClassConstructor; }
+ bool hasInnerFunctions() const { return funCxFlags.hasInnerFunctions; }
+
+ void setHasExtensibleScope() { funCxFlags.hasExtensibleScope = true; }
+ void setHasThisBinding() { funCxFlags.hasThisBinding = true; }
+ void setArgumentsHasLocalBinding() { funCxFlags.argumentsHasLocalBinding = true; }
+ void setDefinitelyNeedsArgsObj() { MOZ_ASSERT(funCxFlags.argumentsHasLocalBinding);
+ funCxFlags.definitelyNeedsArgsObj = true; }
+ void setNeedsHomeObject() { MOZ_ASSERT(function()->allowSuperProperty());
+ funCxFlags.needsHomeObject = true; }
+ void setDerivedClassConstructor() { MOZ_ASSERT(function()->isClassConstructor());
+ funCxFlags.isDerivedClassConstructor = true; }
+ void setHasInnerFunctions() { funCxFlags.hasInnerFunctions = true; }
+
+ bool hasSimpleParameterList() const {
+ return !function()->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 || insideUseAsm;
+ }
+
+ void setStart(const TokenStream& tokenStream) {
+ bufStart = tokenStream.currentToken().pos.begin;
+ tokenStream.srcCoords.lineNumAndColumnIndex(bufStart, &startLine, &startColumn);
+ }
+
+ void trace(JSTracer* trc) override;
+};
+
+inline FunctionBox*
+SharedContext::asFunctionBox()
+{
+ MOZ_ASSERT(isFunctionBox());
+ return static_cast<FunctionBox*>(this);
+}
+
+class MOZ_STACK_CLASS ModuleSharedContext : public SharedContext
+{
+ RootedModuleObject module_;
+ RootedScope enclosingScope_;
+
+ public:
+ Rooted<ModuleScope::Data*> bindings;
+ ModuleBuilder& builder;
+
+ ModuleSharedContext(ExclusiveContext* cx, ModuleObject* module, Scope* enclosingScope,
+ ModuleBuilder& builder);
+
+ HandleModuleObject module() const { return module_; }
+ Scope* compilationEnclosingScope() const override { return enclosingScope_; }
+};
+
+inline ModuleSharedContext*
+SharedContext::asModuleContext()
+{
+ MOZ_ASSERT(isModuleContext());
+ return static_cast<ModuleSharedContext*>(this);
+}
+
+// In generators, we treat all bindings as closed so that they get stored on
+// the heap. This way there is less information to copy off the stack when
+// suspending, and back on when resuming. It also avoids the need to create
+// and invalidate DebugScope proxies for unaliased locals in a generator
+// frame, as the generator frame will be copied out to the heap and released
+// only by GC.
+inline bool
+SharedContext::allBindingsClosedOver()
+{
+ return bindingsAccessedDynamically() || (isFunctionBox() && asFunctionBox()->isGenerator());
+}
+
+} // namespace frontend
+} // namespace js
+
+#endif /* frontend_SharedContext_h */
diff --git a/js/src/frontend/SourceNotes.h b/js/src/frontend/SourceNotes.h
new file mode 100644
index 0000000000..dd2a95ad1e
--- /dev/null
+++ b/js/src/frontend/SourceNotes.h
@@ -0,0 +1,207 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sts=4 et sw=4 tw=99:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 <stdint.h>
+
+#include "jstypes.h"
+
+typedef uint8_t jssrcnote;
+
+namespace js {
+
+/*
+ * Source notes generated along with bytecode for decompiling and debugging.
+ * A source note is a uint8_t with 5 bits of type and 3 of offset from the pc
+ * of the previous note. If 3 bits of offset aren't enough, extended delta
+ * notes (SRC_XDELTA) consisting of 2 set high order bits followed by 6 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+
+ * |note-type|delta| |1 1| ext-delta |
+ * +---------+-----+ +---+-----------+
+ *
+ * At most one "gettable" note (i.e., a note of type other than SRC_NEWLINE,
+ * SRC_COLSPAN, SRC_SETLINE, and SRC_XDELTA) applies to a given bytecode.
+ *
+ * NB: the js_SrcNoteSpec array in BytecodeEmitter.cpp is indexed by this
+ * enum, so its initializers need to match the order here.
+ */
+#define FOR_EACH_SRC_NOTE_TYPE(M) \
+ M(SRC_NULL, "null", 0) /* Terminates a note vector. */ \
+ M(SRC_IF, "if", 0) /* JSOP_IFEQ bytecode is from an if-then. */ \
+ M(SRC_IF_ELSE, "if-else", 1) /* JSOP_IFEQ bytecode is from an if-then-else. */ \
+ M(SRC_COND, "cond", 1) /* JSOP_IFEQ is from conditional ?: operator. */ \
+ M(SRC_FOR, "for", 3) /* JSOP_NOP or JSOP_POP in for(;;) loop head. */ \
+ M(SRC_WHILE, "while", 1) /* JSOP_GOTO to for or while loop condition from before \
+ loop, else JSOP_NOP at top of do-while loop. */ \
+ M(SRC_FOR_IN, "for-in", 1) /* JSOP_GOTO to for-in loop condition from before \
+ loop. */ \
+ M(SRC_FOR_OF, "for-of", 1) /* JSOP_GOTO to for-of loop condition from before \
+ loop. */ \
+ M(SRC_CONTINUE, "continue", 0) /* JSOP_GOTO is a continue. */ \
+ M(SRC_BREAK, "break", 0) /* JSOP_GOTO is a break. */ \
+ M(SRC_BREAK2LABEL, "break2label", 0) /* JSOP_GOTO for 'break label'. */ \
+ M(SRC_SWITCHBREAK, "switchbreak", 0) /* JSOP_GOTO is a break in a switch. */ \
+ M(SRC_TABLESWITCH, "tableswitch", 1) /* JSOP_TABLESWITCH; offset points to end of switch. */ \
+ M(SRC_CONDSWITCH, "condswitch", 2) /* JSOP_CONDSWITCH; 1st offset points to end of switch, \
+ 2nd points to first JSOP_CASE. */ \
+ M(SRC_NEXTCASE, "nextcase", 1) /* Distance forward from one CASE in a CONDSWITCH to \
+ the next. */ \
+ M(SRC_ASSIGNOP, "assignop", 0) /* += or another assign-op follows. */ \
+ M(SRC_TRY, "try", 1) /* JSOP_TRY, offset points to goto at the end of the \
+ try block. */ \
+ /* All notes above here are "gettable". See SN_IS_GETTABLE below. */ \
+ M(SRC_COLSPAN, "colspan", 1) /* Number of columns this opcode spans. */ \
+ M(SRC_NEWLINE, "newline", 0) /* Bytecode follows a source newline. */ \
+ M(SRC_SETLINE, "setline", 1) /* A file-absolute source line number note. */ \
+ M(SRC_UNUSED20, "unused20", 0) /* Unused. */ \
+ M(SRC_UNUSED21, "unused21", 0) /* Unused. */ \
+ M(SRC_UNUSED22, "unused22", 0) /* Unused. */ \
+ M(SRC_UNUSED23, "unused23", 0) /* Unused. */ \
+ M(SRC_XDELTA, "xdelta", 0) /* 24-31 are for extended delta notes. */
+
+enum SrcNoteType {
+#define DEFINE_SRC_NOTE_TYPE(sym, name, arity) sym,
+ FOR_EACH_SRC_NOTE_TYPE(DEFINE_SRC_NOTE_TYPE)
+#undef DEFINE_SRC_NOTE_TYPE
+
+ SRC_LAST,
+ SRC_LAST_GETTABLE = SRC_TRY
+};
+
+static_assert(SRC_XDELTA == 24, "SRC_XDELTA should be 24");
+
+/* A source note array is terminated by an all-zero element. */
+inline void
+SN_MAKE_TERMINATOR(jssrcnote* sn)
+{
+ *sn = SRC_NULL;
+}
+
+inline bool
+SN_IS_TERMINATOR(jssrcnote* sn)
+{
+ return *sn == SRC_NULL;
+}
+
+} // namespace js
+
+#define SN_TYPE_BITS 5
+#define SN_DELTA_BITS 3
+#define SN_XDELTA_BITS 6
+#define SN_TYPE_MASK (JS_BITMASK(SN_TYPE_BITS) << SN_DELTA_BITS)
+#define SN_DELTA_MASK ((ptrdiff_t)JS_BITMASK(SN_DELTA_BITS))
+#define SN_XDELTA_MASK ((ptrdiff_t)JS_BITMASK(SN_XDELTA_BITS))
+
+#define SN_MAKE_NOTE(sn,t,d) (*(sn) = (jssrcnote) \
+ (((t) << SN_DELTA_BITS) \
+ | ((d) & SN_DELTA_MASK)))
+#define SN_MAKE_XDELTA(sn,d) (*(sn) = (jssrcnote) \
+ ((SRC_XDELTA << SN_DELTA_BITS) \
+ | ((d) & SN_XDELTA_MASK)))
+
+#define SN_IS_XDELTA(sn) ((*(sn) >> SN_DELTA_BITS) >= SRC_XDELTA)
+#define SN_TYPE(sn) ((js::SrcNoteType)(SN_IS_XDELTA(sn) \
+ ? SRC_XDELTA \
+ : *(sn) >> SN_DELTA_BITS))
+#define SN_SET_TYPE(sn,type) SN_MAKE_NOTE(sn, type, SN_DELTA(sn))
+#define SN_IS_GETTABLE(sn) (SN_TYPE(sn) <= SRC_LAST_GETTABLE)
+
+#define SN_DELTA(sn) ((ptrdiff_t)(SN_IS_XDELTA(sn) \
+ ? *(sn) & SN_XDELTA_MASK \
+ : *(sn) & SN_DELTA_MASK))
+#define SN_SET_DELTA(sn,delta) (SN_IS_XDELTA(sn) \
+ ? SN_MAKE_XDELTA(sn, delta) \
+ : SN_MAKE_NOTE(sn, SN_TYPE(sn), delta))
+
+#define SN_DELTA_LIMIT ((ptrdiff_t)JS_BIT(SN_DELTA_BITS))
+#define SN_XDELTA_LIMIT ((ptrdiff_t)JS_BIT(SN_XDELTA_BITS))
+
+/*
+ * Offset fields follow certain notes and are frequency-encoded: an offset in
+ * [0,0x7f] consumes one byte, an offset in [0x80,0x7fffffff] takes four, and
+ * the high bit of the first byte is set.
+ */
+#define SN_4BYTE_OFFSET_FLAG 0x80
+#define SN_4BYTE_OFFSET_MASK 0x7f
+
+#define SN_OFFSET_BITS 31
+#define SN_MAX_OFFSET (((size_t) 1 << SN_OFFSET_BITS) - 1)
+
+inline bool
+SN_REPRESENTABLE_OFFSET(ptrdiff_t offset)
+{
+ return 0 <= offset && size_t(offset) <= SN_MAX_OFFSET;
+}
+
+/*
+ * SRC_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 using the same variable-length encoding as offsets,
+ * described above. However, unlike offsets, colspans are signed, so we truncate
+ * colspans (SN_COLSPAN_TO_OFFSET) for storage as offsets, and sign-extend
+ * offsets into colspans when we read them (SN_OFFSET_TO_COLSPAN).
+ */
+#define SN_COLSPAN_SIGN_BIT (1 << (SN_OFFSET_BITS - 1))
+#define SN_MIN_COLSPAN (-SN_COLSPAN_SIGN_BIT)
+#define SN_MAX_COLSPAN (SN_COLSPAN_SIGN_BIT - 1)
+
+inline bool
+SN_REPRESENTABLE_COLSPAN(ptrdiff_t colspan)
+{
+ return SN_MIN_COLSPAN <= colspan && colspan <= SN_MAX_COLSPAN;
+}
+
+inline ptrdiff_t
+SN_OFFSET_TO_COLSPAN(ptrdiff_t offset) {
+ // There should be no bits set outside the field we're going to sign-extend.
+ MOZ_ASSERT(!(offset & ~((1U << SN_OFFSET_BITS) - 1)));
+ // Sign-extend the least significant SN_OFFSET_BITS bits.
+ return (offset ^ SN_COLSPAN_SIGN_BIT) - SN_COLSPAN_SIGN_BIT;
+}
+
+inline ptrdiff_t
+SN_COLSPAN_TO_OFFSET(ptrdiff_t colspan) {
+ // Truncate the two's complement colspan, for storage as an offset.
+ ptrdiff_t offset = colspan & ((1U << SN_OFFSET_BITS) - 1);
+ // When we read this back, we'd better get the value we stored.
+ MOZ_ASSERT(SN_OFFSET_TO_COLSPAN(offset) == colspan);
+ return offset;
+}
+
+#define SN_LENGTH(sn) ((js_SrcNoteSpec[SN_TYPE(sn)].arity == 0) ? 1 \
+ : js::SrcNoteLength(sn))
+#define SN_NEXT(sn) ((sn) + SN_LENGTH(sn))
+
+struct JSSrcNoteSpec {
+ const char* name; /* name for disassembly/debugging output */
+ int8_t arity; /* number of offset operands */
+};
+
+extern JS_FRIEND_DATA(const JSSrcNoteSpec) js_SrcNoteSpec[];
+
+namespace js {
+
+extern JS_FRIEND_API(unsigned)
+SrcNoteLength(jssrcnote* sn);
+
+/*
+ * Get and set the offset operand identified by which (0 for the first, etc.).
+ */
+extern JS_FRIEND_API(ptrdiff_t)
+GetSrcNoteOffset(jssrcnote* sn, unsigned which);
+
+} // namespace js
+
+#endif /* frontend_SourceNotes_h */
diff --git a/js/src/frontend/SyntaxParseHandler.h b/js/src/frontend/SyntaxParseHandler.h
new file mode 100644
index 0000000000..75c7e33337
--- /dev/null
+++ b/js/src/frontend/SyntaxParseHandler.h
@@ -0,0 +1,599 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sts=4 et sw=4 tw=99:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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/Attributes.h"
+
+#include "frontend/ParseNode.h"
+#include "frontend/TokenStream.h"
+
+namespace js {
+namespace frontend {
+
+template <typename ParseHandler>
+class Parser;
+
+// 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.
+ JSAtom* lastAtom;
+ TokenPos lastStringPos;
+ TokenStream& tokenStream;
+
+ public:
+ enum Node {
+ NodeFailure = 0,
+ NodeGeneric,
+ NodeGetProp,
+ NodeStringExprStatement,
+ NodeReturn,
+ NodeBreak,
+ NodeThrow,
+ NodeEmptyStatement,
+
+ NodeVarDeclaration,
+ NodeLexicalDeclaration,
+
+ NodeFunctionDefinition,
+
+ // 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,
+
+ // Nodes representing *parenthesized* IsValidSimpleAssignmentTarget
+ // nodes. We can't simply treat all such parenthesized nodes
+ // identically, because in assignment and increment/decrement contexts
+ // ES6 says that parentheses constitute a syntax error.
+ //
+ // var obj = {};
+ // var val;
+ // (val) = 3; (obj.prop) = 4; // okay per ES5's little mind
+ // [(a)] = [3]; [(obj.prop)] = [4]; // invalid ES6 syntax
+ // // ...and so on for the other IsValidSimpleAssignmentTarget nodes
+ //
+ // We don't know in advance in the current parser when we're parsing
+ // in a place where name parenthesization changes meaning, so we must
+ // have multiple node values for these cases.
+ NodeParenthesizedArgumentsName,
+ NodeParenthesizedEvalName,
+ NodeParenthesizedName,
+
+ NodeDottedProperty,
+ NodeElement,
+
+ // 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.
+
+ // Nodes representing unparenthesized names.
+ NodeUnparenthesizedArgumentsName,
+ NodeUnparenthesizedAsyncName,
+ NodeUnparenthesizedEvalName,
+ NodeUnparenthesizedName,
+
+ // 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,
+
+ // Legacy generator expressions of the form |(expr for (...))| and
+ // array comprehensions of the form |[expr for (...)]|) don't permit
+ // |expr| to be a comma expression. Thus we need this to treat
+ // |(a(), b for (x in []))| as a syntax error and
+ // |((a(), b) for (x in []))| as a generator that calls |a| and then
+ // yields |b| each time it's resumed.
+ NodeUnparenthesizedCommaExpr,
+
+ // Assignment expressions in condition contexts could be typos for
+ // equality checks. (Think |if (x = y)| versus |if (x == y)|.) Thus
+ // we need this to treat |if (x = y)| as a possible typo and
+ // |if ((x = y))| as a deliberate assignment within a condition.
+ //
+ // (Technically this isn't needed, as these are *only* extraWarnings
+ // warnings, and parsing with that option disables syntax parsing. But
+ // it seems best to be consistent, and perhaps the syntax parser will
+ // eventually enforce extraWarnings and will require this then.)
+ 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
+ };
+
+ bool isPropertyAccess(Node node) {
+ return node == NodeDottedProperty || node == NodeElement;
+ }
+
+ 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;
+ }
+
+ static bool isDestructuringPatternAnyParentheses(Node node) {
+ return isUnparenthesizedDestructuringPattern(node) ||
+ isParenthesizedDestructuringPattern(node);
+ }
+
+ public:
+ SyntaxParseHandler(ExclusiveContext* cx, LifoAlloc& alloc,
+ TokenStream& tokenStream, Parser<SyntaxParseHandler>* syntaxParser,
+ LazyScript* lazyOuterFunction)
+ : lastAtom(nullptr),
+ tokenStream(tokenStream)
+ {}
+
+ static Node null() { return NodeFailure; }
+
+ void prepareNodeForMutation(Node node) {}
+ void freeTree(Node node) {}
+
+ void trace(JSTracer* trc) {}
+
+ Node newName(PropertyName* name, const TokenPos& pos, ExclusiveContext* cx) {
+ lastAtom = name;
+ if (name == cx->names().arguments)
+ return NodeUnparenthesizedArgumentsName;
+ if (name == cx->names().async)
+ return NodeUnparenthesizedAsyncName;
+ if (name == cx->names().eval)
+ return NodeUnparenthesizedEvalName;
+ return NodeUnparenthesizedName;
+ }
+
+ Node newComputedName(Node expr, uint32_t start, uint32_t end) {
+ return NodeGeneric;
+ }
+
+ Node newObjectLiteralPropertyName(JSAtom* atom, const TokenPos& pos) {
+ return NodeUnparenthesizedName;
+ }
+
+ Node newNumber(double value, DecimalPoint decimalPoint, const TokenPos& pos) { return NodeGeneric; }
+ Node newBooleanLiteral(bool cond, const TokenPos& pos) { return NodeGeneric; }
+
+ Node newStringLiteral(JSAtom* atom, const TokenPos& pos) {
+ lastAtom = atom;
+ lastStringPos = pos;
+ return NodeUnparenthesizedString;
+ }
+
+ Node newTemplateStringLiteral(JSAtom* atom, const TokenPos& pos) {
+ return NodeGeneric;
+ }
+
+ Node newCallSiteObject(uint32_t begin) {
+ return NodeGeneric;
+ }
+
+ void addToCallSiteObject(Node callSiteObj, Node rawNode, Node cookedNode) {}
+
+ Node newThisLiteral(const TokenPos& pos, Node thisName) { return NodeGeneric; }
+ Node newNullLiteral(const TokenPos& pos) { return NodeGeneric; }
+
+ template <class Boxer>
+ Node newRegExp(RegExpObject* reobj, const TokenPos& pos, Boxer& boxer) { return NodeGeneric; }
+
+ Node newConditional(Node cond, Node thenExpr, Node elseExpr) { return NodeGeneric; }
+
+ Node newElision() { return NodeGeneric; }
+
+ Node newDelete(uint32_t begin, Node expr) {
+ return NodeUnparenthesizedUnary;
+ }
+
+ Node newTypeof(uint32_t begin, Node kid) {
+ return NodeUnparenthesizedUnary;
+ }
+
+ Node newUnary(ParseNodeKind kind, JSOp op, uint32_t begin, Node kid) {
+ return NodeUnparenthesizedUnary;
+ }
+
+ Node newUpdate(ParseNodeKind kind, uint32_t begin, Node kid) {
+ return NodeGeneric;
+ }
+
+ Node newSpread(uint32_t begin, Node kid) {
+ return NodeGeneric;
+ }
+
+ Node newArrayPush(uint32_t begin, Node kid) {
+ return NodeGeneric;
+ }
+
+ Node newBinary(ParseNodeKind kind, JSOp op = JSOP_NOP) { return NodeGeneric; }
+ Node newBinary(ParseNodeKind kind, Node left, JSOp op = JSOP_NOP) { return NodeGeneric; }
+ Node newBinary(ParseNodeKind kind, Node left, Node right, JSOp op = JSOP_NOP) {
+ return NodeGeneric;
+ }
+ Node appendOrCreateList(ParseNodeKind kind, Node left, Node right,
+ ParseContext* pc, JSOp op = JSOP_NOP) {
+ return NodeGeneric;
+ }
+
+ Node newTernary(ParseNodeKind kind, Node first, Node second, Node third, JSOp op = JSOP_NOP) {
+ return NodeGeneric;
+ }
+
+ // Expressions
+
+ Node newArrayComprehension(Node body, const TokenPos& pos) { return NodeGeneric; }
+ Node newArrayLiteral(uint32_t begin) { return NodeUnparenthesizedArray; }
+ MOZ_MUST_USE bool addElision(Node literal, const TokenPos& pos) { return true; }
+ MOZ_MUST_USE bool addSpreadElement(Node literal, uint32_t begin, Node inner) { return true; }
+ void addArrayElement(Node literal, Node element) { }
+
+ Node newCall() { return NodeFunctionCall; }
+ Node newTaggedTemplate() { return NodeGeneric; }
+
+ Node newObjectLiteral(uint32_t begin) { return NodeUnparenthesizedObject; }
+ Node newClassMethodList(uint32_t begin) { return NodeGeneric; }
+ Node newClassNames(Node outer, Node inner, const TokenPos& pos) { return NodeGeneric; }
+ Node newClass(Node name, Node heritage, Node methodBlock) { return NodeGeneric; }
+
+ Node newNewTarget(Node newHolder, Node targetHolder) { return NodeGeneric; }
+ Node newPosHolder(const TokenPos& pos) { return NodeGeneric; }
+ Node newSuperBase(Node thisName, const TokenPos& pos) { return NodeSuperBase; }
+
+ MOZ_MUST_USE bool addPrototypeMutation(Node literal, uint32_t begin, Node expr) { return true; }
+ MOZ_MUST_USE bool addPropertyDefinition(Node literal, Node name, Node expr) { return true; }
+ MOZ_MUST_USE bool addShorthand(Node literal, Node name, Node expr) { return true; }
+ MOZ_MUST_USE bool addObjectMethodDefinition(Node literal, Node name, Node fn, JSOp op) { return true; }
+ MOZ_MUST_USE bool addClassMethodDefinition(Node literal, Node name, Node fn, JSOp op, bool isStatic) { return true; }
+ Node newYieldExpression(uint32_t begin, Node value, Node gen) { return NodeGeneric; }
+ Node newYieldStarExpression(uint32_t begin, Node value, Node gen) { return NodeGeneric; }
+ Node newAwaitExpression(uint32_t begin, Node value, Node gen) { return NodeGeneric; }
+
+ // Statements
+
+ Node newStatementList(const TokenPos& pos) { return NodeGeneric; }
+ void addStatementToList(Node list, Node stmt) {}
+ void addCaseStatementToList(Node list, Node stmt) {}
+ MOZ_MUST_USE bool prependInitialYield(Node stmtList, Node gen) { return true; }
+ Node newEmptyStatement(const TokenPos& pos) { return NodeEmptyStatement; }
+
+ Node newSetThis(Node thisName, Node value) { return value; }
+
+ Node newExprStatement(Node expr, uint32_t end) {
+ return expr == NodeUnparenthesizedString ? NodeStringExprStatement : NodeGeneric;
+ }
+
+ Node newIfStatement(uint32_t begin, Node cond, Node then, Node else_) { return NodeGeneric; }
+ Node newDoWhileStatement(Node body, Node cond, const TokenPos& pos) { return NodeGeneric; }
+ Node newWhileStatement(uint32_t begin, Node cond, Node body) { return NodeGeneric; }
+ Node newSwitchStatement(uint32_t begin, Node discriminant, Node caseList) { return NodeGeneric; }
+ Node newCaseOrDefault(uint32_t begin, Node expr, Node body) { return NodeGeneric; }
+ Node newContinueStatement(PropertyName* label, const TokenPos& pos) { return NodeGeneric; }
+ Node newBreakStatement(PropertyName* label, const TokenPos& pos) { return NodeBreak; }
+ Node newReturnStatement(Node expr, const TokenPos& pos) { return NodeReturn; }
+ Node newWithStatement(uint32_t begin, Node expr, Node body) { return NodeGeneric; }
+
+ Node newLabeledStatement(PropertyName* label, Node stmt, uint32_t begin) {
+ return NodeGeneric;
+ }
+
+ Node newThrowStatement(Node expr, const TokenPos& pos) { return NodeThrow; }
+ Node newTryStatement(uint32_t begin, Node body, Node catchList, Node finallyBlock) {
+ return NodeGeneric;
+ }
+ Node newDebuggerStatement(const TokenPos& pos) { return NodeGeneric; }
+
+ Node newPropertyAccess(Node pn, PropertyName* name, uint32_t end) {
+ lastAtom = name;
+ return NodeDottedProperty;
+ }
+
+ Node newPropertyByValue(Node pn, Node kid, uint32_t end) { return NodeElement; }
+
+ MOZ_MUST_USE bool addCatchBlock(Node catchList, Node letBlock, Node catchName,
+ Node catchGuard, Node catchBody) { return true; }
+
+ MOZ_MUST_USE bool setLastFunctionFormalParameterDefault(Node funcpn, Node pn) { return true; }
+ Node newFunctionDefinition() { return NodeFunctionDefinition; }
+ bool setComprehensionLambdaBody(Node pn, Node body) { return true; }
+ void setFunctionFormalParametersAndBody(Node pn, Node kid) {}
+ void setFunctionBody(Node pn, Node kid) {}
+ void setFunctionBox(Node pn, FunctionBox* funbox) {}
+ void addFunctionFormalParameter(Node pn, Node argpn) {}
+
+ Node newForStatement(uint32_t begin, Node forHead, Node body, unsigned iflags) {
+ return NodeGeneric;
+ }
+
+ Node newComprehensionFor(uint32_t begin, Node forHead, Node body) {
+ return NodeGeneric;
+ }
+
+ Node newComprehensionBinding(Node kid) {
+ // Careful: we're asking this well after the name was parsed, so the
+ // value returned may not correspond to |kid|'s actual name. But it
+ // *will* be truthy iff |kid| was a name, so we're safe.
+ MOZ_ASSERT(isUnparenthesizedName(kid));
+ return NodeGeneric;
+ }
+
+ Node newForHead(Node init, Node test, Node update, const TokenPos& pos) {
+ return NodeGeneric;
+ }
+
+ Node newForInOrOfHead(ParseNodeKind kind, Node target, Node iteratedExpr, const TokenPos& pos) {
+ return NodeGeneric;
+ }
+
+ MOZ_MUST_USE bool finishInitializerAssignment(Node pn, Node init) { return true; }
+
+ 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) {}
+
+ void setPosition(Node pn, const TokenPos& pos) {}
+ TokenPos getPosition(Node pn) {
+ return tokenStream.currentToken().pos;
+ }
+
+ Node newList(ParseNodeKind kind, JSOp op = JSOP_NOP) {
+ MOZ_ASSERT(kind != PNK_VAR);
+ MOZ_ASSERT(kind != PNK_LET);
+ MOZ_ASSERT(kind != PNK_CONST);
+ return NodeGeneric;
+ }
+ Node newList(ParseNodeKind kind, uint32_t begin, JSOp op = JSOP_NOP) {
+ return newList(kind, op);
+ }
+ Node newList(ParseNodeKind kind, Node kid, JSOp op = JSOP_NOP) {
+ return newList(kind, op);
+ }
+
+ Node newDeclarationList(ParseNodeKind kind, JSOp op = JSOP_NOP) {
+ if (kind == PNK_VAR)
+ return NodeVarDeclaration;
+ MOZ_ASSERT(kind == PNK_LET || kind == PNK_CONST);
+ return NodeLexicalDeclaration;
+ }
+ Node newDeclarationList(ParseNodeKind kind, Node kid, JSOp op = JSOP_NOP) {
+ return newDeclarationList(kind, op);
+ }
+
+ bool isDeclarationList(Node node) {
+ return node == NodeVarDeclaration || node == NodeLexicalDeclaration;
+ }
+
+ Node singleBindingFromDeclaration(Node decl) {
+ MOZ_ASSERT(isDeclarationList(decl));
+
+ // This is, unfortunately, very dodgy. Obviously NodeVarDeclaration
+ // and NodeLexicalDeclaration can store no info on the arbitrary
+ // number of bindings it could contain.
+ //
+ // But this method is called only for cloning for-in/of declarations
+ // as initialization targets. That context simplifies matters. If the
+ // binding is a single name, it'll always syntax-parse (or it would
+ // already have been rejected as assigning/binding a forbidden name).
+ // Otherwise the binding is a destructuring pattern. But syntax
+ // parsing would *already* have aborted when it saw a destructuring
+ // pattern. So we can just say any old thing here, because the only
+ // time we'll be wrong is a case that syntax parsing has already
+ // rejected. Use NodeUnparenthesizedName so the SyntaxParseHandler
+ // Parser::cloneLeftHandSide can assert it sees only this.
+ return NodeUnparenthesizedName;
+ }
+
+ Node newCatchList() {
+ return newList(PNK_CATCHLIST, JSOP_NOP);
+ }
+
+ Node newCommaExpressionList(Node kid) {
+ return NodeUnparenthesizedCommaExpr;
+ }
+
+ void addList(Node list, Node kid) {
+ MOZ_ASSERT(list == NodeGeneric ||
+ list == NodeUnparenthesizedArray ||
+ list == NodeUnparenthesizedObject ||
+ list == NodeUnparenthesizedCommaExpr ||
+ list == NodeVarDeclaration ||
+ list == NodeLexicalDeclaration ||
+ list == NodeFunctionCall);
+ }
+
+ Node newAssignment(ParseNodeKind kind, Node lhs, Node rhs, JSOp op) {
+ if (kind == PNK_ASSIGN)
+ return NodeUnparenthesizedAssignment;
+ return newBinary(kind, lhs, rhs, op);
+ }
+
+ bool isUnparenthesizedCommaExpression(Node node) {
+ return node == NodeUnparenthesizedCommaExpr;
+ }
+
+ 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 == NodeFunctionDefinition || pn == NodeVarDeclaration ||
+ pn == NodeBreak ||
+ pn == NodeThrow ||
+ pn == NodeEmptyStatement;
+ }
+
+ bool isSuperBase(Node pn) {
+ return pn == NodeSuperBase;
+ }
+
+ void setOp(Node pn, JSOp op) {}
+ void setListFlag(Node pn, unsigned flag) {}
+ MOZ_MUST_USE Node parenthesize(Node node) {
+ // A number of nodes have different behavior upon parenthesization, but
+ // only in some circumstances. Convert these nodes to special
+ // parenthesized forms.
+ if (node == NodeUnparenthesizedArgumentsName)
+ return NodeParenthesizedArgumentsName;
+ if (node == NodeUnparenthesizedEvalName)
+ return NodeParenthesizedEvalName;
+ if (node == NodeUnparenthesizedName || node == NodeUnparenthesizedAsyncName)
+ return NodeParenthesizedName;
+
+ 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 == NodeUnparenthesizedCommaExpr ||
+ node == NodeUnparenthesizedAssignment ||
+ node == NodeUnparenthesizedUnary)
+ {
+ return NodeGeneric;
+ }
+
+ // In all other cases, the parenthesized form of |node| is equivalent
+ // to the unparenthesized form: return |node| unchanged.
+ return node;
+ }
+ MOZ_MUST_USE Node setLikelyIIFE(Node pn) {
+ return pn; // Remain in syntax-parse mode.
+ }
+ void setPrologue(Node pn) {}
+
+ bool isConstant(Node pn) { return false; }
+
+ bool isUnparenthesizedName(Node node) {
+ return node == NodeUnparenthesizedArgumentsName ||
+ node == NodeUnparenthesizedAsyncName ||
+ node == NodeUnparenthesizedEvalName ||
+ node == NodeUnparenthesizedName;
+ }
+
+ bool isNameAnyParentheses(Node node) {
+ if (isUnparenthesizedName(node))
+ return true;
+ return node == NodeParenthesizedArgumentsName ||
+ node == NodeParenthesizedEvalName ||
+ node == NodeParenthesizedName;
+ }
+
+ bool nameIsEvalAnyParentheses(Node node, ExclusiveContext* cx) {
+ MOZ_ASSERT(isNameAnyParentheses(node),
+ "must only call this function on known names");
+ return node == NodeUnparenthesizedEvalName || node == NodeParenthesizedEvalName;
+ }
+
+ const char* nameIsArgumentsEvalAnyParentheses(Node node, ExclusiveContext* cx) {
+ MOZ_ASSERT(isNameAnyParentheses(node),
+ "must only call this method on known names");
+
+ if (nameIsEvalAnyParentheses(node, cx))
+ return js_eval_str;
+ if (node == NodeUnparenthesizedArgumentsName || node == NodeParenthesizedArgumentsName)
+ return js_arguments_str;
+ return nullptr;
+ }
+
+ bool nameIsUnparenthesizedAsync(Node node, ExclusiveContext* cx) {
+ MOZ_ASSERT(isNameAnyParentheses(node),
+ "must only call this function on known names");
+ return node == NodeUnparenthesizedAsyncName;
+ }
+
+ PropertyName* 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)
+ return nullptr;
+ return lastAtom->asPropertyName();
+ }
+
+ JSAtom* isStringExprStatement(Node pn, TokenPos* pos) {
+ if (pn == NodeStringExprStatement) {
+ *pos = lastStringPos;
+ return lastAtom;
+ }
+ return nullptr;
+ }
+
+ bool canSkipLazyInnerFunctions() {
+ return false;
+ }
+ bool canSkipLazyClosedOverBindings() {
+ return false;
+ }
+ JSAtom* nextLazyClosedOverBinding() {
+ MOZ_CRASH("SyntaxParseHandler::canSkipLazyClosedOverBindings must return false");
+ }
+
+ void adjustGetToSet(Node node) {}
+
+ void disableSyntaxParser() {
+ }
+};
+
+} // namespace frontend
+} // namespace js
+
+#endif /* frontend_SyntaxParseHandler_h */
diff --git a/js/src/frontend/TokenKind.h b/js/src/frontend/TokenKind.h
new file mode 100644
index 0000000000..6f22d78e5f
--- /dev/null
+++ b/js/src/frontend/TokenKind.h
@@ -0,0 +1,245 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sts=4 et sw=4 tw=99:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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
+
+/*
+ * 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, with `TOK_` prefix.
+ *
+ * <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, with `TOK_` prefix. It should end with `_FIRST` or `_LAST`.
+ * This is used to check TokenKind by range-testing:
+ * TOK_BINOP_FIRST <= tt && tt <= TOK_BINOP_LAST
+ *
+ * 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(LB, "'['") \
+ macro(RB, "']'") \
+ macro(LC, "'{'") \
+ macro(RC, "'}'") \
+ macro(LP, "'('") \
+ macro(RP, "')'") \
+ macro(NAME, "identifier") \
+ macro(NUMBER, "numeric literal") \
+ macro(STRING, "string literal") \
+ \
+ /* start of template literal with substitutions */ \
+ macro(TEMPLATE_HEAD, "'${'") \
+ /* template literal without substitutions */ \
+ macro(NO_SUBS_TEMPLATE, "template literal") \
+ \
+ macro(REGEXP, "regular expression literal") \
+ macro(TRUE, "boolean literal 'true'") \
+ macro(FALSE, "boolean literal 'false'") \
+ macro(NULL, "null literal") \
+ macro(THIS, "keyword '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(YIELD, "keyword 'yield'") \
+ macro(AWAIT, "keyword 'await'") \
+ macro(EXPORT, "keyword 'export'") \
+ macro(IMPORT, "keyword 'import'") \
+ macro(CLASS, "keyword 'class'") \
+ macro(EXTENDS, "keyword 'extends'") \
+ macro(SUPER, "keyword 'super'") \
+ macro(RESERVED, "reserved keyword") \
+ /* reserved keywords in strict mode */ \
+ macro(STRICT_RESERVED, "reserved keyword") \
+ \
+ /* \
+ * The following token types occupy contiguous ranges to enable easy \
+ * range-testing. \
+ */ \
+ /* \
+ * Binary operators tokens, TOK_OR thru TOK_POW. These must be in the same \
+ * order as F(OR) and friends in FOR_EACH_PARSE_NODE_KIND in ParseNode.h. \
+ */ \
+ macro(OR, "'||'") /* logical or */ \
+ range(BINOP_FIRST, 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(EQUALITY_START, STRICTEQ) \
+ macro(EQ, "'=='") \
+ macro(STRICTNE, "'!=='") \
+ macro(NE, "'!='") \
+ range(EQUALITY_LAST, NE) \
+ \
+ /* Relational ops, per TokenKindIsRelational. */ \
+ macro(LT, "'<'") \
+ range(RELOP_START, LT) \
+ macro(LE, "'<='") \
+ macro(GT, "'>'") \
+ macro(GE, "'>='") \
+ range(RELOP_LAST, GE) \
+ \
+ macro(INSTANCEOF, "keyword 'instanceof'") \
+ macro(IN, "keyword 'in'") \
+ \
+ /* Shift ops, per TokenKindIsShift. */ \
+ macro(LSH, "'<<'") \
+ range(SHIFTOP_START, LSH) \
+ macro(RSH, "'>>'") \
+ macro(URSH, "'>>>'") \
+ range(SHIFTOP_LAST, URSH) \
+ \
+ macro(ADD, "'+'") \
+ macro(SUB, "'-'") \
+ macro(MUL, "'*'") \
+ macro(DIV, "'/'") \
+ macro(MOD, "'%'") \
+ macro(POW, "'**'") \
+ range(BINOP_LAST, POW) \
+ \
+ /* Unary operation tokens. */ \
+ macro(TYPEOF, "keyword 'typeof'") \
+ macro(VOID, "keyword 'void'") \
+ macro(NOT, "'!'") \
+ macro(BITNOT, "'~'") \
+ \
+ macro(ARROW, "'=>'") /* function arrow */ \
+ \
+ /* Assignment ops, per TokenKindIsAssignment */ \
+ macro(ASSIGN, "'='") \
+ range(ASSIGNMENT_START, ASSIGN) \
+ macro(ADDASSIGN, "'+='") \
+ macro(SUBASSIGN, "'-='") \
+ macro(BITORASSIGN, "'|='") \
+ macro(BITXORASSIGN, "'^='") \
+ macro(BITANDASSIGN, "'&='") \
+ macro(LSHASSIGN, "'<<='") \
+ macro(RSHASSIGN, "'>>='") \
+ macro(URSHASSIGN, "'>>>='") \
+ macro(MULASSIGN, "'*='") \
+ macro(DIVASSIGN, "'/='") \
+ macro(MODASSIGN, "'%='") \
+ macro(POWASSIGN, "'**='") \
+ range(ASSIGNMENT_LAST, 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 TokenKind {
+#define EMIT_ENUM(name, desc) TOK_##name,
+#define EMIT_ENUM_RANGE(name, value) TOK_##name = TOK_##value,
+ FOR_EACH_TOKEN_KIND_WITH_RANGE(EMIT_ENUM, EMIT_ENUM_RANGE)
+#undef EMIT_ENUM
+#undef EMIT_ENUM_RANGE
+ TOK_LIMIT // domain size
+};
+
+inline bool
+TokenKindIsBinaryOp(TokenKind tt)
+{
+ return TOK_BINOP_FIRST <= tt && tt <= TOK_BINOP_LAST;
+}
+
+inline bool
+TokenKindIsEquality(TokenKind tt)
+{
+ return TOK_EQUALITY_START <= tt && tt <= TOK_EQUALITY_LAST;
+}
+
+inline bool
+TokenKindIsRelational(TokenKind tt)
+{
+ return TOK_RELOP_START <= tt && tt <= TOK_RELOP_LAST;
+}
+
+inline bool
+TokenKindIsShift(TokenKind tt)
+{
+ return TOK_SHIFTOP_START <= tt && tt <= TOK_SHIFTOP_LAST;
+}
+
+inline bool
+TokenKindIsAssignment(TokenKind tt)
+{
+ return TOK_ASSIGNMENT_START <= tt && tt <= TOK_ASSIGNMENT_LAST;
+}
+
+} // 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..c166ed4145
--- /dev/null
+++ b/js/src/frontend/TokenStream.cpp
@@ -0,0 +1,1962 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sts=4 et sw=4 tw=99:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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/IntegerTypeTraits.h"
+#include "mozilla/PodOperations.h"
+
+#include <ctype.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "jsatom.h"
+#include "jscntxt.h"
+#include "jscompartment.h"
+#include "jsexn.h"
+#include "jsnum.h"
+
+#include "frontend/BytecodeCompiler.h"
+#include "js/CharacterEncoding.h"
+#include "js/UniquePtr.h"
+#include "vm/HelperThreads.h"
+#include "vm/Keywords.h"
+#include "vm/StringBuffer.h"
+#include "vm/Unicode.h"
+
+using namespace js;
+using namespace js::frontend;
+
+using mozilla::Maybe;
+using mozilla::PodAssign;
+using mozilla::PodCopy;
+using mozilla::PodZero;
+
+struct KeywordInfo {
+ const char* chars; // C string with keyword text
+ TokenKind tokentype;
+};
+
+static const KeywordInfo keywords[] = {
+#define KEYWORD_INFO(keyword, name, type) \
+ {js_##keyword##_str, type},
+ FOR_EACH_JAVASCRIPT_KEYWORD(KEYWORD_INFO)
+#undef KEYWORD_INFO
+};
+
+// Returns a KeywordInfo for the specified characters, or nullptr if the string
+// is not a keyword.
+template <typename CharT>
+static const KeywordInfo*
+FindKeyword(const CharT* s, size_t length)
+{
+ MOZ_ASSERT(length != 0);
+
+ size_t i;
+ const KeywordInfo* kw;
+ const char* chars;
+
+#define JSKW_LENGTH() length
+#define JSKW_AT(column) s[column]
+#define JSKW_GOT_MATCH(index) i = (index); goto got_match;
+#define JSKW_TEST_GUESS(index) i = (index); goto test_guess;
+#define JSKW_NO_MATCH() goto no_match;
+#include "jsautokw.h"
+#undef JSKW_NO_MATCH
+#undef JSKW_TEST_GUESS
+#undef JSKW_GOT_MATCH
+#undef JSKW_AT
+#undef JSKW_LENGTH
+
+ got_match:
+ return &keywords[i];
+
+ test_guess:
+ kw = &keywords[i];
+ chars = kw->chars;
+ do {
+ if (*s++ != (unsigned char)(*chars++))
+ goto no_match;
+ } while (--length != 0);
+ return kw;
+
+ no_match:
+ return nullptr;
+}
+
+static const KeywordInfo*
+FindKeyword(JSLinearString* str)
+{
+ JS::AutoCheckCannotGC nogc;
+ return str->hasLatin1Chars()
+ ? FindKeyword(str->latin1Chars(nogc), str->length())
+ : FindKeyword(str->twoByteChars(nogc), str->length());
+}
+
+template <typename CharT>
+static bool
+IsIdentifier(const CharT* chars, size_t length)
+{
+ if (length == 0)
+ return false;
+
+ if (!unicode::IsIdentifierStart(char16_t(*chars)))
+ return false;
+
+ const CharT* end = chars + length;
+ while (++chars != end) {
+ if (!unicode::IsIdentifierPart(char16_t(*chars)))
+ return false;
+ }
+
+ return true;
+}
+
+bool
+frontend::IsIdentifier(JSLinearString* str)
+{
+ JS::AutoCheckCannotGC nogc;
+ return str->hasLatin1Chars()
+ ? ::IsIdentifier(str->latin1Chars(nogc), str->length())
+ : ::IsIdentifier(str->twoByteChars(nogc), str->length());
+}
+
+bool
+frontend::IsIdentifier(const char16_t* chars, size_t length)
+{
+ return ::IsIdentifier(chars, length);
+}
+
+bool
+frontend::IsKeyword(JSLinearString* str)
+{
+ return FindKeyword(str) != nullptr;
+}
+
+TokenStream::SourceCoords::SourceCoords(ExclusiveContext* cx, uint32_t ln)
+ : lineStartOffsets_(cx), initialLineNum_(ln), lastLineIndex_(0)
+{
+ // This is actually necessary! Removing it causes compile errors on
+ // GCC and clang. You could try declaring this:
+ //
+ // const uint32_t TokenStream::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 0. 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(0);
+ lineStartOffsets_.infallibleAppend(maxPtr);
+}
+
+MOZ_ALWAYS_INLINE bool
+TokenStream::SourceCoords::add(uint32_t lineNum, uint32_t lineStartOffset)
+{
+ uint32_t lineIndex = lineNumToIndex(lineNum);
+ uint32_t sentinelIndex = lineStartOffsets_.length() - 1;
+
+ MOZ_ASSERT(lineStartOffsets_[0] == 0 && lineStartOffsets_[sentinelIndex] == MAX_PTR);
+
+ if (lineIndex == 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))
+ return false;
+
+ lineStartOffsets_[lineIndex] = 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 lineIndex.
+ MOZ_ASSERT_IF(lineIndex < sentinelIndex, lineStartOffsets_[lineIndex] == lineStartOffset);
+ }
+ return true;
+}
+
+MOZ_ALWAYS_INLINE bool
+TokenStream::SourceCoords::fill(const TokenStream::SourceCoords& other)
+{
+ 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
+TokenStream::SourceCoords::lineIndexOf(uint32_t offset) const
+{
+ uint32_t iMin, iMax, iMid;
+
+ if (lineStartOffsets_[lastLineIndex_] <= 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_[lastLineIndex_ + 1])
+ return lastLineIndex_; // lineIndex is same as last time
+
+ // If we reach here, there must be at least one more entry (plus the
+ // sentinel). Try it.
+ lastLineIndex_++;
+ if (offset < lineStartOffsets_[lastLineIndex_ + 1])
+ return lastLineIndex_; // lineIndex is one higher than last time
+
+ // The same logic applies here.
+ lastLineIndex_++;
+ if (offset < lineStartOffsets_[lastLineIndex_ + 1]) {
+ return lastLineIndex_; // lineIndex is two higher than last time
+ }
+
+ // No luck. Oh well, we have a better-than-default starting point for
+ // the binary search.
+ iMin = lastLineIndex_ + 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 && offset < lineStartOffsets_[iMin + 1]);
+ lastLineIndex_ = iMin;
+ return iMin;
+}
+
+uint32_t
+TokenStream::SourceCoords::lineNum(uint32_t offset) const
+{
+ uint32_t lineIndex = lineIndexOf(offset);
+ return lineIndexToNum(lineIndex);
+}
+
+uint32_t
+TokenStream::SourceCoords::columnIndex(uint32_t offset) const
+{
+ uint32_t lineIndex = lineIndexOf(offset);
+ uint32_t lineStartOffset = lineStartOffsets_[lineIndex];
+ MOZ_ASSERT(offset >= lineStartOffset);
+ return offset - lineStartOffset;
+}
+
+void
+TokenStream::SourceCoords::lineNumAndColumnIndex(uint32_t offset, uint32_t* lineNum,
+ uint32_t* columnIndex) const
+{
+ uint32_t lineIndex = lineIndexOf(offset);
+ *lineNum = lineIndexToNum(lineIndex);
+ uint32_t lineStartOffset = lineStartOffsets_[lineIndex];
+ MOZ_ASSERT(offset >= lineStartOffset);
+ *columnIndex = offset - lineStartOffset;
+}
+
+#ifdef _MSC_VER
+#pragma warning(push)
+#pragma warning(disable:4351)
+#endif
+
+TokenStream::TokenStream(ExclusiveContext* cx, const ReadOnlyCompileOptions& options,
+ const char16_t* base, size_t length, StrictModeGetter* smg)
+ : srcCoords(cx, options.lineno),
+ options_(options),
+ tokens(),
+ cursor(),
+ lookahead(),
+ lineno(options.lineno),
+ flags(),
+ linebase(0),
+ prevLinebase(size_t(-1)),
+ userbuf(cx, base, length, options.column),
+ filename(options.filename()),
+ displayURL_(nullptr),
+ sourceMapURL_(nullptr),
+ tokenbuf(cx),
+ cx(cx),
+ mutedErrors(options.mutedErrors()),
+ strictModeGetter(smg)
+{
+ // Nb: the following tables could be static, but initializing them here is
+ // much easier. Don't worry, the time to initialize them for each
+ // TokenStream is trivial. See bug 639420.
+
+ // See Parser::assignExpr() for an explanation of isExprEnding[].
+ memset(isExprEnding, 0, sizeof(isExprEnding));
+ isExprEnding[TOK_COMMA] = 1;
+ isExprEnding[TOK_SEMI] = 1;
+ isExprEnding[TOK_COLON] = 1;
+ isExprEnding[TOK_RP] = 1;
+ isExprEnding[TOK_RB] = 1;
+ isExprEnding[TOK_RC] = 1;
+}
+
+#ifdef _MSC_VER
+#pragma warning(pop)
+#endif
+
+bool
+TokenStream::checkOptions()
+{
+ // Constrain starting columns to half of the range of a signed 32-bit value,
+ // to avoid overflow.
+ if (options().column >= mozilla::MaxValue<int32_t>::value / 2 + 1) {
+ reportErrorNoOffset(JSMSG_BAD_COLUMN_NUMBER);
+ return false;
+ }
+
+ return true;
+}
+
+TokenStream::~TokenStream()
+{
+}
+
+// Use the fastest available getc.
+#if defined(HAVE_GETC_UNLOCKED)
+# define fast_getc getc_unlocked
+#elif defined(HAVE__GETC_NOLOCK)
+# define fast_getc _getc_nolock
+#else
+# define fast_getc getc
+#endif
+
+MOZ_ALWAYS_INLINE void
+TokenStream::updateLineInfoForEOL()
+{
+ prevLinebase = linebase;
+ linebase = userbuf.offset();
+ lineno++;
+ if (!srcCoords.add(lineno, linebase))
+ flags.hitOOM = true;
+}
+
+MOZ_ALWAYS_INLINE void
+TokenStream::updateFlagsForEOL()
+{
+ flags.isDirtyLine = false;
+}
+
+// This gets the next char, normalizing all EOL sequences to '\n' as it goes.
+int32_t
+TokenStream::getChar()
+{
+ int32_t c;
+ if (MOZ_LIKELY(userbuf.hasRawChars())) {
+ c = userbuf.getRawChar();
+
+ // Normalize the char16_t if it was a newline.
+ if (MOZ_UNLIKELY(c == '\n'))
+ goto eol;
+ if (MOZ_UNLIKELY(c == '\r')) {
+ // If it's a \r\n sequence: treat as a single EOL, skip over the \n.
+ if (MOZ_LIKELY(userbuf.hasRawChars()))
+ userbuf.matchRawChar('\n');
+ goto eol;
+ }
+ if (MOZ_UNLIKELY(c == LINE_SEPARATOR || c == PARA_SEPARATOR))
+ goto eol;
+
+ return c;
+ }
+
+ flags.isEOF = true;
+ return EOF;
+
+ eol:
+ updateLineInfoForEOL();
+ return '\n';
+}
+
+// This gets the next char. It does nothing special with EOL sequences, not
+// even updating the line counters. It can be used safely if (a) the
+// resulting char is guaranteed to be ungotten (by ungetCharIgnoreEOL()) if
+// it's an EOL, and (b) the line-related state (lineno, linebase) is not used
+// before it's ungotten.
+int32_t
+TokenStream::getCharIgnoreEOL()
+{
+ if (MOZ_LIKELY(userbuf.hasRawChars()))
+ return userbuf.getRawChar();
+
+ flags.isEOF = true;
+ return EOF;
+}
+
+void
+TokenStream::ungetChar(int32_t c)
+{
+ if (c == EOF)
+ return;
+ MOZ_ASSERT(!userbuf.atStart());
+ userbuf.ungetRawChar();
+ if (c == '\n') {
+#ifdef DEBUG
+ int32_t c2 = userbuf.peekRawChar();
+ MOZ_ASSERT(TokenBuf::isRawEOLChar(c2));
+#endif
+
+ // If it's a \r\n sequence, also unget the \r.
+ if (!userbuf.atStart())
+ userbuf.matchRawCharBackwards('\r');
+
+ MOZ_ASSERT(prevLinebase != size_t(-1)); // we should never get more than one EOL char
+ linebase = prevLinebase;
+ prevLinebase = size_t(-1);
+ lineno--;
+ } else {
+ MOZ_ASSERT(userbuf.peekRawChar() == c);
+ }
+}
+
+void
+TokenStream::ungetCharIgnoreEOL(int32_t c)
+{
+ if (c == EOF)
+ return;
+ MOZ_ASSERT(!userbuf.atStart());
+ userbuf.ungetRawChar();
+}
+
+// Return true iff |n| raw characters can be read from this without reading past
+// EOF or a newline, and copy those characters into |cp| if so. The characters
+// are not consumed: use skipChars(n) to do so after checking that the consumed
+// characters had appropriate values.
+bool
+TokenStream::peekChars(int n, char16_t* cp)
+{
+ int i, j;
+ int32_t c;
+
+ for (i = 0; i < n; i++) {
+ c = getCharIgnoreEOL();
+ if (c == EOF)
+ break;
+ if (c == '\n') {
+ ungetCharIgnoreEOL(c);
+ break;
+ }
+ cp[i] = char16_t(c);
+ }
+ for (j = i - 1; j >= 0; j--)
+ ungetCharIgnoreEOL(cp[j]);
+ return i == n;
+}
+
+size_t
+TokenStream::TokenBuf::findEOLMax(size_t start, size_t max)
+{
+ const char16_t* p = rawCharPtrAt(start);
+
+ size_t n = 0;
+ while (true) {
+ if (p >= limit_)
+ break;
+ if (n >= max)
+ break;
+ n++;
+ if (TokenBuf::isRawEOLChar(*p++))
+ break;
+ }
+ return start + n;
+}
+
+bool
+TokenStream::advance(size_t position)
+{
+ const char16_t* end = userbuf.rawCharPtrAt(position);
+ while (userbuf.addressOfNextRawChar() < end)
+ getChar();
+
+ Token* cur = &tokens[cursor];
+ cur->pos.begin = userbuf.offset();
+ MOZ_MAKE_MEM_UNDEFINED(&cur->type, sizeof(cur->type));
+ lookahead = 0;
+
+ if (flags.hitOOM)
+ return reportError(JSMSG_OUT_OF_MEMORY);
+
+ return true;
+}
+
+void
+TokenStream::tell(Position* pos)
+{
+ pos->buf = userbuf.addressOfNextRawChar(/* allowPoisoned = */ true);
+ pos->flags = flags;
+ pos->lineno = lineno;
+ pos->linebase = linebase;
+ pos->prevLinebase = prevLinebase;
+ pos->lookahead = lookahead;
+ pos->currentToken = currentToken();
+ for (unsigned i = 0; i < lookahead; i++)
+ pos->lookaheadTokens[i] = tokens[(cursor + 1 + i) & ntokensMask];
+}
+
+void
+TokenStream::seek(const Position& pos)
+{
+ userbuf.setAddressOfNextRawChar(pos.buf, /* allowPoisoned = */ true);
+ flags = pos.flags;
+ lineno = pos.lineno;
+ linebase = pos.linebase;
+ prevLinebase = pos.prevLinebase;
+ lookahead = pos.lookahead;
+
+ tokens[cursor] = pos.currentToken;
+ for (unsigned i = 0; i < lookahead; i++)
+ tokens[(cursor + 1 + i) & ntokensMask] = pos.lookaheadTokens[i];
+}
+
+bool
+TokenStream::seek(const Position& pos, const TokenStream& other)
+{
+ if (!srcCoords.fill(other.srcCoords))
+ return false;
+ seek(pos);
+ return true;
+}
+
+bool
+TokenStream::reportStrictModeErrorNumberVA(uint32_t offset, bool strictMode, unsigned errorNumber,
+ va_list args)
+{
+ // In strict mode code, this is an error, not merely a warning.
+ unsigned flags;
+ if (strictMode)
+ flags = JSREPORT_ERROR;
+ else if (options().extraWarningsOption)
+ flags = JSREPORT_WARNING | JSREPORT_STRICT;
+ else
+ return true;
+
+ return reportCompileErrorNumberVA(offset, flags, errorNumber, args);
+}
+
+void
+CompileError::throwError(JSContext* cx)
+{
+ if (JSREPORT_IS_WARNING(flags)) {
+ CallWarningReporter(cx, this);
+ return;
+ }
+
+ // If there's a runtime exception type associated with this error
+ // number, set that as the pending exception. For errors occuring at
+ // compile time, this is very likely to be a JSEXN_SYNTAXERR.
+ //
+ // If an exception is thrown but not caught, the JSREPORT_EXCEPTION
+ // flag will be set in report.flags. Proper behavior for an error
+ // reporter is to ignore a report with this flag for all but top-level
+ // compilation errors. The exception will remain pending, and so long
+ // as the non-top-level "load", "eval", or "compile" native function
+ // returns false, the top-level reporter will eventually receive the
+ // uncaught exception report.
+ ErrorToException(cx, this, nullptr, nullptr);
+}
+
+bool
+TokenStream::reportCompileErrorNumberVA(uint32_t offset, unsigned flags, unsigned errorNumber,
+ va_list args)
+{
+ bool warning = JSREPORT_IS_WARNING(flags);
+
+ if (warning && options().werrorOption) {
+ flags &= ~JSREPORT_WARNING;
+ warning = false;
+ }
+
+ // On the main thread, report the error immediately. When compiling off
+ // thread, save the error so that the main thread can report it later.
+ CompileError tempErr;
+ CompileError* tempErrPtr = &tempErr;
+ if (!cx->isJSContext() && !cx->addPendingCompileError(&tempErrPtr))
+ return false;
+ CompileError& err = *tempErrPtr;
+
+ err.flags = flags;
+ err.errorNumber = errorNumber;
+ err.filename = filename;
+ err.isMuted = mutedErrors;
+ if (offset == NoOffset) {
+ err.lineno = 0;
+ err.column = 0;
+ } else {
+ err.lineno = srcCoords.lineNum(offset);
+ err.column = srcCoords.columnIndex(offset);
+ }
+
+ // If we have no location information, try to get one from the caller.
+ bool callerFilename = false;
+ if (offset != NoOffset && !err.filename && cx->isJSContext()) {
+ NonBuiltinFrameIter iter(cx->asJSContext(),
+ FrameIter::FOLLOW_DEBUGGER_EVAL_PREV_LINK,
+ cx->compartment()->principals());
+ if (!iter.done() && iter.filename()) {
+ callerFilename = true;
+ err.filename = iter.filename();
+ err.lineno = iter.computeLine(&err.column);
+ }
+ }
+
+ if (!ExpandErrorArgumentsVA(cx, GetErrorMessage, nullptr, errorNumber,
+ nullptr, ArgumentsAreLatin1, &err, args))
+ {
+ return false;
+ }
+
+ // Given a token, T, that we want to complain about: if T's (starting)
+ // lineno doesn't match TokenStream's lineno, that means we've scanned past
+ // the line that T starts on, which makes it hard to print some or all of
+ // T's (starting) line for context.
+ //
+ // So we don't even try, leaving report.linebuf and friends zeroed. This
+ // means that any error involving a multi-line token (e.g. an unterminated
+ // multi-line string literal) won't have a context printed.
+ if (offset != NoOffset && err.lineno == lineno && !callerFilename) {
+ // We show only a portion (a "window") of the line around the erroneous
+ // token -- the first char in the token, plus |windowRadius| chars
+ // before it and |windowRadius - 1| chars after it. This is because
+ // lines can be very long and printing the whole line is (a) not that
+ // helpful, and (b) can waste a lot of memory. See bug 634444.
+ static const size_t windowRadius = 60;
+
+ // The window must start within the current line, no earlier than
+ // windowRadius characters before offset.
+ size_t windowStart = (offset - linebase > windowRadius) ?
+ offset - windowRadius :
+ linebase;
+
+ // The window must start within the portion of the current line
+ // that we actually have in our buffer.
+ if (windowStart < userbuf.startOffset())
+ windowStart = userbuf.startOffset();
+
+ // The window must end within the current line, no later than
+ // windowRadius after offset.
+ size_t windowEnd = userbuf.findEOLMax(offset, windowRadius);
+ size_t windowLength = windowEnd - windowStart;
+ MOZ_ASSERT(windowLength <= windowRadius * 2);
+
+ // Create the windowed strings.
+ StringBuffer windowBuf(cx);
+ if (!windowBuf.append(userbuf.rawCharPtrAt(windowStart), windowLength) ||
+ !windowBuf.append('\0'))
+ {
+ return false;
+ }
+
+ // The window into the offending source line, without final \n.
+ UniqueTwoByteChars linebuf(windowBuf.stealChars());
+ if (!linebuf)
+ return false;
+
+ err.initOwnedLinebuf(linebuf.release(), windowLength, offset - windowStart);
+ }
+
+ if (cx->isJSContext())
+ err.throwError(cx->asJSContext());
+
+ return warning;
+}
+
+bool
+TokenStream::reportStrictModeError(unsigned errorNumber, ...)
+{
+ va_list args;
+ va_start(args, errorNumber);
+ bool result = reportStrictModeErrorNumberVA(currentToken().pos.begin, strictMode(),
+ errorNumber, args);
+ va_end(args);
+ return result;
+}
+
+bool
+TokenStream::reportError(unsigned errorNumber, ...)
+{
+ va_list args;
+ va_start(args, errorNumber);
+ bool result = reportCompileErrorNumberVA(currentToken().pos.begin, JSREPORT_ERROR, errorNumber,
+ args);
+ va_end(args);
+ return result;
+}
+
+bool
+TokenStream::reportErrorNoOffset(unsigned errorNumber, ...)
+{
+ va_list args;
+ va_start(args, errorNumber);
+ bool result = reportCompileErrorNumberVA(NoOffset, JSREPORT_ERROR, errorNumber,
+ args);
+ va_end(args);
+ return result;
+}
+
+bool
+TokenStream::reportWarning(unsigned errorNumber, ...)
+{
+ va_list args;
+ va_start(args, errorNumber);
+ bool result = reportCompileErrorNumberVA(currentToken().pos.begin, JSREPORT_WARNING,
+ errorNumber, args);
+ va_end(args);
+ return result;
+}
+
+bool
+TokenStream::reportStrictWarningErrorNumberVA(uint32_t offset, unsigned errorNumber, va_list args)
+{
+ if (!options().extraWarningsOption)
+ return true;
+
+ return reportCompileErrorNumberVA(offset, JSREPORT_STRICT|JSREPORT_WARNING, errorNumber, args);
+}
+
+void
+TokenStream::reportAsmJSError(uint32_t offset, unsigned errorNumber, ...)
+{
+ va_list args;
+ va_start(args, errorNumber);
+ unsigned flags = options().throwOnAsmJSValidationFailureOption
+ ? JSREPORT_ERROR
+ : JSREPORT_WARNING;
+ reportCompileErrorNumberVA(offset, flags, errorNumber, args);
+ va_end(args);
+}
+
+// We have encountered a '\': check for a Unicode escape sequence after it.
+// Return the length of the escape sequence and the character code point (by
+// value) if we found a Unicode escape sequence. Otherwise, return 0. In both
+// cases, do not advance along the buffer.
+uint32_t
+TokenStream::peekUnicodeEscape(uint32_t* codePoint)
+{
+ int32_t c = getCharIgnoreEOL();
+ if (c != 'u') {
+ ungetCharIgnoreEOL(c);
+ return 0;
+ }
+
+ char16_t cp[3];
+ uint32_t length;
+ c = getCharIgnoreEOL();
+ if (JS7_ISHEX(c) && peekChars(3, cp) &&
+ JS7_ISHEX(cp[0]) && JS7_ISHEX(cp[1]) && JS7_ISHEX(cp[2]))
+ {
+ *codePoint = (JS7_UNHEX(c) << 12) |
+ (JS7_UNHEX(cp[0]) << 8) |
+ (JS7_UNHEX(cp[1]) << 4) |
+ JS7_UNHEX(cp[2]);
+ length = 5;
+ } else if (c == '{') {
+ length = peekExtendedUnicodeEscape(codePoint);
+ } else {
+ length = 0;
+ }
+
+ ungetCharIgnoreEOL(c);
+ ungetCharIgnoreEOL('u');
+ return length;
+}
+
+uint32_t
+TokenStream::peekExtendedUnicodeEscape(uint32_t* codePoint)
+{
+ // The opening brace character was already read.
+ int32_t c = getCharIgnoreEOL();
+
+ // Skip leading zeros.
+ uint32_t leadingZeros = 0;
+ while (c == '0') {
+ leadingZeros++;
+ c = getCharIgnoreEOL();
+ }
+
+ char16_t cp[6];
+ size_t i = 0;
+ uint32_t code = 0;
+ while (JS7_ISHEX(c) && i < 6) {
+ cp[i++] = c;
+ code = code << 4 | JS7_UNHEX(c);
+ c = getCharIgnoreEOL();
+ }
+
+ uint32_t length;
+ if (c == '}' && (leadingZeros > 0 || i > 0) && code <= unicode::NonBMPMax) {
+ *codePoint = code;
+ length = leadingZeros + i + 3;
+ } else {
+ length = 0;
+ }
+
+ ungetCharIgnoreEOL(c);
+ while (i--)
+ ungetCharIgnoreEOL(cp[i]);
+ while (leadingZeros--)
+ ungetCharIgnoreEOL('0');
+
+ return length;
+}
+
+uint32_t
+TokenStream::matchUnicodeEscapeIdStart(uint32_t* codePoint)
+{
+ uint32_t length = peekUnicodeEscape(codePoint);
+ if (length > 0 && unicode::IsIdentifierStart(*codePoint)) {
+ skipChars(length);
+ return length;
+ }
+ return 0;
+}
+
+bool
+TokenStream::matchUnicodeEscapeIdent(uint32_t* codePoint)
+{
+ uint32_t length = peekUnicodeEscape(codePoint);
+ if (length > 0 && unicode::IsIdentifierPart(*codePoint)) {
+ skipChars(length);
+ return true;
+ }
+ return false;
+}
+
+// Helper function which returns true if the first length(q) characters in p are
+// the same as the characters in q.
+static bool
+CharsMatch(const char16_t* p, const char* q) {
+ while (*q) {
+ if (*p++ != *q++)
+ return false;
+ }
+ return true;
+}
+
+bool
+TokenStream::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 '#' character.
+
+ if (!getDisplayURL(isMultiline, shouldWarnDeprecated))
+ return false;
+ if (!getSourceMappingURL(isMultiline, shouldWarnDeprecated))
+ return false;
+
+ return true;
+}
+
+bool
+TokenStream::getDirective(bool isMultiline, bool shouldWarnDeprecated,
+ const char* directive, int directiveLength,
+ const char* errorMsgPragma,
+ UniqueTwoByteChars* destination)
+{
+ MOZ_ASSERT(directiveLength <= 18);
+ char16_t peeked[18];
+ int32_t c;
+
+ if (peekChars(directiveLength, peeked) && CharsMatch(peeked, directive)) {
+ if (shouldWarnDeprecated &&
+ !reportWarning(JSMSG_DEPRECATED_PRAGMA, errorMsgPragma))
+ return false;
+
+ skipChars(directiveLength);
+ tokenbuf.clear();
+
+ while ((c = peekChar()) && c != EOF && !unicode::IsSpaceOrBOM2(c)) {
+ getChar();
+ // Debugging directives can occur in both single- and multi-line
+ // comments. If we're currently inside a multi-line comment, we also
+ // need to recognize multi-line comment terminators.
+ if (isMultiline && c == '*' && peekChar() == '/') {
+ ungetChar('*');
+ break;
+ }
+ if (!tokenbuf.append(c))
+ return false;
+ }
+
+ if (tokenbuf.empty()) {
+ // The directive's URL was missing, but this is not quite an
+ // exception that we should stop and drop everything for.
+ return true;
+ }
+
+ size_t length = tokenbuf.length();
+
+ *destination = cx->make_pod_array<char16_t>(length + 1);
+ if (!*destination)
+ return false;
+
+ PodCopy(destination->get(), tokenbuf.begin(), length);
+ (*destination)[length] = '\0';
+ }
+
+ return true;
+}
+
+bool
+TokenStream::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.
+
+ return getDirective(isMultiline, shouldWarnDeprecated, " sourceURL=", 11,
+ "sourceURL", &displayURL_);
+}
+
+bool
+TokenStream::getSourceMappingURL(bool isMultiline, bool shouldWarnDeprecated)
+{
+ // Match comments of the form "//# sourceMappingURL=<url>" or
+ // "/\* //# sourceMappingURL=<url> *\/"
+
+ return getDirective(isMultiline, shouldWarnDeprecated, " sourceMappingURL=", 18,
+ "sourceMappingURL", &sourceMapURL_);
+}
+
+MOZ_ALWAYS_INLINE Token*
+TokenStream::newToken(ptrdiff_t adjust)
+{
+ cursor = (cursor + 1) & ntokensMask;
+ Token* tp = &tokens[cursor];
+ tp->pos.begin = userbuf.offset() + adjust;
+
+ // NOTE: tp->pos.end is not set until the very end of getTokenInternal().
+ MOZ_MAKE_MEM_UNDEFINED(&tp->pos.end, sizeof(tp->pos.end));
+
+ return tp;
+}
+
+MOZ_ALWAYS_INLINE JSAtom*
+TokenStream::atomize(ExclusiveContext* cx, CharBuffer& cb)
+{
+ return AtomizeChars(cx, cb.begin(), cb.length());
+}
+
+#ifdef DEBUG
+static bool
+IsTokenSane(Token* tp)
+{
+ // Nb: TOK_EOL should never be used in an actual Token; it should only be
+ // returned as a TokenKind from peekTokenSameLine().
+ if (tp->type < 0 || tp->type >= TOK_LIMIT || tp->type == TOK_EOL)
+ return false;
+
+ if (tp->pos.end < tp->pos.begin)
+ return false;
+
+ return true;
+}
+#endif
+
+bool
+TokenStream::putIdentInTokenbuf(const char16_t* identStart)
+{
+ int32_t c;
+ uint32_t qc;
+ const char16_t* tmp = userbuf.addressOfNextRawChar();
+ userbuf.setAddressOfNextRawChar(identStart);
+
+ tokenbuf.clear();
+ for (;;) {
+ c = getCharIgnoreEOL();
+ if (!unicode::IsIdentifierPart(char16_t(c))) {
+ if (c != '\\' || !matchUnicodeEscapeIdent(&qc))
+ break;
+ c = qc;
+ }
+ if (!tokenbuf.append(c)) {
+ userbuf.setAddressOfNextRawChar(tmp);
+ return false;
+ }
+ }
+ userbuf.setAddressOfNextRawChar(tmp);
+ return true;
+}
+
+bool
+TokenStream::checkForKeyword(const KeywordInfo* kw, TokenKind* ttp)
+{
+ if (!awaitIsKeyword && kw->tokentype == TOK_AWAIT) {
+ if (ttp)
+ *ttp = TOK_NAME;
+ return true;
+ }
+
+ if (kw->tokentype == TOK_RESERVED)
+ return reportError(JSMSG_RESERVED_ID, kw->chars);
+
+ if (kw->tokentype == TOK_STRICT_RESERVED)
+ return reportStrictModeError(JSMSG_RESERVED_ID, kw->chars);
+
+ // Working keyword.
+ *ttp = kw->tokentype;
+ return true;
+}
+
+bool
+TokenStream::checkForKeyword(JSAtom* atom, TokenKind* ttp)
+{
+ const KeywordInfo* kw = FindKeyword(atom);
+ if (!kw)
+ return true;
+
+ return checkForKeyword(kw, ttp);
+}
+
+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
+ // TOK_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 = TOK_LIMIT - 1,
+
+ Space = TOK_LIMIT,
+ Ident,
+ Dec,
+ String,
+ EOL,
+ BasePrefix,
+ Other,
+
+ LastCharKind = Other
+};
+
+// OneChar: 40, 41, 44, 58, 59, 63, 91, 93, 123, 125, 126:
+// '(', ')', ',', ':', ';', '?', '[', ']', '{', '}', '~'
+// Ident: 36, 65..90, 95, 97..122: '$', 'A'..'Z', '_', 'a'..'z'
+// Dot: 46: '.'
+// Equals: 61: '='
+// String: 34, 39: '"', '\''
+// Dec: 49..57: '1'..'9'
+// Plus: 43: '+'
+// BasePrefix: 48: '0'
+// Space: 9, 11, 12, 32: '\t', '\v', '\f', ' '
+// EOL: 10, 13: '\n', '\r'
+//
+#define T_COMMA TOK_COMMA
+#define T_COLON TOK_COLON
+#define T_BITNOT TOK_BITNOT
+#define Templat String
+#define _______ Other
+static const uint8_t firstCharKinds[] = {
+/* 0 1 2 3 4 5 6 7 8 9 */
+/* 0+ */ _______, _______, _______, _______, _______, _______, _______, _______, _______, Space,
+/* 10+ */ EOL, Space, Space, EOL, _______, _______, _______, _______, _______, _______,
+/* 20+ */ _______, _______, _______, _______, _______, _______, _______, _______, _______, _______,
+/* 30+ */ _______, _______, Space, _______, String, _______, Ident, _______, _______, String,
+/* 40+ */ TOK_LP, TOK_RP, _______, _______, T_COMMA,_______, _______, _______,BasePrefix, Dec,
+/* 50+ */ Dec, Dec, Dec, Dec, Dec, Dec, Dec, Dec, T_COLON,TOK_SEMI,
+/* 60+ */ _______, _______, _______,TOK_HOOK, _______, 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, TOK_LB, _______, TOK_RB, _______, Ident, Templat, 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, TOK_LC, _______, TOK_RC,T_BITNOT, _______
+};
+#undef T_COMMA
+#undef T_COLON
+#undef T_BITNOT
+#undef Templat
+#undef _______
+
+static_assert(LastCharKind < (1 << (sizeof(firstCharKinds[0]) * 8)),
+ "Elements of firstCharKinds[] are too small");
+
+bool
+TokenStream::getTokenInternal(TokenKind* ttp, Modifier modifier)
+{
+ int c;
+ uint32_t qc;
+ Token* tp;
+ FirstCharKind c1kind;
+ const char16_t* numStart;
+ bool hasExp;
+ DecimalPoint decimalPoint;
+ const char16_t* identStart;
+ bool hadUnicodeEscape;
+
+ // Check if in the middle of a template string. Have to get this out of
+ // the way first.
+ if (MOZ_UNLIKELY(modifier == TemplateTail)) {
+ if (!getStringOrTemplateToken('`', &tp))
+ goto error;
+ goto out;
+ }
+
+ retry:
+ if (MOZ_UNLIKELY(!userbuf.hasRawChars())) {
+ tp = newToken(0);
+ tp->type = TOK_EOF;
+ flags.isEOF = true;
+ goto out;
+ }
+
+ c = userbuf.getRawChar();
+ MOZ_ASSERT(c != EOF);
+
+ // Chars not in the range 0..127 are rare. Getting them out of the way
+ // early allows subsequent checking to be faster.
+ if (MOZ_UNLIKELY(c >= 128)) {
+ if (unicode::IsSpaceOrBOM2(c)) {
+ if (c == LINE_SEPARATOR || c == PARA_SEPARATOR) {
+ updateLineInfoForEOL();
+ updateFlagsForEOL();
+ }
+
+ goto retry;
+ }
+
+ tp = newToken(-1);
+
+ static_assert('$' < 128,
+ "IdentifierStart contains '$', but as !IsUnicodeIDStart('$'), "
+ "ensure that '$' is never handled here");
+ static_assert('_' < 128,
+ "IdentifierStart contains '_', but as !IsUnicodeIDStart('_'), "
+ "ensure that '_' is never handled here");
+ if (unicode::IsUnicodeIDStart(c)) {
+ identStart = userbuf.addressOfNextRawChar() - 1;
+ hadUnicodeEscape = false;
+ goto identifier;
+ }
+
+ goto badchar;
+ }
+
+ // 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%
+ // BasePrefix 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.
+ //
+ c1kind = FirstCharKind(firstCharKinds[c]);
+
+ // Look for an unambiguous single-char token.
+ //
+ if (c1kind <= OneChar_Max) {
+ tp = newToken(-1);
+ tp->type = TokenKind(c1kind);
+ goto out;
+ }
+
+ // Skip over non-EOL whitespace chars.
+ //
+ if (c1kind == Space)
+ goto retry;
+
+ // Look for an identifier.
+ //
+ if (c1kind == Ident) {
+ tp = newToken(-1);
+ identStart = userbuf.addressOfNextRawChar() - 1;
+ hadUnicodeEscape = false;
+
+ identifier:
+ for (;;) {
+ c = getCharIgnoreEOL();
+ if (c == EOF)
+ break;
+ if (!unicode::IsIdentifierPart(char16_t(c))) {
+ if (c != '\\' || !matchUnicodeEscapeIdent(&qc))
+ break;
+ hadUnicodeEscape = true;
+ }
+ }
+ ungetCharIgnoreEOL(c);
+
+ // Identifiers containing no Unicode escapes can be processed directly
+ // from userbuf. The rest must use the escapes converted via tokenbuf
+ // before atomizing.
+ const char16_t* chars;
+ size_t length;
+ if (hadUnicodeEscape) {
+ if (!putIdentInTokenbuf(identStart))
+ goto error;
+
+ chars = tokenbuf.begin();
+ length = tokenbuf.length();
+ } else {
+ chars = identStart;
+ length = userbuf.addressOfNextRawChar() - identStart;
+ }
+
+ // Represent keywords as keyword tokens unless told otherwise.
+ if (modifier != KeywordIsName) {
+ if (const KeywordInfo* kw = FindKeyword(chars, length)) {
+ // That said, keywords can't contain escapes. (Contexts where
+ // keywords are treated as names, that also sometimes treat
+ // keywords as keywords, must manually check this requirement.)
+ // There are two exceptions
+ // 1) StrictReservedWords: These keywords need to be treated as
+ // names in non-strict mode.
+ // 2) yield is also treated as a name if it contains an escape
+ // sequence. The parser must handle this case separately.
+ if (hadUnicodeEscape && !(
+ (kw->tokentype == TOK_STRICT_RESERVED && !strictMode()) ||
+ kw->tokentype == TOK_YIELD))
+ {
+ reportError(JSMSG_ESCAPED_KEYWORD);
+ goto error;
+ }
+
+ tp->type = TOK_NAME;
+ if (!checkForKeyword(kw, &tp->type))
+ goto error;
+ if (tp->type != TOK_NAME && !hadUnicodeEscape)
+ goto out;
+ }
+ }
+
+ JSAtom* atom = AtomizeChars(cx, chars, length);
+ if (!atom)
+ goto error;
+ tp->type = TOK_NAME;
+ tp->setName(atom->asPropertyName());
+ goto out;
+ }
+
+ // Look for a decimal number.
+ //
+ if (c1kind == Dec) {
+ tp = newToken(-1);
+ numStart = userbuf.addressOfNextRawChar() - 1;
+
+ decimal:
+ decimalPoint = NoDecimal;
+ hasExp = false;
+ while (JS7_ISDEC(c))
+ c = getCharIgnoreEOL();
+
+ if (c == '.') {
+ decimalPoint = HasDecimal;
+ decimal_dot:
+ do {
+ c = getCharIgnoreEOL();
+ } while (JS7_ISDEC(c));
+ }
+ if (c == 'e' || c == 'E') {
+ hasExp = true;
+ c = getCharIgnoreEOL();
+ if (c == '+' || c == '-')
+ c = getCharIgnoreEOL();
+ if (!JS7_ISDEC(c)) {
+ ungetCharIgnoreEOL(c);
+ reportError(JSMSG_MISSING_EXPONENT);
+ goto error;
+ }
+ do {
+ c = getCharIgnoreEOL();
+ } while (JS7_ISDEC(c));
+ }
+ ungetCharIgnoreEOL(c);
+
+ if (c != EOF && unicode::IsIdentifierStart(char16_t(c))) {
+ reportError(JSMSG_IDSTART_AFTER_NUMBER);
+ goto error;
+ }
+
+ // Unlike identifiers and strings, numbers cannot contain escaped
+ // chars, so we don't need to use tokenbuf. Instead we can just
+ // convert the char16_t characters in userbuf to the numeric value.
+ double dval;
+ if (!((decimalPoint == HasDecimal) || hasExp)) {
+ if (!GetDecimalInteger(cx, numStart, userbuf.addressOfNextRawChar(), &dval))
+ goto error;
+ } else {
+ const char16_t* dummy;
+ if (!js_strtod(cx, numStart, userbuf.addressOfNextRawChar(), &dummy, &dval))
+ goto error;
+ }
+ tp->type = TOK_NUMBER;
+ tp->setNumber(dval, decimalPoint);
+ goto out;
+ }
+
+ // Look for a string or a template string.
+ //
+ if (c1kind == String) {
+ if (!getStringOrTemplateToken(c, &tp))
+ goto error;
+ goto out;
+ }
+
+ // Skip over EOL chars, updating line state along the way.
+ //
+ if (c1kind == EOL) {
+ // If it's a \r\n sequence: treat as a single EOL, skip over the \n.
+ if (c == '\r' && userbuf.hasRawChars())
+ userbuf.matchRawChar('\n');
+ updateLineInfoForEOL();
+ updateFlagsForEOL();
+ goto retry;
+ }
+
+ // Look for a hexadecimal, octal, or binary number.
+ //
+ if (c1kind == BasePrefix) {
+ tp = newToken(-1);
+ int radix;
+ c = getCharIgnoreEOL();
+ if (c == 'x' || c == 'X') {
+ radix = 16;
+ c = getCharIgnoreEOL();
+ if (!JS7_ISHEX(c)) {
+ ungetCharIgnoreEOL(c);
+ reportError(JSMSG_MISSING_HEXDIGITS);
+ goto error;
+ }
+ numStart = userbuf.addressOfNextRawChar() - 1; // one past the '0x'
+ while (JS7_ISHEX(c))
+ c = getCharIgnoreEOL();
+ } else if (c == 'b' || c == 'B') {
+ radix = 2;
+ c = getCharIgnoreEOL();
+ if (c != '0' && c != '1') {
+ ungetCharIgnoreEOL(c);
+ reportError(JSMSG_MISSING_BINARY_DIGITS);
+ goto error;
+ }
+ numStart = userbuf.addressOfNextRawChar() - 1; // one past the '0b'
+ while (c == '0' || c == '1')
+ c = getCharIgnoreEOL();
+ } else if (c == 'o' || c == 'O') {
+ radix = 8;
+ c = getCharIgnoreEOL();
+ if (c < '0' || c > '7') {
+ ungetCharIgnoreEOL(c);
+ reportError(JSMSG_MISSING_OCTAL_DIGITS);
+ goto error;
+ }
+ numStart = userbuf.addressOfNextRawChar() - 1; // one past the '0o'
+ while ('0' <= c && c <= '7')
+ c = getCharIgnoreEOL();
+ } else if (JS7_ISDEC(c)) {
+ radix = 8;
+ numStart = userbuf.addressOfNextRawChar() - 1; // one past the '0'
+ while (JS7_ISDEC(c)) {
+ // Octal integer literals are not permitted in strict mode code.
+ if (!reportStrictModeError(JSMSG_DEPRECATED_OCTAL))
+ goto error;
+
+ // Outside strict mode, we permit 08 and 09 as decimal numbers,
+ // which makes our behaviour a superset of the ECMA numeric
+ // grammar. We might not always be so permissive, so we warn
+ // about it.
+ if (c >= '8') {
+ if (!reportWarning(JSMSG_BAD_OCTAL, c == '8' ? "08" : "09")) {
+ goto error;
+ }
+ goto decimal; // use the decimal scanner for the rest of the number
+ }
+ c = getCharIgnoreEOL();
+ }
+ } else {
+ // '0' not followed by 'x', 'X' or a digit; scan as a decimal number.
+ numStart = userbuf.addressOfNextRawChar() - 1;
+ goto decimal;
+ }
+ ungetCharIgnoreEOL(c);
+
+ if (c != EOF && unicode::IsIdentifierStart(char16_t(c))) {
+ reportError(JSMSG_IDSTART_AFTER_NUMBER);
+ goto error;
+ }
+
+ double dval;
+ const char16_t* dummy;
+ if (!GetPrefixInteger(cx, numStart, userbuf.addressOfNextRawChar(), radix, &dummy, &dval))
+ goto error;
+ tp->type = TOK_NUMBER;
+ tp->setNumber(dval, NoDecimal);
+ goto out;
+ }
+
+ // This handles everything else.
+ //
+ MOZ_ASSERT(c1kind == Other);
+ tp = newToken(-1);
+ switch (c) {
+ case '.':
+ c = getCharIgnoreEOL();
+ if (JS7_ISDEC(c)) {
+ numStart = userbuf.addressOfNextRawChar() - 2;
+ decimalPoint = HasDecimal;
+ hasExp = false;
+ goto decimal_dot;
+ }
+ if (c == '.') {
+ if (matchChar('.')) {
+ tp->type = TOK_TRIPLEDOT;
+ goto out;
+ }
+ }
+ ungetCharIgnoreEOL(c);
+ tp->type = TOK_DOT;
+ goto out;
+
+ case '=':
+ if (matchChar('='))
+ tp->type = matchChar('=') ? TOK_STRICTEQ : TOK_EQ;
+ else if (matchChar('>'))
+ tp->type = TOK_ARROW;
+ else
+ tp->type = TOK_ASSIGN;
+ goto out;
+
+ case '+':
+ if (matchChar('+'))
+ tp->type = TOK_INC;
+ else
+ tp->type = matchChar('=') ? TOK_ADDASSIGN : TOK_ADD;
+ goto out;
+
+ case '\\': {
+ uint32_t escapeLength = matchUnicodeEscapeIdStart(&qc);
+ if (escapeLength > 0) {
+ identStart = userbuf.addressOfNextRawChar() - escapeLength - 1;
+ hadUnicodeEscape = true;
+ goto identifier;
+ }
+ goto badchar;
+ }
+
+ case '|':
+ if (matchChar('|'))
+ tp->type = TOK_OR;
+ else
+ tp->type = matchChar('=') ? TOK_BITORASSIGN : TOK_BITOR;
+ goto out;
+
+ case '^':
+ tp->type = matchChar('=') ? TOK_BITXORASSIGN : TOK_BITXOR;
+ goto out;
+
+ case '&':
+ if (matchChar('&'))
+ tp->type = TOK_AND;
+ else
+ tp->type = matchChar('=') ? TOK_BITANDASSIGN : TOK_BITAND;
+ goto out;
+
+ case '!':
+ if (matchChar('='))
+ tp->type = matchChar('=') ? TOK_STRICTNE : TOK_NE;
+ else
+ tp->type = TOK_NOT;
+ goto out;
+
+ case '<':
+ // NB: treat HTML begin-comment as comment-till-end-of-line.
+ if (matchChar('!')) {
+ if (matchChar('-')) {
+ if (matchChar('-'))
+ goto skipline;
+ ungetChar('-');
+ }
+ ungetChar('!');
+ }
+ if (matchChar('<')) {
+ tp->type = matchChar('=') ? TOK_LSHASSIGN : TOK_LSH;
+ } else {
+ tp->type = matchChar('=') ? TOK_LE : TOK_LT;
+ }
+ goto out;
+
+ case '>':
+ if (matchChar('>')) {
+ if (matchChar('>'))
+ tp->type = matchChar('=') ? TOK_URSHASSIGN : TOK_URSH;
+ else
+ tp->type = matchChar('=') ? TOK_RSHASSIGN : TOK_RSH;
+ } else {
+ tp->type = matchChar('=') ? TOK_GE : TOK_GT;
+ }
+ goto out;
+
+ case '*':
+ if (matchChar('*'))
+ tp->type = matchChar('=') ? TOK_POWASSIGN : TOK_POW;
+ else
+ tp->type = matchChar('=') ? TOK_MULASSIGN : TOK_MUL;
+ goto out;
+
+ case '/':
+ // Look for a single-line comment.
+ if (matchChar('/')) {
+ c = peekChar();
+ if (c == '@' || c == '#') {
+ bool shouldWarn = getChar() == '@';
+ if (!getDirectives(false, shouldWarn))
+ goto error;
+ }
+
+ skipline:
+ while ((c = getChar()) != EOF && c != '\n')
+ continue;
+ ungetChar(c);
+ cursor = (cursor - 1) & ntokensMask;
+ goto retry;
+ }
+
+ // Look for a multi-line comment.
+ if (matchChar('*')) {
+ unsigned linenoBefore = lineno;
+ while ((c = getChar()) != EOF &&
+ !(c == '*' && matchChar('/'))) {
+ if (c == '@' || c == '#') {
+ bool shouldWarn = c == '@';
+ if (!getDirectives(true, shouldWarn))
+ goto error;
+ }
+ }
+ if (c == EOF) {
+ reportError(JSMSG_UNTERMINATED_COMMENT);
+ goto error;
+ }
+ if (linenoBefore != lineno)
+ updateFlagsForEOL();
+ cursor = (cursor - 1) & ntokensMask;
+ goto retry;
+ }
+
+ // Look for a regexp.
+ if (modifier == Operand) {
+ tokenbuf.clear();
+
+ bool inCharClass = false;
+ for (;;) {
+ c = getChar();
+ if (c == '\\') {
+ if (!tokenbuf.append(c))
+ goto error;
+ c = getChar();
+ } else if (c == '[') {
+ inCharClass = true;
+ } else if (c == ']') {
+ inCharClass = false;
+ } else if (c == '/' && !inCharClass) {
+ // For compat with IE, allow unescaped / in char classes.
+ break;
+ }
+ if (c == '\n' || c == EOF) {
+ ungetChar(c);
+ reportError(JSMSG_UNTERMINATED_REGEXP);
+ goto error;
+ }
+ if (!tokenbuf.append(c))
+ goto error;
+ }
+
+ RegExpFlag reflags = NoFlags;
+ unsigned length = tokenbuf.length() + 1;
+ while (true) {
+ c = peekChar();
+ if (c == 'g' && !(reflags & GlobalFlag))
+ reflags = RegExpFlag(reflags | GlobalFlag);
+ else if (c == 'i' && !(reflags & IgnoreCaseFlag))
+ reflags = RegExpFlag(reflags | IgnoreCaseFlag);
+ else if (c == 'm' && !(reflags & MultilineFlag))
+ reflags = RegExpFlag(reflags | MultilineFlag);
+ else if (c == 'y' && !(reflags & StickyFlag))
+ reflags = RegExpFlag(reflags | StickyFlag);
+ else if (c == 'u' && !(reflags & UnicodeFlag))
+ reflags = RegExpFlag(reflags | UnicodeFlag);
+ else
+ break;
+ getChar();
+ length++;
+ }
+
+ c = peekChar();
+ if (JS7_ISLET(c)) {
+ char buf[2] = { '\0', '\0' };
+ tp->pos.begin += length + 1;
+ buf[0] = char(c);
+ reportError(JSMSG_BAD_REGEXP_FLAG, buf);
+ (void) getChar();
+ goto error;
+ }
+ tp->type = TOK_REGEXP;
+ tp->setRegExpFlags(reflags);
+ goto out;
+ }
+
+ tp->type = matchChar('=') ? TOK_DIVASSIGN : TOK_DIV;
+ goto out;
+
+ case '%':
+ tp->type = matchChar('=') ? TOK_MODASSIGN : TOK_MOD;
+ goto out;
+
+ case '-':
+ if (matchChar('-')) {
+ if (peekChar() == '>' && !flags.isDirtyLine)
+ goto skipline;
+ tp->type = TOK_DEC;
+ } else {
+ tp->type = matchChar('=') ? TOK_SUBASSIGN : TOK_SUB;
+ }
+ goto out;
+
+ badchar:
+ default:
+ reportError(JSMSG_ILLEGAL_CHARACTER);
+ goto error;
+ }
+
+ MOZ_CRASH("should have jumped to |out| or |error|");
+
+ out:
+ if (flags.hitOOM)
+ return reportError(JSMSG_OUT_OF_MEMORY);
+
+ flags.isDirtyLine = true;
+ tp->pos.end = userbuf.offset();
+#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
+ // that both gets have used the same modifiers.
+ tp->modifier = modifier;
+ tp->modifierException = NoException;
+#endif
+ MOZ_ASSERT(IsTokenSane(tp));
+ *ttp = tp->type;
+ return true;
+
+ error:
+ if (flags.hitOOM)
+ return reportError(JSMSG_OUT_OF_MEMORY);
+
+ flags.isDirtyLine = true;
+ tp->pos.end = userbuf.offset();
+ MOZ_MAKE_MEM_UNDEFINED(&tp->type, sizeof(tp->type));
+ flags.hadError = true;
+#ifdef DEBUG
+ // Poisoning userbuf on error establishes an invariant: once an erroneous
+ // token has been seen, userbuf will not be consulted again. This is true
+ // because the parser will deal with the illegal token by aborting parsing
+ // immediately.
+ userbuf.poison();
+#endif
+ MOZ_MAKE_MEM_UNDEFINED(ttp, sizeof(*ttp));
+ return false;
+}
+
+bool
+TokenStream::getBracedUnicode(uint32_t* cp)
+{
+ consumeKnownChar('{');
+
+ bool first = true;
+ int32_t c;
+ uint32_t code = 0;
+ while (true) {
+ c = getCharIgnoreEOL();
+ if (c == EOF)
+ return false;
+ if (c == '}') {
+ if (first)
+ return false;
+ break;
+ }
+
+ if (!JS7_ISHEX(c))
+ return false;
+
+ code = (code << 4) | JS7_UNHEX(c);
+ if (code > unicode::NonBMPMax)
+ return false;
+ first = false;
+ }
+
+ *cp = code;
+ return true;
+}
+
+bool
+TokenStream::getStringOrTemplateToken(int untilChar, Token** tp)
+{
+ int c;
+ int nc = -1;
+
+ bool parsingTemplate = (untilChar == '`');
+
+ *tp = newToken(-1);
+ tokenbuf.clear();
+
+ // 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 getCharIgnoreEOL().
+ while ((c = getCharIgnoreEOL()) != untilChar) {
+ if (c == EOF) {
+ ungetCharIgnoreEOL(c);
+ reportError(JSMSG_UNTERMINATED_STRING);
+ return false;
+ }
+
+ if (c == '\\') {
+ switch (c = getChar()) {
+ case 'b': c = '\b'; break;
+ case 'f': c = '\f'; break;
+ case 'n': c = '\n'; break;
+ case 'r': c = '\r'; break;
+ case 't': c = '\t'; break;
+ case 'v': c = '\v'; break;
+
+ case '\n':
+ // ES5 7.8.4: an escaped line terminator represents
+ // no character.
+ continue;
+
+ // Unicode character specification.
+ case 'u': {
+ if (peekChar() == '{') {
+ uint32_t code;
+ if (!getBracedUnicode(&code)) {
+ reportError(JSMSG_MALFORMED_ESCAPE, "Unicode");
+ return false;
+ }
+
+ MOZ_ASSERT(code <= unicode::NonBMPMax);
+ if (code < unicode::NonBMPMin) {
+ c = code;
+ } else {
+ if (!tokenbuf.append(unicode::LeadSurrogate(code)))
+ return false;
+ c = unicode::TrailSurrogate(code);
+ }
+ break;
+ }
+
+ char16_t cp[4];
+ if (peekChars(4, cp) &&
+ JS7_ISHEX(cp[0]) && JS7_ISHEX(cp[1]) && JS7_ISHEX(cp[2]) && JS7_ISHEX(cp[3]))
+ {
+ c = JS7_UNHEX(cp[0]);
+ c = (c << 4) + JS7_UNHEX(cp[1]);
+ c = (c << 4) + JS7_UNHEX(cp[2]);
+ c = (c << 4) + JS7_UNHEX(cp[3]);
+ skipChars(4);
+ } else {
+ reportError(JSMSG_MALFORMED_ESCAPE, "Unicode");
+ return false;
+ }
+ break;
+ }
+
+ // Hexadecimal character specification.
+ case 'x': {
+ char16_t cp[2];
+ if (peekChars(2, cp) && JS7_ISHEX(cp[0]) && JS7_ISHEX(cp[1])) {
+ c = (JS7_UNHEX(cp[0]) << 4) + JS7_UNHEX(cp[1]);
+ skipChars(2);
+ } else {
+ reportError(JSMSG_MALFORMED_ESCAPE, "hexadecimal");
+ return false;
+ }
+ break;
+ }
+
+ default:
+ // Octal character specification.
+ if (JS7_ISOCT(c)) {
+ int32_t val = JS7_UNOCT(c);
+
+ c = peekChar();
+
+ // Strict mode code allows only \0, then a non-digit.
+ if (val != 0 || JS7_ISDEC(c)) {
+ if (parsingTemplate) {
+ reportError(JSMSG_DEPRECATED_OCTAL);
+ return false;
+ }
+ if (!reportStrictModeError(JSMSG_DEPRECATED_OCTAL))
+ return false;
+ flags.sawOctalEscape = true;
+ }
+
+ if (JS7_ISOCT(c)) {
+ val = 8 * val + JS7_UNOCT(c);
+ getChar();
+ c = peekChar();
+ if (JS7_ISOCT(c)) {
+ int32_t save = val;
+ val = 8 * val + JS7_UNOCT(c);
+ if (val <= 0xFF)
+ getChar();
+ else
+ val = save;
+ }
+ }
+
+ c = char16_t(val);
+ }
+ break;
+ }
+ } else if (TokenBuf::isRawEOLChar(c)) {
+ if (!parsingTemplate) {
+ ungetCharIgnoreEOL(c);
+ reportError(JSMSG_UNTERMINATED_STRING);
+ return false;
+ }
+ if (c == '\r') {
+ c = '\n';
+ if (userbuf.peekRawChar() == '\n')
+ skipCharsIgnoreEOL(1);
+ }
+ updateLineInfoForEOL();
+ updateFlagsForEOL();
+ } else if (parsingTemplate && c == '$') {
+ if ((nc = getCharIgnoreEOL()) == '{')
+ break;
+ ungetCharIgnoreEOL(nc);
+ }
+
+ if (!tokenbuf.append(c)) {
+ ReportOutOfMemory(cx);
+ return false;
+ }
+ }
+
+ JSAtom* atom = atomize(cx, tokenbuf);
+ if (!atom)
+ return false;
+
+ if (!parsingTemplate) {
+ (*tp)->type = TOK_STRING;
+ } else {
+ if (c == '$' && nc == '{')
+ (*tp)->type = TOK_TEMPLATE_HEAD;
+ else
+ (*tp)->type = TOK_NO_SUBS_TEMPLATE;
+ }
+
+ (*tp)->setAtom(atom);
+ return true;
+}
+
+JS_FRIEND_API(int)
+js_fgets(char* buf, int size, FILE* file)
+{
+ int n, i, c;
+ bool crflag;
+
+ n = size - 1;
+ if (n < 0)
+ return -1;
+
+ crflag = false;
+ for (i = 0; i < n && (c = fast_getc(file)) != EOF; i++) {
+ buf[i] = c;
+ if (c == '\n') { // any \n ends a line
+ i++; // keep the \n; we know there is room for \0
+ break;
+ }
+ if (crflag) { // \r not followed by \n ends line at the \r
+ ungetc(c, file);
+ break; // and overwrite c in buf with \0
+ }
+ crflag = (c == '\r');
+ }
+
+ buf[i] = '\0';
+ return i;
+}
+
+const char*
+frontend::TokenKindToDesc(TokenKind tt)
+{
+ switch (tt) {
+#define EMIT_CASE(name, desc) case TOK_##name: return desc;
+ FOR_EACH_TOKEN_KIND(EMIT_CASE)
+#undef EMIT_CASE
+ case TOK_LIMIT:
+ MOZ_ASSERT_UNREACHABLE("TOK_LIMIT should not be passed.");
+ break;
+ }
+
+ return "<bad TokenKind>";
+}
+
+#ifdef DEBUG
+const char*
+TokenKindToString(TokenKind tt)
+{
+ switch (tt) {
+#define EMIT_CASE(name, desc) case TOK_##name: return "TOK_" #name;
+ FOR_EACH_TOKEN_KIND(EMIT_CASE)
+#undef EMIT_CASE
+ case TOK_LIMIT: break;
+ }
+
+ return "<bad TokenKind>";
+}
+#endif
diff --git a/js/src/frontend/TokenStream.h b/js/src/frontend/TokenStream.h
new file mode 100644
index 0000000000..29dcead62e
--- /dev/null
+++ b/js/src/frontend/TokenStream.h
@@ -0,0 +1,1057 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sts=4 et sw=4 tw=99:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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_TokenStream_h
+#define frontend_TokenStream_h
+
+// JS lexical scanner interface.
+
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/PodOperations.h"
+
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdio.h>
+
+#include "jscntxt.h"
+#include "jspubtd.h"
+
+#include "frontend/TokenKind.h"
+#include "js/UniquePtr.h"
+#include "js/Vector.h"
+#include "vm/RegExpObject.h"
+
+struct KeywordInfo;
+
+namespace js {
+namespace frontend {
+
+class AutoAwaitIsKeyword;
+
+struct TokenPos {
+ uint32_t begin; // Offset of the token's first char.
+ uint32_t end; // Offset of 1 past the token's last char.
+
+ TokenPos() {}
+ 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 };
+
+class TokenStream;
+
+struct Token
+{
+ private:
+ // Sometimes the parser needs to inform the tokenizer to interpret
+ // subsequent text in a particular manner: for example, to tokenize a
+ // keyword as an identifier, not as the actual keyword, on the right-hand
+ // side of a dotted property access. Such information is communicated to
+ // the tokenizer as a Modifier when getting the next token.
+ //
+ // Ideally this definition would reside in TokenStream as that's the real
+ // user, but the debugging-use of it here causes a cyclic dependency (and
+ // C++ provides no way to forward-declare an enum inside a class). So
+ // define it here, then typedef it into TokenStream with static consts to
+ // bring the initializers into scope.
+ enum Modifier
+ {
+ // Normal operation.
+ None,
+
+ // Looking for an operand, not an operator. In practice, this means
+ // that when '/' is seen, we look for a regexp instead of just returning
+ // TOK_DIV.
+ Operand,
+
+ // Treat keywords as names by returning TOK_NAME.
+ KeywordIsName,
+
+ // Treat subsequent characters as the tail of a template literal, after
+ // a template substitution, beginning with a "}", continuing with zero
+ // or more template literal characters, and ending with either "${" or
+ // the end of the template literal. For example:
+ //
+ // var entity = "world";
+ // var s = `Hello ${entity}!`;
+ // ^ TemplateTail context
+ TemplateTail,
+ };
+ enum ModifierException
+ {
+ NoException,
+
+ // Used in following 2 cases:
+ // a) After |yield| we look for a token on the same line that starts an
+ // expression (Operand): |yield <expr>|. If no token is found, the
+ // |yield| stands alone, and the next token on a subsequent line must
+ // be: a comma continuing a comma expression, a semicolon terminating
+ // the statement that ended with |yield|, or the start of another
+ // statement (possibly an expression statement). The comma/semicolon
+ // cases are gotten as operators (None), contrasting with Operand
+ // earlier.
+ // b) After an arrow function with a block body in an expression
+ // statement, the next token must be: a colon in a conditional
+ // expression, a comma continuing a comma expression, a semicolon
+ // terminating the statement, or the token on a subsequent line that is
+ // the start of another statement (possibly an expression statement).
+ // Colon is gotten as operator (None), and it should only be gotten in
+ // conditional expression and missing it results in SyntaxError.
+ // Comma/semicolon cases are also gotten as operators (None), and 4th
+ // case is gotten after them. If no comma/semicolon found but EOL,
+ // the next token should be gotten as operand in 4th case (especially if
+ // '/' is the first character). So we should peek the token as
+ // operand before try getting colon/comma/semicolon.
+ // See also the comment in Parser::assignExpr().
+ NoneIsOperand,
+
+ // If a semicolon is inserted automatically, the next token is already
+ // gotten with None, but we expect Operand.
+ OperandIsNone,
+
+ // If name of method definition is `get` or `set`, the next token is
+ // already gotten with KeywordIsName, but we expect None.
+ NoneIsKeywordIsName,
+ };
+ friend class TokenStream;
+
+ public:
+ TokenKind type; // char value or above enumerator
+ TokenPos pos; // token position in file
+ union {
+ private:
+ friend struct Token;
+ PropertyName* name; // non-numeric atom
+ JSAtom* atom; // potentially-numeric atom
+ struct {
+ double value; // floating point number
+ DecimalPoint decimalPoint; // literal contains '.'
+ } number;
+ RegExpFlag reflags; // regexp flags; use tokenbuf to access
+ // regexp chars
+ } u;
+#ifdef DEBUG
+ Modifier modifier; // Modifier used to get this token
+ ModifierException modifierException; // Exception for this modifier
+#endif
+
+ // Mutators
+
+ void setName(PropertyName* name) {
+ MOZ_ASSERT(type == TOK_NAME);
+ u.name = name;
+ }
+
+ void setAtom(JSAtom* atom) {
+ MOZ_ASSERT(type == TOK_STRING ||
+ type == TOK_TEMPLATE_HEAD ||
+ type == TOK_NO_SUBS_TEMPLATE);
+ u.atom = atom;
+ }
+
+ void setRegExpFlags(js::RegExpFlag flags) {
+ MOZ_ASSERT(type == TOK_REGEXP);
+ MOZ_ASSERT((flags & AllFlags) == flags);
+ u.reflags = flags;
+ }
+
+ void setNumber(double n, DecimalPoint decimalPoint) {
+ MOZ_ASSERT(type == TOK_NUMBER);
+ u.number.value = n;
+ u.number.decimalPoint = decimalPoint;
+ }
+
+ // Type-safe accessors
+
+ PropertyName* name() const {
+ MOZ_ASSERT(type == TOK_NAME);
+ return u.name->JSAtom::asPropertyName(); // poor-man's type verification
+ }
+
+ bool nameContainsEscape() const {
+ PropertyName* n = name();
+ return pos.begin + n->length() != pos.end;
+ }
+
+ JSAtom* atom() const {
+ MOZ_ASSERT(type == TOK_STRING ||
+ type == TOK_TEMPLATE_HEAD ||
+ type == TOK_NO_SUBS_TEMPLATE);
+ return u.atom;
+ }
+
+ js::RegExpFlag regExpFlags() const {
+ MOZ_ASSERT(type == TOK_REGEXP);
+ MOZ_ASSERT((u.reflags & AllFlags) == u.reflags);
+ return u.reflags;
+ }
+
+ double number() const {
+ MOZ_ASSERT(type == TOK_NUMBER);
+ return u.number.value;
+ }
+
+ DecimalPoint decimalPoint() const {
+ MOZ_ASSERT(type == TOK_NUMBER);
+ return u.number.decimalPoint;
+ }
+};
+
+class CompileError : public JSErrorReport {
+public:
+ void throwError(JSContext* cx);
+};
+
+// Ideally, tokenizing would be entirely independent of context. But the
+// strict mode flag, which is in SharedContext, affects tokenizing, and
+// TokenStream needs to see it.
+//
+// This class is a tiny back-channel from TokenStream to the strict mode flag
+// that avoids exposing the rest of SharedContext to TokenStream.
+//
+class StrictModeGetter {
+ public:
+ virtual bool strictMode() = 0;
+};
+
+// TokenStream is the lexical scanner for Javascript source text.
+//
+// It takes a buffer of char16_t characters 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 initialized
+// with random data (i.e. not initialized). It is 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 methods seek() and tell() allow to rescan from a previous visited
+// location of the buffer.
+//
+class MOZ_STACK_CLASS TokenStream
+{
+ // Unicode separators that are treated as line terminators, in addition to \n, \r.
+ enum {
+ LINE_SEPARATOR = 0x2028,
+ PARA_SEPARATOR = 0x2029
+ };
+
+ static const size_t ntokens = 4; // 1 current + 2 lookahead, rounded
+ // to power of 2 to avoid divmod by 3
+ static const unsigned maxLookahead = 2;
+ static const unsigned ntokensMask = ntokens - 1;
+
+ public:
+ typedef Vector<char16_t, 32> CharBuffer;
+
+ TokenStream(ExclusiveContext* cx, const ReadOnlyCompileOptions& options,
+ const char16_t* base, size_t length, StrictModeGetter* smg);
+
+ ~TokenStream();
+
+ MOZ_MUST_USE bool checkOptions();
+
+ // Accessors.
+ const Token& currentToken() const { return tokens[cursor]; }
+ bool isCurrentTokenType(TokenKind type) const {
+ return currentToken().type == type;
+ }
+ const CharBuffer& getTokenbuf() const { return tokenbuf; }
+ const char* getFilename() const { return filename; }
+ bool getMutedErrors() const { return mutedErrors; }
+ JSVersion versionNumber() const { return VersionNumber(options().version); }
+ JSVersion versionWithFlags() const { return options().version; }
+
+ PropertyName* currentName() const {
+ if (isCurrentTokenType(TOK_YIELD))
+ return cx->names().yield;
+ MOZ_ASSERT(isCurrentTokenType(TOK_NAME));
+ return currentToken().name();
+ }
+
+ PropertyName* nextName() const {
+ if (nextToken().type == TOK_YIELD)
+ return cx->names().yield;
+ MOZ_ASSERT(nextToken().type == TOK_NAME);
+ return nextToken().name();
+ }
+
+ bool nextNameContainsEscape() const {
+ if (nextToken().type == TOK_YIELD)
+ return false;
+ MOZ_ASSERT(nextToken().type == TOK_NAME);
+ return nextToken().nameContainsEscape();
+ }
+
+ bool isCurrentTokenAssignment() const {
+ return TokenKindIsAssignment(currentToken().type);
+ }
+
+ // Flag methods.
+ bool isEOF() const { return flags.isEOF; }
+ bool sawOctalEscape() const { return flags.sawOctalEscape; }
+ bool hadError() const { return flags.hadError; }
+ void clearSawOctalEscape() { flags.sawOctalEscape = false; }
+
+ // TokenStream-specific error reporters.
+ bool reportError(unsigned errorNumber, ...);
+ bool reportErrorNoOffset(unsigned errorNumber, ...);
+ bool reportWarning(unsigned errorNumber, ...);
+
+ static const uint32_t NoOffset = UINT32_MAX;
+
+ // General-purpose error reporters. You should avoid calling these
+ // directly, and instead use the more succinct alternatives (e.g.
+ // reportError()) in TokenStream, Parser, and BytecodeEmitter.
+ bool reportCompileErrorNumberVA(uint32_t offset, unsigned flags, unsigned errorNumber,
+ va_list args);
+ bool reportStrictModeErrorNumberVA(uint32_t offset, bool strictMode, unsigned errorNumber,
+ va_list args);
+ bool reportStrictWarningErrorNumberVA(uint32_t offset, unsigned errorNumber,
+ va_list args);
+
+ // asm.js reporter
+ void reportAsmJSError(uint32_t offset, unsigned errorNumber, ...);
+
+ JSAtom* getRawTemplateStringAtom() {
+ MOZ_ASSERT(currentToken().type == TOK_TEMPLATE_HEAD ||
+ currentToken().type == TOK_NO_SUBS_TEMPLATE);
+ const char16_t* cur = userbuf.rawCharPtrAt(currentToken().pos.begin + 1);
+ const char16_t* end;
+ if (currentToken().type == TOK_TEMPLATE_HEAD) {
+ // Of the form |`...${| or |}...${|
+ end = userbuf.rawCharPtrAt(currentToken().pos.end - 2);
+ } else {
+ // NO_SUBS_TEMPLATE is of the form |`...`| or |}...`|
+ end = userbuf.rawCharPtrAt(currentToken().pos.end - 1);
+ }
+
+ CharBuffer charbuf(cx);
+ while (cur < end) {
+ int32_t ch = *cur;
+ if (ch == '\r') {
+ ch = '\n';
+ if ((cur + 1 < end) && (*(cur + 1) == '\n'))
+ cur++;
+ }
+ if (!charbuf.append(ch))
+ return nullptr;
+ cur++;
+ }
+ return AtomizeChars(cx, charbuf.begin(), charbuf.length());
+ }
+
+ private:
+ // These are private because they should only be called by the tokenizer
+ // while tokenizing not by, for example, BytecodeEmitter.
+ bool reportStrictModeError(unsigned errorNumber, ...);
+ bool strictMode() const { return strictModeGetter && strictModeGetter->strictMode(); }
+
+ static JSAtom* atomize(ExclusiveContext* cx, CharBuffer& cb);
+ MOZ_MUST_USE bool putIdentInTokenbuf(const char16_t* identStart);
+
+ struct Flags
+ {
+ bool isEOF:1; // Hit end of file.
+ bool isDirtyLine:1; // Non-whitespace since start of line.
+ bool sawOctalEscape:1; // Saw an octal character escape.
+ bool hadError:1; // Hit a syntax error, at start or during a
+ // token.
+ bool hitOOM:1; // Hit OOM.
+
+ Flags()
+ : isEOF(), isDirtyLine(), sawOctalEscape(), hadError(), hitOOM()
+ {}
+ };
+
+ bool awaitIsKeyword = false;
+ friend class AutoAwaitIsKeyword;
+
+ public:
+ typedef Token::Modifier Modifier;
+ static constexpr Modifier None = Token::None;
+ static constexpr Modifier Operand = Token::Operand;
+ static constexpr Modifier KeywordIsName = Token::KeywordIsName;
+ static constexpr Modifier TemplateTail = Token::TemplateTail;
+
+ typedef Token::ModifierException ModifierException;
+ static constexpr ModifierException NoException = Token::NoException;
+ static constexpr ModifierException NoneIsOperand = Token::NoneIsOperand;
+ static constexpr ModifierException OperandIsNone = Token::OperandIsNone;
+ static constexpr ModifierException NoneIsKeywordIsName = Token::NoneIsKeywordIsName;
+
+ void addModifierException(ModifierException modifierException) {
+#ifdef DEBUG
+ const Token& next = nextToken();
+ if (next.modifierException == NoneIsOperand)
+ {
+ // Token after yield expression without operand already has
+ // NoneIsOperand exception.
+ MOZ_ASSERT(modifierException == OperandIsNone);
+ MOZ_ASSERT(next.type != TOK_DIV,
+ "next token requires contextual specifier to be parsed unambiguously");
+
+ // Do not update modifierException.
+ return;
+ }
+
+ MOZ_ASSERT(next.modifierException == NoException);
+ switch (modifierException) {
+ case NoneIsOperand:
+ MOZ_ASSERT(next.modifier == Operand);
+ MOZ_ASSERT(next.type != TOK_DIV,
+ "next token requires contextual specifier to be parsed unambiguously");
+ break;
+ case OperandIsNone:
+ MOZ_ASSERT(next.modifier == None);
+ MOZ_ASSERT(next.type != TOK_DIV && next.type != TOK_REGEXP,
+ "next token requires contextual specifier to be parsed unambiguously");
+ break;
+ case NoneIsKeywordIsName:
+ MOZ_ASSERT(next.modifier == KeywordIsName);
+ MOZ_ASSERT(next.type != TOK_NAME);
+ break;
+ default:
+ MOZ_CRASH("unexpected modifier exception");
+ }
+ tokens[(cursor + 1) & ntokensMask].modifierException = modifierException;
+#endif
+ }
+
+ void
+ verifyConsistentModifier(Modifier modifier, Token lookaheadToken) {
+#ifdef DEBUG
+ // Easy case: modifiers match.
+ if (modifier == lookaheadToken.modifier)
+ return;
+
+ if (lookaheadToken.modifierException == OperandIsNone) {
+ // getToken(Operand) permissibly following getToken().
+ if (modifier == Operand && lookaheadToken.modifier == None)
+ return;
+ }
+
+ if (lookaheadToken.modifierException == NoneIsOperand) {
+ // getToken() permissibly following getToken(Operand).
+ if (modifier == None && lookaheadToken.modifier == Operand)
+ return;
+ }
+
+ if (lookaheadToken.modifierException == NoneIsKeywordIsName) {
+ // getToken() permissibly following getToken(KeywordIsName).
+ if (modifier == None && lookaheadToken.modifier == KeywordIsName)
+ return;
+ }
+
+ MOZ_ASSERT_UNREACHABLE("this token was previously looked up with a "
+ "different modifier, potentially making "
+ "tokenization non-deterministic");
+#endif
+ }
+
+ // Advance to the next token. If the token stream encountered an error,
+ // return false. Otherwise return true and store the token kind in |*ttp|.
+ MOZ_MUST_USE bool getToken(TokenKind* ttp, Modifier modifier = None) {
+ // Check for a pushed-back token resulting from mismatching lookahead.
+ if (lookahead != 0) {
+ MOZ_ASSERT(!flags.hadError);
+ lookahead--;
+ cursor = (cursor + 1) & ntokensMask;
+ TokenKind tt = currentToken().type;
+ MOZ_ASSERT(tt != TOK_EOL);
+ verifyConsistentModifier(modifier, currentToken());
+ *ttp = tt;
+ return true;
+ }
+
+ return getTokenInternal(ttp, modifier);
+ }
+
+ // Push the last scanned token back into the stream.
+ void ungetToken() {
+ MOZ_ASSERT(lookahead < maxLookahead);
+ lookahead++;
+ cursor = (cursor - 1) & ntokensMask;
+ }
+
+ MOZ_MUST_USE bool peekToken(TokenKind* ttp, Modifier modifier = None) {
+ if (lookahead > 0) {
+ MOZ_ASSERT(!flags.hadError);
+ verifyConsistentModifier(modifier, nextToken());
+ *ttp = nextToken().type;
+ return true;
+ }
+ if (!getTokenInternal(ttp, modifier))
+ return false;
+ ungetToken();
+ return true;
+ }
+
+ MOZ_MUST_USE bool peekTokenPos(TokenPos* posp, Modifier modifier = None) {
+ if (lookahead == 0) {
+ TokenKind tt;
+ if (!getTokenInternal(&tt, modifier))
+ return false;
+ ungetToken();
+ MOZ_ASSERT(hasLookahead());
+ } else {
+ MOZ_ASSERT(!flags.hadError);
+ verifyConsistentModifier(modifier, nextToken());
+ }
+ *posp = nextToken().pos;
+ 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 TOK_EOL in |*ttp|. In that case, no token with
+ // TOK_EOL is actually created, just a TOK_EOL TokenKind is returned, and
+ // currentToken() shouldn't be consulted. (This is the only place TOK_EOL
+ // is produced.)
+ MOZ_ALWAYS_INLINE MOZ_MUST_USE bool
+ peekTokenSameLine(TokenKind* ttp, Modifier modifier = None) {
+ const Token& curr = 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 TOK_EOL.
+ if (lookahead != 0) {
+ bool onThisLine;
+ if (!srcCoords.isOnThisLine(curr.pos.end, lineno, &onThisLine))
+ return reportError(JSMSG_OUT_OF_MEMORY);
+ if (onThisLine) {
+ MOZ_ASSERT(!flags.hadError);
+ verifyConsistentModifier(modifier, nextToken());
+ *ttp = nextToken().type;
+ return true;
+ }
+ }
+
+ // The above check misses two cases where we don't have to return
+ // TOK_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 = currentToken();
+ ungetToken();
+
+ *ttp = srcCoords.lineNum(curr.pos.end) == srcCoords.lineNum(next.pos.begin)
+ ? next.type
+ : TOK_EOL;
+ return true;
+ }
+
+ // Get the next token from the stream if its kind is |tt|.
+ MOZ_MUST_USE bool matchToken(bool* matchedp, TokenKind tt, Modifier modifier = None) {
+ TokenKind token;
+ if (!getToken(&token, modifier))
+ return false;
+ if (token == tt) {
+ *matchedp = true;
+ } else {
+ ungetToken();
+ *matchedp = false;
+ }
+ return true;
+ }
+
+ void consumeKnownToken(TokenKind tt, Modifier modifier = None) {
+ bool matched;
+ MOZ_ASSERT(hasLookahead());
+ MOZ_ALWAYS_TRUE(matchToken(&matched, tt, modifier));
+ MOZ_ALWAYS_TRUE(matched);
+ }
+
+ // Like matchToken(..., TOK_NAME) but further matching the name token only
+ // if it has the given characters, without containing escape sequences.
+ // If the name token has the given characters yet *does* contain an escape,
+ // a syntax error will be reported.
+ //
+ // This latter behavior makes this method unsuitable for use in any context
+ // where ASI might occur. In such places, an escaped "contextual keyword"
+ // on a new line is the start of an ExpressionStatement, not a continuation
+ // of a StatementListItem (or ImportDeclaration or ExportDeclaration, in
+ // modules).
+ MOZ_MUST_USE bool matchContextualKeyword(bool* matchedp, Handle<PropertyName*> keyword,
+ Modifier modifier = None)
+ {
+ TokenKind token;
+ if (!getToken(&token, modifier))
+ return false;
+ if (token == TOK_NAME && currentToken().name() == keyword) {
+ if (currentToken().nameContainsEscape()) {
+ reportError(JSMSG_ESCAPED_KEYWORD);
+ return false;
+ }
+
+ *matchedp = true;
+ } else {
+ *matchedp = false;
+ ungetToken();
+ }
+ return true;
+ }
+
+ MOZ_MUST_USE bool nextTokenEndsExpr(bool* endsExpr) {
+ TokenKind tt;
+ if (!peekToken(&tt))
+ return false;
+ *endsExpr = isExprEnding[tt];
+ return true;
+ }
+
+ class MOZ_STACK_CLASS Position {
+ public:
+ // The Token fields may contain pointers to atoms, so for correct
+ // rooting we must ensure collection of atoms is disabled while objects
+ // of this class are live. Do this by requiring a dummy AutoKeepAtoms
+ // reference in the constructor.
+ //
+ // This class is explicity ignored by the analysis, so don't add any
+ // more pointers to GC things here!
+ explicit Position(AutoKeepAtoms&) { }
+ private:
+ Position(const Position&) = delete;
+ friend class TokenStream;
+ const char16_t* buf;
+ Flags flags;
+ unsigned lineno;
+ size_t linebase;
+ size_t prevLinebase;
+ Token currentToken;
+ unsigned lookahead;
+ Token lookaheadTokens[maxLookahead];
+ };
+
+ MOZ_MUST_USE bool advance(size_t position);
+ void tell(Position*);
+ void seek(const Position& pos);
+ MOZ_MUST_USE bool seek(const Position& pos, const TokenStream& other);
+#ifdef DEBUG
+ inline bool debugHasNoLookahead() const {
+ return lookahead == 0;
+ }
+#endif
+
+ const char16_t* rawCharPtrAt(size_t offset) const {
+ return userbuf.rawCharPtrAt(offset);
+ }
+
+ const char16_t* rawLimit() const {
+ return userbuf.limit();
+ }
+
+ bool hasDisplayURL() const {
+ return displayURL_ != nullptr;
+ }
+
+ char16_t* displayURL() {
+ return displayURL_.get();
+ }
+
+ bool hasSourceMapURL() const {
+ return sourceMapURL_ != nullptr;
+ }
+
+ char16_t* sourceMapURL() {
+ return sourceMapURL_.get();
+ }
+
+ // If |atom| is not a keyword in this version, return true with *ttp
+ // unchanged.
+ //
+ // If it is a reserved word in this version and strictness mode, and thus
+ // can't be present in correct code, report a SyntaxError and return false.
+ //
+ // If it is a keyword, like "if", return true with the keyword's TokenKind
+ // in *ttp.
+ MOZ_MUST_USE bool checkForKeyword(JSAtom* atom, TokenKind* ttp);
+
+ // Same semantics as above, but for the provided keyword.
+ MOZ_MUST_USE bool checkForKeyword(const KeywordInfo* kw, TokenKind* ttp);
+
+ // This class maps a userbuf offset (which is 0-indexed) to a line number
+ // (which is 1-indexed) and a column index (which is 0-indexed).
+ 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:
+ //
+ // 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 a "line index" (i.e. an index into
+ // |lineStartOffsets_|), subtract |initialLineNum_|. E.g. line 3's
+ // line 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 0, and the last element is always the
+ // MAX_PTR sentinel.
+ //
+ // offset-to-line/column 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 (lastLineIndex_) as
+ // a starting point.
+ //
+ // Checking if an offset lies within a particular line number
+ // (isOnThisLine()) is O(1).
+ //
+ Vector<uint32_t, 128> lineStartOffsets_;
+ uint32_t initialLineNum_;
+
+ // This is mutable because it's modified on every search, but that fact
+ // isn't visible outside this class.
+ mutable uint32_t lastLineIndex_;
+
+ uint32_t lineIndexOf(uint32_t offset) const;
+
+ static const uint32_t MAX_PTR = UINT32_MAX;
+
+ uint32_t lineIndexToNum(uint32_t lineIndex) const { return lineIndex + initialLineNum_; }
+ uint32_t lineNumToIndex(uint32_t lineNum) const { return lineNum - initialLineNum_; }
+
+ public:
+ SourceCoords(ExclusiveContext* cx, uint32_t ln);
+
+ MOZ_MUST_USE bool add(uint32_t lineNum, uint32_t lineStartOffset);
+ MOZ_MUST_USE bool fill(const SourceCoords& other);
+
+ bool isOnThisLine(uint32_t offset, uint32_t lineNum, bool* onThisLine) const {
+ uint32_t lineIndex = lineNumToIndex(lineNum);
+ if (lineIndex + 1 >= lineStartOffsets_.length()) // +1 due to sentinel
+ return false;
+ *onThisLine = lineStartOffsets_[lineIndex] <= offset &&
+ offset < lineStartOffsets_[lineIndex + 1];
+ return true;
+ }
+
+ uint32_t lineNum(uint32_t offset) const;
+ uint32_t columnIndex(uint32_t offset) const;
+ void lineNumAndColumnIndex(uint32_t offset, uint32_t* lineNum, uint32_t* columnIndex) const;
+ };
+
+ SourceCoords srcCoords;
+
+ JSAtomState& names() const {
+ return cx->names();
+ }
+
+ ExclusiveContext* context() const {
+ return cx;
+ }
+
+ const ReadOnlyCompileOptions& options() const {
+ return options_;
+ }
+
+ private:
+ // This is the low-level interface to the JS source code buffer. It just
+ // gets raw chars, basically. 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
+ // chars" 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]|.
+ class TokenBuf {
+ public:
+ TokenBuf(ExclusiveContext* cx, const char16_t* buf, size_t length, size_t startOffset)
+ : base_(buf),
+ startOffset_(startOffset),
+ limit_(buf + length),
+ ptr(buf)
+ { }
+
+ bool hasRawChars() const {
+ return ptr < limit_;
+ }
+
+ bool atStart() const {
+ return offset() == 0;
+ }
+
+ size_t startOffset() const {
+ return startOffset_;
+ }
+
+ size_t offset() const {
+ return startOffset_ + mozilla::PointerRangeSize(base_, ptr);
+ }
+
+ const char16_t* rawCharPtrAt(size_t offset) const {
+ MOZ_ASSERT(startOffset_ <= offset);
+ MOZ_ASSERT(offset - startOffset_ <= mozilla::PointerRangeSize(base_, limit_));
+ return base_ + (offset - startOffset_);
+ }
+
+ const char16_t* limit() const {
+ return limit_;
+ }
+
+ char16_t getRawChar() {
+ return *ptr++; // this will nullptr-crash if poisoned
+ }
+
+ char16_t peekRawChar() const {
+ return *ptr; // this will nullptr-crash if poisoned
+ }
+
+ bool matchRawChar(char16_t c) {
+ if (*ptr == c) { // this will nullptr-crash if poisoned
+ ptr++;
+ return true;
+ }
+ return false;
+ }
+
+ bool matchRawCharBackwards(char16_t c) {
+ MOZ_ASSERT(ptr); // make sure it hasn't been poisoned
+ if (*(ptr - 1) == c) {
+ ptr--;
+ return true;
+ }
+ return false;
+ }
+
+ void ungetRawChar() {
+ MOZ_ASSERT(ptr); // make sure it hasn't been poisoned
+ ptr--;
+ }
+
+ const char16_t* addressOfNextRawChar(bool allowPoisoned = false) const {
+ MOZ_ASSERT_IF(!allowPoisoned, ptr); // make sure it hasn't been poisoned
+ return ptr;
+ }
+
+ // Use this with caution!
+ void setAddressOfNextRawChar(const char16_t* a, bool allowPoisoned = false) {
+ MOZ_ASSERT_IF(!allowPoisoned, a);
+ ptr = a;
+ }
+
+#ifdef DEBUG
+ // Poison the TokenBuf so it cannot be accessed again.
+ void poison() {
+ ptr = nullptr;
+ }
+#endif
+
+ static bool isRawEOLChar(int32_t c) {
+ return c == '\n' || c == '\r' || c == LINE_SEPARATOR || c == PARA_SEPARATOR;
+ }
+
+ // Returns the offset of the next EOL, but stops once 'max' characters
+ // have been scanned (*including* the char at startOffset_).
+ size_t findEOLMax(size_t start, size_t max);
+
+ private:
+ const char16_t* base_; // base of buffer
+ uint32_t startOffset_; // offset of base_[0]
+ const char16_t* limit_; // limit for quick bounds check
+ const char16_t* ptr; // next char to get
+ };
+
+ MOZ_MUST_USE bool getTokenInternal(TokenKind* ttp, Modifier modifier);
+
+ MOZ_MUST_USE bool getBracedUnicode(uint32_t* code);
+ MOZ_MUST_USE bool getStringOrTemplateToken(int untilChar, Token** tp);
+
+ int32_t getChar();
+ int32_t getCharIgnoreEOL();
+ void ungetChar(int32_t c);
+ void ungetCharIgnoreEOL(int32_t c);
+ Token* newToken(ptrdiff_t adjust);
+ uint32_t peekUnicodeEscape(uint32_t* codePoint);
+ uint32_t peekExtendedUnicodeEscape(uint32_t* codePoint);
+ uint32_t matchUnicodeEscapeIdStart(uint32_t* codePoint);
+ bool matchUnicodeEscapeIdent(uint32_t* codePoint);
+ bool peekChars(int n, char16_t* cp);
+
+ MOZ_MUST_USE bool getDirectives(bool isMultiline, bool shouldWarnDeprecated);
+ MOZ_MUST_USE bool getDirective(bool isMultiline, bool shouldWarnDeprecated,
+ const char* directive, int directiveLength,
+ const char* errorMsgPragma,
+ UniquePtr<char16_t[], JS::FreePolicy>* destination);
+ MOZ_MUST_USE bool getDisplayURL(bool isMultiline, bool shouldWarnDeprecated);
+ MOZ_MUST_USE bool getSourceMappingURL(bool isMultiline, bool shouldWarnDeprecated);
+
+ // |expect| cannot be an EOL char.
+ bool matchChar(int32_t expect) {
+ MOZ_ASSERT(!TokenBuf::isRawEOLChar(expect));
+ return MOZ_LIKELY(userbuf.hasRawChars()) &&
+ userbuf.matchRawChar(expect);
+ }
+
+ void consumeKnownChar(int32_t expect) {
+ mozilla::DebugOnly<int32_t> c = getChar();
+ MOZ_ASSERT(c == expect);
+ }
+
+ int32_t peekChar() {
+ int32_t c = getChar();
+ ungetChar(c);
+ return c;
+ }
+
+ void skipChars(int n) {
+ while (--n >= 0)
+ getChar();
+ }
+
+ void skipCharsIgnoreEOL(int n) {
+ while (--n >= 0)
+ getCharIgnoreEOL();
+ }
+
+ void updateLineInfoForEOL();
+ void updateFlagsForEOL();
+
+ const Token& nextToken() const {
+ MOZ_ASSERT(hasLookahead());
+ return tokens[(cursor + 1) & ntokensMask];
+ }
+
+ bool hasLookahead() const { return lookahead > 0; }
+
+ // Options used for parsing/tokenizing.
+ const ReadOnlyCompileOptions& options_;
+
+ Token tokens[ntokens]; // circular token buffer
+ unsigned cursor; // index of last parsed token
+ unsigned lookahead; // count of lookahead tokens
+ unsigned lineno; // current line number
+ Flags flags; // flags -- see above
+ size_t linebase; // start of current line
+ size_t prevLinebase; // start of previous line; size_t(-1) if on the first line
+ TokenBuf userbuf; // user input buffer
+ const char* filename; // input filename or null
+ UniqueTwoByteChars displayURL_; // the user's requested source URL or null
+ UniqueTwoByteChars sourceMapURL_; // source map's filename or null
+ CharBuffer tokenbuf; // current token string buffer
+ uint8_t isExprEnding[TOK_LIMIT];// which tokens definitely terminate exprs?
+ ExclusiveContext* const cx;
+ bool mutedErrors;
+ StrictModeGetter* strictModeGetter; // used to test for strict mode
+};
+
+class MOZ_STACK_CLASS AutoAwaitIsKeyword
+{
+private:
+ TokenStream* ts_;
+ bool oldAwaitIsKeyword_;
+
+public:
+ AutoAwaitIsKeyword(TokenStream* ts, bool awaitIsKeyword) {
+ ts_ = ts;
+ oldAwaitIsKeyword_ = ts_->awaitIsKeyword;
+ ts_->awaitIsKeyword = awaitIsKeyword;
+ }
+
+ ~AutoAwaitIsKeyword() {
+ ts_->awaitIsKeyword = oldAwaitIsKeyword_;
+ ts_ = nullptr;
+ }
+};
+
+extern const char*
+TokenKindToDesc(TokenKind tt);
+
+} // namespace frontend
+} // namespace js
+
+extern JS_FRIEND_API(int)
+js_fgets(char* buf, int size, FILE* file);
+
+#ifdef DEBUG
+extern const char*
+TokenKindToString(js::frontend::TokenKind tt);
+#endif
+
+#endif /* frontend_TokenStream_h */