diff options
Diffstat (limited to 'js/src/frontend')
-rw-r--r-- | js/src/frontend/BytecodeCompiler.cpp | 152 | ||||
-rw-r--r-- | js/src/frontend/BytecodeCompiler.h | 41 | ||||
-rw-r--r-- | js/src/frontend/BytecodeEmitter.cpp | 2180 | ||||
-rw-r--r-- | js/src/frontend/BytecodeEmitter.h | 54 | ||||
-rw-r--r-- | js/src/frontend/FullParseHandler.h | 7 | ||||
-rw-r--r-- | js/src/frontend/ParseNode-inl.h | 2 | ||||
-rw-r--r-- | js/src/frontend/ParseNode.cpp | 18 | ||||
-rw-r--r-- | js/src/frontend/ParseNode.h | 19 | ||||
-rw-r--r-- | js/src/frontend/Parser.cpp | 100 | ||||
-rw-r--r-- | js/src/frontend/Parser.h | 16 | ||||
-rw-r--r-- | js/src/frontend/SharedContext.h | 8 | ||||
-rw-r--r-- | js/src/frontend/SyntaxParseHandler.h | 3 | ||||
-rw-r--r-- | js/src/frontend/TokenStream.cpp | 149 | ||||
-rw-r--r-- | js/src/frontend/TokenStream.h | 1 |
14 files changed, 1944 insertions, 806 deletions
diff --git a/js/src/frontend/BytecodeCompiler.cpp b/js/src/frontend/BytecodeCompiler.cpp index d4c758b6c..76afe80b1 100644 --- a/js/src/frontend/BytecodeCompiler.cpp +++ b/js/src/frontend/BytecodeCompiler.cpp @@ -7,6 +7,7 @@ #include "frontend/BytecodeCompiler.h" #include "mozilla/IntegerPrintfMacros.h" +#include "mozilla/Maybe.h" #include "jscntxt.h" #include "jsscript.h" @@ -28,6 +29,7 @@ using namespace js; using namespace js::frontend; using mozilla::Maybe; +using mozilla::Nothing; class MOZ_STACK_CLASS AutoCompilationTraceLogger { @@ -57,24 +59,24 @@ class MOZ_STACK_CLASS BytecodeCompiler // Call setters for optional arguments. void maybeSetSourceCompressor(SourceCompressionTask* sourceCompressor); - void setSourceArgumentsNotIncluded(); JSScript* compileGlobalScript(ScopeKind scopeKind); JSScript* compileEvalScript(HandleObject environment, HandleScope enclosingScope); ModuleObject* compileModule(); - bool compileFunctionBody(MutableHandleFunction fun, Handle<PropertyNameVector> formals, - GeneratorKind generatorKind, FunctionAsyncKind asyncKind); + bool compileStandaloneFunction(MutableHandleFunction fun, GeneratorKind generatorKind, + FunctionAsyncKind asyncKind, + Maybe<uint32_t> parameterListEnd); ScriptSourceObject* sourceObjectPtr() const; private: JSScript* compileScript(HandleObject environment, SharedContext* sc); bool checkLength(); - bool createScriptSource(); + bool createScriptSource(Maybe<uint32_t> parameterListEnd); bool maybeCompressSource(); bool canLazilyParse(); bool createParser(); - bool createSourceAndParser(); + bool createSourceAndParser(Maybe<uint32_t> parameterListEnd = Nothing()); bool createScript(); bool emplaceEmitter(Maybe<BytecodeEmitter>& emitter, SharedContext* sharedContext); bool handleParseFailure(const Directives& newDirectives); @@ -90,7 +92,6 @@ class MOZ_STACK_CLASS BytecodeCompiler SourceBufferHolder& sourceBuffer; RootedScope enclosingScope; - bool sourceArgumentsNotIncluded; RootedScriptSource sourceObject; ScriptSource* scriptSource; @@ -130,7 +131,6 @@ BytecodeCompiler::BytecodeCompiler(ExclusiveContext* cx, options(options), sourceBuffer(sourceBuffer), enclosingScope(cx, enclosingScope), - sourceArgumentsNotIncluded(false), sourceObject(cx), scriptSource(nullptr), sourceCompressor(nullptr), @@ -147,12 +147,6 @@ BytecodeCompiler::maybeSetSourceCompressor(SourceCompressionTask* sourceCompress this->sourceCompressor = sourceCompressor; } -void -BytecodeCompiler::setSourceArgumentsNotIncluded() -{ - sourceArgumentsNotIncluded = true; -} - bool BytecodeCompiler::checkLength() { @@ -169,12 +163,12 @@ BytecodeCompiler::checkLength() } bool -BytecodeCompiler::createScriptSource() +BytecodeCompiler::createScriptSource(Maybe<uint32_t> parameterListEnd) { if (!checkLength()) return false; - sourceObject = CreateScriptSourceObject(cx, options); + sourceObject = CreateScriptSourceObject(cx, options, parameterListEnd); if (!sourceObject) return false; @@ -193,9 +187,7 @@ BytecodeCompiler::maybeCompressSource() if (!cx->compartment()->behaviors().discardSource()) { if (options.sourceIsLazy) { scriptSource->setSourceRetrievable(); - } else if (!scriptSource->setSourceCopy(cx, sourceBuffer, sourceArgumentsNotIncluded, - sourceCompressor)) - { + } else if (!scriptSource->setSourceCopy(cx, sourceBuffer, sourceCompressor)) { return false; } } @@ -242,9 +234,9 @@ BytecodeCompiler::createParser() } bool -BytecodeCompiler::createSourceAndParser() +BytecodeCompiler::createSourceAndParser(Maybe<uint32_t> parameterListEnd /* = Nothing() */) { - return createScriptSource() && + return createScriptSource(parameterListEnd) && maybeCompressSource() && createParser(); } @@ -344,10 +336,10 @@ BytecodeCompiler::compileScript(HandleObject environment, SharedContext* sc) if (!deoptimizeArgumentsInEnclosingScripts(cx->asJSContext(), environment)) return nullptr; } - if (!NameFunctions(cx, pn)) - return nullptr; if (!emitter->emitScript(pn)) return nullptr; + if (!NameFunctions(cx, pn)) + return nullptr; parser->handler.freeTree(pn); break; @@ -405,15 +397,15 @@ BytecodeCompiler::compileModule() if (!pn) return nullptr; - if (!NameFunctions(cx, pn)) - return nullptr; - Maybe<BytecodeEmitter> emitter; if (!emplaceEmitter(emitter, &modulesc)) return nullptr; if (!emitter->emitScript(pn->pn_body)) return nullptr; + if (!NameFunctions(cx, pn)) + return nullptr; + parser->handler.freeTree(pn); if (!builder.initModule()) @@ -432,18 +424,19 @@ BytecodeCompiler::compileModule() return module; } +// Compile a standalone JS function, which might appear as the value of an +// event handler attribute in an HTML <INPUT> tag, or in a Function() +// constructor. bool -BytecodeCompiler::compileFunctionBody(MutableHandleFunction fun, - Handle<PropertyNameVector> formals, - GeneratorKind generatorKind, - FunctionAsyncKind asyncKind) +BytecodeCompiler::compileStandaloneFunction(MutableHandleFunction fun, + GeneratorKind generatorKind, + FunctionAsyncKind asyncKind, + Maybe<uint32_t> parameterListEnd) { MOZ_ASSERT(fun); MOZ_ASSERT(fun->isTenured()); - fun->setArgCount(formals.length()); - - if (!createSourceAndParser()) + if (!createSourceAndParser(parameterListEnd)) return false; // Speculatively parse using the default directives implied by the context. @@ -454,15 +447,12 @@ BytecodeCompiler::compileFunctionBody(MutableHandleFunction fun, ParseNode* fn; do { Directives newDirectives = directives; - fn = parser->standaloneFunctionBody(fun, enclosingScope, formals, generatorKind, asyncKind, - directives, &newDirectives); + fn = parser->standaloneFunction(fun, enclosingScope, parameterListEnd, 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()); @@ -479,6 +469,9 @@ BytecodeCompiler::compileFunctionBody(MutableHandleFunction fun, MOZ_ASSERT(IsAsmJSModule(fun)); } + if (!NameFunctions(cx, fn)) + return false; + if (!maybeCompleteCompressSource()) return false; @@ -492,14 +485,15 @@ BytecodeCompiler::sourceObjectPtr() const } ScriptSourceObject* -frontend::CreateScriptSourceObject(ExclusiveContext* cx, const ReadOnlyCompileOptions& options) +frontend::CreateScriptSourceObject(ExclusiveContext* cx, const ReadOnlyCompileOptions& options, + Maybe<uint32_t> parameterListEnd /* = Nothing() */) { ScriptSource* ss = cx->new_<ScriptSource>(); if (!ss) return nullptr; ScriptSourceHolder ssHolder(ss); - if (!ss->initFromOptions(cx, options)) + if (!ss->initFromOptions(cx, options, parameterListEnd)) return nullptr; RootedScriptSource sso(cx, ScriptSourceObject::create(cx, ss)); @@ -652,9 +646,6 @@ frontend::CompileLazyFunction(JSContext* cx, Handle<LazyScript*> lazy, const cha if (!pn) return false; - if (!NameFunctions(cx, pn)) - return false; - RootedScriptSource sourceObject(cx, lazy->sourceObject()); MOZ_ASSERT(sourceObject); @@ -673,66 +664,53 @@ frontend::CompileLazyFunction(JSContext* cx, Handle<LazyScript*> lazy, const cha if (!bce.init()) return false; - return bce.emitFunctionScript(pn->pn_body); -} - -// Compile a JS function body, which might appear as the value of an event -// handler attribute in an HTML <INPUT> tag, or in a Function() constructor. -static bool -CompileFunctionBody(JSContext* cx, MutableHandleFunction fun, const ReadOnlyCompileOptions& options, - Handle<PropertyNameVector> formals, SourceBufferHolder& srcBuf, - HandleScope enclosingScope, GeneratorKind generatorKind, - FunctionAsyncKind asyncKind) -{ - MOZ_ASSERT(!options.isRunOnce); + if (!bce.emitFunctionScript(pn->pn_body)) + return false; - // FIXME: make Function pass in two strings and parse them as arguments and - // ProgramElements respectively. + if (!NameFunctions(cx, pn)) + return false; - BytecodeCompiler compiler(cx, cx->tempLifoAlloc(), options, srcBuf, enclosingScope, - TraceLogger_ParserCompileFunction); - compiler.setSourceArgumentsNotIncluded(); - return compiler.compileFunctionBody(fun, formals, generatorKind, asyncKind); + return true; } bool -frontend::CompileFunctionBody(JSContext* cx, MutableHandleFunction fun, - const ReadOnlyCompileOptions& options, - Handle<PropertyNameVector> formals, JS::SourceBufferHolder& srcBuf, - HandleScope enclosingScope) +frontend::CompileStandaloneFunction(JSContext* cx, MutableHandleFunction fun, + const ReadOnlyCompileOptions& options, + JS::SourceBufferHolder& srcBuf, + Maybe<uint32_t> parameterListEnd, + HandleScope enclosingScope /* = nullptr */) { - return CompileFunctionBody(cx, fun, options, formals, srcBuf, enclosingScope, NotGenerator, - SyncFunction); -} + RootedScope scope(cx, enclosingScope); + if (!scope) + scope = &cx->global()->emptyGlobalScope(); -bool -frontend::CompileFunctionBody(JSContext* cx, MutableHandleFunction fun, - const ReadOnlyCompileOptions& options, - Handle<PropertyNameVector> formals, JS::SourceBufferHolder& srcBuf) -{ - RootedScope emptyGlobalScope(cx, &cx->global()->emptyGlobalScope()); - return CompileFunctionBody(cx, fun, options, formals, srcBuf, emptyGlobalScope, - NotGenerator, SyncFunction); + BytecodeCompiler compiler(cx, cx->tempLifoAlloc(), options, srcBuf, scope, + TraceLogger_ParserCompileFunction); + return compiler.compileStandaloneFunction(fun, NotGenerator, SyncFunction, parameterListEnd); } bool -frontend::CompileStarGeneratorBody(JSContext* cx, MutableHandleFunction fun, - const ReadOnlyCompileOptions& options, - Handle<PropertyNameVector> formals, - JS::SourceBufferHolder& srcBuf) +frontend::CompileStandaloneGenerator(JSContext* cx, MutableHandleFunction fun, + const ReadOnlyCompileOptions& options, + JS::SourceBufferHolder& srcBuf, + Maybe<uint32_t> parameterListEnd) { RootedScope emptyGlobalScope(cx, &cx->global()->emptyGlobalScope()); - return CompileFunctionBody(cx, fun, options, formals, srcBuf, emptyGlobalScope, - StarGenerator, SyncFunction); + + BytecodeCompiler compiler(cx, cx->tempLifoAlloc(), options, srcBuf, emptyGlobalScope, + TraceLogger_ParserCompileFunction); + return compiler.compileStandaloneFunction(fun, StarGenerator, SyncFunction, parameterListEnd); } bool -frontend::CompileAsyncFunctionBody(JSContext* cx, MutableHandleFunction fun, - const ReadOnlyCompileOptions& options, - Handle<PropertyNameVector> formals, - JS::SourceBufferHolder& srcBuf) +frontend::CompileStandaloneAsyncFunction(JSContext* cx, MutableHandleFunction fun, + const ReadOnlyCompileOptions& options, + JS::SourceBufferHolder& srcBuf, + Maybe<uint32_t> parameterListEnd) { RootedScope emptyGlobalScope(cx, &cx->global()->emptyGlobalScope()); - return CompileFunctionBody(cx, fun, options, formals, srcBuf, emptyGlobalScope, - StarGenerator, AsyncFunction); + + BytecodeCompiler compiler(cx, cx->tempLifoAlloc(), options, srcBuf, emptyGlobalScope, + TraceLogger_ParserCompileFunction); + return compiler.compileStandaloneFunction(fun, StarGenerator, AsyncFunction, parameterListEnd); } diff --git a/js/src/frontend/BytecodeCompiler.h b/js/src/frontend/BytecodeCompiler.h index 1d86f1160..72e967639 100644 --- a/js/src/frontend/BytecodeCompiler.h +++ b/js/src/frontend/BytecodeCompiler.h @@ -7,6 +7,8 @@ #ifndef frontend_BytecodeCompiler_h #define frontend_BytecodeCompiler_h +#include "mozilla/Maybe.h" + #include "NamespaceImports.h" #include "vm/Scope.h" @@ -51,22 +53,36 @@ CompileModule(ExclusiveContext* cx, const ReadOnlyCompileOptions& options, MOZ_MUST_USE bool CompileLazyFunction(JSContext* cx, Handle<LazyScript*> lazy, const char16_t* chars, size_t length); +// +// Compile a single function. The source in srcBuf must match the ECMA-262 +// FunctionExpression production. +// +// If nonzero, parameterListEnd is the offset within srcBuf where the parameter +// list is expected to end. During parsing, if we find that it ends anywhere +// else, it's a SyntaxError. This is used to implement the Function constructor; +// it's how we detect that these weird cases are SyntaxErrors: +// +// Function("/*", "*/x) {") +// Function("x){ if (3", "return x;}") +// MOZ_MUST_USE bool -CompileFunctionBody(JSContext* cx, MutableHandleFunction fun, - const ReadOnlyCompileOptions& options, - Handle<PropertyNameVector> formals, JS::SourceBufferHolder& srcBuf, - HandleScope enclosingScope); +CompileStandaloneFunction(JSContext* cx, MutableHandleFunction fun, + const ReadOnlyCompileOptions& options, + JS::SourceBufferHolder& srcBuf, + mozilla::Maybe<uint32_t> parameterListEnd, + HandleScope enclosingScope = nullptr); -// As above, but defaults to the global lexical scope as the enclosing scope. MOZ_MUST_USE bool -CompileFunctionBody(JSContext* cx, MutableHandleFunction fun, - const ReadOnlyCompileOptions& options, - Handle<PropertyNameVector> formals, JS::SourceBufferHolder& srcBuf); +CompileStandaloneGenerator(JSContext* cx, MutableHandleFunction fun, + const ReadOnlyCompileOptions& options, + JS::SourceBufferHolder& srcBuf, + mozilla::Maybe<uint32_t> parameterListEnd); MOZ_MUST_USE bool -CompileStarGeneratorBody(JSContext* cx, MutableHandleFunction fun, - const ReadOnlyCompileOptions& options, - Handle<PropertyNameVector> formals, JS::SourceBufferHolder& srcBuf); +CompileStandaloneAsyncFunction(JSContext* cx, MutableHandleFunction fun, + const ReadOnlyCompileOptions& options, + JS::SourceBufferHolder& srcBuf, + mozilla::Maybe<uint32_t> parameterListEnd); MOZ_MUST_USE bool CompileAsyncFunctionBody(JSContext* cx, MutableHandleFunction fun, @@ -74,7 +90,8 @@ CompileAsyncFunctionBody(JSContext* cx, MutableHandleFunction fun, Handle<PropertyNameVector> formals, JS::SourceBufferHolder& srcBuf); ScriptSourceObject* -CreateScriptSourceObject(ExclusiveContext* cx, const ReadOnlyCompileOptions& options); +CreateScriptSourceObject(ExclusiveContext* cx, const ReadOnlyCompileOptions& options, + mozilla::Maybe<uint32_t> parameterListEnd = mozilla::Nothing()); /* * True if str consists of an IdentifierStart character, followed by one or diff --git a/js/src/frontend/BytecodeEmitter.cpp b/js/src/frontend/BytecodeEmitter.cpp index 1e9d8f224..c7c615ccf 100644 --- a/js/src/frontend/BytecodeEmitter.cpp +++ b/js/src/frontend/BytecodeEmitter.cpp @@ -58,6 +58,7 @@ using mozilla::Some; class BreakableControl; class LabelControl; class LoopControl; +class ForOfLoopControl; class TryFinallyControl; static bool @@ -152,6 +153,13 @@ BytecodeEmitter::NestableControl::is<LoopControl>() const template <> bool +BytecodeEmitter::NestableControl::is<ForOfLoopControl>() const +{ + return kind_ == StatementKind::ForOfLoop; +} + +template <> +bool BytecodeEmitter::NestableControl::is<TryFinallyControl>() const { return kind_ == StatementKind::Try || kind_ == StatementKind::Finally; @@ -236,9 +244,9 @@ class LoopControl : public BreakableControl loopDepth_ = enclosingLoop ? enclosingLoop->loopDepth_ + 1 : 1; int loopSlots; - if (loopKind == StatementKind::Spread) + if (loopKind == StatementKind::Spread || loopKind == StatementKind::ForOfLoop) loopSlots = 3; - else if (loopKind == StatementKind::ForInLoop || loopKind == StatementKind::ForOfLoop) + else if (loopKind == StatementKind::ForInLoop) loopSlots = 2; else loopSlots = 0; @@ -1053,7 +1061,7 @@ BytecodeEmitter::EmitterScope::enterFunction(BytecodeEmitter* bce, FunctionBox* if (p) { MOZ_ASSERT(bi.kind() == BindingKind::FormalParameter); MOZ_ASSERT(!funbox->hasDestructuringArgs); - MOZ_ASSERT(!funbox->function()->hasRest()); + MOZ_ASSERT(!funbox->hasRest()); p->value() = loc; continue; } @@ -1497,6 +1505,659 @@ BytecodeEmitter::TDZCheckCache::noteTDZCheck(BytecodeEmitter* bce, JSAtom* name, return true; } +class MOZ_STACK_CLASS TryEmitter +{ + public: + enum Kind { + TryCatch, + TryCatchFinally, + TryFinally + }; + enum ShouldUseRetVal { + UseRetVal, + DontUseRetVal + }; + enum ShouldUseControl { + UseControl, + DontUseControl, + }; + + private: + BytecodeEmitter* bce_; + Kind kind_; + ShouldUseRetVal retValKind_; + + // 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. + // + // If ShouldUseControl is DontUseControl, all that handling is skipped. + // DontUseControl is used by yield* and the internal try-catch around + // IteratorClose. These internal uses must: + // * have only one catch block + // * have no catch guard + // * have JSOP_GOTO at the end of catch-block + // * have no non-local-jump + // * don't use finally block for normal completion of try-block and + // catch-block + // + // Additionally, a finally block may be emitted when ShouldUseControl is + // DontUseControl, even if the kind is not TryCatchFinally or TryFinally, + // because GOSUBs are not emitted. This internal use shares the + // requirements as above. + Maybe<TryFinallyControl> controlInfo_; + + int depth_; + unsigned noteIndex_; + ptrdiff_t tryStart_; + JumpList catchAndFinallyJump_; + JumpTarget tryEnd_; + JumpTarget finallyStart_; + + enum State { + Start, + Try, + TryEnd, + Catch, + CatchEnd, + Finally, + FinallyEnd, + End + }; + State state_; + + bool hasCatch() const { + return kind_ == TryCatch || kind_ == TryCatchFinally; + } + bool hasFinally() const { + return kind_ == TryCatchFinally || kind_ == TryFinally; + } + + public: + TryEmitter(BytecodeEmitter* bce, Kind kind, ShouldUseRetVal retValKind = UseRetVal, + ShouldUseControl controlKind = UseControl) + : bce_(bce), + kind_(kind), + retValKind_(retValKind), + depth_(0), + noteIndex_(0), + tryStart_(0), + state_(Start) + { + if (controlKind == UseControl) + controlInfo_.emplace(bce_, hasFinally() ? StatementKind::Finally : StatementKind::Try); + finallyStart_.offset = 0; + } + + bool emitJumpOverCatchAndFinally() { + if (!bce_->emitJump(JSOP_GOTO, &catchAndFinallyJump_)) + return false; + return true; + } + + bool emitTry() { + MOZ_ASSERT(state_ == Start); + + // Since an exception can be thrown at any place inside the try block, + // we need to restore the stack and the scope chain before we transfer + // the control to the exception handler. + // + // For that we store in a try note associated with the catch or + // finally block the stack depth upon the try entry. The interpreter + // uses this depth to properly unwind the stack and the scope chain. + depth_ = bce_->stackDepth; + + // Record the try location, then emit the try block. + if (!bce_->newSrcNote(SRC_TRY, ¬eIndex_)) + return false; + if (!bce_->emit1(JSOP_TRY)) + return false; + tryStart_ = bce_->offset(); + + state_ = Try; + return true; + } + + private: + bool emitTryEnd() { + MOZ_ASSERT(state_ == Try); + MOZ_ASSERT(depth_ == bce_->stackDepth); + + // GOSUB to finally, if present. + if (hasFinally() && controlInfo_) { + if (!bce_->emitJump(JSOP_GOSUB, &controlInfo_->gosubs)) + return false; + } + + // Source note points to the jump at the end of the try block. + if (!bce_->setSrcNoteOffset(noteIndex_, 0, bce_->offset() - tryStart_ + JSOP_TRY_LENGTH)) + return false; + + // Emit jump over catch and/or finally. + if (!bce_->emitJump(JSOP_GOTO, &catchAndFinallyJump_)) + return false; + + if (!bce_->emitJumpTarget(&tryEnd_)) + return false; + + return true; + } + + public: + bool emitCatch() { + if (state_ == Try) { + if (!emitTryEnd()) + return false; + } else { + MOZ_ASSERT(state_ == Catch); + if (!emitCatchEnd(true)) + return false; + } + + MOZ_ASSERT(bce_->stackDepth == depth_); + + if (retValKind_ == UseRetVal) { + // Clear the frame's return value that might have been set by the + // try block: + // + // eval("try { 1; throw 2 } catch(e) {}"); // undefined, not 1 + if (!bce_->emit1(JSOP_UNDEFINED)) + return false; + if (!bce_->emit1(JSOP_SETRVAL)) + return false; + } + + state_ = Catch; + return true; + } + + private: + bool emitCatchEnd(bool hasNext) { + MOZ_ASSERT(state_ == Catch); + + if (!controlInfo_) + return true; + + // gosub <finally>, if required. + if (hasFinally()) { + if (!bce_->emitJump(JSOP_GOSUB, &controlInfo_->gosubs)) + return false; + MOZ_ASSERT(bce_->stackDepth == depth_); + } + + // Jump over the remaining catch blocks. This will get fixed + // up to jump to after catch/finally. + if (!bce_->emitJump(JSOP_GOTO, &catchAndFinallyJump_)) + return false; + + // If this catch block had a guard clause, patch the guard jump to + // come here. + if (controlInfo_->guardJump.offset != -1) { + if (!bce_->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 (!hasNext) { + if (!bce_->emit1(JSOP_EXCEPTION)) + return false; + if (!bce_->emit1(JSOP_THROW)) + return false; + } + } + + return true; + } + + public: + bool emitFinally(Maybe<uint32_t> finallyPos = Nothing()) { + // If we are using controlInfo_ (i.e., emitting a syntactic try + // blocks), we must have specified up front if there will be a finally + // close. For internal try blocks, like those emitted for yield* and + // IteratorClose inside for-of loops, we can emitFinally even without + // specifying up front, since the internal try blocks emit no GOSUBs. + if (!controlInfo_) { + if (kind_ == TryCatch) + kind_ = TryCatchFinally; + } else { + MOZ_ASSERT(hasFinally()); + } + + if (state_ == Try) { + if (!emitTryEnd()) + return false; + } else { + MOZ_ASSERT(state_ == Catch); + if (!emitCatchEnd(false)) + return false; + } + + MOZ_ASSERT(bce_->stackDepth == depth_); + + if (!bce_->emitJumpTarget(&finallyStart_)) + return false; + + if (controlInfo_) { + // Fix up the gosubs that might have been emitted before non-local + // jumps to the finally code. + bce_->patchJumpsToTarget(controlInfo_->gosubs, finallyStart_); + + // Indicate that we're emitting a subroutine body. + controlInfo_->setEmittingSubroutine(); + } + if (finallyPos) { + if (!bce_->updateSourceCoordNotes(finallyPos.value())) + return false; + } + if (!bce_->emit1(JSOP_FINALLY)) + return false; + + if (retValKind_ == UseRetVal) { + if (!bce_->emit1(JSOP_GETRVAL)) + return false; + + // Clear the frame's return value to make break/continue return + // correct value even if there's no other statement before them: + // + // eval("x: try { 1 } finally { break x; }"); // undefined, not 1 + if (!bce_->emit1(JSOP_UNDEFINED)) + return false; + if (!bce_->emit1(JSOP_SETRVAL)) + return false; + } + + state_ = Finally; + return true; + } + + private: + bool emitFinallyEnd() { + MOZ_ASSERT(state_ == Finally); + + if (retValKind_ == UseRetVal) { + if (!bce_->emit1(JSOP_SETRVAL)) + return false; + } + + if (!bce_->emit1(JSOP_RETSUB)) + return false; + + bce_->hasTryFinally = true; + return true; + } + + public: + bool emitEnd() { + if (state_ == Catch) { + MOZ_ASSERT(!hasFinally()); + if (!emitCatchEnd(false)) + return false; + } else { + MOZ_ASSERT(state_ == Finally); + MOZ_ASSERT(hasFinally()); + if (!emitFinallyEnd()) + return false; + } + + MOZ_ASSERT(bce_->stackDepth == depth_); + + // ReconstructPCStack needs a NOP here to mark the end of the last + // catch block. + if (!bce_->emit1(JSOP_NOP)) + return false; + + // Fix up the end-of-try/catch jumps to come here. + if (!bce_->emitJumpTargetAndPatch(catchAndFinallyJump_)) + return false; + + // Add the try note last, to let post-order give us the right ordering + // (first to last for a given nesting level, inner to outer by level). + if (hasCatch()) { + if (!bce_->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 (hasFinally()) { + if (!bce_->tryNoteList.append(JSTRY_FINALLY, depth_, tryStart_, finallyStart_.offset)) + return false; + } + + state_ = End; + 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 +}; + +class ForOfLoopControl : public LoopControl +{ + // The stack depth of the iterator. + int32_t iterDepth_; + + // for-of loops, when throwing from non-iterator code (i.e. from the body + // or from evaluating the LHS of the loop condition), need to call + // IteratorClose. This is done by enclosing non-iterator code with + // try-catch and call IteratorClose in `catch` block. + // If IteratorClose itself throws, we must not re-call IteratorClose. Since + // non-local jumps like break and return call IteratorClose, whenever a + // non-local jump is emitted, we must tell catch block not to perform + // IteratorClose. + // + // for (x of y) { + // // Operations for iterator (IteratorNext etc) are outside of + // // try-block. + // try { + // ... + // if (...) { + // // Before non-local jump, clear iterator on the stack to tell + // // catch block not to perform IteratorClose. + // tmpIterator = iterator; + // iterator = undefined; + // IteratorClose(tmpIterator, { break }); + // break; + // } + // ... + // } catch (e) { + // // Just throw again when iterator is cleared by non-local jump. + // if (iterator === undefined) + // throw e; + // IteratorClose(iterator, { throw, e }); + // } + // } + Maybe<TryEmitter> tryCatch_; + + // Used to track if any yields were emitted between calls to to + // emitBeginCodeNeedingIteratorClose and emitEndCodeNeedingIteratorClose. + uint32_t numYieldsAtBeginCodeNeedingIterClose_; + + bool allowSelfHosted_; + + public: + ForOfLoopControl(BytecodeEmitter* bce, int32_t iterDepth, bool allowSelfHosted) + : LoopControl(bce, StatementKind::ForOfLoop), + iterDepth_(iterDepth), + numYieldsAtBeginCodeNeedingIterClose_(UINT32_MAX), + allowSelfHosted_(allowSelfHosted) + { + } + + bool emitBeginCodeNeedingIteratorClose(BytecodeEmitter* bce) { + tryCatch_.emplace(bce, TryEmitter::TryCatch, TryEmitter::DontUseRetVal, + TryEmitter::DontUseControl); + + if (!tryCatch_->emitTry()) + return false; + + MOZ_ASSERT(numYieldsAtBeginCodeNeedingIterClose_ == UINT32_MAX); + numYieldsAtBeginCodeNeedingIterClose_ = bce->yieldOffsetList.numYields; + + return true; + } + + bool emitEndCodeNeedingIteratorClose(BytecodeEmitter* bce) { + if (!tryCatch_->emitCatch()) // ITER ... + return false; + + if (!bce->emit1(JSOP_EXCEPTION)) // ITER ... EXCEPTION + return false; + unsigned slotFromTop = bce->stackDepth - iterDepth_; + if (!bce->emitDupAt(slotFromTop)) // ITER ... EXCEPTION ITER + return false; + + // If ITER is undefined, it means the exception is thrown by + // IteratorClose for non-local jump, and we should't perform + // IteratorClose again here. + if (!bce->emit1(JSOP_UNDEFINED)) // ITER ... EXCEPTION ITER UNDEF + return false; + if (!bce->emit1(JSOP_STRICTNE)) // ITER ... EXCEPTION NE + return false; + + IfThenElseEmitter ifIteratorIsNotClosed(bce); + if (!ifIteratorIsNotClosed.emitIf()) // ITER ... EXCEPTION + return false; + + MOZ_ASSERT(slotFromTop == unsigned(bce->stackDepth - iterDepth_)); + if (!bce->emitDupAt(slotFromTop)) // ITER ... EXCEPTION ITER + return false; + if (!emitIteratorClose(bce, CompletionKind::Throw)) // ITER ... EXCEPTION + return false; + + if (!ifIteratorIsNotClosed.emitEnd()) // ITER ... EXCEPTION + return false; + + if (!bce->emit1(JSOP_THROW)) // ITER ... + return false; + + // If any yields were emitted, then this for-of loop is inside a star + // generator and must handle the case of Generator.return. Like in + // yield*, it is handled with a finally block. + uint32_t numYieldsEmitted = bce->yieldOffsetList.numYields; + if (numYieldsEmitted > numYieldsAtBeginCodeNeedingIterClose_) { + if (!tryCatch_->emitFinally()) + return false; + + IfThenElseEmitter ifGeneratorClosing(bce); + if (!bce->emit1(JSOP_ISGENCLOSING)) // ITER ... FTYPE FVALUE CLOSING + return false; + if (!ifGeneratorClosing.emitIf()) // ITER ... FTYPE FVALUE + return false; + if (!bce->emitDupAt(slotFromTop + 1)) // ITER ... FTYPE FVALUE ITER + return false; + if (!emitIteratorClose(bce, CompletionKind::Normal)) // ITER ... FTYPE FVALUE + return false; + if (!ifGeneratorClosing.emitEnd()) // ITER ... FTYPE FVALUE + return false; + } + + if (!tryCatch_->emitEnd()) + return false; + + tryCatch_.reset(); + numYieldsAtBeginCodeNeedingIterClose_ = UINT32_MAX; + + return true; + } + + bool emitIteratorClose(BytecodeEmitter* bce, + CompletionKind completionKind = CompletionKind::Normal) { + ptrdiff_t start = bce->offset(); + if (!bce->emitIteratorClose(completionKind, allowSelfHosted_)) + return false; + ptrdiff_t end = bce->offset(); + return bce->tryNoteList.append(JSTRY_FOR_OF_ITERCLOSE, 0, start, end); + } + + bool emitPrepareForNonLocalJump(BytecodeEmitter* bce, bool isTarget) { + // Pop unnecessary values from the stack. Effectively this means + // leaving try-catch block. However, the performing IteratorClose can + // reach the depth for try-catch, and effectively re-enter the + // try-catch block. + if (!bce->emit1(JSOP_POP)) // ITER RESULT + return false; + if (!bce->emit1(JSOP_POP)) // ITER + return false; + + // Clear ITER slot on the stack to tell catch block to avoid performing + // IteratorClose again. + if (!bce->emit1(JSOP_UNDEFINED)) // ITER UNDEF + return false; + if (!bce->emit1(JSOP_SWAP)) // UNDEF ITER + return false; + + if (!emitIteratorClose(bce)) // UNDEF + return false; + + if (isTarget) { + // At the level of the target block, there's bytecode after the + // loop that will pop the iterator and the value, so push + // undefineds to balance the stack. + if (!bce->emit1(JSOP_UNDEFINED)) // UNDEF UNDEF + return false; + if (!bce->emit1(JSOP_UNDEFINED)) // UNDEF UNDEF UNDEF + return false; + } else { + if (!bce->emit1(JSOP_POP)) // + return false; + } + + return true; + } +}; + BytecodeEmitter::BytecodeEmitter(BytecodeEmitter* parent, Parser<FullParseHandler>* parser, SharedContext* sc, HandleScript script, Handle<LazyScript*> lazyScript, @@ -1855,6 +2516,12 @@ BytecodeEmitter::emitCheckIsObj(CheckIsObjectKind kind) return emit2(JSOP_CHECKISOBJ, uint8_t(kind)); } +bool +BytecodeEmitter::emitCheckIsCallable(CheckIsCallableKind kind) +{ + return emit2(JSOP_CHECKISCALLABLE, uint8_t(kind)); +} + static inline unsigned LengthOfSetLine(unsigned line) { @@ -2000,35 +2667,45 @@ BytecodeEmitter::emitUint32Operand(JSOp op, uint32_t operand) 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 { +class NonLocalExitControl +{ + public: + enum Kind + { + // IteratorClose is handled especially inside the exception unwinder. + Throw, + + // A 'continue' statement does not call IteratorClose for the loop it + // is continuing, i.e. excluding the target loop. + Continue, + + // A 'break' or 'return' statement does call IteratorClose for the + // loop it is breaking out of or returning from, i.e. including the + // target loop. + Break, + Return + }; + + private: BytecodeEmitter* bce_; const uint32_t savedScopeNoteIndex_; const int savedDepth_; uint32_t openScopeNoteIndex_; + Kind kind_; NonLocalExitControl(const NonLocalExitControl&) = delete; MOZ_MUST_USE bool leaveScope(BytecodeEmitter::EmitterScope* scope); public: - explicit NonLocalExitControl(BytecodeEmitter* bce) + NonLocalExitControl(BytecodeEmitter* bce, Kind kind) : bce_(bce), savedScopeNoteIndex_(bce->scopeNoteList.length()), savedDepth_(bce->stackDepth), - openScopeNoteIndex_(bce->innermostEmitterScope->noteIndex()) + openScopeNoteIndex_(bce->innermostEmitterScope->noteIndex()), + kind_(kind) { } ~NonLocalExitControl() { @@ -2076,9 +2753,16 @@ NonLocalExitControl::prepareForNonLocalJump(BytecodeEmitter::NestableControl* ta EmitterScope* es = bce_->innermostEmitterScope; int npops = 0; + // For 'continue', 'break', and 'return' statements, emit IteratorClose + // bytecode inline. 'continue' statements do not call IteratorClose for + // the loop they are continuing. + bool emitIteratorClose = kind_ == Continue || kind_ == Break || kind_ == Return; + bool emitIteratorCloseAtTarget = emitIteratorClose && kind_ != Continue; + auto flushPops = [&npops](BytecodeEmitter* bce) { - if (npops && !bce->flushPops(&npops)) + if (npops && !bce->emitUint16Operand(JSOP_POPN, npops)) return false; + npops = 0; return true; }; @@ -2107,22 +2791,33 @@ NonLocalExitControl::prepareForNonLocalJump(BytecodeEmitter::NestableControl* ta } else { if (!flushPops(bce_)) return false; - if (!bce_->emitJump(JSOP_GOSUB, &finallyControl.gosubs)) + if (!bce_->emitJump(JSOP_GOSUB, &finallyControl.gosubs)) // ... return false; } break; } case StatementKind::ForOfLoop: - npops += 2; + if (emitIteratorClose) { + if (!flushPops(bce_)) + return false; + + ForOfLoopControl& loopinfo = control->as<ForOfLoopControl>(); + if (!loopinfo.emitPrepareForNonLocalJump(bce_, /* isTarget = */ false)) // ... + return false; + } else { + npops += 3; + } 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)) + + // The iterator and the current value are on the stack. + if (!bce_->emit1(JSOP_POP)) // ... ITER + return false; + if (!bce_->emit1(JSOP_ENDITER)) // ... return false; break; @@ -2131,13 +2826,22 @@ NonLocalExitControl::prepareForNonLocalJump(BytecodeEmitter::NestableControl* ta } } + if (!flushPops(bce_)) + return false; + + if (target && emitIteratorCloseAtTarget && target->is<ForOfLoopControl>()) { + ForOfLoopControl& loopinfo = target->as<ForOfLoopControl>(); + if (!loopinfo.emitPrepareForNonLocalJump(bce_, /* isTarget = */ true)) // ... UNDEF UNDEF UNDEF + return false; + } + EmitterScope* targetEmitterScope = target ? target->emitterScope() : bce_->varEmitterScope; for (; es != targetEmitterScope; es = es->enclosingInFrame()) { if (!leaveScope(es)) return false; } - return flushPops(bce_); + return true; } } // anonymous namespace @@ -2145,7 +2849,9 @@ NonLocalExitControl::prepareForNonLocalJump(BytecodeEmitter::NestableControl* ta bool BytecodeEmitter::emitGoto(NestableControl* target, JumpList* jumplist, SrcNoteType noteType) { - NonLocalExitControl nle(this); + NonLocalExitControl nle(this, noteType == SRC_CONTINUE + ? NonLocalExitControl::Continue + : NonLocalExitControl::Break); if (!nle.prepareForNonLocalJump(target)) return false; @@ -4070,7 +4776,7 @@ BytecodeEmitter::isRunOnceLambda() FunctionBox* funbox = sc->asFunctionBox(); return !funbox->argumentsHasLocalBinding() && !funbox->isGenerator() && - !funbox->function()->name(); + !funbox->function()->explicitName(); } bool @@ -4091,6 +4797,11 @@ BytecodeEmitter::emitYieldOp(JSOp op) return false; } + if (op == JSOP_YIELD) + yieldOffsetList.numYields++; + else + yieldOffsetList.numAwaits++; + SET_UINT24(code(off), yieldIndex); if (!yieldOffsetList.append(offset())) @@ -4325,7 +5036,69 @@ BytecodeEmitter::emitDestructuringDeclsWithEmitter(ParseNode* pattern, NameEmitt } bool -BytecodeEmitter::emitDestructuringLHS(ParseNode* target, DestructuringFlavor flav) +BytecodeEmitter::emitDestructuringLHSRef(ParseNode* target, size_t* emitted) +{ + *emitted = 0; + + if (target->isKind(PNK_SPREAD)) + target = target->pn_kid; + else if (target->isKind(PNK_ASSIGN)) + target = target->pn_left; + + // No need to recur into PNK_ARRAY and PNK_OBJECT subpatterns here, since + // emitSetOrInitializeDestructuring does the recursion when setting or + // initializing value. Getting reference doesn't recur. + if (target->isKind(PNK_NAME) || target->isKind(PNK_ARRAY) || target->isKind(PNK_OBJECT)) + return true; + +#ifdef DEBUG + int depth = stackDepth; +#endif + + switch (target->getKind()) { + case PNK_DOT: { + if (target->as<PropertyAccess>().isSuper()) { + if (!emitSuperPropLHS(&target->as<PropertyAccess>().expression())) + return false; + *emitted = 2; + } else { + if (!emitTree(target->pn_expr)) + return false; + *emitted = 1; + } + break; + } + + case PNK_ELEM: { + if (target->as<PropertyByValue>().isSuper()) { + if (!emitSuperElemOperands(target, EmitElemOption::Ref)) + return false; + *emitted = 3; + } else { + if (!emitElemOperands(target, EmitElemOption::Ref)) + return false; + *emitted = 2; + } + break; + } + + case PNK_CALL: + MOZ_ASSERT_UNREACHABLE("Parser::reportIfNotValidSimpleAssignmentTarget " + "rejects function calls as assignment " + "targets in destructuring assignments"); + break; + + default: + MOZ_CRASH("emitDestructuringLHSRef: bad lhs kind"); + } + + MOZ_ASSERT(stackDepth == depth + int(*emitted)); + + return true; +} + +bool +BytecodeEmitter::emitSetOrInitializeDestructuring(ParseNode* target, DestructuringFlavor flav) { // Now emit the lvalue opcode sequence. If the lvalue is a nested // destructuring initialiser-form, call ourselves to handle it, then pop @@ -4401,44 +5174,28 @@ BytecodeEmitter::emitDestructuringLHS(ParseNode* target, DestructuringFlavor fla } 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. + // The reference is already pushed by emitDestructuringLHSRef. JSOp setOp; - if (target->as<PropertyAccess>().isSuper()) { - if (!emitSuperPropLHS(&target->as<PropertyAccess>().expression())) - return false; - if (!emit2(JSOP_PICK, 2)) - return false; + if (target->as<PropertyAccess>().isSuper()) setOp = sc->strict() ? JSOP_STRICTSETPROP_SUPER : JSOP_SETPROP_SUPER; - } else { - if (!emitTree(target->pn_expr)) - return false; - if (!emit1(JSOP_SWAP)) - return false; + else 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. + // The reference is already pushed by emitDestructuringLHSRef. if (target->as<PropertyByValue>().isSuper()) { JSOp setOp = sc->strict() ? JSOP_STRICTSETELEM_SUPER : JSOP_SETELEM_SUPER; - if (!emitSuperElemOp(target, setOp)) + // emitDestructuringLHSRef already did emitSuperElemOperands + // part of emitSuperElemOp. Perform remaining part here. + if (!emitElemOpBase(setOp)) return false; } else { JSOp setOp = sc->strict() ? JSOP_STRICTSETELEM : JSOP_SETELEM; - if (!emitElemOp(target, setOp)) + if (!emitElemOpBase(setOp)) return false; } break; @@ -4451,7 +5208,7 @@ BytecodeEmitter::emitDestructuringLHS(ParseNode* target, DestructuringFlavor fla break; default: - MOZ_CRASH("emitDestructuringLHS: bad lhs kind"); + MOZ_CRASH("emitSetOrInitializeDestructuring: bad lhs kind"); } // Pop the assigned value. @@ -4463,14 +5220,7 @@ BytecodeEmitter::emitDestructuringLHS(ParseNode* target, DestructuringFlavor fla } bool -BytecodeEmitter::emitConditionallyExecutedDestructuringLHS(ParseNode* target, DestructuringFlavor flav) -{ - TDZCheckCache tdzCache(this); - return emitDestructuringLHS(target, flav); -} - -bool -BytecodeEmitter::emitIteratorNext(ParseNode* pn, bool allowSelfHosted) +BytecodeEmitter::emitIteratorNext(ParseNode* pn, bool allowSelfHosted /* = false */) { MOZ_ASSERT(allowSelfHosted || emitterMode != BytecodeEmitter::SelfHosting, ".next() iteration is prohibited in self-hosted code because it " @@ -4491,178 +5241,241 @@ BytecodeEmitter::emitIteratorNext(ParseNode* pn, bool allowSelfHosted) } bool -BytecodeEmitter::emitDefault(ParseNode* defaultExpr) +BytecodeEmitter::emitIteratorClose(CompletionKind completionKind /* = CompletionKind::Normal */, + bool allowSelfHosted /* = false */) { - if (!emit1(JSOP_DUP)) // VALUE VALUE - return false; - if (!emit1(JSOP_UNDEFINED)) // VALUE VALUE UNDEFINED - return false; - if (!emit1(JSOP_STRICTEQ)) // VALUE EQL? + MOZ_ASSERT(allowSelfHosted || emitterMode != BytecodeEmitter::SelfHosting, + ".close() on iterators is prohibited in self-hosted code because it " + "can run user-modifiable iteration code"); + + // Generate inline logic corresponding to IteratorClose (ES 7.4.6). + // + // Callers need to ensure that the iterator object is at the top of the + // stack. + + if (!emit1(JSOP_DUP)) // ... ITER ITER return false; - // Emit source note to enable ion compilation. - if (!newSrcNote(SRC_IF)) + + // Step 3. + // + // Get the "return" method. + if (!emitAtomOp(cx->names().return_, JSOP_CALLPROP)) // ... ITER RET return false; - JumpList jump; - if (!emitJump(JSOP_IFEQ, &jump)) // VALUE + + // Step 4. + // + // Do nothing if "return" is null or undefined. + IfThenElseEmitter ifReturnMethodIsDefined(this); + if (!emit1(JSOP_DUP)) // ... ITER RET RET return false; - if (!emit1(JSOP_POP)) // . + if (!emit1(JSOP_UNDEFINED)) // ... ITER RET RET UNDEFINED return false; - if (!emitConditionallyExecutedTree(defaultExpr)) // DEFAULTVALUE + if (!emit1(JSOP_NE)) // ... ITER RET ?NEQL return false; - if (!emitJumpTargetAndPatch(jump)) + if (!ifReturnMethodIsDefined.emitIfElse()) 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) - {} + if (completionKind == CompletionKind::Throw) { + // 7.4.6 IteratorClose ( iterator, completion ) + // ... + // 3. Let return be ? GetMethod(iterator, "return"). + // 4. If return is undefined, return Completion(completion). + // 5. Let innerResult be Call(return, iterator, « »). + // 6. If completion.[[Type]] is throw, return Completion(completion). + // 7. If innerResult.[[Type]] is throw, return + // Completion(innerResult). + // + // For CompletionKind::Normal case, JSOP_CALL for step 5 checks if RET + // is callable, and throws if not. Since step 6 doesn't match and + // error handling in step 3 and step 7 can be merged. + // + // For CompletionKind::Throw case, an error thrown by JSOP_CALL for + // step 5 is ignored by try-catch. So we should check if RET is + // callable here, outside of try-catch, and the throw immediately if + // not. + CheckIsCallableKind kind = CheckIsCallableKind::IteratorReturn; + if (!emitCheckIsCallable(kind)) // ... ITER RET + return false; + } - ~IfThenElseEmitter() - {} + // Steps 5, 8. + // + // Call "return" if it is not undefined or null, and check that it returns + // an Object. + if (!emit1(JSOP_SWAP)) // ... RET ITER + return false; - private: - bool emitIf(State nextState) { - MOZ_ASSERT(state_ == Start || state_ == Else); - MOZ_ASSERT(nextState == If || nextState == IfElse || nextState == Cond); + Maybe<TryEmitter> tryCatch; - // Clear jumpAroundThen_ offset that points previous JSOP_IFEQ. - if (state_ == Else) - jumpAroundThen_ = JumpList(); + if (completionKind == CompletionKind::Throw) { + tryCatch.emplace(this, TryEmitter::TryCatch, TryEmitter::DontUseRetVal, + TryEmitter::DontUseControl); - // 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_)) + // Mutate stack to balance stack for try-catch. + if (!emit1(JSOP_UNDEFINED)) // ... RET ITER UNDEF return false; - if (!bce_->emitJump(JSOP_IFEQ, &jumpAroundThen_)) + if (!tryCatch->emitTry()) // ... RET ITER UNDEF + return false; + if (!emitDupAt(2)) // ... RET ITER UNDEF RET + return false; + if (!emitDupAt(2)) // ... RET ITER UNDEF RET ITER 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); + if (!emitCall(JSOP_CALL, 0)) // ... ... RESULT + return false; + checkTypeSet(JSOP_CALL); - calculateOrCheckPushed(); + if (completionKind == CompletionKind::Throw) { + if (!emit1(JSOP_SWAP)) // ... RET ITER RESULT UNDEF + return false; + if (!emit1(JSOP_POP)) // ... RET ITER RESULT + return false; - // 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_)) + if (!tryCatch->emitCatch()) // ... RET ITER RESULT return false; - // Ensure the branch-if-false comes here, then emit the else. - if (!bce_->emitJumpTargetAndPatch(jumpAroundThen_)) + // Just ignore the exception thrown by call. + if (!emit1(JSOP_EXCEPTION)) // ... RET ITER RESULT EXC + return false; + if (!emit1(JSOP_POP)) // ... RET ITER RESULT 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)) - { + if (!tryCatch->emitEnd()) // ... RET ITER RESULT return false; - } - // Restore stack depth of the then part. - bce_->stackDepth = thenDepth_; - state_ = Else; - return true; + // Restore stack. + if (!emit2(JSOP_UNPICK, 2)) // ... RESULT RET ITER + return false; + if (!emit1(JSOP_POP)) // ... RESULT RET + return false; + if (!emit1(JSOP_POP)) // ... RESULT + return false; + } else { + if (!emitCheckIsObj(CheckIsObjectKind::IteratorReturn)) // ... RESULT + return false; } - bool emitEnd() { - MOZ_ASSERT(state_ == If || state_ == Else); + if (!ifReturnMethodIsDefined.emitElse()) + return false; + if (!emit1(JSOP_POP)) // ... ITER + return false; + if (!ifReturnMethodIsDefined.emitEnd()) + return false; - calculateOrCheckPushed(); + return emit1(JSOP_POP); // ... +} - if (state_ == If) { - // No else part, fixup the branch-if-false to come here. - if (!bce_->emitJumpTargetAndPatch(jumpAroundThen_)) +template <typename InnerEmitter> +bool +BytecodeEmitter::wrapWithDestructuringIteratorCloseTryNote(int32_t iterDepth, InnerEmitter emitter) +{ + MOZ_ASSERT(this->stackDepth >= iterDepth); + + // Pad a nop at the beginning of the bytecode covered by the trynote so + // that when unwinding environments, we may unwind to the scope + // corresponding to the pc *before* the start, in case the first bytecode + // emitted by |emitter| is the start of an inner scope. See comment above + // UnwindEnvironmentToTryPc. + if (!emit1(JSOP_TRY_DESTRUCTURING_ITERCLOSE)) + return false; + + ptrdiff_t start = offset(); + if (!emitter(this)) + return false; + ptrdiff_t end = offset(); + if (start != end) + return tryNoteList.append(JSTRY_DESTRUCTURING_ITERCLOSE, iterDepth, start, end); + return true; +} + +bool +BytecodeEmitter::emitDefault(ParseNode* defaultExpr, ParseNode* pattern) +{ + 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 (!emitInitializerInBranch(defaultExpr, pattern)) // DEFAULTVALUE + return false; + if (!emitJumpTargetAndPatch(jump)) + return false; + return true; +} + +bool +BytecodeEmitter::setOrEmitSetFunName(ParseNode* maybeFun, HandleAtom name, + FunctionPrefixKind prefixKind) +{ + if (maybeFun->isKind(PNK_FUNCTION)) { + // Function doesn't have 'name' property at this point. + // Set function's name at compile time. + RootedFunction fun(cx, maybeFun->pn_funbox->function()); + + // Single node can be emitted multiple times if it appears in + // array destructuring default. If function already has a name, + // just return. + if (fun->hasCompileTimeName()) { +#ifdef DEBUG + RootedAtom funName(cx, NameToFunctionName(cx, name, prefixKind)); + if (!funName) return false; + MOZ_ASSERT(funName == maybeFun->pn_funbox->function()->compileTimeName()); +#endif + return true; } - // Patch all the jumps around else parts. - if (!bce_->emitJumpTargetAndPatch(jumpsAroundElse_)) + RootedAtom funName(cx, NameToFunctionName(cx, name, prefixKind)); + if (!funName) return false; - - state_ = End; + fun->setCompileTimeName(name); return true; } - void calculateOrCheckPushed() { -#ifdef DEBUG - if (!calculatedPushed_) { - pushed_ = bce_->stackDepth - thenDepth_; - calculatedPushed_ = true; - } else { - MOZ_ASSERT(pushed_ == bce_->stackDepth - thenDepth_); - } -#endif - } + uint32_t nameIndex; + if (!makeAtomIndex(name, &nameIndex)) + return false; + if (!emitIndexOp(JSOP_STRING, nameIndex)) // FUN NAME + return false; + uint8_t kind = uint8_t(prefixKind); + if (!emit2(JSOP_SETFUNNAME, kind)) // FUN + return false; + return true; +} -#ifdef DEBUG - int32_t pushed() const { - return pushed_; - } +bool +BytecodeEmitter::emitInitializer(ParseNode* initializer, ParseNode* pattern) +{ + if (!emitTree(initializer)) + return false; - int32_t popped() const { - return -pushed_; + if (!pattern->isInParens() && pattern->isKind(PNK_NAME) && + initializer->isDirectRHSAnonFunction()) + { + RootedAtom name(cx, pattern->name()); + if (!setOrEmitSetFunName(initializer, name, FunctionPrefixKind::None)) + return false; } -#endif -}; + + return true; +} + +bool +BytecodeEmitter::emitInitializerInBranch(ParseNode* initializer, ParseNode* pattern) +{ + TDZCheckCache tdzCache(this); + return emitInitializer(initializer, pattern); +} bool BytecodeEmitter::emitDestructuringOpsArray(ParseNode* pattern, DestructuringFlavor flav) @@ -4673,240 +5486,304 @@ BytecodeEmitter::emitDestructuringOpsArray(ParseNode* pattern, DestructuringFlav // Here's pseudo code for |let [a, b, , c=y, ...d] = x;| // + // Lines that are annotated "covered by trynote" mean that upon throwing + // an exception, IteratorClose is called on iter only if done is false. + // // let x, y; // let a, b, c, d; - // let tmp, done, iter, result; // stack values + // let iter, lref, result, done, value; // stack values // // iter = x[Symbol.iterator](); // // // ==== emitted by loop for a ==== + // lref = GetReference(a); // covered by trynote + // // result = iter.next(); // done = result.done; // - // if (done) { - // a = undefined; + // if (done) + // value = undefined; + // else + // value = result.value; // - // result = undefined; - // done = true; - // } else { - // a = result.value; - // - // // Do next element's .next() and .done access here - // result = iter.next(); - // done = result.done; - // } + // SetOrInitialize(lref, value); // covered by trynote // // // ==== emitted by loop for b ==== - // if (done) { - // b = undefined; + // lref = GetReference(b); // covered by trynote // - // result = undefined; - // done = true; + // if (done) { + // value = undefined; // } else { - // b = result.value; - // // result = iter.next(); // done = result.done; + // if (done) + // value = undefined; + // else + // value = result.value; // } // + // SetOrInitialize(lref, value); // covered by trynote + // // // ==== emitted by loop for elision ==== // if (done) { - // result = undefined - // done = true + // value = undefined; // } else { - // result.value; - // // result = iter.next(); // done = result.done; + // if (done) + // value = undefined; + // else + // value = result.value; // } // // // ==== emitted by loop for c ==== + // lref = GetReference(c); // covered by trynote + // // if (done) { - // c = y; + // value = undefined; // } 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. + // result = iter.next(); + // done = result.done; + // if (done) + // value = undefined; + // else + // value = result.value; // } // + // if (value === undefined) + // value = y; // covered by trynote + // + // SetOrInitialize(lref, value); // covered by trynote + // // // ==== emitted by loop for d ==== - // if (done) { - // // Assing empty array when completed - // d = []; - // } else { - // d = [...iter]; - // } + // lref = GetReference(d); // covered by trynote + // + // if (done) + // value = []; + // else + // value = [...iter]; + // + // SetOrInitialize(lref, value); // covered by trynote + // + // // === emitted after loop === + // if (!done) + // IteratorClose(iter); - /* - * Use an iterator to destructure the RHS, instead of index lookup. We - * must leave the *original* value on the stack. - */ + // 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 + if (!emitIterator()) // ... OBJ ITER + return false; + + // For an empty pattern [], call IteratorClose unconditionally. Nothing + // else needs to be done. + if (!pattern->pn_head) + return emitIteratorClose(); // ... OBJ + + // Push an initial FALSE value for DONE. + if (!emit1(JSOP_FALSE)) // ... OBJ ITER FALSE return false; - bool needToPopIterator = true; + + // JSTRY_DESTRUCTURING_ITERCLOSE expects the iterator and the done value + // to be the second to top and the top of the stack, respectively. + // IteratorClose is called upon exception only if done is false. + int32_t tryNoteDepth = stackDepth; for (ParseNode* member = pattern->pn_head; member; member = member->pn_next) { - bool isHead = member == pattern->pn_head; + bool isFirst = member == pattern->pn_head; + DebugOnly<bool> hasNext = !!member->pn_next; + + size_t emitted = 0; + + // Spec requires LHS reference to be evaluated first. + ParseNode* lhsPattern = member; + if (lhsPattern->isKind(PNK_ASSIGN)) + lhsPattern = lhsPattern->pn_left; + + bool isElision = lhsPattern->isKind(PNK_ELISION); + if (!isElision) { + auto emitLHSRef = [lhsPattern, &emitted](BytecodeEmitter* bce) { + return bce->emitDestructuringLHSRef(lhsPattern, &emitted); // ... OBJ ITER DONE *LREF + }; + if (!wrapWithDestructuringIteratorCloseTryNote(tryNoteDepth, emitLHSRef)) + return false; + } + + // Pick the DONE value to the top of the stack. + if (emitted) { + if (!emit2(JSOP_PICK, emitted)) // ... OBJ ITER *LREF DONE + return false; + } + + if (isFirst) { + // If this element is the first, DONE is always FALSE, so pop it. + // + // Non-first elements should emit if-else depending on the + // member pattern, below. + if (!emit1(JSOP_POP)) // ... OBJ ITER *LREF + return false; + } + if (member->isKind(PNK_SPREAD)) { IfThenElseEmitter ifThenElse(this); - if (!isHead) { + if (!isFirst) { // If spread is not the first element of the pattern, // iterator can already be completed. - if (!ifThenElse.emitIfElse()) // ... OBJ? ITER + // ... OBJ ITER *LREF DONE + if (!ifThenElse.emitIfElse()) // ... OBJ ITER *LREF return false; - if (!emit1(JSOP_POP)) // ... OBJ? - return false; - if (!emitUint32Operand(JSOP_NEWARRAY, 0)) // ... OBJ? ARRAY - return false; - if (!emitConditionallyExecutedDestructuringLHS(member, flav)) // ... OBJ? + if (!emitUint32Operand(JSOP_NEWARRAY, 0)) // ... OBJ ITER *LREF ARRAY return false; - - if (!ifThenElse.emitElse()) // ... OBJ? ITER + if (!ifThenElse.emitElse()) // ... OBJ ITER *LREF 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 + if (!emitDupAt(emitted)) // ... OBJ ITER *LREF ITER return false; - if (!emitNumberOp(0)) // ... OBJ? ITER ARRAY INDEX + if (!emitUint32Operand(JSOP_NEWARRAY, 0)) // ... OBJ ITER *LREF ITER ARRAY return false; - if (!emitSpread()) // ... OBJ? ARRAY INDEX + if (!emitNumberOp(0)) // ... OBJ ITER *LREF ITER ARRAY INDEX return false; - if (!emit1(JSOP_POP)) // ... OBJ? ARRAY + if (!emitSpread()) // ... OBJ ITER *LREF ARRAY INDEX return false; - if (!emitConditionallyExecutedDestructuringLHS(member, flav)) // ... OBJ? + if (!emit1(JSOP_POP)) // ... OBJ ITER *LREF ARRAY return false; - if (!isHead) { + if (!isFirst) { if (!ifThenElse.emitEnd()) return false; - MOZ_ASSERT(ifThenElse.popped() == 1); + MOZ_ASSERT(ifThenElse.pushed() == 1); } - needToPopIterator = false; - MOZ_ASSERT(!member->pn_next); + + // At this point the iterator is done. Unpick a TRUE value for DONE above ITER. + if (!emit1(JSOP_TRUE)) // ... OBJ ITER *LREF ARRAY TRUE + return false; + if (!emit2(JSOP_UNPICK, emitted + 1)) // ... OBJ ITER TRUE *LREF ARRAY + return false; + + auto emitAssignment = [member, flav](BytecodeEmitter* bce) { + return bce->emitSetOrInitializeDestructuring(member, flav); // ... OBJ ITER TRUE + }; + if (!wrapWithDestructuringIteratorCloseTryNote(tryNoteDepth, emitAssignment)) + return false; + + MOZ_ASSERT(!hasNext); break; } ParseNode* pndefault = nullptr; - ParseNode* subpattern = member; - if (subpattern->isKind(PNK_ASSIGN)) { - pndefault = subpattern->pn_right; - subpattern = subpattern->pn_left; - } + if (member->isKind(PNK_ASSIGN)) + pndefault = member->pn_right; - 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(!member->isKind(PNK_SPREAD)); - MOZ_ASSERT(!subpattern->isKind(PNK_SPREAD)); + IfThenElseEmitter ifAlreadyDone(this); + if (!isFirst) { + // ... OBJ ITER *LREF DONE + if (!ifAlreadyDone.emitIfElse()) // ... OBJ ITER *LREF + return false; - auto emitNext = [pattern](ExclusiveContext* cx, BytecodeEmitter* bce) { - if (!bce->emit1(JSOP_DUP)) // ... OBJ? ITER ITER + if (!emit1(JSOP_UNDEFINED)) // ... OBJ ITER *LREF UNDEF return false; - if (!bce->emitIteratorNext(pattern)) // ... OBJ? ITER RESULT + if (!emit1(JSOP_NOP_DESTRUCTURING)) // ... OBJ ITER *LREF UNDEF return false; - if (!bce->emit1(JSOP_DUP)) // ... OBJ? ITER RESULT RESULT + + // The iterator is done. Unpick a TRUE value for DONE above ITER. + if (!emit1(JSOP_TRUE)) // ... OBJ ITER *LREF UNDEF TRUE return false; - if (!bce->emitAtomOp(cx->names().done, JSOP_GETPROP)) // ... OBJ? ITER RESULT DONE? + if (!emit2(JSOP_UNPICK, emitted + 1)) // ... OBJ ITER TRUE *LREF UNDEF return false; - return true; - }; - if (isHead) { - if (!emitNext(cx, this)) // ... OBJ? ITER RESULT DONE? + if (!ifAlreadyDone.emitElse()) // ... OBJ ITER *LREF 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 + if (emitted) { + if (!emitDupAt(emitted)) // ... OBJ ITER *LREF ITER 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 + if (!emit1(JSOP_DUP)) // ... OBJ ITER *LREF ITER return false; } + if (!emitIteratorNext(pattern)) // ... OBJ ITER *LREF RESULT + return false; + if (!emit1(JSOP_DUP)) // ... OBJ ITER *LREF RESULT RESULT + return false; + if (!emitAtomOp(cx->names().done, JSOP_GETPROP)) // ... OBJ ITER *LREF RESULT DONE + 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 (!emit1(JSOP_DUP)) // ... OBJ ITER *LREF RESULT DONE DONE + return false; + if (!emit2(JSOP_UNPICK, emitted + 2)) // ... OBJ ITER DONE *LREF RESULT DONE + return false; + + IfThenElseEmitter ifDone(this); + if (!ifDone.emitIfElse()) // ... OBJ ITER DONE *LREF RESULT + return false; - if (!ifThenElse.emitElse()) // ... OBJ? ITER RESULT + if (!emit1(JSOP_POP)) // ... OBJ ITER DONE *LREF + return false; + if (!emit1(JSOP_UNDEFINED)) // ... OBJ ITER DONE *LREF UNDEF + return false; + if (!emit1(JSOP_NOP_DESTRUCTURING)) // ... OBJ ITER DONE *LREF UNDEF return false; - if (!emitAtomOp(cx->names().value, JSOP_GETPROP)) // ... OBJ? ITER VALUE + if (!ifDone.emitElse()) // ... OBJ ITER DONE *LREF RESULT return false; - if (pndefault) { - if (!emitDefault(pndefault)) // ... OBJ? ITER VALUE + if (!emitAtomOp(cx->names().value, JSOP_GETPROP)) // ... OBJ ITER DONE *LREF VALUE + return false; + + if (!ifDone.emitEnd()) + return false; + MOZ_ASSERT(ifDone.pushed() == 0); + + if (!isFirst) { + if (!ifAlreadyDone.emitEnd()) return false; + MOZ_ASSERT(ifAlreadyDone.pushed() == 2); } - if (!isElision) { - if (!emitConditionallyExecutedDestructuringLHS(subpattern, flav)) // ... OBJ? ITER - return false; - } else { - if (!emit1(JSOP_POP)) // ... OBJ? ITER + if (pndefault) { + auto emitDefault = [pndefault, lhsPattern](BytecodeEmitter* bce) { + return bce->emitDefault(pndefault, lhsPattern); // ... OBJ ITER DONE *LREF VALUE + }; + + if (!wrapWithDestructuringIteratorCloseTryNote(tryNoteDepth, emitDefault)) return false; } - // Setup next element's result when the iterator is not done. - if (hasNextNonSpread) { - if (!emitNext(cx, this)) // ... OBJ? ITER RESULT DONE? + if (!isElision) { + auto emitAssignment = [lhsPattern, flav](BytecodeEmitter* bce) { + return bce->emitSetOrInitializeDestructuring(lhsPattern, flav); // ... OBJ ITER DONE + }; + + if (!wrapWithDestructuringIteratorCloseTryNote(tryNoteDepth, emitAssignment)) return false; - } else if (hasNextSpread) { - if (!emit1(JSOP_FALSE)) // ... OBJ? ITER DONE? + } else { + if (!emit1(JSOP_POP)) // ... 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; - } + // The last DONE value is on top of the stack. If not DONE, call + // IteratorClose. + // ... OBJ ITER DONE + IfThenElseEmitter ifDone(this); + if (!ifDone.emitIfElse()) // ... OBJ ITER + return false; + if (!emit1(JSOP_POP)) // ... OBJ + return false; + if (!ifDone.emitElse()) // ... OBJ ITER + return false; + if (!emitIteratorClose()) // ... OBJ + return false; + if (!ifDone.emitEnd()) + return false; return true; } @@ -4930,27 +5807,43 @@ BytecodeEmitter::emitDestructuringOpsObject(ParseNode* pattern, DestructuringFla 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 + ParseNode* subpattern; + if (member->isKind(PNK_MUTATEPROTO)) + subpattern = member->pn_kid; + else + subpattern = member->pn_right; + ParseNode* lhs = subpattern; + if (lhs->isKind(PNK_ASSIGN)) + lhs = lhs->pn_left; + + size_t emitted; + if (!emitDestructuringLHSRef(lhs, &emitted)) // ... RHS *LREF return false; + // Duplicate the value being destructured to use as a reference base. + if (emitted) { + if (!emitDupAt(emitted)) // ... RHS *LREF RHS + return false; + } else { + 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 + if (!emitAtomOp(cx->names().proto, JSOP_GETPROP)) // ... RHS *LREF 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 + if (!emitNumberOp(key->pn_dval)) // ... RHS *LREF RHS KEY return false; } else if (key->isKind(PNK_OBJECT_PROPERTY_NAME) || key->isKind(PNK_STRING)) { PropertyName* name = key->pn_atom->asPropertyName(); @@ -4960,33 +5853,30 @@ BytecodeEmitter::emitDestructuringOpsObject(ParseNode* pattern, DestructuringFla // as indexes for simplification of downstream analysis. jsid id = NameToId(name); if (id != IdToTypeId(id)) { - if (!emitTree(key)) // ... RHS RHS KEY + if (!emitTree(key)) // ... RHS *LREF RHS KEY return false; } else { - if (!emitAtomOp(name, JSOP_GETPROP)) // ...RHS PROP + if (!emitAtomOp(name, JSOP_GETPROP)) // ... RHS *LREF PROP return false; needsGetElem = false; } } else { - if (!emitComputedPropertyName(key)) // ... RHS RHS KEY + if (!emitComputedPropertyName(key)) // ... RHS *LREF RHS KEY return false; } - - subpattern = member->pn_right; } // Get the property value if not done already. - if (needsGetElem && !emitElemOpBase(JSOP_GETELEM)) // ... RHS PROP + if (needsGetElem && !emitElemOpBase(JSOP_GETELEM)) // ... RHS *LREF PROP return false; if (subpattern->isKind(PNK_ASSIGN)) { - if (!emitDefault(subpattern->pn_right)) + if (!emitDefault(subpattern->pn_right, lhs)) // ... RHS *LREF VALUE return false; - subpattern = subpattern->pn_left; } - // Destructure PROP per this member's subpattern. - if (!emitDestructuringLHS(subpattern, flav)) + // Destructure PROP per this member's lhs. + if (!emitSetOrInitializeDestructuring(subpattern, flav)) // ... RHS return false; } @@ -5094,7 +5984,7 @@ BytecodeEmitter::emitSingleDeclaration(ParseNode* declList, ParseNode* decl, if (!initializer && declList->isKind(PNK_VAR)) return true; - auto emitRhs = [initializer, declList](BytecodeEmitter* bce, const NameLocation&, bool) { + auto emitRhs = [initializer, declList, decl](BytecodeEmitter* bce, const NameLocation&, bool) { if (!initializer) { // Lexical declarations are initialized to undefined without an // initializer. @@ -5105,7 +5995,7 @@ BytecodeEmitter::emitSingleDeclaration(ParseNode* declList, ParseNode* decl, } MOZ_ASSERT(initializer); - return bce->emitTree(initializer); + return bce->emitInitializer(initializer, decl); }; if (!emitInitializeName(decl, emitRhs)) @@ -5164,6 +6054,12 @@ BytecodeEmitter::emitAssignment(ParseNode* lhs, JSOp op, ParseNode* rhs) if (!EmitAssignmentRhs(bce, rhs, emittedBindOp ? 2 : 1)) return false; + if (!lhs->isInParens() && op == JSOP_NOP && rhs && rhs->isDirectRHSAnonFunction()) { + RootedAtom name(bce->cx, lhs->name()); + if (!bce->setOrEmitSetFunName(rhs, name, FunctionPrefixKind::None)) + return false; + } + // Emit the compound assignment op if there is one. if (op != JSOP_NOP && !bce->emit1(op)) return false; @@ -5558,7 +6454,7 @@ BytecodeEmitter::emitCatch(ParseNode* pn) return false; { - NonLocalExitControl nle(this); + NonLocalExitControl nle(this, NonLocalExitControl::Throw); // Move exception back to cx->exception to prepare for // the next catch. @@ -5592,57 +6488,28 @@ BytecodeEmitter::emitCatch(ParseNode* pn) 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); + ParseNode* catchList = pn->pn_kid2; + ParseNode* finallyNode = pn->pn_kid3; - // GOSUB to finally, if present. - if (pn->pn_kid3) { - if (!emitJump(JSOP_GOSUB, &controlInfo.gosubs)) - return false; + TryEmitter::Kind kind; + if (catchList) { + if (finallyNode) + kind = TryEmitter::TryCatchFinally; + else + kind = TryEmitter::TryCatch; + } else { + MOZ_ASSERT(finallyNode); + kind = TryEmitter::TryFinally; } + TryEmitter tryCatch(this, kind); - // 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)) + if (!tryCatch.emitTry()) return false; - JumpTarget tryEnd; - if (!emitJumpTarget(&tryEnd)) + if (!emitTree(pn->pn_kid1)) return false; // If this try has a catch block, emit it. - ParseNode* catchList = pn->pn_kid2; if (catchList) { MOZ_ASSERT(catchList->isKind(PNK_CATCHLIST)); @@ -5672,110 +6539,26 @@ BytecodeEmitter::emitTry(ParseNode* pn) // 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)) + if (!tryCatch.emitCatch()) return false; // Emit the lexical scope and catch body. MOZ_ASSERT(pn3->isKind(PNK_LEXICALSCOPE)); if (!emitTree(pn3)) return false; - - // gosub <finally>, if required. - if (pn->pn_kid3) { - if (!emitJump(JSOP_GOSUB, &controlInfo.gosubs)) - return false; - MOZ_ASSERT(this->stackDepth == depth); - } - - // Jump over the remaining catch blocks. This will get fixed - // up to jump to after catch/finally. - if (!emitJump(JSOP_GOTO, &catchJump)) - return false; - - // If this catch block had a guard clause, patch the guard jump to - // come here. - if (controlInfo.guardJump.offset != -1) { - if (!emitJumpTargetAndPatch(controlInfo.guardJump)) - return false; - controlInfo.guardJump.offset = -1; - - // If this catch block is the last one, rethrow, delegating - // execution of any finally block to the exception handler. - if (!pn3->pn_next) { - if (!emit1(JSOP_EXCEPTION)) - return false; - if (!emit1(JSOP_THROW)) - return false; - } - } } } - MOZ_ASSERT(this->stackDepth == depth); - // Emit the finally handler, if there is one. - JumpTarget finallyStart{ 0 }; - if (pn->pn_kid3) { - if (!emitJumpTarget(&finallyStart)) + if (finallyNode) { + if (!tryCatch.emitFinally(Some(finallyNode->pn_pos.begin))) 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)) + if (!emitTree(finallyNode)) 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)) + if (!tryCatch.emitEnd()) return false; return true; @@ -5788,7 +6571,7 @@ BytecodeEmitter::emitIf(ParseNode* pn) if_again: /* Emit code for the condition before pushing stmtInfo. */ - if (!emitConditionallyExecutedTree(pn->pn_kid1)) + if (!emitTreeInBranch(pn->pn_kid1)) return false; ParseNode* elseNode = pn->pn_kid3; @@ -5801,7 +6584,7 @@ BytecodeEmitter::emitIf(ParseNode* pn) } /* Emit code for the then part. */ - if (!emitConditionallyExecutedTree(pn->pn_kid2)) + if (!emitTreeInBranch(pn->pn_kid2)) return false; if (elseNode) { @@ -5814,7 +6597,7 @@ BytecodeEmitter::emitIf(ParseNode* pn) } /* Emit code for the else part. */ - if (!emitConditionallyExecutedTree(elseNode)) + if (!emitTreeInBranch(elseNode)) return false; } @@ -6048,7 +6831,7 @@ BytecodeEmitter::emitSpread(bool allowSelfHosted) 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? + 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 @@ -6144,19 +6927,34 @@ BytecodeEmitter::emitForOf(ParseNode* forOfLoop, EmitterScope* headLexicalEmitte MOZ_ASSERT(forOfHead->isKind(PNK_FOROF)); MOZ_ASSERT(forOfHead->isArity(PN_TERNARY)); - // Evaluate the expression being iterated. ParseNode* forHeadExpr = forOfHead->pn_kid3; + + // Certain builtins (e.g. Array.from) are implemented in self-hosting + // as for-of loops. + bool allowSelfHostedIter = false; + if (emitterMode == BytecodeEmitter::SelfHosting && + forHeadExpr->isKind(PNK_CALL) && + forHeadExpr->pn_head->name() == cx->names().allowContentIter) + { + allowSelfHostedIter = true; + } + + // Evaluate the expression being iterated. 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. + int32_t iterDepth = stackDepth; + + // For-of loops have both the iterator, the result, and the result.value + // on the stack. Push undefineds to balance the stack. if (!emit1(JSOP_UNDEFINED)) // ITER RESULT return false; + if (!emit1(JSOP_UNDEFINED)) // ITER RESULT UNDEF + return false; - LoopControl loopInfo(this, StatementKind::ForOfLoop); + ForOfLoopControl loopInfo(this, iterDepth, allowSelfHostedIter); // Annotate so IonMonkey can find the loop-closing jump. unsigned noteIndex; @@ -6164,11 +6962,11 @@ BytecodeEmitter::emitForOf(ParseNode* forOfLoop, EmitterScope* headLexicalEmitte return false; JumpList initialJump; - if (!emitJump(JSOP_GOTO, &initialJump)) // ITER RESULT + if (!emitJump(JSOP_GOTO, &initialJump)) // ITER RESULT UNDEF return false; JumpTarget top{ -1 }; - if (!emitLoopHead(nullptr, &top)) // ITER RESULT + if (!emitLoopHead(nullptr, &top)) // ITER RESULT UNDEF return false; // If the loop had an escaping lexical declaration, replace the current @@ -6185,7 +6983,7 @@ BytecodeEmitter::emitForOf(ParseNode* forOfLoop, EmitterScope* headLexicalEmitte MOZ_ASSERT(headLexicalEmitterScope->scope(this)->kind() == ScopeKind::Lexical); if (headLexicalEmitterScope->hasEnvironment()) { - if (!emit1(JSOP_RECREATELEXICALENV)) // ITER RESULT + if (!emit1(JSOP_RECREATELEXICALENV)) // ITER RESULT UNDEF return false; } @@ -6202,46 +7000,69 @@ BytecodeEmitter::emitForOf(ParseNode* forOfLoop, EmitterScope* headLexicalEmitte #endif // Emit code to assign result.value to the iteration variable. + // + // Note that ES 13.7.5.13, step 5.c says getting result.value does not + // call IteratorClose, so start JSTRY_ITERCLOSE after the GETPROP. + if (!emit1(JSOP_POP)) // ITER RESULT + return false; 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 + if (!loopInfo.emitBeginCodeNeedingIteratorClose(this)) return false; - if (!emit1(JSOP_POP)) // ITER RESULT + if (!emitInitializeForInOrOfTarget(forOfHead)) // ITER RESULT VALUE return false; - MOZ_ASSERT(this->stackDepth == loopDepth, + MOZ_ASSERT(stackDepth == loopDepth, "the stack must be balanced around the initializing " "operation"); + // Remove VALUE from the stack to release it. + if (!emit1(JSOP_POP)) // ITER RESULT + return false; + if (!emit1(JSOP_UNDEFINED)) // ITER RESULT UNDEF + return false; + // Perform the loop body. ParseNode* forBody = forOfLoop->pn_right; - if (!emitTree(forBody)) // ITER RESULT + if (!emitTree(forBody)) // ITER RESULT UNDEF + return false; + + MOZ_ASSERT(stackDepth == loopDepth, + "the stack must be balanced around the for-of body"); + + if (!loopInfo.emitEndCodeNeedingIteratorClose(this)) return false; // Set offset for continues. loopInfo.continueTarget = { offset() }; - if (!emitLoopEntry(forHeadExpr, initialJump)) // ITER RESULT + if (!emitLoopEntry(forHeadExpr, initialJump)) // ITER RESULT UNDEF return false; - if (!emit1(JSOP_POP)) // ITER + if (!emit1(JSOP_SWAP)) // ITER UNDEF RESULT + return false; + if (!emit1(JSOP_POP)) // ITER UNDEF return false; - if (!emit1(JSOP_DUP)) // ITER ITER + if (!emitDupAt(1)) // ITER UNDEF ITER return false; - if (!emitIteratorNext(forOfHead)) // ITER RESULT + if (!emitIteratorNext(forOfHead, allowSelfHostedIter)) // ITER UNDEF RESULT return false; - if (!emit1(JSOP_DUP)) // ITER RESULT RESULT + + if (!emit1(JSOP_SWAP)) // ITER RESULT UNDEF return false; - if (!emitAtomOp(cx->names().done, JSOP_GETPROP)) // ITER RESULT DONE? + + if (!emitDupAt(1)) // ITER RESULT UNDEF RESULT + return false; + if (!emitAtomOp(cx->names().done, JSOP_GETPROP)) // ITER RESULT UNDEF DONE return false; if (!emitBackwardJump(JSOP_IFEQ, top, &beq, &breakTarget)) - return false; // ITER RESULT + return false; // ITER RESULT UNDEF MOZ_ASSERT(this->stackDepth == loopDepth); } @@ -6256,7 +7077,7 @@ BytecodeEmitter::emitForOf(ParseNode* forOfLoop, EmitterScope* headLexicalEmitte if (!tryNoteList.append(JSTRY_FOR_OF, stackDepth, top.offset, breakTarget.offset)) return false; - return emitUint16Operand(JSOP_POPN, 2); // + return emitUint16Operand(JSOP_POPN, 3); // } bool @@ -6283,8 +7104,8 @@ BytecodeEmitter::emitForIn(ParseNode* forInLoop, EmitterScope* headLexicalEmitte if (!updateSourceCoordNotes(decl->pn_pos.begin)) return false; - auto emitRhs = [initializer](BytecodeEmitter* bce, const NameLocation&, bool) { - return bce->emitTree(initializer); + auto emitRhs = [decl, initializer](BytecodeEmitter* bce, const NameLocation&, bool) { + return bce->emitInitializer(initializer, decl); }; if (!emitInitializeName(decl, emitRhs)) @@ -6504,7 +7325,7 @@ BytecodeEmitter::emitCStyleFor(ParseNode* pn, EmitterScope* headLexicalEmitterSc if (jmp.offset == -1 && !emitLoopEntry(forBody, jmp)) return false; - if (!emitConditionallyExecutedTree(forBody)) + if (!emitTreeInBranch(forBody)) return false; // Set loop and enclosing "update" offsets, for continue. Note that we @@ -6666,6 +7487,8 @@ BytecodeEmitter::emitComprehensionForOf(ParseNode* pn) // Push a dummy result so that we properly enter iteration midstream. if (!emit1(JSOP_UNDEFINED)) // ITER RESULT return false; + if (!emit1(JSOP_UNDEFINED)) // ITER RESULT VALUE + return false; // Enter the block before the loop body, after evaluating the obj. // Initialize let bindings with undefined when entering, as the name @@ -6703,42 +7526,59 @@ BytecodeEmitter::emitComprehensionForOf(ParseNode* pn) #endif // Emit code to assign result.value to the iteration variable. + if (!emit1(JSOP_POP)) // ITER RESULT + return false; if (!emit1(JSOP_DUP)) // ITER RESULT RESULT return false; if (!emitAtomOp(cx->names().value, JSOP_GETPROP)) // ITER RESULT VALUE return false; + + // Notice: Comprehension for-of doesn't perform IteratorClose, since it's + // not in the spec. + if (!emitAssignment(loopVariableName, JSOP_NOP, nullptr)) // ITER RESULT VALUE return false; + + // Remove VALUE from the stack to release it. if (!emit1(JSOP_POP)) // ITER RESULT return false; + if (!emit1(JSOP_UNDEFINED)) // ITER RESULT UNDEF + 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)) + if (!emitTree(forBody)) // ITER RESULT UNDEF return false; + // The stack should be balanced around the assignment opcode sequence. + MOZ_ASSERT(this->stackDepth == loopDepth); + // Set offset for continues. loopInfo.continueTarget = { offset() }; if (!emitLoopEntry(forHeadExpr, jmp)) return false; - if (!emit1(JSOP_POP)) // ITER + if (!emit1(JSOP_SWAP)) // ITER UNDEF RESULT return false; - if (!emit1(JSOP_DUP)) // ITER ITER + if (!emit1(JSOP_POP)) // ITER UNDEF return false; - if (!emitIteratorNext(forHead)) // ITER RESULT + if (!emitDupAt(1)) // ITER UNDEF ITER return false; - if (!emit1(JSOP_DUP)) // ITER RESULT RESULT + if (!emitIteratorNext(forHead)) // ITER UNDEF RESULT + return false; + if (!emit1(JSOP_SWAP)) // ITER RESULT UNDEF return false; - if (!emitAtomOp(cx->names().done, JSOP_GETPROP)) // ITER RESULT DONE? + if (!emitDupAt(1)) // ITER RESULT UNDEF RESULT + return false; + if (!emitAtomOp(cx->names().done, JSOP_GETPROP)) // ITER RESULT UNDEF DONE return false; JumpList beq; JumpTarget breakTarget{ -1 }; - if (!emitBackwardJump(JSOP_IFEQ, top, &beq, &breakTarget)) // ITER RESULT + if (!emitBackwardJump(JSOP_IFEQ, top, &beq, &breakTarget)) // ITER RESULT UNDEF return false; MOZ_ASSERT(this->stackDepth == loopDepth); @@ -6760,7 +7600,7 @@ BytecodeEmitter::emitComprehensionForOf(ParseNode* pn) } // Pop the result and the iter. - return emitUint16Operand(JSOP_POPN, 2); // + return emitUint16Operand(JSOP_POPN, 3); // } bool @@ -6903,16 +7743,15 @@ BytecodeEmitter::emitFunction(ParseNode* pn, bool needsProto) { FunctionBox* funbox = pn->pn_funbox; RootedFunction fun(cx, funbox->function()); - RootedAtom name(cx, fun->name()); + RootedAtom name(cx, fun->explicitName()); 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()) { + if (funbox->wasEmitted) { // 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 @@ -7041,7 +7880,7 @@ BytecodeEmitter::emitFunction(ParseNode* pn, bool needsProto) } if (needsProto) { - MOZ_ASSERT(pn->getOp() == JSOP_FUNWITHPROTO || pn->getOp() == JSOP_LAMBDA); + MOZ_ASSERT(pn->getOp() == JSOP_LAMBDA); pn->setOp(JSOP_FUNWITHPROTO); } @@ -7284,7 +8123,7 @@ BytecodeEmitter::emitWhile(ParseNode* pn) if (!emitLoopHead(pn->pn_right, &top)) return false; - if (!emitConditionallyExecutedTree(pn->pn_right)) + if (!emitTreeInBranch(pn->pn_right)) return false; if (!emitLoopEntry(pn->pn_left, jmp)) @@ -7458,7 +8297,7 @@ BytecodeEmitter::emitReturn(ParseNode* pn) return false; } - NonLocalExitControl nle(this); + NonLocalExitControl nle(this, NonLocalExitControl::Return); if (!nle.prepareForNonLocalJumpToOutermost()) return false; @@ -7528,110 +8367,208 @@ BytecodeEmitter::emitYieldStar(ParseNode* iter, ParseNode* gen) MOZ_ASSERT(sc->isFunctionBox()); MOZ_ASSERT(sc->asFunctionBox()->isStarGenerator()); - if (!emitTree(iter)) // ITERABLE + if (!emitTree(iter)) // ITERABLE return false; - if (!emitIterator()) // ITER + if (!emitIterator()) // ITER return false; // Initial send value is undefined. - if (!emit1(JSOP_UNDEFINED)) // ITER RECEIVED + if (!emit1(JSOP_UNDEFINED)) // ITER RECEIVED return false; - int depth = stackDepth; - MOZ_ASSERT(depth >= 2); + int32_t savedDepthTemp; + int32_t startDepth = stackDepth; + MOZ_ASSERT(startDepth >= 2); - JumpList send; - if (!emitJump(JSOP_GOTO, &send)) // goto send + TryEmitter tryCatch(this, TryEmitter::TryCatchFinally, TryEmitter::DontUseRetVal, + TryEmitter::DontUseControl); + if (!tryCatch.emitJumpOverCatchAndFinally()) // ITER RESULT return false; - // Try prologue. // ITER RESULT - unsigned noteIndex; - if (!newSrcNote(SRC_TRY, ¬eIndex)) - return false; JumpTarget tryStart{ offset() }; - if (!emit1(JSOP_TRY)) // tryStart: + if (!tryCatch.emitTry()) // ITER RESULT return false; - MOZ_ASSERT(this->stackDepth == depth); + + MOZ_ASSERT(this->stackDepth == startDepth); // Load the generator object. - if (!emitTree(gen)) // ITER RESULT GENOBJ + if (!emitTree(gen)) // ITER RESULT GENOBJ return false; // Yield RESULT as-is, without re-boxing. - if (!emitYieldOp(JSOP_YIELD)) // ITER RECEIVED + 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 + if (!tryCatch.emitCatch()) // ITER RESULT return false; - JumpTarget tryEnd; - if (!emitJumpTarget(&tryEnd)) // tryEnd: + stackDepth = startDepth; // ITER RESULT + if (!emit1(JSOP_EXCEPTION)) // ITER RESULT EXCEPTION + return false; + if (!emitDupAt(2)) // ITER RESULT EXCEPTION ITER + return false; + if (!emit1(JSOP_DUP)) // ITER RESULT EXCEPTION ITER ITER + return false; + if (!emitAtomOp(cx->names().throw_, JSOP_CALLPROP)) // ITER RESULT EXCEPTION ITER THROW + return false; + if (!emit1(JSOP_DUP)) // ITER RESULT EXCEPTION ITER THROW THROW + return false; + if (!emit1(JSOP_UNDEFINED)) // ITER RESULT EXCEPTION ITER THROW THROW UNDEFINED + return false; + if (!emit1(JSOP_EQ)) // ITER RESULT EXCEPTION ITER THROW ?EQL return false; - // Catch location. - stackDepth = uint32_t(depth); // ITER RESULT - if (!emit1(JSOP_POP)) // ITER + IfThenElseEmitter ifThrowMethodIsNotDefined(this); + if (!ifThrowMethodIsNotDefined.emitIf()) // ITER RESULT EXCEPTION ITER THROW + return false; + savedDepthTemp = stackDepth; + if (!emit1(JSOP_POP)) // ITER RESULT EXCEPTION ITER + return false; + // ES 14.4.13, YieldExpression : yield * AssignmentExpression, step 5.b.iii.2 + // + // If the iterator does not have a "throw" method, it calls IteratorClose + // and then throws a TypeError. + if (!emitIteratorClose()) // ITER RESULT EXCEPTION + return false; + if (!emitUint16Operand(JSOP_THROWMSG, JSMSG_ITERATOR_NO_THROW)) // throw return false; - // THROW? = 'throw' in ITER - if (!emit1(JSOP_EXCEPTION)) // ITER EXCEPTION + stackDepth = savedDepthTemp; + if (!ifThrowMethodIsNotDefined.emitEnd()) // ITER OLDRESULT EXCEPTION ITER THROW return false; - if (!emit1(JSOP_SWAP)) // EXCEPTION ITER + // ES 14.4.13, YieldExpression : yield * AssignmentExpression, step 5.b.iii.4. + // RESULT = ITER.throw(EXCEPTION) // ITER OLDRESULT EXCEPTION ITER THROW + if (!emit1(JSOP_SWAP)) // ITER OLDRESULT EXCEPTION THROW ITER return false; - if (!emit1(JSOP_DUP)) // EXCEPTION ITER ITER + if (!emit2(JSOP_PICK, 2)) // ITER OLDRESULT THROW ITER EXCEPTION return false; - if (!emitAtomOp(cx->names().throw_, JSOP_STRING)) // EXCEPTION ITER ITER "throw" + if (!emitCall(JSOP_CALL, 1, iter)) // ITER OLDRESULT RESULT + return false; + checkTypeSet(JSOP_CALL); + if (!emitCheckIsObj(CheckIsObjectKind::IteratorThrow)) // ITER OLDRESULT RESULT return false; - if (!emit1(JSOP_SWAP)) // EXCEPTION ITER "throw" ITER + if (!emit1(JSOP_SWAP)) // ITER RESULT OLDRESULT return false; - if (!emit1(JSOP_IN)) // EXCEPTION ITER THROW? + if (!emit1(JSOP_POP)) // ITER RESULT return false; - // if (THROW?) goto delegate - JumpList checkThrow; - if (!emitJump(JSOP_IFNE, &checkThrow)) // EXCEPTION ITER + MOZ_ASSERT(this->stackDepth == startDepth); + JumpList checkResult; + // ES 14.4.13, YieldExpression : yield * AssignmentExpression, step 5.b.ii. + // + // Note that there is no GOSUB to the finally block here. If the iterator has a + // "throw" method, it does not perform IteratorClose. + if (!emitJump(JSOP_GOTO, &checkResult)) // goto checkResult return false; - if (!emit1(JSOP_POP)) // EXCEPTION + + if (!tryCatch.emitFinally()) + return false; + + // ES 14.4.13, yield * AssignmentExpression, step 5.c + // + // Call iterator.return() for receiving a "forced return" completion from + // the generator. + + IfThenElseEmitter ifGeneratorClosing(this); + if (!emit1(JSOP_ISGENCLOSING)) // ITER RESULT FTYPE FVALUE CLOSING return false; - if (!emit1(JSOP_THROW)) // throw EXCEPTION + if (!ifGeneratorClosing.emitIf()) // ITER RESULT FTYPE FVALUE return false; - if (!emitJumpTargetAndPatch(checkThrow)) // delegate: + // Step ii. + // + // Get the "return" method. + if (!emitDupAt(3)) // ITER RESULT FTYPE FVALUE ITER return false; - // RESULT = ITER.throw(EXCEPTION) // EXCEPTION ITER - stackDepth = uint32_t(depth); - if (!emit1(JSOP_DUP)) // EXCEPTION ITER ITER + if (!emit1(JSOP_DUP)) // ITER RESULT FTYPE FVALUE ITER ITER return false; - if (!emit1(JSOP_DUP)) // EXCEPTION ITER ITER ITER + if (!emitAtomOp(cx->names().return_, JSOP_CALLPROP)) // ITER RESULT FTYPE FVALUE ITER RET return false; - if (!emitAtomOp(cx->names().throw_, JSOP_CALLPROP)) // EXCEPTION ITER ITER THROW + + // Step iii. + // + // Do nothing if "return" is undefined. + IfThenElseEmitter ifReturnMethodIsDefined(this); + if (!emit1(JSOP_DUP)) // ITER RESULT FTYPE FVALUE ITER RET RET return false; - if (!emit1(JSOP_SWAP)) // EXCEPTION ITER THROW ITER + if (!emit1(JSOP_UNDEFINED)) // ITER RESULT FTYPE FVALUE ITER RET RET UNDEFINED return false; - if (!emit2(JSOP_PICK, 3)) // ITER THROW ITER EXCEPTION + if (!emit1(JSOP_NE)) // ITER RESULT FTYPE FVALUE ITER RET ?NEQL return false; - if (!emitCall(JSOP_CALL, 1, iter)) // ITER RESULT + + // Step iv. + // + // Call "return" with the argument passed to Generator.prototype.return, + // which is currently in rval.value. + if (!ifReturnMethodIsDefined.emitIfElse()) // ITER OLDRESULT FTYPE FVALUE ITER RET + return false; + if (!emit1(JSOP_SWAP)) // ITER OLDRESULT FTYPE FVALUE RET ITER + return false; + if (!emit1(JSOP_GETRVAL)) // ITER OLDRESULT FTYPE FVALUE RET ITER RVAL + return false; + if (!emitAtomOp(cx->names().value, JSOP_GETPROP)) // ITER OLDRESULT FTYPE FVALUE RET ITER VALUE + return false; + if (!emitCall(JSOP_CALL, 1)) // ITER OLDRESULT FTYPE FVALUE RESULT return false; checkTypeSet(JSOP_CALL); - MOZ_ASSERT(this->stackDepth == depth); - JumpList checkResult; - if (!emitJump(JSOP_GOTO, &checkResult)) // goto checkResult + + // Step v. + if (!emitCheckIsObj(CheckIsObjectKind::IteratorReturn)) // ITER OLDRESULT FTYPE FVALUE RESULT return false; - // Catch epilogue. + // Steps vi-viii. + // + // Check if the returned object from iterator.return() is done. If not, + // continuing yielding. + IfThenElseEmitter ifReturnDone(this); + if (!emit1(JSOP_DUP)) // ITER OLDRESULT FTYPE FVALUE RESULT RESULT + return false; + if (!emitAtomOp(cx->names().done, JSOP_GETPROP)) // ITER OLDRESULT FTYPE FVALUE RESULT DONE + return false; + if (!ifReturnDone.emitIfElse()) // ITER OLDRESULT FTYPE FVALUE RESULT + return false; + if (!emitAtomOp(cx->names().value, JSOP_GETPROP)) // ITER OLDRESULT FTYPE FVALUE VALUE + return false; + if (!emitPrepareIteratorResult()) // ITER OLDRESULT FTYPE FVALUE VALUE RESULT + return false; + if (!emit1(JSOP_SWAP)) // ITER OLDRESULT FTYPE FVALUE RESULT VALUE + return false; + if (!emitFinishIteratorResult(true)) // ITER OLDRESULT FTYPE FVALUE RESULT + return false; + if (!emit1(JSOP_SETRVAL)) // ITER OLDRESULT FTYPE FVALUE + return false; + savedDepthTemp = this->stackDepth; + if (!ifReturnDone.emitElse()) // ITER OLDRESULT FTYPE FVALUE RESULT + return false; + if (!emit2(JSOP_UNPICK, 3)) // ITER RESULT OLDRESULT FTYPE FVALUE + return false; + if (!emitUint16Operand(JSOP_POPN, 3)) // ITER RESULT + return false; + { + // goto tryStart; + JumpList beq; + JumpTarget breakTarget{ -1 }; + if (!emitBackwardJump(JSOP_GOTO, tryStart, &beq, &breakTarget)) // ITER RESULT + return false; + } + this->stackDepth = savedDepthTemp; + if (!ifReturnDone.emitEnd()) + return false; - // This is a peace offering to ReconstructPCStack. See the note in EmitTry. - if (!emit1(JSOP_NOP)) + if (!ifReturnMethodIsDefined.emitElse()) // ITER RESULT FTYPE FVALUE ITER RET return false; - if (!tryNoteList.append(JSTRY_CATCH, depth, tryStart.offset + JSOP_TRY_LENGTH, tryEnd.offset)) + if (!emit1(JSOP_POP)) // ITER RESULT FTYPE FVALUE ITER + return false; + if (!emit1(JSOP_POP)) // ITER RESULT FTYPE FVALUE + return false; + if (!ifReturnMethodIsDefined.emitEnd()) + return false; + + if (!ifGeneratorClosing.emitEnd()) return false; - // After the try/catch block: send the received value to the iterator. - if (!emitJumpTargetAndPatch(send)) // send: + if (!tryCatch.emitEnd()) return false; - // Send location. + // After the try-catch-finally block: send the received value to the iterator. // result = iter.next(received) // ITER RECEIVED if (!emit1(JSOP_SWAP)) // RECEIVED ITER return false; @@ -7650,7 +8587,7 @@ BytecodeEmitter::emitYieldStar(ParseNode* iter, ParseNode* gen) if (!emitCheckIsObj(CheckIsObjectKind::IteratorNext)) // ITER RESULT return false; checkTypeSet(JSOP_CALL); - MOZ_ASSERT(this->stackDepth == depth); + MOZ_ASSERT(this->stackDepth == startDepth); if (!emitJumpTargetAndPatch(checkResult)) // checkResult: return false; @@ -7661,10 +8598,12 @@ BytecodeEmitter::emitYieldStar(ParseNode* iter, ParseNode* gen) 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; + { + JumpList beq; + JumpTarget breakTarget{ -1 }; + if (!emitBackwardJump(JSOP_IFEQ, tryStart, &beq, &breakTarget)) // ITER RESULT + return false; + } // result.value if (!emit1(JSOP_SWAP)) // RESULT ITER @@ -7674,7 +8613,7 @@ BytecodeEmitter::emitYieldStar(ParseNode* iter, ParseNode* gen) if (!emitAtomOp(cx->names().value, JSOP_GETPROP)) // VALUE return false; - MOZ_ASSERT(this->stackDepth == depth - 1); + MOZ_ASSERT(this->stackDepth == startDepth - 1); return true; } @@ -7996,10 +8935,10 @@ BytecodeEmitter::emitSelfHostedForceInterpreter(ParseNode* pn) } bool -BytecodeEmitter::emitSelfHostedAllowContentSpread(ParseNode* pn) +BytecodeEmitter::emitSelfHostedAllowContentIter(ParseNode* pn) { if (pn->pn_count != 2) { - reportError(pn, JSMSG_MORE_ARGS_NEEDED, "allowContentSpread", "1", ""); + reportError(pn, JSMSG_MORE_ARGS_NEEDED, "allowContentIter", "1", ""); return false; } @@ -8017,7 +8956,7 @@ BytecodeEmitter::isRestParameter(ParseNode* pn, bool* result) FunctionBox* funbox = sc->asFunctionBox(); RootedFunction fun(cx, funbox->function()); - if (!fun->hasRest()) { + if (!funbox->hasRest()) { *result = false; return true; } @@ -8025,7 +8964,7 @@ BytecodeEmitter::isRestParameter(ParseNode* pn, bool* result) 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) + if (pn2->getKind() == PNK_NAME && pn2->name() == cx->names().allowContentIter) return isRestParameter(pn2->pn_next, result); } *result = false; @@ -8133,8 +9072,8 @@ BytecodeEmitter::emitCallOrNew(ParseNode* pn) return emitSelfHostedResumeGenerator(pn); if (pn2->name() == cx->names().forceInterpreter) return emitSelfHostedForceInterpreter(pn); - if (pn2->name() == cx->names().allowContentSpread) - return emitSelfHostedAllowContentSpread(pn); + if (pn2->name() == cx->names().allowContentIter) + return emitSelfHostedAllowContentIter(pn); // Fall through. } if (!emitGetName(pn2, callop)) @@ -8449,13 +9388,13 @@ BytecodeEmitter::emitConditionalExpression(ConditionalExpression& conditional) if (!ifThenElse.emitCond()) return false; - if (!emitConditionallyExecutedTree(&conditional.thenExpression())) + if (!emitTreeInBranch(&conditional.thenExpression())) return false; if (!ifThenElse.emitElse()) return false; - if (!emitConditionallyExecutedTree(&conditional.elseExpression())) + if (!emitTreeInBranch(&conditional.elseExpression())) return false; if (!ifThenElse.emitEnd()) @@ -8532,6 +9471,10 @@ BytecodeEmitter::emitPropertyList(ParseNode* pn, MutableHandlePlainObject objp, op == JSOP_INITPROP_GETTER || op == JSOP_INITPROP_SETTER); + FunctionPrefixKind prefixKind = op == JSOP_INITPROP_GETTER ? FunctionPrefixKind::Get + : op == JSOP_INITPROP_SETTER ? FunctionPrefixKind::Set + : FunctionPrefixKind::None; + if (op == JSOP_INITPROP_GETTER || op == JSOP_INITPROP_SETTER) objp.set(nullptr); @@ -8573,6 +9516,12 @@ BytecodeEmitter::emitPropertyList(ParseNode* pn, MutableHandlePlainObject objp, case JSOP_INITHIDDENPROP_SETTER: op = JSOP_INITHIDDENELEM_SETTER; break; default: MOZ_CRASH("Invalid op"); } + if (propdef->pn_right->isDirectRHSAnonFunction()) { + if (!emitDupAt(1)) + return false; + if (!emit2(JSOP_SETFUNNAME, uint8_t(prefixKind))) + return false; + } if (!emit1(op)) return false; } else { @@ -8597,6 +9546,11 @@ BytecodeEmitter::emitPropertyList(ParseNode* pn, MutableHandlePlainObject objp, objp.set(nullptr); } + if (propdef->pn_right->isDirectRHSAnonFunction()) { + RootedAtom keyName(cx, key->pn_atom); + if (!setOrEmitSetFunName(propdef->pn_right, keyName, prefixKind)) + return false; + } if (!emitIndex32(op, index)) return false; } @@ -8774,7 +9728,7 @@ BytecodeEmitter::emitArray(ParseNode* pn, uint32_t count, JSOp op) if (!updateSourceCoordNotes(pn2->pn_pos.begin)) return false; - bool allowSelfHostedSpread = false; + bool allowSelfHostedIter = false; if (pn2->isKind(PNK_ELISION)) { if (!emit1(JSOP_HOLE)) return false; @@ -8785,9 +9739,9 @@ BytecodeEmitter::emitArray(ParseNode* pn, uint32_t count, JSOp op) if (emitterMode == BytecodeEmitter::SelfHosting && expr->isKind(PNK_CALL) && - expr->pn_head->name() == cx->names().allowContentSpread) + expr->pn_head->name() == cx->names().allowContentIter) { - allowSelfHostedSpread = true; + allowSelfHostedIter = true; } } else { expr = pn2; @@ -8802,7 +9756,7 @@ BytecodeEmitter::emitArray(ParseNode* pn, uint32_t count, JSOp op) return false; if (!emit2(JSOP_PICK, 2)) // ITER ARRAY INDEX return false; - if (!emitSpread(allowSelfHostedSpread)) // ARRAY INDEX + if (!emitSpread(allowSelfHostedIter)) // ARRAY INDEX return false; } else if (afterSpread) { if (!emit1(JSOP_INITELEM_INC)) @@ -8960,7 +9914,7 @@ BytecodeEmitter::emitFunctionFormalParameters(ParseNode* pn) EmitterScope* funScope = innermostEmitterScope; bool hasParameterExprs = funbox->hasParameterExprs; - bool hasRest = funbox->function()->hasRest(); + bool hasRest = funbox->hasRest(); uint16_t argSlot = 0; for (ParseNode* arg = pn->pn_head; arg != funBody; arg = arg->pn_next, argSlot++) { @@ -9017,7 +9971,7 @@ BytecodeEmitter::emitFunctionFormalParameters(ParseNode* pn) return false; if (!emit1(JSOP_POP)) return false; - if (!emitConditionallyExecutedTree(initializer)) + if (!emitInitializerInBranch(initializer, bindingElement)) return false; if (!emitJumpTargetAndPatch(jump)) return false; @@ -9728,7 +10682,7 @@ BytecodeEmitter::emitTree(ParseNode* pn, EmitLineNumberNote emitLineNote) } bool -BytecodeEmitter::emitConditionallyExecutedTree(ParseNode* pn) +BytecodeEmitter::emitTreeInBranch(ParseNode* pn) { // Code that may be conditionally executed always need their own TDZ // cache. @@ -9962,15 +10916,6 @@ CGConstList::finish(ConstArray* array) 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. * @@ -9982,15 +10927,9 @@ CGObjectList::isAdded(ObjectBox* objbox) unsigned CGObjectList::add(ObjectBox* objbox) { - if (isAdded(objbox)) - return indexOf(objbox->object); - + MOZ_ASSERT(!objbox->emitLink); objbox->emitLink = lastbox; lastbox = objbox; - - // See the comment in CGObjectList::isAdded. - if (!firstbox) - firstbox = objbox; return length++; } @@ -10017,12 +10956,7 @@ CGObjectList::finish(ObjectArray* array) 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); + } while ((objbox = objbox->emitLink) != nullptr); MOZ_ASSERT(cursor == array->vector); } diff --git a/js/src/frontend/BytecodeEmitter.h b/js/src/frontend/BytecodeEmitter.h index 1bb4191ee..04307c8c1 100644 --- a/js/src/frontend/BytecodeEmitter.h +++ b/js/src/frontend/BytecodeEmitter.h @@ -43,12 +43,10 @@ class CGConstList { 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) {} + CGObjectList() : length(0), lastbox(nullptr) {} - bool isAdded(ObjectBox* objbox); unsigned add(ObjectBox* objbox); unsigned indexOf(JSObject* obj); void finish(ObjectArray* array); @@ -102,7 +100,9 @@ struct CGScopeNoteList { struct CGYieldOffsetList { Vector<uint32_t> list; - explicit CGYieldOffsetList(ExclusiveContext* cx) : list(cx) {} + uint32_t numYields; + uint32_t numAwaits; + explicit CGYieldOffsetList(ExclusiveContext* cx) : list(cx), numYields(0), numAwaits(0) {} MOZ_MUST_USE bool append(uint32_t offset) { return list.append(offset); } size_t length() const { return list.length(); } @@ -435,7 +435,7 @@ struct MOZ_STACK_CLASS BytecodeEmitter 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); + MOZ_MUST_USE bool emitTreeInBranch(ParseNode* pn); // Emit global, eval, or module code for tree rooted at body. Always // encompasses the entire source. @@ -454,8 +454,6 @@ struct MOZ_STACK_CLASS BytecodeEmitter 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. @@ -475,6 +473,9 @@ struct MOZ_STACK_CLASS BytecodeEmitter // Helper to emit JSOP_CHECKISOBJ. MOZ_MUST_USE bool emitCheckIsObj(CheckIsObjectKind kind); + // Helper to emit JSOP_CHECKISCALLABLE. + MOZ_MUST_USE bool emitCheckIsCallable(CheckIsCallableKind 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); @@ -611,7 +612,7 @@ struct MOZ_STACK_CLASS BytecodeEmitter // 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 }; + enum class EmitElemOption { Get, Set, Call, IncDec, CompoundAssign, Ref }; MOZ_MUST_USE bool emitElemOperands(ParseNode* pn, EmitElemOption opts); MOZ_MUST_USE bool emitElemOpBase(JSOp op); @@ -644,12 +645,18 @@ struct MOZ_STACK_CLASS BytecodeEmitter 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); + // emitDestructuringLHSRef emits the lhs expression's reference. + // If the lhs expression is object property |OBJ.prop|, it emits |OBJ|. + // If it's object element |OBJ[ELEM]|, it emits |OBJ| and |ELEM|. + // If there's nothing to evaluate for the reference, it emits nothing. + // |emitted| parameter receives the number of values pushed onto the stack. + MOZ_MUST_USE bool emitDestructuringLHSRef(ParseNode* target, size_t* emitted); + + // emitSetOrInitializeDestructuring assumes the lhs expression's reference + // and the to-be-destructured value has been pushed on the stack. It emits + // code to destructure a single lhs expression (either a name or a compound + // []/{} expression). + MOZ_MUST_USE bool emitSetOrInitializeDestructuring(ParseNode* target, DestructuringFlavor flav); // emitDestructuringOps assumes the to-be-destructured value has been // pushed on the stack and emits code to destructure each part of a [] or @@ -675,10 +682,25 @@ struct MOZ_STACK_CLASS BytecodeEmitter // 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); + MOZ_MUST_USE bool emitIteratorClose(CompletionKind completionKind = CompletionKind::Normal, + bool allowSelfHosted = false); + + template <typename InnerEmitter> + MOZ_MUST_USE bool wrapWithDestructuringIteratorCloseTryNote(int32_t iterDepth, + InnerEmitter emitter); // 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); + // |pattern| is a lhs node of the default expression. If it's an + // identifier and |defaultExpr| is an anonymous function, |SetFunctionName| + // is called at compile time. + MOZ_MUST_USE bool emitDefault(ParseNode* defaultExpr, ParseNode* pattern); + + MOZ_MUST_USE bool setOrEmitSetFunName(ParseNode* maybeFun, HandleAtom name, + FunctionPrefixKind prefixKind); + + MOZ_MUST_USE bool emitInitializer(ParseNode* initializer, ParseNode* pattern); + MOZ_MUST_USE bool emitInitializerInBranch(ParseNode* initializer, ParseNode* pattern); MOZ_MUST_USE bool emitCallSiteObject(ParseNode* pn); MOZ_MUST_USE bool emitTemplateString(ParseNode* pn); @@ -713,7 +735,7 @@ struct MOZ_STACK_CLASS BytecodeEmitter 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 emitSelfHostedAllowContentIter(ParseNode* pn); MOZ_MUST_USE bool emitComprehensionFor(ParseNode* compFor); MOZ_MUST_USE bool emitComprehensionForIn(ParseNode* pn); diff --git a/js/src/frontend/FullParseHandler.h b/js/src/frontend/FullParseHandler.h index add881900..0fd137796 100644 --- a/js/src/frontend/FullParseHandler.h +++ b/js/src/frontend/FullParseHandler.h @@ -665,6 +665,11 @@ class FullParseHandler ParseNode* pn); inline void setLastFunctionFormalParameterDestructuring(ParseNode* funcpn, ParseNode* pn); + void checkAndSetIsDirectRHSAnonFunction(ParseNode* pn) { + if (IsAnonymousFunctionDefinition(pn)) + pn->setDirectRHSAnonFunction(true); + } + ParseNode* newFunctionDefinition() { return new_<CodeNode>(PNK_FUNCTION, pos()); } @@ -942,6 +947,8 @@ FullParseHandler::setLastFunctionFormalParameterDefault(ParseNode* funcpn, Parse if (!pn) return false; + checkAndSetIsDirectRHSAnonFunction(defaultValue); + funcpn->pn_body->pn_pos.end = pn->pn_pos.end; ParseNode* pnchild = funcpn->pn_body->pn_head; ParseNode* pnlast = funcpn->pn_body->last(); diff --git a/js/src/frontend/ParseNode-inl.h b/js/src/frontend/ParseNode-inl.h index 395d09b5b..21bd83766 100644 --- a/js/src/frontend/ParseNode-inl.h +++ b/js/src/frontend/ParseNode-inl.h @@ -18,7 +18,7 @@ inline PropertyName* ParseNode::name() const { MOZ_ASSERT(isKind(PNK_FUNCTION) || isKind(PNK_NAME)); - JSAtom* atom = isKind(PNK_FUNCTION) ? pn_funbox->function()->name() : pn_atom; + JSAtom* atom = isKind(PNK_FUNCTION) ? pn_funbox->function()->explicitName() : pn_atom; return atom->asPropertyName(); } diff --git a/js/src/frontend/ParseNode.cpp b/js/src/frontend/ParseNode.cpp index f79baba9e..ece3a45df 100644 --- a/js/src/frontend/ParseNode.cpp +++ b/js/src/frontend/ParseNode.cpp @@ -902,3 +902,21 @@ FunctionBox::trace(JSTracer* trc) if (enclosingScope_) TraceRoot(trc, &enclosingScope_, "funbox-enclosingScope"); } + +bool +js::frontend::IsAnonymousFunctionDefinition(ParseNode* pn) +{ + // ES 2017 draft + // 12.15.2 (ArrowFunction, AsyncArrowFunction). + // 14.1.12 (FunctionExpression). + // 14.4.8 (GeneratorExpression). + // 14.6.8 (AsyncFunctionExpression) + if (pn->isKind(PNK_FUNCTION) && !pn->pn_funbox->function()->explicitName()) + return true; + + // 14.5.8 (ClassExpression) + if (pn->is<ClassNode>() && !pn->as<ClassNode>().names()) + return true; + + return false; +} diff --git a/js/src/frontend/ParseNode.h b/js/src/frontend/ParseNode.h index d37aaaae0..c58dab431 100644 --- a/js/src/frontend/ParseNode.h +++ b/js/src/frontend/ParseNode.h @@ -450,6 +450,9 @@ class ParseNode 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 */ + bool pn_rhs_anon_fun:1; /* this expr is anonymous function or class that + * is a direct RHS of PNK_ASSIGN or PNK_COLON of + * property, that needs SetFunctionName. */ ParseNode(const ParseNode& other) = delete; void operator=(const ParseNode& other) = delete; @@ -460,6 +463,7 @@ class ParseNode pn_op(op), pn_arity(arity), pn_parens(false), + pn_rhs_anon_fun(false), pn_pos(0, 0), pn_next(nullptr) { @@ -472,6 +476,7 @@ class ParseNode pn_op(op), pn_arity(arity), pn_parens(false), + pn_rhs_anon_fun(false), pn_pos(pos), pn_next(nullptr) { @@ -512,6 +517,13 @@ class ParseNode bool isLikelyIIFE() const { return isInParens(); } void setInParens(bool enabled) { pn_parens = enabled; } + bool isDirectRHSAnonFunction() const { + return pn_rhs_anon_fun; + } + void setDirectRHSAnonFunction(bool enabled) { + pn_rhs_anon_fun = enabled; + } + TokenPos pn_pos; /* two 16-bit pairs here, for 64 bits */ ParseNode* pn_next; /* intrinsic link in parent PN_LIST */ @@ -637,14 +649,12 @@ class ParseNode 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); + return !isOp(JSOP_LAMBDA) && !isOp(JSOP_LAMBDA_ARROW) && !isOp(JSOP_DEFFUN); } /* @@ -1444,6 +1454,9 @@ FunctionFormalParametersList(ParseNode* fn, unsigned* numFormals) return argsBody->pn_head; } +bool +IsAnonymousFunctionDefinition(ParseNode* pn); + } /* namespace frontend */ } /* namespace js */ diff --git a/js/src/frontend/Parser.cpp b/js/src/frontend/Parser.cpp index 49fef2bf9..f4c02720a 100644 --- a/js/src/frontend/Parser.cpp +++ b/js/src/frontend/Parser.cpp @@ -326,10 +326,14 @@ ParseContext::init() if (fun->isNamedLambda()) { if (!namedLambdaScope_->init(this)) return false; - AddDeclaredNamePtr p = namedLambdaScope_->lookupDeclaredNameForAdd(fun->name()); + AddDeclaredNamePtr p = + namedLambdaScope_->lookupDeclaredNameForAdd(fun->explicitName()); MOZ_ASSERT(!p); - if (!namedLambdaScope_->addDeclaredName(this, p, fun->name(), DeclarationKind::Const)) + if (!namedLambdaScope_->addDeclaredName(this, p, fun->explicitName(), + DeclarationKind::Const)) + { return false; + } } if (!functionScope_->init(this)) @@ -367,7 +371,7 @@ ParseContext::removeInnerFunctionBoxesForAnnexB(JSAtom* name) { for (uint32_t i = 0; i < innerFunctionBoxesForAnnexB_->length(); i++) { if (FunctionBox* funbox = innerFunctionBoxesForAnnexB_[i]) { - if (funbox->function()->name() == name) + if (funbox->function()->explicitName() == name) innerFunctionBoxesForAnnexB_[i] = nullptr; } } @@ -465,6 +469,7 @@ FunctionBox::FunctionBox(ExclusiveContext* cx, LifoAlloc& alloc, ObjectBox* trac usesApply(false), usesThis(false), usesReturn(false), + hasRest_(false), funCxFlags() { // Functions created at parse time may be set singleton after parsing and @@ -477,7 +482,6 @@ void FunctionBox::initFromLazyFunction() { JSFunction* fun = function(); - length = fun->nargs() - fun->hasRest(); if (fun->lazyScript()->isDerivedClassConstructor()) setDerivedClassConstructor(); if (fun->lazyScript()->needsHomeObject()) @@ -492,8 +496,6 @@ FunctionBox::initStandaloneFunction(Scope* enclosingScope) // Standalone functions are Function or Generator constructors and are // always scoped to the global. MOZ_ASSERT(enclosingScope->is<GlobalScope>()); - JSFunction* fun = function(); - length = fun->nargs() - fun->hasRest(); enclosingScope_ = enclosingScope; allowNewTarget_ = true; thisBinding_ = ThisBinding::Function; @@ -836,7 +838,7 @@ Parser<ParseHandler>::reportBadReturn(Node pn, ParseReportKind kind, unsigned errnum, unsigned anonerrnum) { JSAutoByteString name; - if (JSAtom* atom = pc->functionBox()->function()->name()) { + if (JSAtom* atom = pc->functionBox()->function()->explicitName()) { if (!AtomToPrintableString(context, atom, &name)) return false; } else { @@ -2214,6 +2216,8 @@ Parser<SyntaxParseHandler>::finishFunction() lazy->setStrict(); lazy->setGeneratorKind(funbox->generatorKind()); lazy->setAsyncKind(funbox->asyncKind()); + if (funbox->hasRest()) + lazy->setHasRest(); if (funbox->isLikelyConstructorWrapper()) lazy->setLikelyConstructorWrapper(); if (funbox->isDerivedClassConstructor()) @@ -2245,13 +2249,13 @@ GetYieldHandling(GeneratorKind generatorKind, FunctionAsyncKind asyncKind) template <> ParseNode* -Parser<FullParseHandler>::standaloneFunctionBody(HandleFunction fun, - HandleScope enclosingScope, - Handle<PropertyNameVector> formals, - GeneratorKind generatorKind, - FunctionAsyncKind asyncKind, - Directives inheritedDirectives, - Directives* newDirectives) +Parser<FullParseHandler>::standaloneFunction(HandleFunction fun, + HandleScope enclosingScope, + Maybe<uint32_t> parameterListEnd, + GeneratorKind generatorKind, + FunctionAsyncKind asyncKind, + Directives inheritedDirectives, + Directives* newDirectives) { MOZ_ASSERT(checkOptionsCalled); @@ -2274,25 +2278,14 @@ Parser<FullParseHandler>::standaloneFunctionBody(HandleFunction fun, 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) + if (!functionFormalParametersAndBody(InAllowed, yieldHandling, fn, Statement, + parameterListEnd)) + { return null(); + } TokenKind tt; if (!tokenStream.getToken(&tt, TokenStream::Operand)) @@ -2303,15 +2296,7 @@ Parser<FullParseHandler>::standaloneFunctionBody(HandleFunction fun, 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()) + if (!FoldConstants(context, &fn, this)) return null(); return fn; @@ -2776,7 +2761,7 @@ Parser<ParseHandler>::functionArguments(YieldHandling yieldHandling, FunctionSyn } hasRest = true; - funbox->function()->setHasRest(); + funbox->setHasRest(); if (!tokenStream.getToken(&tt)) return false; @@ -3415,7 +3400,8 @@ template <typename ParseHandler> bool Parser<ParseHandler>::functionFormalParametersAndBody(InHandling inHandling, YieldHandling yieldHandling, - Node pn, FunctionSyntaxKind kind) + Node pn, FunctionSyntaxKind kind, + Maybe<uint32_t> parameterListEnd /* = Nothing() */) { // Given a properly initialized parse context, try to parse an actual // function without concern for conversion to strict mode, use of lazy @@ -3447,6 +3433,13 @@ Parser<ParseHandler>::functionFormalParametersAndBody(InHandling inHandling, } } + // When parsing something for new Function() we have to make sure to + // only treat a certain part of the source as a parameter list. + if (parameterListEnd.isSome() && parameterListEnd.value() != pos().begin) { + report(ParseError, false, null(), JSMSG_UNEXPECTED_PARAMLIST_END); + return false; + } + // Parse the function body. FunctionBodyType bodyType = StatementListBody; TokenKind tt; @@ -3488,8 +3481,8 @@ Parser<ParseHandler>::functionFormalParametersAndBody(InHandling inHandling, if (!body) return false; - if ((kind != Method && !IsConstructorKind(kind)) && fun->name()) { - RootedPropertyName propertyName(context, fun->name()->asPropertyName()); + if ((kind != Method && !IsConstructorKind(kind)) && fun->explicitName()) { + RootedPropertyName propertyName(context, fun->explicitName()->asPropertyName()); if (!checkStrictBinding(propertyName, handler.getPosition(pn))) return false; } @@ -3502,7 +3495,7 @@ Parser<ParseHandler>::functionFormalParametersAndBody(InHandling inHandling, report(ParseError, false, null(), JSMSG_CURLY_AFTER_BODY); return false; } - funbox->bufEnd = pos().begin + 1; + funbox->bufEnd = pos().end; } else { #if !JS_HAS_EXPR_CLOSURES MOZ_ASSERT(kind == Arrow); @@ -4348,6 +4341,8 @@ Parser<ParseHandler>::declarationPattern(Node decl, DeclarationKind declKind, To if (!init) return null(); + handler.checkAndSetIsDirectRHSAnonFunction(init); + if (forHeadKind) { // For for(;;) declarations, consistency with |for (;| parsing requires // that the ';' first be examined as Operand, even though absence of a @@ -4377,6 +4372,8 @@ Parser<ParseHandler>::initializerInNameDeclaration(Node decl, Node binding, if (!initializer) return false; + handler.checkAndSetIsDirectRHSAnonFunction(initializer); + if (forHeadKind) { if (initialDeclaration) { bool isForIn, isForOf; @@ -5074,7 +5071,7 @@ Parser<FullParseHandler>::exportDeclaration() if (!kid) return null(); - if (!checkExportedName(kid->pn_funbox->function()->name())) + if (!checkExportedName(kid->pn_funbox->function()->explicitName())) return null(); break; @@ -6681,8 +6678,6 @@ Parser<ParseHandler>::classDefinition(YieldHandling yieldHandling, return null(); } - // FIXME: Implement ES6 function "name" property semantics - // (bug 883377). RootedAtom funName(context); switch (propType) { case PropertyType::GetterNoExpressionClosure: @@ -6705,6 +6700,8 @@ Parser<ParseHandler>::classDefinition(YieldHandling yieldHandling, if (!fn) return null(); + handler.checkAndSetIsDirectRHSAnonFunction(fn); + JSOp op = JSOpFromPropertyType(propType); if (!handler.addClassMethodDefinition(classMethods, propName, fn, op, isStatic)) return null(); @@ -7764,6 +7761,9 @@ Parser<ParseHandler>::assignExpr(InHandling inHandling, YieldHandling yieldHandl return null(); } + if (kind == PNK_ASSIGN) + handler.checkAndSetIsDirectRHSAnonFunction(rhs); + return handler.newAssignment(kind, lhs, rhs, op); } @@ -9166,6 +9166,8 @@ Parser<ParseHandler>::objectLiteral(YieldHandling yieldHandling, PossibleError* if (!propExpr) return null(); + handler.checkAndSetIsDirectRHSAnonFunction(propExpr); + if (foldConstants && !FoldConstants(context, &propExpr, this)) return null(); @@ -9279,6 +9281,8 @@ Parser<ParseHandler>::objectLiteral(YieldHandling yieldHandling, PossibleError* return null(); } + handler.checkAndSetIsDirectRHSAnonFunction(rhs); + Node propExpr = handler.newAssignment(PNK_ASSIGN, lhs, rhs, JSOP_NOP); if (!propExpr) return null(); @@ -9289,8 +9293,6 @@ Parser<ParseHandler>::objectLiteral(YieldHandling yieldHandling, PossibleError* if (!abortIfSyntaxParser()) return null(); } else { - // FIXME: Implement ES6 function "name" property semantics - // (bug 883377). RootedAtom funName(context); if (!tokenStream.isCurrentTokenType(TOK_RB)) { funName = propAtom; @@ -9306,6 +9308,8 @@ Parser<ParseHandler>::objectLiteral(YieldHandling yieldHandling, PossibleError* if (!fn) return null(); + handler.checkAndSetIsDirectRHSAnonFunction(fn); + JSOp op = JSOpFromPropertyType(propType); if (!handler.addObjectMethodDefinition(literal, propName, fn, op)) return null(); diff --git a/js/src/frontend/Parser.h b/js/src/frontend/Parser.h index 0ad4d56a0..b58b021cd 100644 --- a/js/src/frontend/Parser.h +++ b/js/src/frontend/Parser.h @@ -1020,12 +1020,12 @@ class Parser final : private JS::AutoGCRooter, public StrictModeGetter // Parse a module. Node moduleBody(ModuleSharedContext* modulesc); - // Parse a function, given only its body. Used for the Function and - // Generator constructors. - Node standaloneFunctionBody(HandleFunction fun, HandleScope enclosingScope, - Handle<PropertyNameVector> formals, - GeneratorKind generatorKind, FunctionAsyncKind asyncKind, - Directives inheritedDirectives, Directives* newDirectives); + // Parse a function, used for the Function, GeneratorFunction, and + // AsyncFunction constructors. + Node standaloneFunction(HandleFunction fun, HandleScope enclosingScope, + mozilla::Maybe<uint32_t> parameterListEnd, + GeneratorKind generatorKind, FunctionAsyncKind asyncKind, + Directives inheritedDirectives, Directives* newDirectives); // Parse a function, given only its arguments and body. Used for lazily // parsed functions. @@ -1041,7 +1041,9 @@ class Parser final : private JS::AutoGCRooter, public StrictModeGetter // 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); + Node pn, FunctionSyntaxKind kind, + mozilla::Maybe<uint32_t> parameterListEnd = mozilla::Nothing()); + // Determine whether |yield| is a valid name in the current context, or // whether it's prohibited due to strictness, JS version, or occurrence diff --git a/js/src/frontend/SharedContext.h b/js/src/frontend/SharedContext.h index 39df47c20..a6ac542f6 100644 --- a/js/src/frontend/SharedContext.h +++ b/js/src/frontend/SharedContext.h @@ -471,6 +471,7 @@ class FunctionBox : public ObjectBox, public SharedContext bool usesApply:1; /* contains an f.apply() call */ bool usesThis:1; /* contains 'this' */ bool usesReturn:1; /* contains a 'return' statement */ + bool hasRest_:1; /* has rest parameter */ FunctionContextFlags funCxFlags; @@ -539,6 +540,11 @@ class FunctionBox : public ObjectBox, public SharedContext bool isAsync() const { return asyncKind() == AsyncFunction; } bool isArrow() const { return function()->isArrow(); } + bool hasRest() const { return hasRest_; } + void setHasRest() { + hasRest_ = true; + } + 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 @@ -567,7 +573,7 @@ class FunctionBox : public ObjectBox, public SharedContext void setHasInnerFunctions() { funCxFlags.hasInnerFunctions = true; } bool hasSimpleParameterList() const { - return !function()->hasRest() && !hasParameterExprs && !hasDestructuringArgs; + return !hasRest() && !hasParameterExprs && !hasDestructuringArgs; } bool hasMappedArgsObj() const { diff --git a/js/src/frontend/SyntaxParseHandler.h b/js/src/frontend/SyntaxParseHandler.h index 75c7e3333..b7f00605b 100644 --- a/js/src/frontend/SyntaxParseHandler.h +++ b/js/src/frontend/SyntaxParseHandler.h @@ -339,6 +339,9 @@ class SyntaxParseHandler Node catchGuard, Node catchBody) { return true; } MOZ_MUST_USE bool setLastFunctionFormalParameterDefault(Node funcpn, Node pn) { return true; } + + void checkAndSetIsDirectRHSAnonFunction(Node pn) {} + Node newFunctionDefinition() { return NodeFunctionDefinition; } bool setComprehensionLambdaBody(Node pn, Node body) { return true; } void setFunctionFormalParametersAndBody(Node pn, Node kid) {} diff --git a/js/src/frontend/TokenStream.cpp b/js/src/frontend/TokenStream.cpp index c166ed414..179a7c244 100644 --- a/js/src/frontend/TokenStream.cpp +++ b/js/src/frontend/TokenStream.cpp @@ -118,13 +118,57 @@ IsIdentifier(const CharT* chars, size_t length) return true; } +static uint32_t +GetSingleCodePoint(const char16_t** p, const char16_t* end) +{ + uint32_t codePoint; + if (MOZ_UNLIKELY(unicode::IsLeadSurrogate(**p)) && *p + 1 < end) { + char16_t lead = **p; + char16_t maybeTrail = *(*p + 1); + if (unicode::IsTrailSurrogate(maybeTrail)) { + *p += 2; + return unicode::UTF16Decode(lead, maybeTrail); + } + } + + codePoint = **p; + (*p)++; + return codePoint; +} + +static bool +IsIdentifierMaybeNonBMP(const char16_t* chars, size_t length) +{ + if (IsIdentifier(chars, length)) + return true; + + if (length == 0) + return false; + + const char16_t* p = chars; + const char16_t* end = chars + length; + uint32_t codePoint; + + codePoint = GetSingleCodePoint(&p, end); + if (!unicode::IsIdentifierStart(codePoint)) + return false; + + while (p < end) { + codePoint = GetSingleCodePoint(&p, end); + if (!unicode::IsIdentifierPart(codePoint)) + return false; + } + + return true; +} + bool frontend::IsIdentifier(JSLinearString* str) { JS::AutoCheckCannotGC nogc; return str->hasLatin1Chars() ? ::IsIdentifier(str->latin1Chars(nogc), str->length()) - : ::IsIdentifier(str->twoByteChars(nogc), str->length()); + : ::IsIdentifierMaybeNonBMP(str->twoByteChars(nogc), str->length()); } bool @@ -993,6 +1037,21 @@ IsTokenSane(Token* tp) #endif bool +TokenStream::matchTrailForLeadSurrogate(char16_t lead, char16_t* trail, uint32_t* codePoint) +{ + int32_t maybeTrail = getCharIgnoreEOL(); + if (!unicode::IsTrailSurrogate(maybeTrail)) { + ungetCharIgnoreEOL(maybeTrail); + return false; + } + + if (trail) + *trail = maybeTrail; + *codePoint = unicode::UTF16Decode(lead, maybeTrail); + return true; +} + +bool TokenStream::putIdentInTokenbuf(const char16_t* identStart) { int32_t c; @@ -1003,11 +1062,39 @@ TokenStream::putIdentInTokenbuf(const char16_t* identStart) tokenbuf.clear(); for (;;) { c = getCharIgnoreEOL(); + + if (MOZ_UNLIKELY(unicode::IsLeadSurrogate(c))) { + char16_t trail; + uint32_t codePoint; + if (matchTrailForLeadSurrogate(c, &trail, &codePoint)) { + if (!unicode::IsIdentifierPart(codePoint)) + break; + + if (!tokenbuf.append(c) || !tokenbuf.append(trail)) { + userbuf.setAddressOfNextRawChar(tmp); + return false; + } + continue; + } + } + if (!unicode::IsIdentifierPart(char16_t(c))) { if (c != '\\' || !matchUnicodeEscapeIdent(&qc)) break; + + if (MOZ_UNLIKELY(unicode::IsSupplementary(qc))) { + char16_t lead, trail; + unicode::UTF16Encode(qc, &lead, &trail); + if (!tokenbuf.append(lead) || !tokenbuf.append(trail)) { + userbuf.setAddressOfNextRawChar(tmp); + return false; + } + continue; + } + c = qc; } + if (!tokenbuf.append(c)) { userbuf.setAddressOfNextRawChar(tmp); return false; @@ -1168,12 +1255,23 @@ TokenStream::getTokenInternal(TokenKind* ttp, Modifier modifier) static_assert('_' < 128, "IdentifierStart contains '_', but as !IsUnicodeIDStart('_'), " "ensure that '_' is never handled here"); - if (unicode::IsUnicodeIDStart(c)) { + if (unicode::IsUnicodeIDStart(char16_t(c))) { identStart = userbuf.addressOfNextRawChar() - 1; hadUnicodeEscape = false; goto identifier; } + if (MOZ_UNLIKELY(unicode::IsLeadSurrogate(c))) { + uint32_t codePoint; + if (matchTrailForLeadSurrogate(c, nullptr, &codePoint) && + unicode::IsUnicodeIDStart(codePoint)) + { + identStart = userbuf.addressOfNextRawChar() - 2; + hadUnicodeEscape = false; + goto identifier; + } + } + goto badchar; } @@ -1224,6 +1322,17 @@ TokenStream::getTokenInternal(TokenKind* ttp, Modifier modifier) c = getCharIgnoreEOL(); if (c == EOF) break; + + if (MOZ_UNLIKELY(unicode::IsLeadSurrogate(c))) { + uint32_t codePoint; + if (matchTrailForLeadSurrogate(c, nullptr, &codePoint)) { + if (!unicode::IsIdentifierPart(codePoint)) + break; + + continue; + } + } + if (!unicode::IsIdentifierPart(char16_t(c))) { if (c != '\\' || !matchUnicodeEscapeIdent(&qc)) break; @@ -1318,9 +1427,21 @@ TokenStream::getTokenInternal(TokenKind* ttp, Modifier modifier) } ungetCharIgnoreEOL(c); - if (c != EOF && unicode::IsIdentifierStart(char16_t(c))) { - reportError(JSMSG_IDSTART_AFTER_NUMBER); - goto error; + if (c != EOF) { + if (unicode::IsIdentifierStart(char16_t(c))) { + reportError(JSMSG_IDSTART_AFTER_NUMBER); + goto error; + } + + if (MOZ_UNLIKELY(unicode::IsLeadSurrogate(c))) { + uint32_t codePoint; + if (matchTrailForLeadSurrogate(c, nullptr, &codePoint) && + unicode::IsIdentifierStart(codePoint)) + { + reportError(JSMSG_IDSTART_AFTER_NUMBER); + goto error; + } + } } // Unlike identifiers and strings, numbers cannot contain escaped @@ -1425,9 +1546,21 @@ TokenStream::getTokenInternal(TokenKind* ttp, Modifier modifier) } ungetCharIgnoreEOL(c); - if (c != EOF && unicode::IsIdentifierStart(char16_t(c))) { - reportError(JSMSG_IDSTART_AFTER_NUMBER); - goto error; + if (c != EOF) { + if (unicode::IsIdentifierStart(char16_t(c))) { + reportError(JSMSG_IDSTART_AFTER_NUMBER); + goto error; + } + + if (MOZ_UNLIKELY(unicode::IsLeadSurrogate(c))) { + uint32_t codePoint; + if (matchTrailForLeadSurrogate(c, nullptr, &codePoint) && + unicode::IsIdentifierStart(codePoint)) + { + reportError(JSMSG_IDSTART_AFTER_NUMBER); + goto error; + } + } } double dval; diff --git a/js/src/frontend/TokenStream.h b/js/src/frontend/TokenStream.h index 29dcead62..5d6b4b795 100644 --- a/js/src/frontend/TokenStream.h +++ b/js/src/frontend/TokenStream.h @@ -952,6 +952,7 @@ class MOZ_STACK_CLASS TokenStream uint32_t peekExtendedUnicodeEscape(uint32_t* codePoint); uint32_t matchUnicodeEscapeIdStart(uint32_t* codePoint); bool matchUnicodeEscapeIdent(uint32_t* codePoint); + bool matchTrailForLeadSurrogate(char16_t lead, char16_t* trail, uint32_t* codePoint); bool peekChars(int n, char16_t* cp); MOZ_MUST_USE bool getDirectives(bool isMultiline, bool shouldWarnDeprecated); |