ForOfLoopControl.cpp (8713B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- 2 * vim: set ts=8 sts=2 et sw=2 tw=80: 3 * This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #include "frontend/ForOfLoopControl.h" 8 9 #include "frontend/BytecodeEmitter.h" // BytecodeEmitter 10 #include "frontend/EmitterScope.h" // EmitterScope 11 #include "frontend/IfEmitter.h" // InternalIfEmitter 12 #include "vm/CompletionKind.h" // CompletionKind 13 #include "vm/Opcodes.h" // JSOp 14 15 using namespace js; 16 using namespace js::frontend; 17 18 ForOfLoopControl::ForOfLoopControl(BytecodeEmitter* bce, int32_t iterDepth, 19 SelfHostedIter selfHostedIter, 20 IteratorKind iterKind) 21 : LoopControl(bce, StatementKind::ForOfLoop), 22 iterDepth_(iterDepth), 23 numYieldsAtBeginCodeNeedingIterClose_(UINT32_MAX), 24 selfHostedIter_(selfHostedIter), 25 iterKind_(iterKind) {} 26 27 bool ForOfLoopControl::emitBeginCodeNeedingIteratorClose(BytecodeEmitter* bce) { 28 tryCatch_.emplace(bce, TryEmitter::Kind::TryCatch, 29 TryEmitter::ControlKind::NonSyntactic); 30 31 if (!tryCatch_->emitTry()) { 32 return false; 33 } 34 35 MOZ_ASSERT(numYieldsAtBeginCodeNeedingIterClose_ == UINT32_MAX); 36 numYieldsAtBeginCodeNeedingIterClose_ = bce->bytecodeSection().numYields(); 37 38 return true; 39 } 40 41 #ifdef ENABLE_EXPLICIT_RESOURCE_MANAGEMENT 42 bool ForOfLoopControl::prepareForForOfLoopIteration( 43 BytecodeEmitter* bce, const EmitterScope* headLexicalEmitterScope, 44 bool hasAwaitUsing) { 45 MOZ_ASSERT(headLexicalEmitterScope); 46 if (headLexicalEmitterScope->hasDisposables()) { 47 forOfDisposalEmitter_.emplace(bce, hasAwaitUsing); 48 return forOfDisposalEmitter_->prepareForForOfLoopIteration(); 49 } 50 return true; 51 } 52 #endif 53 54 bool ForOfLoopControl::emitEndCodeNeedingIteratorClose(BytecodeEmitter* bce) { 55 if (!tryCatch_->emitCatch(TryEmitter::ExceptionStack::Yes)) { 56 // [stack] ITER ... EXCEPTION STACK 57 return false; 58 } 59 60 #ifdef ENABLE_EXPLICIT_RESOURCE_MANAGEMENT 61 // Explicit Resource Management Proposal 62 // https://arai-a.github.io/ecma262-compare/?pr=3000&id=sec-runtime-semantics-forin-div-ofbodyevaluation-lhs-stmt-iterator-lhskind-labelset 63 // Step 9.i.i.1 Set result to 64 // Completion(DisposeResources(iterationEnv.[[DisposeCapability]], result)). 65 if (forOfDisposalEmitter_.isSome()) { 66 if (!forOfDisposalEmitter_->emitEnd()) { 67 // [stack] ITER ... EXCEPTION STACK 68 return false; 69 } 70 } 71 #endif 72 73 unsigned slotFromTop = bce->bytecodeSection().stackDepth() - iterDepth_; 74 if (!bce->emitDupAt(slotFromTop)) { 75 // [stack] ITER ... EXCEPTION STACK ITER 76 return false; 77 } 78 79 if (!emitIteratorCloseInInnermostScopeWithTryNote(bce, 80 CompletionKind::Throw)) { 81 return false; // ITER ... EXCEPTION STACK 82 } 83 84 if (!bce->emit1(JSOp::ThrowWithStack)) { 85 // [stack] ITER ... 86 return false; 87 } 88 89 // If any yields were emitted, then this for-of loop is inside a star 90 // generator and must handle the case of Generator.return. Like in 91 // yield*, it is handled with a finally block. If the generator is 92 // closing, then the exception/resumeindex value (third value on 93 // the stack) will be a magic JS_GENERATOR_CLOSING value. 94 // TODO: Refactor this to eliminate the swaps. 95 uint32_t numYieldsEmitted = bce->bytecodeSection().numYields(); 96 if (numYieldsEmitted > numYieldsAtBeginCodeNeedingIterClose_) { 97 if (!tryCatch_->emitFinally()) { 98 return false; 99 } 100 // [stack] ITER ... FVALUE FSTACK FTHROWING 101 InternalIfEmitter ifGeneratorClosing(bce); 102 if (!bce->emitPickN(2)) { 103 // [stack] ITER ... FSTACK FTHROWING FVALUE 104 return false; 105 } 106 if (!bce->emit1(JSOp::IsGenClosing)) { 107 // [stack] ITER ... FSTACK FTHROWING FVALUE CLOSING 108 return false; 109 } 110 if (!ifGeneratorClosing.emitThen()) { 111 // [stack] ITER ... FSTACK FTHROWING FVALUE 112 return false; 113 } 114 if (!bce->emitDupAt(slotFromTop + 1)) { 115 // [stack] ITER ... FSTACK FTHROWING FVALUE ITER 116 return false; 117 } 118 if (!emitIteratorCloseInInnermostScopeWithTryNote(bce, 119 CompletionKind::Normal)) { 120 // [stack] ITER ... FSTACK FTHROWING FVALUE 121 return false; 122 } 123 if (!ifGeneratorClosing.emitEnd()) { 124 // [stack] ITER ... FSTACK FTHROWING FVALUE 125 return false; 126 } 127 if (!bce->emitUnpickN(2)) { 128 // [stack] ITER ... FVALUE FSTACK FTHROWING 129 return false; 130 } 131 } 132 133 if (!tryCatch_->emitEnd()) { 134 return false; 135 } 136 137 tryCatch_.reset(); 138 numYieldsAtBeginCodeNeedingIterClose_ = UINT32_MAX; 139 140 return true; 141 } 142 143 bool ForOfLoopControl::emitIteratorCloseInInnermostScopeWithTryNote( 144 BytecodeEmitter* bce, CompletionKind completionKind) { 145 BytecodeOffset start = bce->bytecodeSection().offset(); 146 if (!emitIteratorCloseInScope(bce, *bce->innermostEmitterScope(), 147 completionKind)) { 148 return false; 149 } 150 BytecodeOffset end = bce->bytecodeSection().offset(); 151 return bce->addTryNote(TryNoteKind::ForOfIterClose, 0, start, end); 152 } 153 154 bool ForOfLoopControl::emitIteratorCloseInScope(BytecodeEmitter* bce, 155 EmitterScope& currentScope, 156 CompletionKind completionKind) { 157 return bce->emitIteratorCloseInScope(currentScope, iterKind_, completionKind, 158 selfHostedIter_); 159 } 160 161 // Since we're in the middle of emitting code that will leave 162 // |bce->innermostEmitterScope()|, passing the innermost emitter scope to 163 // emitIteratorCloseInScope and looking up .generator there would be very, 164 // very wrong. We'd find .generator in the function environment, and we'd 165 // compute a NameLocation with the correct slot, but we'd compute a 166 // hop-count to the function environment that was too big. At runtime we'd 167 // either crash, or we'd find a user-controllable value in that slot, and 168 // Very Bad Things would ensue as we reinterpreted that value as an 169 // iterator. 170 bool ForOfLoopControl::emitPrepareForNonLocalJumpFromScope( 171 BytecodeEmitter* bce, EmitterScope& currentScope, bool isTarget, 172 BytecodeOffset* tryNoteStart) { 173 // Pop unnecessary value from the stack. Effectively this means 174 // leaving try-catch block. However, the performing IteratorClose can 175 // reach the depth for try-catch, and effectively re-enter the 176 // try-catch block. 177 if (!bce->emit1(JSOp::Pop)) { 178 // [stack] NEXT ITER 179 return false; 180 } 181 182 // Pop the iterator's next method. 183 if (!bce->emit1(JSOp::Swap)) { 184 // [stack] ITER NEXT 185 return false; 186 } 187 if (!bce->emit1(JSOp::Pop)) { 188 // [stack] ITER 189 return false; 190 } 191 192 #ifdef ENABLE_EXPLICIT_RESOURCE_MANAGEMENT 193 // Explicit Resource Management Proposal 194 // https://arai-a.github.io/ecma262-compare/?pr=3000&id=sec-runtime-semantics-forin-div-ofbodyevaluation-lhs-stmt-iterator-lhskind-labelset 195 // Step 9.k.i. Set result to 196 // Completion(DisposeResources(iterationEnv.[[DisposeCapability]], result)). 197 NonLocalIteratorCloseUsingEmitter disposeBeforeIterClose(bce); 198 199 if (!disposeBeforeIterClose.prepareForIteratorClose(currentScope)) { 200 // [stack] EXC-DISPOSE? DISPOSE-THROWING? ITER 201 return false; 202 } 203 #endif 204 205 if (!bce->emit1(JSOp::Dup)) { 206 // [stack] EXC-DISPOSE? DISPOSE-THROWING? ITER ITER 207 return false; 208 } 209 210 *tryNoteStart = bce->bytecodeSection().offset(); 211 if (!emitIteratorCloseInScope(bce, currentScope, CompletionKind::Normal)) { 212 // [stack] EXC-DISPOSE? DISPOSE-THROWING? ITER 213 return false; 214 } 215 216 #ifdef ENABLE_EXPLICIT_RESOURCE_MANAGEMENT 217 if (!disposeBeforeIterClose.emitEnd()) { 218 // [stack] ITER 219 return false; 220 } 221 #endif 222 223 if (isTarget) { 224 // At the level of the target block, there's bytecode after the 225 // loop that will pop the next method, the iterator, and the 226 // value, so push two undefineds to balance the stack. 227 if (!bce->emit1(JSOp::Undefined)) { 228 // [stack] ITER UNDEF 229 return false; 230 } 231 if (!bce->emit1(JSOp::Undefined)) { 232 // [stack] ITER UNDEF UNDEF 233 return false; 234 } 235 } else { 236 if (!bce->emit1(JSOp::Pop)) { 237 // [stack] 238 return false; 239 } 240 } 241 242 return true; 243 }