ForOfIterator.cpp (4648B)
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 "js/ForOfIterator.h" 8 9 #include "js/Exception.h" 10 #include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_* 11 #include "vm/Interpreter.h" 12 #include "vm/Iteration.h" 13 #include "vm/JSContext.h" 14 #include "vm/JSObject.h" 15 16 #include "vm/JSContext-inl.h" 17 #include "vm/JSObject-inl.h" 18 19 using namespace js; 20 using JS::ForOfIterator; 21 22 bool ForOfIterator::init(HandleValue iterable, 23 NonIterableBehavior nonIterableBehavior) { 24 JSContext* cx = cx_; 25 RootedObject iterableObj(cx, ToObject(cx, iterable)); 26 if (!iterableObj) { 27 return false; 28 } 29 30 MOZ_ASSERT(index == NOT_ARRAY); 31 32 if (IsArrayWithDefaultIterator<MustBePacked::No>(iterableObj, cx)) { 33 // Array is optimizable. 34 index = 0; 35 iterator = iterableObj; 36 nextMethod.setUndefined(); 37 return true; 38 } 39 40 MOZ_ASSERT(index == NOT_ARRAY); 41 42 RootedValue callee(cx); 43 RootedId iteratorId(cx, PropertyKey::Symbol(cx->wellKnownSymbols().iterator)); 44 if (!GetProperty(cx, iterableObj, iterable, iteratorId, &callee)) { 45 return false; 46 } 47 48 // If obj[@@iterator] is undefined and we were asked to allow non-iterables, 49 // bail out now without setting iterator. This will make valueIsIterable(), 50 // which our caller should check, return false. 51 if (nonIterableBehavior == AllowNonIterable && callee.isUndefined()) { 52 return true; 53 } 54 55 // Throw if obj[@@iterator] isn't callable. 56 // js::Invoke is about to check for this kind of error anyway, but it would 57 // throw an inscrutable error message about |method| rather than this nice 58 // one about |obj|. 59 if (!callee.isObject() || !callee.toObject().isCallable()) { 60 UniqueChars bytes = 61 DecompileValueGenerator(cx, JSDVG_SEARCH_STACK, iterable, nullptr); 62 if (!bytes) { 63 return false; 64 } 65 JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_NOT_ITERABLE, 66 bytes.get()); 67 return false; 68 } 69 70 RootedValue res(cx); 71 if (!js::Call(cx, callee, iterable, &res)) { 72 return false; 73 } 74 75 if (!res.isObject()) { 76 return ThrowCheckIsObject(cx, CheckIsObjectKind::GetIterator); 77 } 78 79 RootedObject iteratorObj(cx, &res.toObject()); 80 if (!GetProperty(cx, iteratorObj, iteratorObj, cx->names().next, &res)) { 81 return false; 82 } 83 84 iterator = iteratorObj; 85 nextMethod = res; 86 return true; 87 } 88 89 inline bool ForOfIterator::nextFromOptimizedArray(MutableHandleValue vp, 90 bool* done) { 91 MOZ_ASSERT(index != NOT_ARRAY); 92 93 if (!CheckForInterrupt(cx_)) { 94 return false; 95 } 96 97 ArrayObject* arr = &iterator->as<ArrayObject>(); 98 99 if (index >= arr->length()) { 100 vp.setUndefined(); 101 *done = true; 102 return true; 103 } 104 *done = false; 105 106 // Try to get array element via direct access. 107 if (index < arr->getDenseInitializedLength()) { 108 vp.set(arr->getDenseElement(index)); 109 if (!vp.isMagic(JS_ELEMENTS_HOLE)) { 110 ++index; 111 return true; 112 } 113 } 114 115 return GetElement(cx_, iterator, iterator, index++, vp); 116 } 117 118 bool ForOfIterator::next(MutableHandleValue vp, bool* done) { 119 MOZ_ASSERT(iterator); 120 if (index != NOT_ARRAY) { 121 return nextFromOptimizedArray(vp, done); 122 } 123 124 RootedValue v(cx_); 125 if (!js::Call(cx_, nextMethod, iterator, &v)) { 126 return false; 127 } 128 129 if (!v.isObject()) { 130 return ThrowCheckIsObject(cx_, CheckIsObjectKind::IteratorNext); 131 } 132 133 RootedObject resultObj(cx_, &v.toObject()); 134 if (!GetProperty(cx_, resultObj, resultObj, cx_->names().done, &v)) { 135 return false; 136 } 137 138 *done = ToBoolean(v); 139 if (*done) { 140 vp.setUndefined(); 141 return true; 142 } 143 144 return GetProperty(cx_, resultObj, resultObj, cx_->names().value, vp); 145 } 146 147 void ForOfIterator::closeThrow() { 148 MOZ_ASSERT(iterator); 149 150 // Don't handle uncatchable exceptions to match `for-of` bytecode behavior, 151 // which also doesn't run IteratorClose when an interrupt was requested. 152 if (!cx_->isExceptionPending()) { 153 return; 154 } 155 156 // Save the current exception state. The destructor restores the saved 157 // exception state, unless there's a new pending exception. 158 JS::AutoSaveExceptionState savedExc(cx_); 159 160 // Perform IteratorClose on the iterator. 161 MOZ_ALWAYS_TRUE(CloseIterOperation(cx_, iterator, CompletionKind::Throw)); 162 163 // CloseIterOperation clears any pending exception. 164 MOZ_ASSERT(!cx_->isExceptionPending()); 165 }