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 #include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_*
9 #include "vm/Interpreter.h"
10 #include "vm/JSContext.h"
11 #include "vm/JSObject.h"
12 #include "vm/PIC.h"
13 #include "vm/Realm.h"
14
15 #include "vm/JSObject-inl.h"
16
17 using namespace js;
18 using JS::ForOfIterator;
19
init(HandleValue iterable,NonIterableBehavior nonIterableBehavior)20 bool ForOfIterator::init(HandleValue iterable,
21 NonIterableBehavior nonIterableBehavior) {
22 JSContext* cx = cx_;
23 RootedObject iterableObj(cx, ToObject(cx, iterable));
24 if (!iterableObj) {
25 return false;
26 }
27
28 MOZ_ASSERT(index == NOT_ARRAY);
29
30 // Check the PIC first for a match.
31 if (iterableObj->is<ArrayObject>()) {
32 ForOfPIC::Chain* stubChain = ForOfPIC::getOrCreate(cx);
33 if (!stubChain) {
34 return false;
35 }
36
37 bool optimized;
38 if (!stubChain->tryOptimizeArray(cx, iterableObj.as<ArrayObject>(),
39 &optimized)) {
40 return false;
41 }
42
43 if (optimized) {
44 // Got optimized stub. Array is optimizable.
45 index = 0;
46 iterator = iterableObj;
47 nextMethod.setUndefined();
48 return true;
49 }
50 }
51
52 MOZ_ASSERT(index == NOT_ARRAY);
53
54 RootedValue callee(cx);
55 RootedId iteratorId(cx, SYMBOL_TO_JSID(cx->wellKnownSymbols().iterator));
56 if (!GetProperty(cx, iterableObj, iterable, iteratorId, &callee)) {
57 return false;
58 }
59
60 // If obj[@@iterator] is undefined and we were asked to allow non-iterables,
61 // bail out now without setting iterator. This will make valueIsIterable(),
62 // which our caller should check, return false.
63 if (nonIterableBehavior == AllowNonIterable && callee.isUndefined()) {
64 return true;
65 }
66
67 // Throw if obj[@@iterator] isn't callable.
68 // js::Invoke is about to check for this kind of error anyway, but it would
69 // throw an inscrutable error message about |method| rather than this nice
70 // one about |obj|.
71 if (!callee.isObject() || !callee.toObject().isCallable()) {
72 UniqueChars bytes =
73 DecompileValueGenerator(cx, JSDVG_SEARCH_STACK, iterable, nullptr);
74 if (!bytes) {
75 return false;
76 }
77 JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_NOT_ITERABLE,
78 bytes.get());
79 return false;
80 }
81
82 RootedValue res(cx);
83 if (!js::Call(cx, callee, iterable, &res)) {
84 return false;
85 }
86
87 if (!res.isObject()) {
88 return ThrowCheckIsObject(cx, CheckIsObjectKind::GetIterator);
89 }
90
91 RootedObject iteratorObj(cx, &res.toObject());
92 if (!GetProperty(cx, iteratorObj, iteratorObj, cx->names().next, &res)) {
93 return false;
94 }
95
96 iterator = iteratorObj;
97 nextMethod = res;
98 return true;
99 }
100
nextFromOptimizedArray(MutableHandleValue vp,bool * done)101 inline bool ForOfIterator::nextFromOptimizedArray(MutableHandleValue vp,
102 bool* done) {
103 MOZ_ASSERT(index != NOT_ARRAY);
104
105 if (!CheckForInterrupt(cx_)) {
106 return false;
107 }
108
109 ArrayObject* arr = &iterator->as<ArrayObject>();
110
111 if (index >= arr->length()) {
112 vp.setUndefined();
113 *done = true;
114 return true;
115 }
116 *done = false;
117
118 // Try to get array element via direct access.
119 if (index < arr->getDenseInitializedLength()) {
120 vp.set(arr->getDenseElement(index));
121 if (!vp.isMagic(JS_ELEMENTS_HOLE)) {
122 ++index;
123 return true;
124 }
125 }
126
127 return GetElement(cx_, iterator, iterator, index++, vp);
128 }
129
next(MutableHandleValue vp,bool * done)130 bool ForOfIterator::next(MutableHandleValue vp, bool* done) {
131 MOZ_ASSERT(iterator);
132 if (index != NOT_ARRAY) {
133 return nextFromOptimizedArray(vp, done);
134 }
135
136 RootedValue v(cx_);
137 if (!js::Call(cx_, nextMethod, iterator, &v)) {
138 return false;
139 }
140
141 if (!v.isObject()) {
142 return ThrowCheckIsObject(cx_, CheckIsObjectKind::IteratorNext);
143 }
144
145 RootedObject resultObj(cx_, &v.toObject());
146 if (!GetProperty(cx_, resultObj, resultObj, cx_->names().done, &v)) {
147 return false;
148 }
149
150 *done = ToBoolean(v);
151 if (*done) {
152 vp.setUndefined();
153 return true;
154 }
155
156 return GetProperty(cx_, resultObj, resultObj, cx_->names().value, vp);
157 }
158
159 // ES 2017 draft 0f10dba4ad18de92d47d421f378233a2eae8f077 7.4.6.
160 // When completion.[[Type]] is throw.
closeThrow()161 void ForOfIterator::closeThrow() {
162 MOZ_ASSERT(iterator);
163
164 RootedValue completionException(cx_);
165 RootedSavedFrame completionExceptionStack(cx_);
166 if (cx_->isExceptionPending()) {
167 if (!GetAndClearExceptionAndStack(cx_, &completionException,
168 &completionExceptionStack)) {
169 completionException.setUndefined();
170 completionExceptionStack = nullptr;
171 }
172 }
173
174 // Steps 1-2 (implicit)
175
176 // Step 3 (partial).
177 RootedValue returnVal(cx_);
178 if (!GetProperty(cx_, iterator, iterator, cx_->names().return_, &returnVal)) {
179 return;
180 }
181
182 // Step 4.
183 if (returnVal.isUndefined()) {
184 cx_->setPendingException(completionException, completionExceptionStack);
185 return;
186 }
187
188 // Step 3 (remaining part)
189 if (!returnVal.isObject()) {
190 JS_ReportErrorNumberASCII(cx_, GetErrorMessage, nullptr,
191 JSMSG_RETURN_NOT_CALLABLE);
192 return;
193 }
194 RootedObject returnObj(cx_, &returnVal.toObject());
195 if (!returnObj->isCallable()) {
196 JS_ReportErrorNumberASCII(cx_, GetErrorMessage, nullptr,
197 JSMSG_RETURN_NOT_CALLABLE);
198 return;
199 }
200
201 // Step 5.
202 RootedValue innerResultValue(cx_);
203 if (!js::Call(cx_, returnVal, iterator, &innerResultValue)) {
204 if (cx_->isExceptionPending()) {
205 cx_->clearPendingException();
206 }
207 }
208
209 // Step 6.
210 cx_->setPendingException(completionException, completionExceptionStack);
211 }
212