/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- * vim: set ts=8 sts=2 et sw=2 tw=80: * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "frontend/ForOfLoopControl.h" #include "frontend/BytecodeEmitter.h" // BytecodeEmitter #include "frontend/IfEmitter.h" // InternalIfEmitter #include "vm/CompletionKind.h" // CompletionKind #include "vm/Opcodes.h" // JSOp using namespace js; using namespace js::frontend; ForOfLoopControl::ForOfLoopControl(BytecodeEmitter* bce, int32_t iterDepth, SelfHostedIter selfHostedIter, IteratorKind iterKind) : LoopControl(bce, StatementKind::ForOfLoop), iterDepth_(iterDepth), numYieldsAtBeginCodeNeedingIterClose_(UINT32_MAX), selfHostedIter_(selfHostedIter), iterKind_(iterKind) {} bool ForOfLoopControl::emitBeginCodeNeedingIteratorClose(BytecodeEmitter* bce) { tryCatch_.emplace(bce, TryEmitter::Kind::TryCatch, TryEmitter::ControlKind::NonSyntactic); if (!tryCatch_->emitTry()) { return false; } MOZ_ASSERT(numYieldsAtBeginCodeNeedingIterClose_ == UINT32_MAX); numYieldsAtBeginCodeNeedingIterClose_ = bce->bytecodeSection().numYields(); return true; } bool ForOfLoopControl::emitEndCodeNeedingIteratorClose(BytecodeEmitter* bce) { if (!tryCatch_->emitCatch()) { // [stack] ITER ... EXCEPTION return false; } unsigned slotFromTop = bce->bytecodeSection().stackDepth() - iterDepth_; if (!bce->emitDupAt(slotFromTop)) { // [stack] ITER ... EXCEPTION ITER return false; } if (!emitIteratorCloseInInnermostScopeWithTryNote(bce, CompletionKind::Throw)) { return false; // ITER ... EXCEPTION } if (!bce->emit1(JSOp::Throw)) { // [stack] ITER ... return false; } // If any yields were emitted, then this for-of loop is inside a star // generator and must handle the case of Generator.return. Like in // yield*, it is handled with a finally block. If the generator is // closing, then the exception/resumeindex value (second value on // the stack) will be a magic JS_GENERATOR_CLOSING value. // TODO: Refactor this to eliminate the swaps. uint32_t numYieldsEmitted = bce->bytecodeSection().numYields(); if (numYieldsEmitted > numYieldsAtBeginCodeNeedingIterClose_) { if (!tryCatch_->emitFinally()) { return false; } // [stack] ITER ... FVALUE FTYPE InternalIfEmitter ifGeneratorClosing(bce); if (!bce->emit1(JSOp::Swap)) { // [stack] ITER ... FTYPE FVALUE return false; } if (!bce->emit1(JSOp::IsGenClosing)) { // [stack] ITER ... FTYPE FVALUE CLOSING return false; } if (!ifGeneratorClosing.emitThen()) { // [stack] ITER ... FTYPE FVALUE return false; } if (!bce->emitDupAt(slotFromTop + 1)) { // [stack] ITER ... FTYPE FVALUE ITER return false; } if (!emitIteratorCloseInInnermostScopeWithTryNote(bce, CompletionKind::Normal)) { // [stack] ITER ... FTYPE FVALUE return false; } if (!ifGeneratorClosing.emitEnd()) { // [stack] ITER ... FTYPE FVALUE return false; } if (!bce->emit1(JSOp::Swap)) { // [stack] ITER ... FVALUE FTYPE return false; } } if (!tryCatch_->emitEnd()) { return false; } tryCatch_.reset(); numYieldsAtBeginCodeNeedingIterClose_ = UINT32_MAX; return true; } bool ForOfLoopControl::emitIteratorCloseInInnermostScopeWithTryNote( BytecodeEmitter* bce, CompletionKind completionKind /* = CompletionKind::Normal */) { BytecodeOffset start = bce->bytecodeSection().offset(); if (!emitIteratorCloseInScope(bce, *bce->innermostEmitterScope(), completionKind)) { return false; } BytecodeOffset end = bce->bytecodeSection().offset(); return bce->addTryNote(TryNoteKind::ForOfIterClose, 0, start, end); } bool ForOfLoopControl::emitIteratorCloseInScope( BytecodeEmitter* bce, EmitterScope& currentScope, CompletionKind completionKind /* = CompletionKind::Normal */) { return bce->emitIteratorCloseInScope(currentScope, iterKind_, completionKind, selfHostedIter_); } // Since we're in the middle of emitting code that will leave // |bce->innermostEmitterScope()|, passing the innermost emitter scope to // emitIteratorCloseInScope and looking up .generator there would be very, // very wrong. We'd find .generator in the function environment, and we'd // compute a NameLocation with the correct slot, but we'd compute a // hop-count to the function environment that was too big. At runtime we'd // either crash, or we'd find a user-controllable value in that slot, and // Very Bad Things would ensue as we reinterpreted that value as an // iterator. bool ForOfLoopControl::emitPrepareForNonLocalJumpFromScope( BytecodeEmitter* bce, EmitterScope& currentScope, bool isTarget, BytecodeOffset* tryNoteStart) { // Pop unnecessary value from the stack. Effectively this means // leaving try-catch block. However, the performing IteratorClose can // reach the depth for try-catch, and effectively re-enter the // try-catch block. if (!bce->emit1(JSOp::Pop)) { // [stack] NEXT ITER return false; } // Pop the iterator's next method. if (!bce->emit1(JSOp::Swap)) { // [stack] ITER NEXT return false; } if (!bce->emit1(JSOp::Pop)) { // [stack] ITER return false; } if (!bce->emit1(JSOp::Dup)) { // [stack] ITER ITER return false; } *tryNoteStart = bce->bytecodeSection().offset(); if (!emitIteratorCloseInScope(bce, currentScope, CompletionKind::Normal)) { // [stack] ITER return false; } if (isTarget) { // At the level of the target block, there's bytecode after the // loop that will pop the next method, the iterator, and the // value, so push two undefineds to balance the stack. if (!bce->emit1(JSOp::Undefined)) { // [stack] ITER UNDEF return false; } if (!bce->emit1(JSOp::Undefined)) { // [stack] ITER UNDEF UNDEF return false; } } else { if (!bce->emit1(JSOp::Pop)) { // [stack] return false; } } return true; }