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