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