From 5f8de423f190bbb79a62f804151bc24824fa32d8 Mon Sep 17 00:00:00 2001 From: "Matt A. Tobin" Date: Fri, 2 Feb 2018 04:16:08 -0500 Subject: Add m-esr52 at 52.6.0 --- js/src/frontend/BytecodeCompiler.cpp | 738 +++ js/src/frontend/BytecodeCompiler.h | 108 + js/src/frontend/BytecodeEmitter.cpp | 10170 +++++++++++++++++++++++++++++++++ js/src/frontend/BytecodeEmitter.h | 763 +++ js/src/frontend/FoldConstants.cpp | 1928 +++++++ js/src/frontend/FoldConstants.h | 41 + js/src/frontend/FullParseHandler.h | 977 ++++ js/src/frontend/NameAnalysisTypes.h | 366 ++ js/src/frontend/NameCollections.h | 338 ++ js/src/frontend/NameFunctions.cpp | 838 +++ js/src/frontend/NameFunctions.h | 28 + js/src/frontend/ParseNode-inl.h | 35 + js/src/frontend/ParseNode.cpp | 904 +++ js/src/frontend/ParseNode.h | 1450 +++++ js/src/frontend/Parser.cpp | 9627 +++++++++++++++++++++++++++++++ js/src/frontend/Parser.h | 1430 +++++ js/src/frontend/SharedContext.h | 639 +++ js/src/frontend/SourceNotes.h | 207 + js/src/frontend/SyntaxParseHandler.h | 599 ++ js/src/frontend/TokenKind.h | 245 + js/src/frontend/TokenStream.cpp | 1962 +++++++ js/src/frontend/TokenStream.h | 1057 ++++ 22 files changed, 34450 insertions(+) create mode 100644 js/src/frontend/BytecodeCompiler.cpp create mode 100644 js/src/frontend/BytecodeCompiler.h create mode 100644 js/src/frontend/BytecodeEmitter.cpp create mode 100644 js/src/frontend/BytecodeEmitter.h create mode 100644 js/src/frontend/FoldConstants.cpp create mode 100644 js/src/frontend/FoldConstants.h create mode 100644 js/src/frontend/FullParseHandler.h create mode 100644 js/src/frontend/NameAnalysisTypes.h create mode 100644 js/src/frontend/NameCollections.h create mode 100644 js/src/frontend/NameFunctions.cpp create mode 100644 js/src/frontend/NameFunctions.h create mode 100644 js/src/frontend/ParseNode-inl.h create mode 100644 js/src/frontend/ParseNode.cpp create mode 100644 js/src/frontend/ParseNode.h create mode 100644 js/src/frontend/Parser.cpp create mode 100644 js/src/frontend/Parser.h create mode 100644 js/src/frontend/SharedContext.h create mode 100644 js/src/frontend/SourceNotes.h create mode 100644 js/src/frontend/SyntaxParseHandler.h create mode 100644 js/src/frontend/TokenKind.h create mode 100644 js/src/frontend/TokenStream.cpp create mode 100644 js/src/frontend/TokenStream.h (limited to 'js/src/frontend') diff --git a/js/src/frontend/BytecodeCompiler.cpp b/js/src/frontend/BytecodeCompiler.cpp new file mode 100644 index 000000000..d4c758b6c --- /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 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& 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 maybeSourceCompressor; + SourceCompressionTask* sourceCompressor; + + Maybe usedNames; + Maybe> syntaxParser; + Maybe> 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*) 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& 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() || env->is()) { + if (env->is()) { + RootedScript script(cx, env->as().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 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 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 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 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 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_(); + 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 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 parser(cx, cx->tempLifoAlloc(), options, chars, length, + /* foldConstants = */ true, usedNames, nullptr, lazy); + if (!parser.checkOptions()) + return false; + + Rooted 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 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 tag, or in a Function() constructor. +static bool +CompileFunctionBody(JSContext* cx, MutableHandleFunction fun, const ReadOnlyCompileOptions& options, + Handle 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 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 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 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 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 000000000..1d86f1160 --- /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 lazy, const char16_t* chars, size_t length); + +MOZ_MUST_USE bool +CompileFunctionBody(JSContext* cx, MutableHandleFunction fun, + const ReadOnlyCompileOptions& options, + Handle 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 formals, JS::SourceBufferHolder& srcBuf); + +MOZ_MUST_USE bool +CompileStarGeneratorBody(JSContext* cx, MutableHandleFunction fun, + const ReadOnlyCompileOptions& options, + Handle formals, JS::SourceBufferHolder& srcBuf); + +MOZ_MUST_USE bool +CompileAsyncFunctionBody(JSContext* cx, MutableHandleFunction fun, + const ReadOnlyCompileOptions& options, + Handle 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 000000000..1e9d8f224 --- /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 + +#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 +{ + PooledMapPtr cache_; + + MOZ_MUST_USE bool ensureCache(BytecodeEmitter* bce) { + return cache_ || cache_.acquire(bce->cx); + } + + public: + explicit TDZCheckCache(BytecodeEmitter* bce) + : Nestable(&bce->innermostTDZCheckCache), + cache_(bce->cx->frontendCollectionPool()) + { } + + Maybe needsTDZCheck(BytecodeEmitter* bce, JSAtom* name); + MOZ_MUST_USE bool noteTDZCheck(BytecodeEmitter* bce, JSAtom* name, MaybeCheckTDZ check); +}; + +class BytecodeEmitter::NestableControl : public Nestable +{ + StatementKind kind_; + + // The innermost scope when this was pushed. + EmitterScope* emitterScope_; + + protected: + NestableControl(BytecodeEmitter* bce, StatementKind kind) + : Nestable(&bce->innermostNestableControl), + kind_(kind), + emitterScope_(bce->innermostEmitterScope) + { } + + public: + using Nestable::enclosing; + using Nestable::findNearest; + + StatementKind kind() const { + return kind_; + } + + EmitterScope* emitterScope() const { + return emitterScope_; + } + + template + bool is() const; + + template + T& as() { + MOZ_ASSERT(this->is()); + return static_cast(*this); + } +}; + +// Template specializations are disallowed in different namespaces; specialize +// all the NestableControl subtypes up front. +namespace js { +namespace frontend { + +template <> +bool +BytecodeEmitter::NestableControl::is() const +{ + return StatementKindIsUnlabeledBreakTarget(kind_) || kind_ == StatementKind::Label; +} + +template <> +bool +BytecodeEmitter::NestableControl::is() const +{ + return kind_ == StatementKind::Label; +} + +template <> +bool +BytecodeEmitter::NestableControl::is() const +{ + return StatementKindIsLoop(kind_); +} + +template <> +bool +BytecodeEmitter::NestableControl::is() 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()); + } + + 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* enclosingLoop = findNearest(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()); + } + + 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 +{ + // The cache of bound names that may be looked up in the + // scope. Initially populated as the set of names this scope binds. As + // names are looked up in enclosing scopes, they are cached on the + // current scope. + PooledMapPtr nameCache_; + + // If this scope's cache does not include free names, such as the + // global scope, the NameLocation to return. + Maybe fallbackFreeNameLocation_; + + // True if there is a corresponding EnvironmentObject on the environment + // chain, false if all bindings are stored in frame slots on the stack. + bool hasEnvironment_; + + // The number of enclosing environments. Used for error checking. + uint8_t environmentChainLength_; + + // The next usable slot on the frame for not-closed over bindings. + // + // The initial frame slot when assigning slots to bindings is the + // enclosing scope's nextFrameSlot. For the first scope in a frame, + // the initial frame slot is 0. + uint32_t nextFrameSlot_; + + // The index in the BytecodeEmitter's interned scope vector, otherwise + // ScopeNote::NoScopeIndex. + 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 + 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(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 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 + MOZ_MUST_USE bool internScope(BytecodeEmitter* bce, ScopeCreator createScope); + template + 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(&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 bindings); + MOZ_MUST_USE bool enterNamedLambda(BytecodeEmitter* bce, FunctionBox* funbox); + MOZ_MUST_USE bool enterComprehensionFor(BytecodeEmitter* bce, + Handle 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::enclosing(); + } + + NameLocation lookup(BytecodeEmitter* bce, JSAtom* name) { + if (Maybe loc = lookupInCache(bce, name)) + return *loc; + return searchAndCache(bce, name); + } + + Maybe 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 +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 +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().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().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()) + 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 loc; + uint8_t hops = hasEnvironment() ? 1 : 0; + DebugOnly inCurrentScript = enclosingInFrame(); + + // Start searching in the current compilation. + for (EmitterScope* es = enclosing(&bce); es; es = es->enclosing(&bce)) { + loc = es->lookupInCache(bce, name); + if (loc) { + if (loc->kind() == NameLocation::Kind::EnvironmentCoordinate) + *loc = loc->addHops(hops); + break; + } + + if (es->hasEnvironment()) + hops++; + +#ifdef DEBUG + if (!es->enclosingInFrame()) + inCurrentScript = false; +#endif + } + + // If the name is not found in the current compilation, walk the Scope + // chain encompassing the compilation. + if (!loc) { + 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 +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 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 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 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 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()) + 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 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 +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* parser, SharedContext* sc, + HandleScript script, Handle 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* parser, SharedContext* sc, + HandleScript script, Handle 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 bool */> +BytecodeEmitter::NestableControl* +BytecodeEmitter::findInnermostNestableControl(Predicate predicate) const +{ + return NestableControl::findNearest(innermostNestableControl, predicate); +} + +template +T* +BytecodeEmitter::findInnermostNestableControl() const +{ + return NestableControl::findNearest(innermostNestableControl); +} + +template bool */> +T* +BytecodeEmitter::findInnermostNestableControl(Predicate predicate) const +{ + return NestableControl::findNearest(innermostNestableControl, predicate); +} + +NameLocation +BytecodeEmitter::lookupName(JSAtom* name) +{ + return innermostEmitterScope->lookup(this, name); +} + +Maybe +BytecodeEmitter::locationOfNameBoundInScope(JSAtom* name, EmitterScope* target) +{ + return innermostEmitterScope->locationBoundInScope(this, name, target); +} + +Maybe +BytecodeEmitter::locationOfNameBoundInFunctionScope(JSAtom* name, EmitterScope* source) +{ + EmitterScope* funScope = source; + while (!funScope->scope(this)->is()) + 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(); + 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(); + 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(); +} + +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(cx, kind, TenuredObject)); + if (!obj) + return false; + + Rooted value_id(cx, AtomToId(cx->names().value)); + Rooted 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 +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()); + 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 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().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().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().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().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().isSuper(); + JSOp binop = GetIncDecInfo(pn->getKind(), &post); + + if (isSuper) { + ParseNode* base = &pn->pn_kid->as().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().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().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 tdzCache; + Maybe 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() : nullptr; + if (caseCount == 0 || + (caseCount == 1 && (hasDefault = firstCase->isDefault()))) + { + caseCount = 0; + low = 0; + high = -1; + } else { + Vector 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, ¬eIndex)) + 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, ¬eIndex)) + return false; + } + + // Emit switchOp followed by switchSize bytes of jump or lookup table. + MOZ_ASSERT(top == offset()); + if (!emitN(switchOp, switchSize)) + return false; + + Vector 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(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 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 +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().isSuper()) { + if (!emitSuperPropLHS(&target->as().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().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, ¬eIndex_)) + 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().isSuper()) { + if (!emitSuperPropLHS(&lhs->as().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().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().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().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().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().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 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(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().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(); + + /* 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, ¬eIndex)) + 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 + // POST: pop + // < catch block contents > + // debugleaveblock + // [poplexicalenv] only if any local aliased + // goto non-local; finally applies + // + // If there's no catch block without a catchguard, the last 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 , 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().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, ¬eIndex)) + 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, ¬eIndex)) + 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 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(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, ¬eIndex)) + 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, ¬eIndex)) + 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; + 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, ¬eIndex)) + 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; + 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, ¬eIndex)) + 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 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(); + 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 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 sourceObject(cx, script->sourceObject()); + Rooted 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, ¬eIndex)) + return false; + if (!emit1(JSOP_NOP)) + return false; + + unsigned noteIndex2; + if (!newSrcNote(SRC_WHILE, ¬eIndex2)) + 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, ¬eIndex)) + 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(hasSameLabel); + noteType = SRC_BREAK2LABEL; + } else { + auto isNotLabel = [](BreakableControl* control) { + return !control->is(); + }; + target = findInnermostNestableControl(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() || control->as().label() != label) { + if (control->is()) + target = &control->as(); + control = control->enclosing(); + } + } else { + target = findInnermostNestableControl(); + } + 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(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, ¬eIndex)) + 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() && + innermostNestableControl->as().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().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().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 | to + // effectively |, true|. + bool useful = false; + if (!checkSideEffects(expression, &useful)) + return false; + + if (useful) { + if (!emitTree(expression)) + return false; + if (!emit1(JSOP_POP)) + return false; + } + + return emit1(JSOP_TRUE); +} + +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 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().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().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().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().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 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(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() && + obj->as().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 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, ¶mLoc](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 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(); + + 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(); + 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 tdzCache; + Maybe 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().label())) + return false; + break; + + case PNK_CONTINUE: + if (!emitContinue(pn->as().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())) + 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())) + 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().isSuper()) { + if (!emitSuperPropOp(pn, JSOP_GETPROP_SUPER)) + return false; + } else { + if (!emitPropOp(pn, JSOP_GETPROP)) + return false; + } + break; + + case PNK_ELEM: + if (pn->as().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().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(¬es[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(¬es[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 = ¬es[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(¬e); + + 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 000000000..1bb4191ee --- /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 class Parser; +class SharedContext; +class TokenStream; + +class CGConstList { + Vector 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> vector; + + explicit CGScopeList(ExclusiveContext* cx) + : vector(cx, GCVector(cx)) + { } + + bool append(Scope* scope) { return vector.append(scope); } + uint32_t length() const { return vector.length(); } + void finish(ScopeArray* array); +}; + +struct CGTryNoteList { + Vector 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 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 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 BytecodeVector; +typedef Vector 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 script; /* the JSScript we're ultimately producing */ + + Rooted 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* const parser; + + PooledMapPtr 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* parser, SharedContext* sc, + HandleScript script, Handle 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* parser, SharedContext* sc, + HandleScript script, Handle lazyScript, + TokenPos bodyPosition, EmitterMode emitterMode = Normal); + + MOZ_MUST_USE bool init(); + + template bool */> + NestableControl* findInnermostNestableControl(Predicate predicate) const; + + template + T* findInnermostNestableControl() const; + + template 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 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 locationOfNameBoundInFunctionScope(JSAtom* name, + EmitterScope* source); + + mozilla::Maybe 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 + MOZ_MUST_USE bool emitSetOrInitializeNameAtLocation(HandleAtom name, const NameLocation& loc, + RHSEmitter emitRhs, bool initialize); + template + MOZ_MUST_USE bool emitSetOrInitializeName(HandleAtom name, RHSEmitter emitRhs, + bool initialize) + { + return emitSetOrInitializeNameAtLocation(name, lookupName(name), emitRhs, initialize); + } + template + MOZ_MUST_USE bool emitSetName(ParseNode* pn, RHSEmitter emitRhs) { + RootedAtom name(cx, pn->name()); + return emitSetName(name, emitRhs); + } + template + MOZ_MUST_USE bool emitSetName(HandleAtom name, RHSEmitter emitRhs) { + return emitSetOrInitializeName(name, emitRhs, false); + } + template + MOZ_MUST_USE bool emitInitializeName(ParseNode* pn, RHSEmitter emitRhs) { + RootedAtom name(cx, pn->name()); + return emitInitializeName(name, emitRhs); + } + template + 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 + 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 000000000..6f62ffac6 --- /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().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(), result); + } + + // List nodes with all non-null children. + case PNK_STATEMENTLIST: + return ListContainsHoistedDeclaration(cx, &node->as(), 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 | 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& parser, bool inGenexpLambda); + +static bool +FoldCondition(ExclusiveContext* cx, ParseNode** nodePtr, Parser& 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& 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& 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& 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& 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& 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& 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& 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::PermitAssignmentToFunctionCalls)); + + if (!Fold(cx, &target, parser, inGenexpLambda)) + return false; + + MOZ_ASSERT(parser.isValidSimpleAssignmentTarget(target, Parser::PermitAssignmentToFunctionCalls)); + + return true; +} + +static bool +FoldAndOr(ExclusiveContext* cx, ParseNode** nodePtr, Parser& 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& 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& 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& 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(); + return PositiveInfinity(); + } + + 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& 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& 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& 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& 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& 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& 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& 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& 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& 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(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& 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(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 = ¤t->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& 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& 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& 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& 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& 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& 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* 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 000000000..274daaaae --- /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* parser); + +inline MOZ_MUST_USE bool +FoldConstants(ExclusiveContext* cx, SyntaxParseHandler::Node* pnp, + Parser* 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 000000000..add881900 --- /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 +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(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 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* 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* 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_(PNK_NAME, JSOP_GETNAME, name, pos); + } + + ParseNode* newComputedName(ParseNode* expr, uint32_t begin, uint32_t end) { + TokenPos pos(begin, end); + return new_(PNK_COMPUTED_NAME, JSOP_NOP, pos, expr); + } + + ParseNode* newObjectLiteralPropertyName(JSAtom* atom, const TokenPos& pos) { + return new_(PNK_OBJECT_PROPERTY_NAME, JSOP_NOP, pos, atom); + } + + ParseNode* newNumber(double value, DecimalPoint decimalPoint, const TokenPos& pos) { + ParseNode* pn = new_(PNK_NUMBER, pos); + if (!pn) + return nullptr; + pn->initNumber(value, decimalPoint); + return pn; + } + + ParseNode* newBooleanLiteral(bool cond, const TokenPos& pos) { + return new_(cond, pos); + } + + ParseNode* newStringLiteral(JSAtom* atom, const TokenPos& pos) { + return new_(PNK_STRING, JSOP_NOP, pos, atom); + } + + ParseNode* newTemplateStringLiteral(JSAtom* atom, const TokenPos& pos) { + return new_(PNK_TEMPLATE_STRING, JSOP_NOP, pos, atom); + } + + ParseNode* newCallSiteObject(uint32_t begin) { + ParseNode* callSite = new_(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_(pos, thisName); + } + + ParseNode* newNullLiteral(const TokenPos& pos) { + return new_(pos); + } + + // The Boxer object here is any object that can allocate ObjectBoxes. + // Specifically, a Boxer has a .newObjectBox(T) method that accepts a + // Rooted argument and returns an ObjectBox*. + template + ParseNode* newRegExp(RegExpObject* reobj, const TokenPos& pos, Boxer& boxer) { + ObjectBox* objbox = boxer.newObjectBox(reobj); + if (!objbox) + return null(); + return new_(objbox, pos); + } + + ParseNode* newConditional(ParseNode* cond, ParseNode* thenExpr, ParseNode* elseExpr) { + return new_(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_(kind, JSOP_NOP, pos, kid); + } + + ParseNode* newNullary(ParseNodeKind kind, JSOp op, const TokenPos& pos) { + return new_(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_(kind, op, pos, kid); + } + + ParseNode* newUpdate(ParseNodeKind kind, uint32_t begin, ParseNode* kid) { + TokenPos pos(begin, kid->pn_pos.end); + return new_(kind, JSOP_NOP, pos, kid); + } + + ParseNode* newSpread(uint32_t begin, ParseNode* kid) { + TokenPos pos(begin, kid->pn_pos.end); + return new_(PNK_SPREAD, JSOP_NOP, pos, kid); + } + + ParseNode* newArrayPush(uint32_t begin, ParseNode* kid) { + TokenPos pos(begin, kid->pn_pos.end); + return new_(PNK_ARRAYPUSH, JSOP_ARRAYPUSH, pos, kid); + } + + ParseNode* newBinary(ParseNodeKind kind, JSOp op = JSOP_NOP) { + return new_(kind, op, pos(), (ParseNode*) nullptr, (ParseNode*) nullptr); + } + ParseNode* newBinary(ParseNodeKind kind, ParseNode* left, + JSOp op = JSOP_NOP) { + return new_(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_(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_(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_(PNK_ARRAYCOMP, pos); + if (!pn) + return nullptr; + pn->append(body); + return pn; + } + + ParseNode* newArrayLiteral(uint32_t begin) { + ParseNode* literal = new_(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_(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_(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_(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_(name, heritage, methodBlock); + } + ParseNode* newClassMethodList(uint32_t begin) { + return new_(PNK_CLASSMETHODLIST, TokenPos(begin, begin + 1)); + } + ParseNode* newClassNames(ParseNode* outer, ParseNode* inner, const TokenPos& pos) { + return new_(outer, inner, pos); + } + ParseNode* newNewTarget(ParseNode* newHolder, ParseNode* targetHolder) { + return new_(PNK_NEWTARGET, JSOP_NOP, newHolder, targetHolder); + } + ParseNode* newPosHolder(const TokenPos& pos) { + return new_(PNK_POSHOLDER, pos); + } + ParseNode* newSuperBase(ParseNode* thisName, const TokenPos& pos) { + return new_(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_(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_(PNK_YIELD, op, pos, value, gen); + } + + ParseNode* newYieldStarExpression(uint32_t begin, ParseNode* value, ParseNode* gen) { + TokenPos pos(begin, value->pn_pos.end); + return new_(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_(PNK_AWAIT, JSOP_YIELD, pos, value, gen); + } + + // Statements + + ParseNode* newStatementList(const TokenPos& pos) { + return new_(PNK_STATEMENTLIST, pos); + } + + MOZ_MUST_USE bool isFunctionStmt(ParseNode* stmt) { + while (stmt->isKind(PNK_LABEL)) + stmt = stmt->as().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_(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_(PNK_SEMI, JSOP_NOP, pos, (ParseNode*) nullptr); + } + + ParseNode* newImportDeclaration(ParseNode* importSpecSet, + ParseNode* moduleSpec, const TokenPos& pos) + { + ParseNode* pn = new_(PNK_IMPORT, JSOP_NOP, pos, + importSpecSet, moduleSpec); + if (!pn) + return null(); + return pn; + } + + ParseNode* newExportDeclaration(ParseNode* kid, const TokenPos& pos) { + return new_(PNK_EXPORT, JSOP_NOP, pos, kid); + } + + ParseNode* newExportFromDeclaration(uint32_t begin, ParseNode* exportSpecSet, + ParseNode* moduleSpec) + { + ParseNode* pn = new_(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_(PNK_EXPORT_DEFAULT, JSOP_NOP, pos, kid, maybeBinding); + } + + ParseNode* newExprStatement(ParseNode* expr, uint32_t end) { + MOZ_ASSERT(expr->pn_pos.end <= end); + return new_(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_(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_(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_(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_(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_(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_(PNK_LET, JSOP_NOP, kid); + } + + ParseNode* newForHead(ParseNode* init, ParseNode* test, ParseNode* update, + const TokenPos& pos) + { + return new_(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_(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_(PNK_SWITCH, JSOP_NOP, pos, discriminant, caseList); + } + + ParseNode* newCaseOrDefault(uint32_t begin, ParseNode* expr, ParseNode* body) { + return new_(expr, body, begin); + } + + ParseNode* newContinueStatement(PropertyName* label, const TokenPos& pos) { + return new_(label, pos); + } + + ParseNode* newBreakStatement(PropertyName* label, const TokenPos& pos) { + return new_(label, pos); + } + + ParseNode* newReturnStatement(ParseNode* expr, const TokenPos& pos) { + MOZ_ASSERT_IF(expr, pos.encloses(expr->pn_pos)); + return new_(PNK_RETURN, JSOP_RETURN, pos, expr); + } + + ParseNode* newWithStatement(uint32_t begin, ParseNode* expr, ParseNode* body) { + return new_(PNK_WITH, JSOP_NOP, TokenPos(begin, body->pn_pos.end), + expr, body); + } + + ParseNode* newLabeledStatement(PropertyName* label, ParseNode* stmt, uint32_t begin) { + return new_(label, stmt, begin); + } + + ParseNode* newThrowStatement(ParseNode* expr, const TokenPos& pos) { + MOZ_ASSERT(pos.encloses(expr->pn_pos)); + return new_(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_(PNK_TRY, JSOP_NOP, body, catchList, finallyBlock, pos); + } + + ParseNode* newDebuggerStatement(const TokenPos& pos) { + return new_(pos); + } + + ParseNode* newPropertyAccess(ParseNode* pn, PropertyName* name, uint32_t end) { + return new_(pn, name, pn->pn_pos.begin, end); + } + + ParseNode* newPropertyByValue(ParseNode* lhs, ParseNode* index, uint32_t end) { + return new_(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_(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_(PNK_MODULE, pos()); + } + + ParseNode* newLexicalScope(LexicalScope::Data* bindings, ParseNode* body) { + return new_(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_(kind, op, pos()); + } + + ParseNode* newList(ParseNodeKind kind, uint32_t begin, JSOp op = JSOP_NOP) { + MOZ_ASSERT(!isDeclarationKind(kind)); + return new_(kind, op, TokenPos(begin, begin + 1)); + } + + ParseNode* newList(ParseNodeKind kind, ParseNode* kid, JSOp op = JSOP_NOP) { + MOZ_ASSERT(!isDeclarationKind(kind)); + return new_(kind, op, kid); + } + + ParseNode* newDeclarationList(ParseNodeKind kind, JSOp op = JSOP_NOP) { + MOZ_ASSERT(isDeclarationKind(kind)); + return new_(kind, op, pos()); + } + + ParseNode* newDeclarationList(ParseNodeKind kind, ParseNode* kid, JSOp op = JSOP_NOP) { + MOZ_ASSERT(isDeclarationKind(kind)); + return new_(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_(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() ? &pn->as().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 000000000..d39e177fb --- /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(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; + +} // namespace frontend +} // namespace js + +namespace mozilla { + +template <> +struct IsPod : TrueType {}; + +template <> +struct IsPod : 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 000000000..58c5d0ac0 --- /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 +class CollectionPool +{ + using RecyclableCollections = Vector; + + RecyclableCollections all_; + RecyclableCollections recyclable_; + + static RepresentativeCollection* asRepresentative(void* p) { + return reinterpret_cast(p); + } + + RepresentativeCollection* allocate() { + size_t newAllLength = all_.length() + 1; + if (!all_.reserve(newAllLength) || !recyclable_.reserve(newAllLength)) + return nullptr; + + RepresentativeCollection* collection = js_new(); + if (collection) + all_.infallibleAppend(collection); + return collection; + } + + public: + ~CollectionPool() { + purgeAll(); + } + + 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 + Collection* acquire(ExclusiveContext* cx) { + ConcreteCollectionPool::template assertInvariants(); + + RepresentativeCollection* collection; + if (recyclable_.empty()) { + collection = allocate(); + if (!collection) + ReportOutOfMemory(cx); + } else { + collection = asRepresentative(recyclable_.popCopy()); + collection->clear(); + } + return reinterpret_cast(collection); + } + + // Release a collection back to the pool. + template + void release(Collection** collection) { + ConcreteCollectionPool::template assertInvariants(); + MOZ_ASSERT(*collection); + +#ifdef DEBUG + bool ok = false; + // Make sure the collection is in |all_| but not already in |recyclable_|. + for (void** it = all_.begin(); it != all_.end(); ++it) { + if (*it == *collection) { + ok = true; + break; + } + } + MOZ_ASSERT(ok); + for (void** it = recyclable_.begin(); it != recyclable_.end(); ++it) + MOZ_ASSERT(*it != *collection); +#endif + + MOZ_ASSERT(recyclable_.length() < all_.length()); + // Reserved in allocateFresh. + recyclable_.infallibleAppend(*collection); + *collection = nullptr; + } +}; + +template +struct RecyclableAtomMapValueWrapper +{ + 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 +using RecyclableNameMap = InlineMap, + 24, + DefaultHasher, + SystemAllocPolicy>; + +using DeclaredNameMap = RecyclableNameMap; +using CheckTDZMap = RecyclableNameMap; +using NameLocationMap = RecyclableNameMap; +using AtomIndexMap = RecyclableNameMap; + +#undef RECYCLABLE_NAME_MAP_TYPE + +template +class InlineTablePool + : public CollectionPool> +{ + public: + template + static void assertInvariants() { + static_assert(Table::SizeOfInlineEntries == RepresentativeTable::SizeOfInlineEntries, + "Only tables with the same size for inline entries are usable in the pool."); + static_assert(mozilla::IsPod::value, + "Only tables with POD values are usable in the pool."); + } +}; + +using FunctionBoxVector = Vector; + +template +class VectorPool : public CollectionPool> +{ + public: + template + static void assertInvariants() { + static_assert(Vector::sMaxInlineStorage == RepresentativeVector::sMaxInlineStorage, + "Only vectors with the same size for inline entries are usable in the pool."); + static_assert(mozilla::IsPod::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 mapPool_; + VectorPool vectorPool_; + uint32_t activeCompilations_; + + public: + NameCollectionPool() + : activeCompilations_(0) + { } + + bool hasActiveCompilation() const { + return activeCompilations_ != 0; + } + + void addActiveCompilation() { + activeCompilations_++; + } + + void removeActiveCompilation() { + MOZ_ASSERT(hasActiveCompilation()); + activeCompilations_--; + } + + template + Map* acquireMap(ExclusiveContext* cx) { + MOZ_ASSERT(hasActiveCompilation()); + return mapPool_.acquire(cx); + } + + template + void releaseMap(Map** map) { + MOZ_ASSERT(hasActiveCompilation()); + MOZ_ASSERT(map); + if (*map) + mapPool_.release(map); + } + + template + Vector* acquireVector(ExclusiveContext* cx) { + MOZ_ASSERT(hasActiveCompilation()); + return vectorPool_.acquire(cx); + } + + template + 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(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 +class PooledMapPtr +{ + POOLED_COLLECTION_PTR_METHODS(PooledMapPtr, Map) +}; + +template +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 : TrueType {}; + +template +struct IsPod> : IsPod {}; + +} // 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 000000000..ce1318f0b --- /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 "[]" 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().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().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 000000000..a04e7040e --- /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 000000000..395d09b5b --- /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 000000000..f79baba9e --- /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 || in + // SpiderMonkey's |catch (e if )| 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(); + + list->append(right); + list->pn_pos.end = right->pn_pos.end; + + return list; + } + } + + ParseNode* list = handler->new_(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, "#", + (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 +static void +DumpName(const CharT* s, size_t len) +{ + if (len == 0) + fprintf(stderr, "#"); + + 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, "#"); + } else if (getOp() == JSOP_GETARG && pn_atom->length() == 0) { + // Dump destructuring parameter. + fprintf(stderr, "(# "); + 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().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()); + MOZ_ASSERT(object->isTenured()); +} + +ObjectBox::ObjectBox(JSFunction* function, ObjectBox* traceLink) + : object(function), + traceLink(traceLink), + emitLink(nullptr) +{ + MOZ_ASSERT(object->is()); + MOZ_ASSERT(asFunctionBox()->function() == function); + MOZ_ASSERT(object->isTenured()); +} + +FunctionBox* +ObjectBox::asFunctionBox() +{ + MOZ_ASSERT(isFunctionBox()); + return static_cast(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 000000000..d37aaaae0 --- /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 + * > so that code generation can emit a left-associative branch + * around 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 + * ----- ------- ------- + * + * 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 + * + * + * 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 + * + * + * 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 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::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 + inline bool is() const { + return NodeType::test(*this); + } + + /* Casting operations. */ + template + inline NodeType& as() { + MOZ_ASSERT(NodeType::test(*this)); + return *static_cast(this); + } + + template + inline const NodeType& as() const { + MOZ_ASSERT(NodeType::test(*this)); + return *static_cast(this); + } + +#ifdef DEBUG + 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() : 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()); + MOZ_ASSERT(methodsOrBlock->is() || + 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() : nullptr; + } + ParseNode* heritage() const { + return pn_kid2; + } + ParseNode* methodList() const { + if (pn_kid3->isKind(PNK_CLASSMETHODLIST)) + return pn_kid3; + + MOZ_ASSERT(pn_kid3->is()); + ParseNode* list = pn_kid3->scopeBody(); + MOZ_ASSERT(list->isKind(PNK_CLASSMETHODLIST)); + return list; + } + Handle scopeBindings() const { + MOZ_ASSERT(pn_kid3->is()); + 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(); } + 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 000000000..49fef2bf9 --- /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 +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().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().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()) + env = &env->as().environment(); + + if (env->is()) { + computeThisBinding(env->as().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()); + 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 +bool +Parser::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 +bool +Parser::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 +bool +Parser::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 +bool +Parser::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::abortIfSyntaxParser() +{ + handler.disableSyntaxParser(); + return true; +} + +template <> +bool +Parser::abortIfSyntaxParser() +{ + abortedSyntaxParse = true; + return false; +} + +template +Parser::Parser(ExclusiveContext* cx, LifoAlloc& alloc, + const ReadOnlyCompileOptions& options, + const char16_t* chars, size_t length, + bool foldConstants, + UsedNameTracker& usedNames, + Parser* 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 +bool +Parser::checkOptions() +{ +#ifdef DEBUG + checkOptionsCalled = true; +#endif + + if (!tokenStream.checkOptions()) + return false; + + return true; +} + +template +Parser::~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 +ObjectBox* +Parser::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_(obj, traceListHead); + if (!objbox) { + ReportOutOfMemory(context); + return nullptr; + } + + traceListHead = objbox; + + return objbox; +} + +template +FunctionBox* +Parser::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_(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 +void +Parser::trace(JSTracer* trc) +{ + ObjectBox::TraceList(trc, traceListHead); +} + +void +MarkParser(JSTracer* trc, AutoGCRooter* parser) +{ + static_cast*>(parser)->trace(trc); +} + +/* + * Parse a top-level JS script. + */ +template +typename ParseHandler::Node +Parser::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 +bool +Parser::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 +bool +Parser::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 +bool +Parser::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 +bool +Parser::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 +void +Parser::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 +bool +Parser::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 +bool +Parser::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 +Maybe +Parser::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 +bool +Parser::tryDeclareVar(HandlePropertyName name, DeclarationKind kind, + Maybe* 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 +bool +Parser::tryDeclareVarForAnnexBLexicalFunction(HandlePropertyName name, + bool* tryAnnexB) +{ + Maybe 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 +bool +Parser::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 +bool +Parser::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 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 +bool +Parser::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 +bool +Parser::hasUsedName(HandlePropertyName name) +{ + if (UsedNamePtr p = usedNames.lookup(name)) + return p->value().isUsedInScript(pc->scriptId()); + return false; +} + +template +bool +Parser::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::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::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 +static typename Scope::Data* +NewEmptyBindingData(ExclusiveContext* cx, LifoAlloc& alloc, uint32_t numBindings) +{ + size_t allocSize = Scope::sizeOfData(numBindings); + typename Scope::Data* bindings = static_cast(alloc.alloc(allocSize)); + if (!bindings) { + ReportOutOfMemory(cx); + return nullptr; + } + PodZero(bindings); + return bindings; +} + +template <> +Maybe +Parser::newGlobalScopeData(ParseContext::Scope& scope) +{ + Vector funs(context); + Vector vars(context); + Vector lets(context); + Vector 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(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 +Parser::newModuleScopeData(ParseContext::Scope& scope) +{ + Vector imports(context); + Vector vars(context); + Vector lets(context); + Vector 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(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 +Parser::newEvalScopeData(ParseContext::Scope& scope) +{ + Vector funs(context); + Vector 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(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 +Parser::newFunctionScopeData(ParseContext::Scope& scope, bool hasParameterExprs) +{ + Vector positionalFormals(context); + Vector formals(context); + Vector 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(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 +Parser::newVarScopeData(ParseContext::Scope& scope) +{ + Vector 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(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 +Parser::newLexicalScopeData(ParseContext::Scope& scope) +{ + Vector lets(context); + Vector 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(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::finishLexicalScope(ParseContext::Scope& scope, Node body) +{ + if (!propagateFreeNamesAndMarkClosedOverBindings(scope)) + return null(); + return body; +} + +template <> +ParseNode* +Parser::finishLexicalScope(ParseContext::Scope& scope, ParseNode* body) +{ + if (!propagateFreeNamesAndMarkClosedOverBindings(scope)) + return nullptr; + Maybe 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()) { + // 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()) { + // It's an error to use 'arguments' in a legacy generator expression. + JSScript* script = si.scope()->as().script(); + return script->isGeneratorExp() && script->isLegacyGenerator(); + } + } + + return false; +} + +template <> +ParseNode* +Parser::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().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 bindings = newEvalScopeData(pc->varScope()); + if (!bindings) + return nullptr; + evalsc->bindings = *bindings; + + return body; +} + +template <> +ParseNode* +Parser::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 bindings = newGlobalScopeData(pc->varScope()); + if (!bindings) + return nullptr; + globalsc->bindings = *bindings; + + return body; +} + +template <> +ParseNode* +Parser::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 bindings = newModuleScopeData(modulepc.varScope()); + if (!bindings) + return nullptr; + + modulesc->bindings = *bindings; + return mn; +} + +template <> +SyntaxParseHandler::Node +Parser::moduleBody(ModuleSharedContext* modulesc) +{ + MOZ_ALWAYS_FALSE(abortIfSyntaxParser()); + return SyntaxParseHandler::NodeFailure; +} + +template +bool +Parser::hasUsedFunctionSpecialName(HandlePropertyName name) +{ + MOZ_ASSERT(name == context->names().arguments || name == context->names().dotThis); + return hasUsedName(name) || pc->functionBox()->bindingsAccessedDynamically(); +} + +template +bool +Parser::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::Node +Parser::newInternalDotName(HandlePropertyName name) +{ + Node nameNode = newName(name); + if (!nameNode) + return null(); + if (!noteUsedName(name)) + return null(); + return nameNode; +} + +template +typename ParseHandler::Node +Parser::newThisName() +{ + return newInternalDotName(context->names().dotThis); +} + +template +typename ParseHandler::Node +Parser::newDotGeneratorName() +{ + return newInternalDotName(context->names().dotGenerator); +} + +template +bool +Parser::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 +bool +Parser::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::finishFunction() +{ + if (!finishFunctionScopes()) + return false; + + FunctionBox* funbox = pc->functionBox(); + bool hasParameterExprs = funbox->hasParameterExprs; + + if (hasParameterExprs) { + Maybe bindings = newVarScopeData(pc->varScope()); + if (!bindings) + return false; + funbox->extraVarScopeBindings().set(*bindings); + } + + { + Maybe bindings = newFunctionScopeData(pc->functionScope(), + hasParameterExprs); + if (!bindings) + return false; + funbox->functionScopeBindings().set(*bindings); + } + + if (funbox->function()->isNamedLambda()) { + Maybe bindings = newLexicalScopeData(pc->namedLambdaScope()); + if (!bindings) + return false; + funbox->namedLambdaBindings().set(*bindings); + } + + return true; +} + +template <> +bool +Parser::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::standaloneFunctionBody(HandleFunction fun, + HandleScope enclosingScope, + Handle 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 +bool +Parser::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::Node +Parser::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 +JSFunction* +Parser::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 +bool +Parser::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::finishFunction. + if (!outerpc->innerFunctionsForLazy.append(pc->functionBox()->function())) + return false; + + PropagateTransitiveParseFlags(pc->functionBox(), outerpc->sc()); + + return true; +} + +template +JSAtom* +Parser::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(context, prefix, propAtom)); + if (!str) + return nullptr; + + return AtomizeString(context, str); +} + +template +bool +Parser::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 +bool +Parser::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::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 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::skipLazyInnerFunction(Node pn, FunctionSyntaxKind kind, + bool tryAnnexB) +{ + MOZ_CRASH("Cannot skip lazy inner functions when syntax parsing"); +} + +template +bool +Parser::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 +bool +Parser::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::Node +Parser::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::Node +Parser::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::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* 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::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 +bool +Parser::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 +bool +Parser::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 +bool +Parser::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::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 +bool +Parser::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 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::Node +Parser::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 synthesizedStmtForAnnexB; + Maybe 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::Node +Parser::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 +bool +Parser::checkUnescapedName() +{ + if (!tokenStream.currentToken().nameContainsEscape()) + return true; + + report(ParseError, false, null(), JSMSG_ESCAPED_KEYWORD); + return false; +} + +template <> +bool +Parser::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::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 +bool +Parser::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::Node +Parser::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::Node +Parser::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 +bool +Parser::matchLabel(YieldHandling yieldHandling, MutableHandle 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 +Parser::PossibleError::PossibleError(Parser& parser) + : parser_(parser) +{} + +template +typename Parser::PossibleError::Error& +Parser::PossibleError::error(ErrorKind kind) +{ + if (kind == ErrorKind::Expression) + return exprError_; + MOZ_ASSERT(kind == ErrorKind::Destructuring); + return destructuringError_; +} + +template +void +Parser::PossibleError::setResolved(ErrorKind kind) +{ + error(kind).state_ = ErrorState::None; +} + +template +bool +Parser::PossibleError::hasError(ErrorKind kind) +{ + return error(kind).state_ == ErrorState::Pending; +} + +template +void +Parser::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 +void +Parser::PossibleError::setPendingDestructuringError(Node pn, unsigned errorNumber) +{ + setPending(ErrorKind::Destructuring, pn, errorNumber); +} + +template +void +Parser::PossibleError::setPendingExpressionError(Node pn, unsigned errorNumber) +{ + setPending(ErrorKind::Expression, pn, errorNumber); +} + +template +bool +Parser::PossibleError::checkForError(ErrorKind kind) +{ + if (!hasError(kind)) + return true; + + Error& err = error(kind); + parser_.reportWithOffset(ParseError, false, err.offset_, err.errorNumber_); + return false; +} + +template +bool +Parser::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 +bool +Parser::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 +void +Parser::PossibleError::transferErrorTo(ErrorKind kind, PossibleError* other) +{ + if (hasError(kind) && !other->hasError(kind)) { + Error& err = error(kind); + Error& otherErr = other->error(kind); + otherErr.offset_ = err.offset_; + otherErr.errorNumber_ = err.errorNumber_; + otherErr.state_ = err.state_; + } +} + +template +void +Parser::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 +bool +Parser::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::checkDestructuringName(ParseNode* expr, Maybe 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::checkDestructuringPattern(ParseNode* pattern, + Maybe maybeDecl, + PossibleError* possibleError /* = nullptr */); + +template <> +bool +Parser::checkDestructuringObject(ParseNode* objectPattern, + Maybe 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::checkDestructuringArray(ParseNode* arrayPattern, + Maybe 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::checkDestructuringPattern(ParseNode* pattern, + Maybe 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::checkDestructuringPattern(Node pattern, + Maybe maybeDecl, + PossibleError* possibleError /* = nullptr */) +{ + return abortIfSyntaxParser(); +} + +template +typename ParseHandler::Node +Parser::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::Node +Parser::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::Node +Parser::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::Node +Parser::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::Node +Parser::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 +bool +Parser::initializerInNameDeclaration(Node decl, Node binding, + Handle 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::Node +Parser::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::Node +Parser::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::Node +Parser::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::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 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::namedImportsOrNamespaceImport(TokenKind tt, Node importSpecSet) +{ + MOZ_ALWAYS_FALSE(abortIfSyntaxParser()); + return false; +} + +template<> +ParseNode* +Parser::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::importDeclaration() +{ + JS_ALWAYS_FALSE(abortIfSyntaxParser()); + return SyntaxParseHandler::NodeFailure; +} + +template<> +bool +Parser::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::checkExportedName(JSAtom* exportName) +{ + MOZ_ALWAYS_FALSE(abortIfSyntaxParser()); + return false; +} + +template<> +bool +Parser::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::checkExportedNamesForDeclaration(Node node) +{ + MOZ_ALWAYS_FALSE(abortIfSyntaxParser()); + return false; +} + +template<> +ParseNode* +Parser::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(); + 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::exportDeclaration() +{ + JS_ALWAYS_FALSE(abortIfSyntaxParser()); + return SyntaxParseHandler::NodeFailure; +} + +template +typename ParseHandler::Node +Parser::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 +typename ParseHandler::Node +Parser::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::Node +Parser::ifStatement(YieldHandling yieldHandling) +{ + Vector condList(context), thenList(context); + Vector 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::Node +Parser::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::Node +Parser::whileStatement(YieldHandling yieldHandling) +{ + uint32_t begin = pos().begin; + ParseContext::Statement stmt(pc, StatementKind::WhileLoop); + Node cond = condition(InAllowed, yieldHandling); + if (!cond) + return null(); + Node body = statement(yieldHandling); + if (!body) + return null(); + return handler.newWhileStatement(begin, cond, body); +} + +template +bool +Parser::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 +bool +Parser::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 +bool +Parser::forHeadStart(YieldHandling yieldHandling, + ParseNodeKind* forHeadKind, + Node* forInitialPart, + Maybe& 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 +typename ParseHandler::Node +Parser::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 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::Node +Parser::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::Node +Parser::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()) { + if (stmt->as().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::Node +Parser::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(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::Node +Parser::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::Node +Parser::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::Node +Parser::newAwaitExpression(uint32_t begin, typename ParseHandler::Node expr) +{ + Node generator = newDotGeneratorName(); + if (!generator) + return null(); + return handler.newAwaitExpression(begin, expr, generator); +} + +template +typename ParseHandler::Node +Parser::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::Node +Parser::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::Node +Parser::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::Node +Parser::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(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::Node +Parser::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::Node +Parser::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 ) + * 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::Node +Parser::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::Node +Parser::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::Node +Parser::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 classStmt; + Maybe 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 +bool +Parser::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::Node +Parser::variableStatement(YieldHandling yieldHandling) +{ + Node vars = declarationList(yieldHandling, PNK_VAR); + if (!vars) + return null(); + if (!MatchOrInsertSemicolonAfterExpression(tokenStream)) + return null(); + return vars; +} + +template +typename ParseHandler::Node +Parser::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::Node +Parser::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::Node +Parser::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 +MOZ_ALWAYS_INLINE typename ParseHandler::Node +Parser::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 +MOZ_ALWAYS_INLINE typename ParseHandler::Node +Parser::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 +bool +Parser::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 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::Node +Parser::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 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 +bool +Parser::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 +bool +Parser::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 +bool +Parser::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 +bool +Parser::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::Node +Parser::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::Node +Parser::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::Node +Parser::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::Node +Parser::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::Node +Parser::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::Node +Parser::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::Node +Parser::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::Node +Parser::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::Node +Parser::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::Node +Parser::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 +bool +Parser::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 +bool +Parser::checkAndMarkSuperScope() +{ + if (!pc->sc()->allowSuperProperty()) + return false; + pc->setSuperScopeNeedsHomeObject(); + return true; +} + +template +typename ParseHandler::Node +Parser::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::Node +Parser::newName(PropertyName* name) +{ + return newName(name, pos()); +} + +template +typename ParseHandler::Node +Parser::newName(PropertyName* name, TokenPos pos) +{ + return handler.newName(name, pos, context); +} + +template +PropertyName* +Parser::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 +PropertyName* +Parser::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::Node +Parser::identifierReference(Handle name) +{ + Node pn = newName(name); + if (!pn) + return null(); + + if (!pc->inDestructuringDecl && !noteUsedName(name)) + return null(); + + return pn; +} + +template +typename ParseHandler::Node +Parser::stringLiteral() +{ + return handler.newStringLiteral(stopStringCompression(), pos()); +} + +template +typename ParseHandler::Node +Parser::noSubstitutionTemplate() +{ + return handler.newTemplateStringLiteral(stopStringCompression(), pos()); +} + +template +JSAtom * Parser::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::Node +Parser::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 reobj(context); + reobj = RegExpObject::create(context, chars, length, flags, &tokenStream, alloc); + if (!reobj) + return null(); + + return handler.newRegExp(reobj, pos(), *this); +} + +template +typename ParseHandler::Node +Parser::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(cx, HandleValue::fromMarkedLocation(&tmp)); +} + +template +typename ParseHandler::Node +Parser::propertyName(YieldHandling yieldHandling, Node propList, + PropertyType* propType, MutableHandleAtom propAtom) +{ + TokenKind ltok; + if (!tokenStream.getToken(<ok, 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(<ok, 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::Node +Parser::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::Node +Parser::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 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 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::Node +Parser::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 +bool +Parser::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::Node +Parser::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 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::Node +Parser::exprInParens(InHandling inHandling, YieldHandling yieldHandling, + TripledotHandling tripledotHandling, + PossibleError* possibleError /* = nullptr */) +{ + MOZ_ASSERT(tokenStream.isCurrentTokenType(TOK_LP)); + return expr(inHandling, yieldHandling, tripledotHandling, possibleError, PredictInvoked); +} + +template +void +Parser::addTelemetry(JSCompartment::DeprecatedLanguageExtension e) +{ + JSContext* cx = context->maybeJSContext(); + if (!cx) + return; + cx->compartment()->addTelemetry(getFilename(), e); +} + +template +bool +Parser::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 +bool +Parser::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; +template class Parser; + +} /* namespace frontend */ +} /* namespace js */ diff --git a/js/src/frontend/Parser.h b/js/src/frontend/Parser.h new file mode 100644 index 000000000..0ad4d56a0 --- /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 +{ + public: + // The intra-function statement stack. + // + // Used for early error checking that depend on the nesting structure of + // statements, such as continue/break targets, labels, and unbraced + // lexical declarations. + class Statement : public Nestable + { + StatementKind kind_; + + public: + using Nestable::enclosing; + using Nestable::findNearest; + + Statement(ParseContext* pc, StatementKind kind) + : Nestable(&pc->innermostStatement_), + kind_(kind) + { } + + template inline bool is() const; + template inline T& as(); + + StatementKind kind() const { + return kind_; + } + + void refineForKind(StatementKind newForKind) { + MOZ_ASSERT(kind_ == StatementKind::ForLoop); + MOZ_ASSERT(newForKind == StatementKind::ForInLoop || + newForKind == StatementKind::ForOfLoop); + kind_ = newForKind; + } + }; + + class LabelStatement : public Statement + { + 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 + { + // Names declared in this scope. Corresponds to the union of + // VarDeclaredNames and LexicallyDeclaredNames in the ES spec. + // + // A 'var' declared name is a member of the declared name set of every + // scope in its scope contour. + // + // A lexically declared name is a member only of the declared name set of + // the scope in which it is declared. + PooledMapPtr declared_; + + // 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::enclosing; + + template + explicit Scope(Parser* parser) + : Nestable(&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 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 + explicit VarScope(Parser* 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 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 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 innerFunctionBoxesForAnnexB_; + + // Simple formal parameter names, in order of appearance. Only used when + // isFunctionBox(). + PooledVectorPtr positionalFormalParameterNames_; + + // Closed over binding names, in order of appearance. Null-delimited + // between scopes. Only used when syntax parsing. + PooledVectorPtr closedOverBindingsForLazy_; + + // 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> 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 inDestructuringDecl; + + // Set when parsing a function and it has 'return ;' + bool funHasReturnExpr; + + // Set when parsing a function and it has 'return;' + bool funHasReturnVoid; + + public: + template + ParseContext(Parser* prs, SharedContext* sc, Directives* newDirectives) + : Nestable(&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(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 bool */> + Statement* findInnermostStatement(Predicate predicate) { + return Statement::findNearest(innermostStatement_, predicate); + } + + template bool */> + T* findInnermostStatement(Predicate predicate) { + return Statement::findNearest(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() const +{ + return kind_ == StatementKind::Label; +} + +template +inline T& +ParseContext::Statement::as() +{ + MOZ_ASSERT(is()); + return static_cast(*this); +} + +inline ParseContext::Scope::BindingIter +ParseContext::Scope::bindings(ParseContext* pc) +{ + // In function scopes with parameter expressions, function special names + // (like '.this') are declared as vars in the function scope, despite its + // not being the var scope. + return BindingIter(*this, pc->varScope_ == this || pc->functionScope_.ptrOr(nullptr) == this); +} + +inline +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 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>; + + 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 +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& 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& 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* 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 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& 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 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 name); + + bool matchLabel(YieldHandling yieldHandling, MutableHandle 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 isVarRedeclaredInEval(HandlePropertyName name, + DeclarationKind kind); + bool tryDeclareVar(HandlePropertyName name, DeclarationKind kind, + mozilla::Maybe* 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 newGlobalScopeData(ParseContext::Scope& scope); + mozilla::Maybe newModuleScopeData(ParseContext::Scope& scope); + mozilla::Maybe newEvalScopeData(ParseContext::Scope& scope); + mozilla::Maybe newFunctionScopeData(ParseContext::Scope& scope, + bool hasParameterExprs); + mozilla::Maybe newVarScopeData(ParseContext::Scope& scope); + mozilla::Maybe newLexicalScopeData(ParseContext::Scope& scope); + 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 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 maybeDecl); + bool checkDestructuringObject(Node objectPattern, mozilla::Maybe maybeDecl); + bool checkDestructuringName(Node expr, mozilla::Maybe 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 000000000..39df47c20 --- /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 +class MOZ_STACK_CLASS Nestable +{ + Concrete** stack_; + Concrete* enclosing_; + + protected: + explicit Nestable(Concrete** stack) + : stack_(stack), + enclosing_(*stack) + { + *stack_ = static_cast(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 bool */> + static Concrete* findNearest(Concrete* it, Predicate predicate) { + while (it && !predicate(it)) + it = it->enclosing(); + return it; + } + + template + static T* findNearest(Concrete* it) { + while (it && !it->template is()) + it = it->enclosing(); + return it ? &it->template as() : nullptr; + } + + template bool */> + static T* findNearest(Concrete* it, Predicate predicate) { + while (it && (!it->template is() || !predicate(&it->template as()))) + it = it->enclosing(); + return it ? &it->template as() : nullptr; + } + + public: + ~Nestable() { + MOZ_ASSERT(*stack_ == static_cast(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 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(this); +} + +class MOZ_STACK_CLASS EvalSharedContext : public SharedContext +{ + RootedScope enclosingScope_; + + public: + Rooted 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(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 namedLambdaBindings() { + MOZ_ASSERT(context->compartment()->runtimeFromAnyThread()->keepAtoms()); + return MutableHandle::fromMarkedLocation(&namedLambdaBindings_); + } + + MutableHandle functionScopeBindings() { + MOZ_ASSERT(context->compartment()->runtimeFromAnyThread()->keepAtoms()); + return MutableHandle::fromMarkedLocation(&functionScopeBindings_); + } + + MutableHandle extraVarScopeBindings() { + MOZ_ASSERT(context->compartment()->runtimeFromAnyThread()->keepAtoms()); + return MutableHandle::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(); } + + 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(this); +} + +class MOZ_STACK_CLASS ModuleSharedContext : public SharedContext +{ + RootedModuleObject module_; + RootedScope enclosingScope_; + + public: + Rooted 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(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 000000000..dd2a95ad1 --- /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 + +#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 000000000..75c7e3333 --- /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 +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* 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 + 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 000000000..6f22d78e5 --- /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(, ) + * + * or + * + * range(, ) + * + * where ; + * is a legal C identifier of the token, that will be used in + * the JS engine source, with `TOK_` prefix. + * + * is a string that describe about the token, and will be used in + * error message. + * + * is a legal C identifier of the range that will be used to + * JS engine source, 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 , + * should be same as one of in other `macro`s. + * + * To use this macro, define two macros for `macro` and `range`, and pass them + * as arguments. + * + * #define EMIT_TOKEN(name, desc) ... + * #define EMIT_RANGE(name, value) ... + * FOR_EACH_TOKEN_KIND_WITH_RANGE(EMIT_TOKEN, EMIT_RANGE) + * #undef EMIT_TOKEN + * #undef EMIT_RANGE + * + * If you don't need range data, use FOR_EACH_TOKEN_KIND instead. + * + * #define EMIT_TOKEN(name, desc) ... + * FOR_EACH_TOKEN_KIND(EMIT_TOKEN) + * #undef EMIT_TOKEN + * + * Note that this list does not contain ERROR and LIMIT. + */ +#define FOR_EACH_TOKEN_KIND_WITH_RANGE(macro, range) \ + macro(EOF, "end of script") \ + \ + /* only returned by peekTokenSameLine() */ \ + macro(EOL, "line terminator") \ + \ + macro(SEMI, "';'") \ + macro(COMMA, "','") \ + macro(HOOK, "'?'") /* conditional */ \ + macro(COLON, "':'") /* conditional */ \ + macro(INC, "'++'") /* increment */ \ + macro(DEC, "'--'") /* decrement */ \ + macro(DOT, "'.'") /* member operator */ \ + macro(TRIPLEDOT, "'...'") /* rest arguments and spread operator */ \ + macro(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 000000000..c166ed414 --- /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 +#include +#include +#include + +#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 +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 +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::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(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=" or + // "/\* //# sourceURL= *\/" + // + // Note that while these are labeled "sourceURL" in the source text, + // internally we refer to it as a "displayURL" to distinguish what the + // developer would like to refer to the source as from the source's actual + // URL. + + return getDirective(isMultiline, shouldWarnDeprecated, " sourceURL=", 11, + "sourceURL", &displayURL_); +} + +bool +TokenStream::getSourceMappingURL(bool isMultiline, bool shouldWarnDeprecated) +{ + // Match comments of the form "//# sourceMappingURL=" or + // "/\* //# sourceMappingURL= *\/" + + 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 ""; +} + +#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 ""; +} +#endif diff --git a/js/src/frontend/TokenStream.h b/js/src/frontend/TokenStream.h new file mode 100644 index 000000000..29dcead62 --- /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 +#include +#include + +#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 |. 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 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 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 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* 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 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 */ -- cgit v1.2.3